Quellcode durchsuchen

📦 升级更新脚本

快乐的梦鱼 vor 4 Wochen
Ursprung
Commit
4e4d2deb82

+ 395 - 0
scripts/UpdateScript/app.mjs

@@ -0,0 +1,395 @@
+/**
+ * 更新发布工具
+ *
+ * Copyright © 2025 imengyu.top imengyu-update-server
+ */
+
+import { program } from 'commander';
+import { input, confirm, select } from '@inquirer/prompts';
+import { postAppUpdate, postWebUpdate } from './postUpdate.mjs';
+import Table from 'cli-table3';
+import { config } from './postConfig.mjs';
+import {
+  axiosInstance,
+  initAuth,
+  login,
+  logout,
+  checkLogged,
+} from './auth.mjs';
+import consola from 'consola';
+import { logError } from './utils.mjs';
+
+// 版本相关
+//========================================
+
+export async function viewVersion(type) {
+  if (type === 'all' || !type) {
+    const data = await axiosInstance.get('/version/list');
+    const table = new Table({
+      head: ['ID', '版本'], 
+      colWidths: [10, 20 ]
+    });
+
+    data.data.forEach((d) => {
+      table.push([ d.id, d.version ]);
+    })
+    console.log(table.toString());
+  } else {
+    let data = null;
+    try {
+      if (Number.isNaN(Number(type)))
+        data = await axiosInstance.get('/version/get-by-name?name=' + type);
+      else
+        data = await axiosInstance.get('/version/' + type);
+    }
+    catch (e) {
+      logError('Failed to load version info', e);
+      return;
+    }
+    const table = new Table({
+      head: ['key', 'data'], 
+      colWidths: [30, 60]
+    });
+    table.push(
+      [ 'ID', data.data.id ],
+      [ '状态', stateConstant[data.data.status] ],
+      [ '版本号', data.data.version ],
+      [ '创建时间', new Date(data.data.createAt).toString() ],
+      [ '设置', data.data.config ],
+      [ '激活的Web更新ID', data.data.webUpdateId ],
+      [ '激活的App更新ID', data.data.appUpdateId ],
+      [ '激活的下一个App更新ID', data.data.appUpdateNextId ],
+    );
+    console.log(table.toString());
+  }
+}
+export async function getVersion(action, type) {
+  switch(action) {
+    case 'view':
+      await viewVersion(type);
+      break;
+    case 'new': {
+      const version = await input({ message: 'Enter version name, like (1.0.0)' });
+      try {
+        await axiosInstance.post('/version', {
+          version: version,
+          status: 1,
+          config: "{}",
+        });
+        consola.success('Add version success');
+      } catch (e) {
+        logError('Failed to add version', e);
+      }
+      break;
+    }
+    case 'delete': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      if (!await confirm({ message: `确定删除版本 ${versionId}?`, default: false }))
+        return;
+      if (!await confirm({ message: '确认删除版本?此操作会删除所属版本的所有更新项目、存储等,无法恢复,是否确定删除?', default: false }))
+        return;
+      try {
+        await axiosInstance.delete('/version/' + versionId);
+        consola.success('删除版本成功');
+      } catch (e) {
+        logError('删除版本失败', e);
+      }
+      break;
+    }
+    case 'set-state': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      const state = await select({
+        message: '设置状态',
+        choices: [
+          {
+            name: 'NotEnable',
+            value: 0,
+          },
+          {
+            name: 'Normal',
+            value: 1,
+          },
+          {
+            name: 'Deprecated',
+            value: 2,
+          },
+        ],
+      });
+
+      try {
+        await axiosInstance.put('/version/' + versionId, {
+          status: state
+        });
+        consola.success('设置状态成功');
+      } catch (e) {
+        logError('设置状态失败', e);
+      }
+      break;
+    }
+    case 'set-config': {
+      const versionId = type ? type : await input({ message: '输入版本ID' });
+      const config = await input({ message: '输入配置Json' });
+      try {
+        await axiosInstance.put('/version/' + versionId, {
+          config: config,
+        });
+        consola.success('设置配置成功');
+      } catch (e) {
+        logError('设置配置失败', e);
+      }
+      break;
+    }
+    case 'set-active-app-update': {
+      const versionId = await input({ message: '输入版本ID' });
+      const updateId = await input({ message: '输入更新ID' });
+      const isNext = await confirm({ message: 'Set as next active?', default: false });
+
+      try {
+        await axiosInstance.post('/update/active/app', { versionId, updateId, isNext });
+        consola.success('成功');
+      } catch (e) {
+        logError('失败', e);
+      }
+      break;
+    }
+    case 'set-active-web-update': {
+      const versionId = await input({ message: '输入版本ID' });
+      const updateId = await input({ message: '输入更新ID' });
+
+      try {
+        await axiosInstance.post('/update/active/web', { versionId, updateId });
+        consola.success('成功');
+      } catch (e) {
+        logError('失败', e);
+      }
+      break;
+    }
+    default:
+      logError('未知参数', action);
+      break;
+  }
+}
+
+//选择方法
+//========================================
+
+export async function selectVersion(defaultVersionId = null) {
+  const data = (await axiosInstance.get('/version/list?full=true&search=' + JSON.stringify({ 
+    appId: config.appId 
+  }))).data;
+  if (data.length === 0) {
+    logError('没有版本,请在管理后台创建 appId ' + config.appId + ' 的版本');
+    return;
+  }
+  const versionId = (await select({
+    choices: data.map(p => ({
+      value: p.id,
+      name: p.version,
+    })),
+    default: defaultVersionId,
+    message: '选择一个版本',
+  }));
+  const versionName = data.find(p => p.id === versionId).version;
+  return { versionId, versionName };
+}
+export async function selectUpdate() {
+  const { versionId } = await selectVersion();
+  const data = (await axiosInstance.get('/version/update?search=' + JSON.stringify({ versionId }))).data;
+  if (data.length === 0) {
+    logError('没有更新');
+    return;
+  }
+  const resultId = (await select({
+    choices: data.map(p => ({
+      value: p.id,
+      name: p.version,
+    })),
+    message: '选择一个更新',
+  }));
+  return resultId;
+}
+
+//更新相关
+//========================================
+
+const stateConstant = [ 'Deleted', 'Normal', 'Deprecated' ];
+const typeConstant = [ 'Unknow', 'Web', 'app' ];
+const storageTypeConstant = [ 'Unknow', 'LocalStorage', 'AliOSS' ];
+
+export async function postUpdate(type, options) {
+  switch (type) {
+    case 'web': {
+      await postWebUpdate(axiosInstance, options);
+      break;
+    }
+    case 'app': {
+      await postAppUpdate(axiosInstance, options);
+      break;
+    }
+    default:
+      logError('Unknow type', type);
+      break;
+  }
+}
+export async function deprecateOrDeleteUpdate(updateId) {
+  if (!updateId)
+    updateId = await selectUpdate();
+
+  const { currentUpdateInfo, currentVersionInfo } = await viewUpdate(updateId);
+
+  const deprecate = (await select({
+    choices: [
+      {
+        name: 'Deprecate',
+        value: 0,
+      },
+      {
+        name: 'Delete',
+        value: 1,
+      },
+    ],
+    message: '删除或弃用?',
+  })) === 0;
+
+  if (deprecate && currentUpdateInfo.type !== 1) {
+    consola.warn('只有Web更新允许弃用');
+    return;
+  }
+  if (currentUpdateInfo.status === 0) {
+    consola.warn(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
+    return;
+  }
+
+  if (deprecate) {
+    if (!await confirm({ message: `确定弃用当前版本 ${currentUpdateInfo.versionCode} ?弃用会删除存储文件以及备份。此操作无法恢复!`, default: false }))
+      return;
+  } else {
+    if (!await confirm({ message: `确定删除当前版本 ${currentUpdateInfo.versionCode} ?此操作无法恢复!`, default: false }))
+      return;
+  }
+
+  try {
+    await axiosInstance.post(`/update/${deprecate ? 'deprecate' : 'delete'}`, { updateId });
+    consola.success(`${deprecate ? '弃用' : '删除'} 成功`);
+  } catch (e) {
+    logError(`操作失败`, e);
+  }
+}
+export async function viewUpdate(updateId) {
+  
+  let currentUpdateInfo = null
+  let currentVersionInfo = null
+
+  try {
+    currentUpdateInfo = (await axiosInstance.get('/update/' + updateId)).data;
+  } catch (e) {
+    logError('加载更新信息失败', updateId);
+  }
+  try {
+    currentVersionInfo = (await axiosInstance.get('/version/' + currentUpdateInfo.versionId)).data;
+  } catch (e) {
+    logError('加载版本信息失败', currentUpdateInfo.versionId);
+  }
+
+  const table = new Table({
+    head: ['key', 'data'], 
+    colWidths: [20, 40]
+  });
+  table.push(
+    [ 'ID', currentUpdateInfo.id ],
+    [ '所属应用', currentUpdateInfo.name ],
+    [ '更新信息', currentUpdateInfo.updateInfo ],
+    [ '版本号', currentUpdateInfo.versionCode ],
+    [ '创建时间', new Date(currentUpdateInfo.createAt).toString() ],
+    [ '类型', typeConstant[currentUpdateInfo.type] ],
+    [ '状态', stateConstant[currentUpdateInfo.status] ],
+    [ '强制更新', currentUpdateInfo.force ],
+    [ '公共访问路径', currentUpdateInfo.publicUrl ],
+    [ '存储类型', storageTypeConstant[currentUpdateInfo.storageType] ],
+    [ '存储路径', currentUpdateInfo.storagePath ],
+  );
+
+  console.log(table.toString());
+
+  if (currentUpdateInfo.activeWebVersionName) {
+    table.push(
+      [ '使用中的Web版本', currentUpdateInfo.activeWebVersionName ],
+    );
+  }
+  if (currentUpdateInfo.activeAppVersionName) {
+    table.push(
+      [ '使用中的App版本', currentUpdateInfo.activeAppVersionName ],
+    );
+  }
+
+  return {
+    currentUpdateInfo,
+    currentVersionInfo,
+  }
+}
+export async function getUpdate(action, type, all, options) {
+  const typeNotANumber = isNaN(new Number(type)); 
+
+  if (action === 'view' && (!type || typeNotANumber)) {
+
+    let hasSerch = false;
+    const search = {};
+    const sort = {
+      field: "createAt",
+      order: "descend"
+    }
+    if (typeNotANumber && type !== 'all') {
+      hasSerch = true;
+      search.version = type;
+    }
+
+    const data = await axiosInstance.get('/update/list?full=true' + (hasSerch ? ('&search=' + JSON.stringify(search)) : '') + '&sort=' + JSON.stringify(sort));
+    const table = new Table({
+      head: ['ID', '版本', '版本号', '类型', '状态'], 
+      colWidths: [10, 10, 20, 10, 15 ]
+    });
+
+    if (type !== 'all' && all !== 'all' && data.data.length > 10) {
+      data.data = data.data.slice(0, 10);
+      consola.info('filter!', all);
+    }
+
+    data.data.forEach((d) => {
+      table.push([ 
+        d.id, 
+        (d.activeAppVersionName ? `${d.activeAppVersionName} (App)` : (
+          d.activeWebVersionName? `${d.activeWebVersionName} (Web)` : '无'
+        )),  
+        d.versionCode, typeConstant[d.type], stateConstant[d.status] 
+      ]);
+    })
+    
+    console.log(table.toString());
+  } else {
+    switch (action) {
+      case 'post': {
+        await postUpdate(type, options);
+        break;
+      }
+      case 'delete': {
+        await deprecateOrDeleteUpdate(type);
+        break;
+      }
+      case 'view': {
+        viewUpdate(type);
+        break;
+      }
+      default: 
+        logError('Unknow action', action);
+        break;
+    }
+  }
+}
+export async function testVersion(version) {
+  try {
+    const data = await axiosInstance.get('/update-get-info?version=' + version);
+    consola.info('版本信息', data.data);
+  } catch(e) {
+    logError('获取失败', e);
+  }
+}

+ 273 - 0
scripts/UpdateScript/auth.mjs

@@ -0,0 +1,273 @@
+/**
+ * 更新发布工具 - 认证与服务器连接模块
+ *
+ * Copyright © 2025 imengyu.top imengyu-update-server
+ */
+
+import { readFile, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import axios from 'axios';
+import md5 from 'md5';
+import { password, input } from '@inquirer/prompts';
+import { config } from './postConfig.mjs';
+import consola from 'consola';
+import { logError } from './utils.mjs';
+
+// 基础配置
+// ========================================
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export const constant = {
+  ServerUrl: config.server,
+  TokenSave: path.resolve(__dirname, './_token.json'),
+};
+
+let currentData = {
+  token: '',
+  refreshToken: '',
+  identifier: '',
+};
+
+function initIdentifier() {
+  if (!currentData.identifier)
+    currentData.identifier = `commandClient${Math.floor(Math.random() * 1000)}`;
+}
+
+function getErrorMessage(e) {
+  return e instanceof Error ? e.message : (typeof e === 'object' ? e : '' + e);
+}
+
+async function saveCurrentData() {
+  await writeFile(constant.TokenSave, JSON.stringify(currentData));
+}
+
+// Axios 实例
+// ========================================
+
+export const axiosInstance = axios.create({
+  baseURL: constant.ServerUrl,
+  timeoutErrorMessage: '请求超时,请检查网络连接',
+  responseType: 'json',
+  withCredentials: false,
+  validateStatus: () => true,
+});
+
+axiosInstance.interceptors.request.use((value) => {
+  value.headers['authorization'] = JSON.stringify({
+    auth: currentData?.token?.authName,
+    validity: currentData?.token?.authKey,
+    nonce: 'aaaaaaaaaa',
+    identifier: currentData.identifier,
+    key: 'abc123',
+  });
+  value.url =
+    value.url +
+    (value.url.includes('?') ? '&' : '?') +
+    `identifier=${currentData.identifier}`;
+  return value;
+});
+
+let refreshPromise = null;
+
+async function tryRefreshToken() {
+  if (!currentData.refreshToken) return false;
+  if (refreshPromise) return refreshPromise;
+  refreshPromise = (async () => {
+    try {
+      const res = await axios.post(
+        `${constant.ServerUrl}auth/refresh?identifier=${currentData.identifier}`,
+        { refreshToken: currentData.refreshToken },
+        { responseType: 'json', validateStatus: () => true }
+      );
+      if (res.data && res.data.success) {
+        currentData.token = {
+          authName: res.data.data.authName,
+          authKey: res.data.data.authKey,
+        };
+        if (res.data.data.refreshToken)
+          currentData.refreshToken = res.data.data.refreshToken;
+        await saveCurrentData();
+        return true;
+      } else
+        return false;
+    } catch {
+      // 刷新失败
+    }
+    return false;
+  })();
+  try {
+    return await refreshPromise;
+  } finally {
+    refreshPromise = null;
+  }
+}
+
+axiosInstance.interceptors.response.use((value) => {
+  if (value.data.success) 
+    return value.data;
+  const err = value.data;
+  err.__config = value.config;
+  return Promise.reject(err);
+}, (error) => {
+  return Promise.reject(error);
+});
+
+axiosInstance.interceptors.response.use(null, async (error) => {
+  const code = error?.code;
+  if (
+    (code === 40102 || code === 40103) &&
+    currentData.refreshToken &&
+    !error.__retried
+  ) {
+    const refreshed = await tryRefreshToken();
+    if (refreshed) {
+      const cfg = error.__config || error.config;
+      if (cfg) {
+        error.__retried = true;
+        return axiosInstance.request(cfg);
+      }
+    }
+  }
+  return Promise.reject(error);
+});
+
+// 认证初始化
+// ========================================
+
+/**
+ * 初始化认证模块,从本地加载 token,并检查登录状态
+ * @returns {Promise<void>}
+ */
+export async function initAuth() {
+  try {
+    const res = await readFile(constant.TokenSave);
+    const parsed = JSON.parse(res);
+    if (parsed && typeof parsed === 'object') {
+      currentData = { ...currentData, ...parsed };
+    }
+  } catch {
+    // 无 token 文件时使用默认值
+  }
+  initIdentifier();
+
+  if (currentData.token) {
+    try {
+      const res = await axiosInstance.get('/user/user-info');
+      if (res?.data) {
+        consola.info(`当前登录用户: ${res.data.name || res.data.user} (ID: ${res.data.id})`);
+        return;
+      }
+    } catch {
+      // 登录已过期,尝试刷新 token
+    }
+    if (currentData.refreshToken) {
+      consola.info('尝试刷新 token');
+      try {
+        const res = await tryRefreshToken();
+        if (res) {
+          consola.success('刷新登录成功');
+          return;
+        }
+      } catch (e) {
+        logError('刷新 token 失败', e);
+      }
+    }
+  }
+
+  consola.warn('未登录,进入登录界面');
+  await interactiveLogin();
+}
+
+/**
+ * 获取当前认证数据(只读)
+ */
+export function getCurrentData() {
+  return { ...currentData };
+}
+
+// 登录相关
+// ========================================
+
+/**
+ * 执行登录请求并保存 token
+ * @param {string} user 用户名
+ * @param {string} pass 密码
+ */
+async function doLogin(user, pass) {
+  const res = await axiosInstance.post('/auth?rember=true', {
+    method: 'key',
+    key: `${user}@${md5(pass)}`,
+  });
+  currentData.token = {
+    authName: res.data.authName,
+    authKey: res.data.authKey,
+  };
+  if (res.data.refreshToken)
+    currentData.refreshToken = res.data.refreshToken;
+  await saveCurrentData();
+}
+
+/**
+ * 交互式登录,循环直至登录成功或用户取消
+ */
+async function interactiveLogin() {
+  while (true) {
+    try {
+      const user = await input({ message: '输入用户名' });
+      const pass = await password({ message: '输入密码' });
+      await doLogin(user, pass);
+      consola.success('登录成功');
+      return;
+    } catch (e) {
+      if (e?.name === 'ExitPromptError' || e?.name === 'CancelPromptError')
+        return;
+      logError('登录失败', getErrorMessage(e));
+    }
+  }
+}
+
+/**
+ * 检查登录状态
+ */
+export async function checkLogged() {
+  try {
+    await axiosInstance.get('/auth');
+    consola.success('已登录');
+  } catch (e) {
+    logError('获取状态失败:', getErrorMessage(e));
+  }
+}
+
+/**
+ * 登录
+ * @param {string} user 用户名
+ */
+export async function login(user) {
+  try {
+    const username = user || await input({ message: '输入用户名' });
+    const pass = await password({ message: '输入密码' });
+    await doLogin(username, pass);
+    consola.success('登录成功');
+  } catch (e) {
+    logError('登录失败', getErrorMessage(e));
+  }
+}
+
+/**
+ * 退出登录
+ */
+export async function logout() {
+  currentData.token = '';
+  currentData.refreshToken = '';
+  await saveCurrentData();
+  try {
+    await axiosInstance.delete('/auth');
+    consola.success('退出登录成功');
+  } catch (e) {
+    logError('退出登录失败', getErrorMessage(e));
+  }
+}

+ 20 - 487
scripts/UpdateScript/index.mjs

@@ -1,493 +1,13 @@
 /**
  * 更新发布工具
- * 
+ *
  * Copyright © 2025 imengyu.top imengyu-update-server
  */
 
 import { program } from 'commander';
-import { password, input, confirm, select } from '@inquirer/prompts';
-import { writeFile, readFile } from 'node:fs/promises';
-import { postAppUpdate, postWebUpdate } from './postUpdate.mjs';
-import Table from 'cli-table3';
-import md5 from 'md5';
-import axios from 'axios';
-import path from 'path';
-import { dirname } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { config } from './postConfig.mjs';
-
-//基础配置
-//========================================
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-const constant = {
-  ServerUrl: config.server,
-  TokenSave: path.resolve(__dirname, './_token.json'),
-};
-let currentData = {
-  token: '',
-  identifier: '',
-};
-
-function initIdentifier() {
-  if (!currentData.identifier) 
-    currentData.identifier = `commandClient${Math.floor(Math.random() * 1000)}`;
-}
-
-readFile(constant.TokenSave).then((res) => {
-  currentData = JSON.parse(res);
-  initIdentifier();
-  start();
-}).catch(() => {
-  initIdentifier();
-  start();
-})
-
-const axiosInstance = axios.create({
-  baseURL: constant.ServerUrl,
-  timeoutErrorMessage: '请求超时,请检查网络连接',
-  responseType: 'json',
-  withCredentials: false,
-  validateStatus: () => true,
-});
-
-axiosInstance.interceptors.request.use((value) => {
-  value.headers['authorization'] = JSON.stringify({
-    auth: currentData?.token?.authName,
-    validity: currentData?.token?.authKey,
-    nonce: "aaaaaaaaaa",
-    identifier: currentData.identifier,
-    key: 'abc123',
-  });  
-  value.url = value.url + (value.url.includes('?') ? '&' : '?' ) + `identifier=${currentData.identifier}`
-  return value;
-});
-axiosInstance.interceptors.response.use((value) => {
-  if (value.data.success) 
-    return value.data;
-  else
-    return Promise.reject(value.data);
-});
-
-function getErrorMessage(e) {
-  return e instanceof Error ? e.message : (typeof e === 'object' ? JSON.stringify(e) : ('' + e));
-}
-
-//登录相关
-//========================================
-
-async function checkLogged() {
-  try {
-    await axiosInstance.get('/auth');
-    console.log('已登录');
-  } catch (e) {
-    console.error('获取状态失败:', getErrorMessage(e));
-  }
-}
-async function login(user) {
-  try {
-    const pass = await password({ message: '输入密码' });
-    const res = await axiosInstance.post('/auth?rember=true', {
-      method: 'key',
-      key: `${user}@${md5(pass)}`,
-    })
-    currentData.token = {
-      authName: res.data.authName,
-      authKey: res.data.authKey,
-    };
-    writeFile(constant.TokenSave, JSON.stringify(currentData));
-    console.log('登录成功');
-  } catch (e) {
-    console.error('登录失败', getErrorMessage(e));
-  }
-}
-async function logout() {
-  currentToken = '';
-  writeFile(constant.TokenSave, JSON.stringify(currentData));
-  try {
-    await axiosInstance.delete('/auth');
-    console.log('退出登录成功');
-  } catch(e) {
-    console.error('退出登录失败', getErrorMessage(e));
-  }
-}
-
-//版本相关
-//========================================
-
-async function viewVersion(type) {
-  if (type === 'all' || !type) {
-    const data = await axiosInstance.get('/version/list');
-    const table = new Table({
-      head: ['ID', '版本'], 
-      colWidths: [10, 20 ]
-    });
-
-    data.data.forEach((d) => {
-      table.push([ d.id, d.version ]);
-    })
-    console.log(table.toString());
-  } else {
-    let data = null;
-    try {
-      if (Number.isNaN(Number(type)))
-        data = await axiosInstance.get('/version/get-by-name?name=' + type);
-      else
-        data = await axiosInstance.get('/version/' + type);
-    }
-    catch (e) {
-      console.error('Failed to load version info', e);
-      return;
-    }
-    const table = new Table({
-      head: ['key', 'data'], 
-      colWidths: [30, 60]
-    });
-    table.push(
-      [ 'ID', data.data.id ],
-      [ '状态', stateConstant[data.data.status] ],
-      [ '版本号', data.data.version ],
-      [ '创建时间', new Date(data.data.createAt).toString() ],
-      [ '设置', data.data.config ],
-      [ '激活的Web更新ID', data.data.webUpdateId ],
-      [ '激活的App更新ID', data.data.appUpdateId ],
-      [ '激活的下一个App更新ID', data.data.appUpdateNextId ],
-    );
-    console.log(table.toString());
-  }
-}
-async function getVersion(action, type) {
-  switch(action) {
-    case 'view':
-      await viewVersion(type);
-      break;
-    case 'new': {
-      const version = await input({ message: 'Enter version name, like (1.0.0)' });
-      try {
-        await axiosInstance.post('/version', {
-          version: version,
-          status: 1,
-          config: "{}",
-        });
-        console.log('Add version success');
-      } catch (e) {
-        console.error('Failed to add version', e);
-      }
-      break;
-    }
-    case 'delete': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      if (!await confirm({ message: `确定删除版本 ${versionId}?`, default: false }))
-        return;
-      if (!await confirm({ message: '确认删除版本?此操作会删除所属版本的所有更新项目、存储等,无法恢复,是否确定删除?', default: false }))
-        return;
-      try {
-        await axiosInstance.delete('/version/' + versionId);
-        console.log('删除版本成功');
-      } catch (e) {
-        console.error('删除版本失败', e);
-      }
-      break;
-    }
-    case 'set-state': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      const state = await select({
-        message: '设置状态',
-        choices: [
-          {
-            name: 'NotEnable',
-            value: 0,
-          },
-          {
-            name: 'Normal',
-            value: 1,
-          },
-          {
-            name: 'Deprecated',
-            value: 2,
-          },
-        ],
-      });
-
-      try {
-        await axiosInstance.put('/version/' + versionId, {
-          status: state
-        });
-        console.log('设置状态成功');
-      } catch (e) {
-        console.error('设置状态失败', e);
-      }
-      break;
-    }
-    case 'set-config': {
-      const versionId = type ? type : await input({ message: '输入版本ID' });
-      const config = await input({ message: '输入配置Json' });
-      try {
-        await axiosInstance.put('/version/' + versionId, {
-          config: config,
-        });
-        console.log('设置配置成功');
-      } catch (e) {
-        console.error('设置配置失败', e);
-      }
-      break;
-    }
-    case 'set-active-app-update': {
-      const versionId = await input({ message: '输入版本ID' });
-      const updateId = await input({ message: '输入更新ID' });
-      const isNext = await confirm({ message: 'Set as next active?', default: false });
-
-      try {
-        await axiosInstance.post('/update/active/app', { versionId, updateId, isNext });
-        console.log('成功');
-      } catch (e) {
-        console.error('失败', e);
-      }
-      break;
-    }
-    case 'set-active-web-update': {
-      const versionId = await input({ message: '输入版本ID' });
-      const updateId = await input({ message: '输入更新ID' });
-
-      try {
-        await axiosInstance.post('/update/active/web', { versionId, updateId });
-        console.log('成功');
-      } catch (e) {
-        console.error('失败', e);
-      }
-      break;
-    }
-    default:
-      console.error('未知参数', action);
-      break;
-  }
-}
-
-//选择方法
-//========================================
-
-export async function selectVersion(defaultVersionId = null) {
-  const data = (await axiosInstance.get('/version/list?full=true&search=' + JSON.stringify({ 
-    appId: config.appId 
-  }))).data;
-  if (data.length === 0) {
-    console.error('没有版本');
-    return;
-  }
-  const versionId = (await select({
-    choices: data.map(p => ({
-      value: p.id,
-      name: p.version,
-    })),
-    default: defaultVersionId,
-    message: '选择一个版本',
-  }));
-  const versionName = data.find(p => p.id === versionId).version;
-  return { versionId, versionName };
-}
-export async function selectUpdate() {
-  const { versionId } = await selectVersion();
-  const data = (await axiosInstance.get('/version/update?search=' + JSON.stringify({ versionId }))).data;
-  if (data.length === 0) {
-    console.error('没有更新');
-    return;
-  }
-  const resultId = (await select({
-    choices: data.map(p => ({
-      value: p.id,
-      name: p.version,
-    })),
-    default: defaultVersionId,
-    message: '选择一个更新',
-  }));
-  return resultId;
-}
-
-//更新相关
-//========================================
-
-const stateConstant = [ 'Deleted', 'Normal', 'Deprecated' ];
-const typeConstant = [ 'Unknow', 'Web', 'app' ];
-const storageTypeConstant = [ 'Unknow', 'LocalStorage', 'AliOSS' ];
-
-async function postUpdate(type, options) {
-  switch (type) {
-    case 'web': {
-      await postWebUpdate(axiosInstance, options);
-      break;
-    }
-    case 'app': {
-      await postAppUpdate(axiosInstance, options);
-      break;
-    }
-    default:
-      console.error('Unknow type', type);
-      break;
-  }
-}
-async function deprecateOrDeleteUpdate(updateId) {
-  if (!updateId)
-    updateId = await selectUpdate();
-
-  const { currentUpdateInfo, currentVersionInfo } = await viewUpdate(updateId);
-
-  const deprecate = (await select({
-    choices: [
-      {
-        name: 'Deprecate',
-        value: 0,
-      },
-      {
-        name: 'Delete',
-        value: 1,
-      },
-    ],
-    message: '删除或弃用?',
-  })) === 0;
-
-  if (deprecate && currentUpdateInfo.type !== 1) {
-    console.log('只有Web更新允许弃用');
-    return;
-  }
-  if (currentUpdateInfo.status === 0) {
-    console.log(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
-    return;
-  }
-
-  if (deprecate) {
-    if (!await confirm({ message: `确定弃用当前版本 ${currentUpdateInfo.versionCode} ?弃用会删除存储文件以及备份。此操作无法恢复!`, default: false }))
-      return;
-  } else {
-    if (!await confirm({ message: `确定删除当前版本 ${currentUpdateInfo.versionCode} ?此操作无法恢复!`, default: false }))
-      return;
-  }
-
-  try {
-    await axiosInstance.post(`/update/${deprecate ? 'deprecate' : 'delete'}`, { updateId });
-    console.log(`${deprecate ? '弃用' : '删除'} 成功`);
-  } catch (e) {
-    console.error(`操作失败`, e);
-  }
-}
-async function viewUpdate(updateId) {
-  
-  let currentUpdateInfo = null
-  let currentVersionInfo = null
-
-  try {
-    currentUpdateInfo = (await axiosInstance.get('/update/' + updateId)).data;
-  } catch (e) {
-    console.error('加载更新信息失败', updateId);
-  }
-  try {
-    currentVersionInfo = (await axiosInstance.get('/version/' + currentUpdateInfo.versionId)).data;
-  } catch (e) {
-    console.error('加载版本信息失败', currentUpdateInfo.versionId);
-  }
-
-  const table = new Table({
-    head: ['key', 'data'], 
-    colWidths: [20, 40]
-  });
-  table.push(
-    [ 'ID', currentUpdateInfo.id ],
-    [ '所属应用', currentUpdateInfo.name ],
-    [ '更新信息', currentUpdateInfo.updateInfo ],
-    [ '版本号', currentUpdateInfo.versionCode ],
-    [ '创建时间', new Date(currentUpdateInfo.createAt).toString() ],
-    [ '类型', typeConstant[currentUpdateInfo.type] ],
-    [ '状态', stateConstant[currentUpdateInfo.status] ],
-    [ '强制更新', currentUpdateInfo.force ],
-    [ '公共访问路径', currentUpdateInfo.publicUrl ],
-    [ '存储类型', storageTypeConstant[currentUpdateInfo.storageType] ],
-    [ '存储路径', currentUpdateInfo.storagePath ],
-  );
-
-  console.log(table.toString());
-
-  if (currentUpdateInfo.activeWebVersionName) {
-    table.push(
-      [ '使用中的Web版本', currentUpdateInfo.activeWebVersionName ],
-    );
-  }
-  if (currentUpdateInfo.activeAppVersionName) {
-    table.push(
-      [ '使用中的App版本', currentUpdateInfo.activeAppVersionName ],
-    );
-  }
-
-  return {
-    currentUpdateInfo,
-    currentVersionInfo,
-  }
-}
-async function getUpdate(action, type, all, options) {
-  const typeNotANumber = isNaN(new Number(type)); 
-
-  if (action === 'view' && (!type || typeNotANumber)) {
-
-    let hasSerch = false;
-    const search = {};
-    const sort = {
-      field: "createAt",
-      order: "descend"
-    }
-    if (typeNotANumber && type !== 'all') {
-      hasSerch = true;
-      search.version = type;
-    }
-
-    const data = await axiosInstance.get('/update/list?full=true' + (hasSerch ? ('&search=' + JSON.stringify(search)) : '') + '&sort=' + JSON.stringify(sort));
-    const table = new Table({
-      head: ['ID', '版本', '版本号', '类型', '状态'], 
-      colWidths: [10, 10, 20, 10, 15 ]
-    });
-
-    if (type !== 'all' && all !== 'all' && data.data.length > 10) {
-      data.data = data.data.slice(0, 10);
-      console.log('filter!', all);
-    }
-
-    data.data.forEach((d) => {
-      table.push([ 
-        d.id, 
-        (d.activeAppVersionName ? `${d.activeAppVersionName} (App)` : (
-          d.activeWebVersionName? `${d.activeWebVersionName} (Web)` : '无'
-        )),  
-        d.versionCode, typeConstant[d.type], stateConstant[d.status] 
-      ]);
-    })
-    
-    console.log(table.toString());
-  } else {
-    switch (action) {
-      case 'post': {
-        await postUpdate(type, options);
-        break;
-      }
-      case 'delete': {
-        await deprecateOrDeleteUpdate(type);
-        break;
-      }
-      case 'view': {
-        viewUpdate(type);
-        break;
-      }
-      default: 
-        console.error('Unknow action', action);
-        break;
-    }
-  }
-}
-async function testVersion(version) {
-  try {
-    const data = await axiosInstance.get('/update-get-info?version=' + version);
-    console.log('版本信息', data.data);
-  } catch(e) {
-    console.log('获取失败', e);
-  }
-}
+import { login, checkLogged, logout, initAuth } from './auth.mjs';
+import { testVersion, getVersion, getUpdate } from './app.mjs';
+import { logError } from './utils.mjs';
 
 //程序入口
 //============================================
@@ -525,6 +45,19 @@ function start() {
   program.parse(process.argv);
 }
 
-process.on('unhandledRejection', (reason, p) => {
-  console.error('Promise: ', p, 'Reason: ', reason)
-})
+initAuth().then(start);
+
+process.on('unhandledRejection', (reason) => {
+  let reasonStr = '';
+  if (typeof reason === 'string') {
+    reasonStr = reason;
+  } else if (typeof reason === 'object') {
+    if (reason.message) {
+      reasonStr = reason.message;
+    }
+  } else {
+    reasonStr = reason.toString();
+  }
+  logError('UnhandledRejection:', reasonStr)
+})
+

+ 52 - 49
scripts/UpdateScript/postUpdate.mjs

@@ -9,11 +9,12 @@ import { writeFile, readFile, access, unlink, stat, constants } from 'node:fs/pr
 import path from 'node:path';
 import OSS from 'ali-oss';
 import cliProgress from 'cli-progress';
-import { selectVersion } from './index.mjs';
+import { selectVersion } from './app.mjs';
 import { config } from './postConfig.mjs';
-import { readFileRange, compressZip, execAsync } from './utils.mjs';
+import { readFileRange, compressZip, execAsync, logError } from './utils.mjs';
 import { dirname } from 'node:path';
 import { fileURLToPath } from 'node:url';
+import consola from 'consola';
 
 //基础配置
 //========================================
@@ -46,19 +47,21 @@ async function saveConfig(postConfig) {
 }
 
 async function getUpdateInfo(postConfig) {
+  let lastUseGit = false;
   let updateInfo = await input({ message: '输入更新信息', default: postConfig.lastUpdateInfo });
   if (updateInfo === 'git') {
-    console.log('开始获取git提交信息');
+    consola.info('开始获取git提交信息');
+    lastUseGit = true;
     updateInfo = await execAsync('git log -1 --pretty=format:"%h %s"');
-    console.log('使用git提交信息 "' + updateInfo + '" 作为更新信息');
+    consola.info('使用git提交信息 "' + updateInfo + '" 作为更新信息');
   }
-  return updateInfo;
+  return { updateInfo, lastUseGit };
 }
-async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
+export async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
 
-  console.log('开始分片上传');
+  consola.start('开始分片上传');
 
-  const multuploadInfo = (await axiosInstance.post('/update-large-token', {
+  const multuploadInfo = (await axiosInstance.post('/update/update-large-token', {
     fileSize: fileInfo.size,
     fileName: path.basename(filePath)
   })).data;
@@ -76,7 +79,7 @@ async function uploadMulitPartLarge(axiosInstance, fileInfo, filePath) {
     subFormData.append("key", multuploadInfo.key);
     subFormData.append("info", JSON.stringify({}));
     
-    (await axiosInstance.post('/update-large', subFormData, {
+    (await axiosInstance.post('/update/update-large', subFormData, {
       headers: { 'Content-Type': 'multipart/form-data' }
     })).data;
     
@@ -94,7 +97,7 @@ async function aliOSSMultipartUpload(client, fileName, uploadFile, progressCallb
   const partSize = 10 * 1024 * 1024; // 分片大小,这里使用10MB
   const partCount = Math.ceil(fileInfo.size / partSize); // 计算总分片数
   
-  console.log(`开始分片上传到阿里OSS,文件大小: ${(fileInfo.size / 1024 / 1024).toFixed(2)}MB,分片数: ${partCount}`);
+  consola.start(`开始分片上传到阿里OSS,文件大小: ${(fileInfo.size / 1024 / 1024).toFixed(2)}MB,分片数: ${partCount}`);
   
   try {
     // 初始化分片上传
@@ -123,19 +126,19 @@ async function aliOSSMultipartUpload(client, fileName, uploadFile, progressCallb
         progressCallback(progress);
       }
       
-      console.log(`已上传分片 ${i + 1}/${partCount} (${(progress * 100).toFixed(2)}%)`);
+      consola.info(`已上传分片 ${i + 1}/${partCount} (${(progress * 100).toFixed(2)}%)`);
     }
     
     // 完成分片上传
     await client.completeMultipartUpload(fileName, uploadId, parts);
     
-    console.log('阿里OSS分片上传完成');
+    consola.success('阿里OSS分片上传完成');
     
     // 获取上传后的文件URL
     const url = await client.generateObjectUrl(fileName);
     return url;
   } catch (error) {
-    console.error('阿里OSS分片上传失败:', error);
+    logError('阿里OSS分片上传失败:', error);
     throw error;
   }
 }
@@ -146,9 +149,9 @@ async function aliOSSMultipartUpload(client, fileName, uploadFile, progressCallb
 export async function postAppUpdate(axiosInstance, param) {
   const postConfig = await getConfig();
   const { versionId, versionName } = await selectVersion(postConfig.lastVersion);
-  const updateInfo = await getUpdateInfo(postConfig);
+  const { updateInfo, lastUseGit } = await getUpdateInfo(postConfig);
 
-  const serverConfig = await axiosInstance.post('/update-post', { 
+  const serverConfig = await axiosInstance.post('/update/update-post', { 
     config: { 
       type: 2, 
       test: true, 
@@ -159,7 +162,7 @@ export async function postAppUpdate(axiosInstance, param) {
   });
 
   postConfig.lastVersion = versionId;
-  postConfig.lastUpdateInfo = updateInfo;
+  postConfig.lastUpdateInfo = lastUseGit ? 'git': updateInfo;
   postConfig.lastTodaySubVersion++;
 
   const updateSource = (await select({
@@ -188,7 +191,7 @@ export async function postAppUpdate(axiosInstance, param) {
   else if (updateSource === 'uploaded-alioss') {
     const fileName = await input({ message: '输入已上传的阿里OSS文件路径' });
     try {
-      const result = (await axiosInstance.post('/update-post', {
+      const result = (await axiosInstance.post('/update/update-post', {
         type: 2,
         ossConfig: {
           ossPath: fileName,
@@ -201,15 +204,15 @@ export async function postAppUpdate(axiosInstance, param) {
         updateInfo,
         versionCode: versionCode
       })).data;
-      console.log('上传成功');
-      console.log('新更新ID: ' + result.updateId);
+      consola.success('上传成功');
+      consola.info('新更新ID: ' + result.updateId);
     } catch (e) {
-      console.error('上传失败', e);
+      logError('上传失败', e);
     }
     return;
   }
   else {
-    console.error('错误的选择');
+    logError('错误的选择');
     return;
   }
 
@@ -218,11 +221,11 @@ export async function postAppUpdate(axiosInstance, param) {
   try {
     await access(uploadFile, constants.R_OK)
   } catch {
-    console.error(`Failed to access ${uploadFile}, did you created it?`);
+    logError(`Failed to access ${uploadFile}, did you created it?`);
     return;
   }
 
-  console.log('开始上传');
+  consola.start('开始上传');
 
   //小于8mb则小文件上传,否则使用阿里OSS上传
   const fileInfo = await stat(uploadFile);
@@ -243,13 +246,13 @@ export async function postAppUpdate(axiosInstance, param) {
     }));
 
     try {
-      const result = (await axiosInstance.post('/update-post', formData, {
+      const result = (await axiosInstance.post('/update/update-post', formData, {
         headers: { 'Content-Type': 'multipart/form-data' }
       })).data;
-      console.log('上传成功');
-      console.log('新更新ID: ' + result.updateId);
+      consola.success('上传成功');
+      consola.info('新更新ID: ' + result.updateId);
     } catch (e) {
-      console.error('上传失败', e);
+      logError('上传失败', e);
     }
   } else {
 
@@ -262,7 +265,7 @@ export async function postAppUpdate(axiosInstance, param) {
     } else if (serverConfig.uploadAppType === UPLOAD_APP_TYPE_ALI_OSS) {
       //阿里OSS上传
       //请求STS进行临时授权
-      const stsToken = (await axiosInstance.post('/update-ali-oss-sts')).data;
+      const stsToken = (await axiosInstance.post('/update/update-ali-oss-sts')).data;
       const client = new OSS({
         region: stsToken.Region,
         accessKeyId: stsToken.AccessKeyId,
@@ -270,7 +273,7 @@ export async function postAppUpdate(axiosInstance, param) {
         stsToken: stsToken.SecurityToken,
         bucket: stsToken.Bucket,
         refreshSTSToken: async () => {
-          const refreshToken = (await axiosInstance.get("/update-ali-oss-sts")).data;
+          const refreshToken = (await axiosInstance.get("/update/update-ali-oss-sts")).data;
           return {
             accessKeyId: refreshToken.AccessKeyId,
             accessKeySecret: refreshToken.AccessKeySecret,
@@ -281,7 +284,7 @@ export async function postAppUpdate(axiosInstance, param) {
       
       //小于96mb则直接上传,否则分片上传
 
-      console.log('Start upload to ali oss');
+      consola.start('Start upload to ali oss');
 
       let returnUrl = '';
       if (fileInfo.size < 96 * 1024 * 1024) {
@@ -302,13 +305,13 @@ export async function postAppUpdate(axiosInstance, param) {
         ossPublic: returnUrl,
       };
 
-      console.log('Upload to ali oss done');
+      consola.success('Upload to ali oss done');
     } else {
-      console.error('错误的 uploadAppType 配置 ' + serverConfig.uploadAppType);
+      logError('错误的 uploadAppType 配置 ' + serverConfig.uploadAppType);
     }
   
     try {
-      const result = (await axiosInstance.post('/update-post', {
+      const result = (await axiosInstance.post('/update/update-post', {
         config: {
           type: 2,
           ossConfig,
@@ -323,10 +326,10 @@ export async function postAppUpdate(axiosInstance, param) {
           fileName
         },
       })).data;
-      console.log('上传成功');
-      console.log('新更新ID: ' + result.updateId);
+      consola.success('上传成功');
+      consola.info('新更新ID: ' + result.updateId);
     } catch (e) {
-      console.error('上传失败', e);
+      logError('上传失败', e);
     }
   }
 }
@@ -340,13 +343,13 @@ export async function postWebUpdate(axiosInstance, param) {
   
   const postConfig = await getConfig();
   const { versionId, versionName } = await selectVersion(postConfig.lastVersion);
-  const updateInfo = await getUpdateInfo(postConfig);
+  const { updateInfo, lastUseGit } = await getUpdateInfo(postConfig);
 
   postConfig.lastVersion = versionId;
-  postConfig.lastUpdateInfo = updateInfo;
+  postConfig.lastUpdateInfo = lastUseGit ? 'git': updateInfo;
   postConfig.lastTodaySubVersion++;
 
-  await axiosInstance.post('/update-post', { config: { type: 1, test: true, versionId, uploadWebConfig: config.uploadWebConfig, submitKey: config.submitKey } });
+  await axiosInstance.post('/update/update-post', { config: { type: 1, test: true, versionId, uploadWebConfig: config.uploadWebConfig, submitKey: config.submitKey } });
 
   const now = new Date();
   const versionCode = await config.buildWebVersionGenerateCommand(now, postConfig.lastTodaySubVersion);
@@ -357,9 +360,9 @@ export async function postWebUpdate(axiosInstance, param) {
   await saveConfig(postConfig);
   
   if (!skipBuild && config.buildWebCommand) {
-    console.log('正在执行构建...');
+    consola.start('正在执行构建...');
     await execAsync(config.buildWebCommand);
-    console.log('构建完成');
+    consola.success('构建完成');
   }
 
   const distDir = path.resolve(__dirname, config.buildWebOutDir);
@@ -367,7 +370,7 @@ export async function postWebUpdate(axiosInstance, param) {
   try {
     await access(distDir, constants.R_OK)
   } catch {
-    console.error(`Failed to access ${distDir}`);
+    logError(`Failed to access ${distDir}`);
     return;
   }
 
@@ -375,11 +378,11 @@ export async function postWebUpdate(axiosInstance, param) {
   const skipFiles = config?.buildWebOptions?.skipFiles ?? [];
 
   if (!skipBuild) {
-    console.log('开始压缩zip...');
+    consola.start('开始压缩zip...');
     await compressZip(distDir, outputPath, skipFiles);
   }
 
-  console.log('开始上传zip');
+  consola.start('开始上传zip');
 
   let success = false;
 
@@ -404,18 +407,18 @@ export async function postWebUpdate(axiosInstance, param) {
 
   try {
     formData.append("config", JSON.stringify(submitConfig));
-    const result = (await axiosInstance.post('/update-post', formData, {
+    const result = (await axiosInstance.post('/update/update-post', formData, {
       headers: { 'Content-Type': 'multipart/form-data' }
     })).data;
-    console.log('上传成功');
-    console.log('新更新ID: ' + result.updateId);
+    consola.success('上传成功');
+    consola.info('新更新ID: ' + result.updateId);
     success = true;
   } catch (e) {
-    console.error('上传失败', e);
+    logError('上传失败', e);
   }
   if (!success || noDelete) 
     return;
     
-  console.log('删除zip');
+  consola.info('删除zip');
   await unlink(outputPath);
 }

+ 48 - 8
scripts/UpdateScript/utils.mjs

@@ -1,7 +1,8 @@
 import fs from 'fs';
-import archiver from 'archiver';
+import { ZipArchive } from 'archiver';
 import { exec } from 'child_process';
 import { readdir, stat } from 'fs/promises';
+import consola from 'consola';
 
 export function readFileRange(file, start, length) {
   return new Promise((resolve, reject) => {
@@ -37,9 +38,15 @@ export function pad(num, n) {
   }
   return strNum;
 }
+
+/**
+ * 异步执行命令
+ * @param {string} command 命令
+ * @returns {Promise<string>} 命令执行结果
+ */
 export function execAsync(command) {
   return new Promise((resolve, reject) => {
-    console.log('[CMD] ' + command);
+    consola.info('[CMD] ' + command);
     exec(command, (error, stdout, stderr) => {
       if (error) {
         reject(error);
@@ -49,9 +56,39 @@ export function execAsync(command) {
     });
   })
 }
+
+/**
+ * 记录错误信息
+ * @param {string} message 错误信息
+ * @param {*} e 错误对象
+ */
+export function logError(message, e) {
+  consola.error(message, e || '');
+}
+
+/**
+ * 获取错误信息
+ * @param {*} e 错误对象
+ * @returns {string} 错误信息
+ */
+export function getErrorMessage(e) {
+  if (e instanceof Error) 
+    return e.message;
+  if (typeof e === 'object' && e.message)
+    return e.message;
+  return '' + e;
+}
+
+/**
+ * 压缩zip
+ * @param {string} dir 压缩目录
+ * @param {string} targetFilePath 压缩文件路径
+ * @param {string[]} skipFiles 压缩忽略文件 支持按开头匹配,和文件类型通配符匹配,如:'*.js', '*.css', '*.html'
+ * @returns {Promise<void>}
+ */
 export async function compressZip(dir, targetFilePath, skipFiles) {
   const output = fs.createWriteStream(targetFilePath);
-  const archive = archiver('zip', {
+  const archive = new ZipArchive({
     zlib: { level: 9 } // Sets the compression level.
   });
   archive.pipe(output);
@@ -63,7 +100,10 @@ export async function compressZip(dir, targetFilePath, skipFiles) {
       return true;
     if (!skipFiles || skipFiles.length === 0)
       return;
-    return skipFiles.find((item) => pathString.startsWith(item) || pathString.startsWith(`/${item}`));
+    return skipFiles.find((item) => (
+      pathString.startsWith(item) || pathString.startsWith(`/${item}`)
+      || (item.includes('*.') && pathString.endsWith(item.split('*')[1]))
+    ));
   }
 
   async function loopDir(path, subPrefix) {
@@ -72,7 +112,7 @@ export async function compressZip(dir, targetFilePath, skipFiles) {
       const subPath = subPrefix + '/' + file;
       if (checkPathSkip(subPath))
         continue;
-      console.log('File: ' + subPath);
+      consola.info('File: ' + subPath);
       const filestat = await stat(dir + subPath);
       if (filestat.isDirectory()) {
         await loopDir(dir + subPath, subPath);
@@ -83,12 +123,12 @@ export async function compressZip(dir, targetFilePath, skipFiles) {
   }
   await loopDir(dir, '')
 
-  console.log('等待压缩zip...');
+  consola.start('等待压缩zip...');
 
   const waitArchive = new Promise((resolve, reject) => {
     output.on('close', function() {
-      console.log(archive.pointer() + ' total bytes');
-      console.log('archiver has been finalized and the output file descriptor has closed.');
+      consola.info(archive.pointer() + ' total bytes');
+      consola.success('archiver has been finalized and the output file descriptor has closed.');
       resolve();
     });
     archive.on('error', function(err) {