From e9eedcd1bd95e5bdc9599286fb4b604576255de7 Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 17 May 2025 15:53:06 +0800 Subject: [PATCH] =?UTF-8?q?"feat:=20=E5=8D=87=E7=BA=A7=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E7=AE=A1=E7=90=86=E4=BE=9D=E8=B5=96=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=BA=94=E7=94=A8=E5=88=A0=E9=99=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8A=E5=BC=BA=E5=88=B6=E8=A6=86=E7=9B=96=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=80=89=E9=A1=B9"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant/package.json | 2 +- assistant/pnpm-lock.yaml | 10 ++--- assistant/src/command/app-manager/index.ts | 35 +++++++++++++---- assistant/src/command/app/index.ts | 6 ++- .../local-app-manager/assistant-app.ts | 4 +- assistant/src/server.ts | 7 ++++ assistant/src/services/app/index.ts | 10 ++++- src/command/app/front-app/index.ts | 4 ++ src/module/download/install.ts | 39 +++++++++++++++++++ 9 files changed, 99 insertions(+), 18 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index b6537d8..81c2a70 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -42,7 +42,7 @@ "devDependencies": { "@kevisual/ai-center": "^0.0.3", "@kevisual/load": "^0.0.6", - "@kevisual/local-app-manager": "^0.1.17", + "@kevisual/local-app-manager": "^0.1.18", "@kevisual/logger": "^0.0.3", "@kevisual/query": "0.0.18", "@kevisual/query-login": "0.0.5", diff --git a/assistant/pnpm-lock.yaml b/assistant/pnpm-lock.yaml index 86238d3..027e454 100644 --- a/assistant/pnpm-lock.yaml +++ b/assistant/pnpm-lock.yaml @@ -19,8 +19,8 @@ importers: specifier: ^0.0.6 version: 0.0.6 '@kevisual/local-app-manager': - specifier: ^0.1.17 - version: 0.1.17(@kevisual/router@0.0.20)(@kevisual/types@0.0.10)(@kevisual/use-config@1.0.17(dotenv@16.5.0))(pm2@6.0.6(supports-color@10.0.0)) + specifier: ^0.1.18 + version: 0.1.18(@kevisual/router@0.0.20)(@kevisual/types@0.0.10)(@kevisual/use-config@1.0.17(dotenv@16.5.0))(pm2@6.0.6(supports-color@10.0.0)) '@kevisual/logger': specifier: ^0.0.3 version: 0.0.3 @@ -382,8 +382,8 @@ packages: '@kevisual/load@0.0.6': resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} - '@kevisual/local-app-manager@0.1.17': - resolution: {integrity: sha512-0Ye+GwxPd9FwaICNJoG5avkScVZ9OnTtUfskFFA6UBiSJ7MT4ZBhS2dzwU4o2Yl6mV951M7rXN5Kbs08pYJWUg==} + '@kevisual/local-app-manager@0.1.18': + resolution: {integrity: sha512-uJbob3/iaqzPWTNMZ2VjOlSTIZNEmN3MXdqtXwR+BAMIMQdsBllueZNGWlqnUyOXdHk09Y8dQU38u7QGsIYkeA==} peerDependencies: '@kevisual/router': ^0.0.6 '@kevisual/types': ^0.0.1 @@ -1782,7 +1782,7 @@ snapshots: dependencies: eventemitter3: 5.0.1 - '@kevisual/local-app-manager@0.1.17(@kevisual/router@0.0.20)(@kevisual/types@0.0.10)(@kevisual/use-config@1.0.17(dotenv@16.5.0))(pm2@6.0.6(supports-color@10.0.0))': + '@kevisual/local-app-manager@0.1.18(@kevisual/router@0.0.20)(@kevisual/types@0.0.10)(@kevisual/use-config@1.0.17(dotenv@16.5.0))(pm2@6.0.6(supports-color@10.0.0))': dependencies: '@kevisual/router': 0.0.20 '@kevisual/types': 0.0.10 diff --git a/assistant/src/command/app-manager/index.ts b/assistant/src/command/app-manager/index.ts index 8dfefc3..ded17fc 100644 --- a/assistant/src/command/app-manager/index.ts +++ b/assistant/src/command/app-manager/index.ts @@ -1,5 +1,6 @@ import { AssistantApp } from '@/module/assistant/index.ts'; import { program, Command, assistantConfig } from '@/program.ts'; +import { AppDownload } from '@/services/app/index.ts'; const appManagerCommand = new Command('app-manager').alias('am').description('Manage Assistant Apps 管理本地的应用模块'); program.addCommand(appManagerCommand); @@ -38,18 +39,20 @@ appManagerCommand manager.start(appKey); console.log('Start App:', appKey); }); - +const stop = async (appKey: string) => { + const manager = new AssistantApp(assistantConfig); + try { + await manager.loadConfig(); + await manager.stop(appKey); + } catch (error) { + console.error(error); + } +}; appManagerCommand .command('stop') .argument('', '应用的 key 名称') .action(async (appKey: string) => { - const manager = new AssistantApp(assistantConfig); - try { - await manager.loadConfig(); - await manager.stop(appKey); - } catch (error) { - console.error(error); - } + await stop(appKey); console.log('Stop App:', appKey); }); @@ -67,6 +70,22 @@ appManagerCommand console.log('Restart App:', appKey); }); +appManagerCommand + .command('delete') + .alias('del') + .argument('', '应用的 key 名称, apps 中的 文件名') + .action(async (id) => { + const app = new AppDownload(assistantConfig); + if (id) { + if (id.includes('/')) { + const [_user, appName] = id.split('/'); + id = appName; + await stop(id); + } + await app.deleteApp({ id, type: 'app' }); + } + }); + const pageListCommand = new Command('page-list') .alias('pl') .option('-a, --all', '列出前端页面的所有信息') diff --git a/assistant/src/command/app/index.ts b/assistant/src/command/app/index.ts index d3345c6..34d962e 100644 --- a/assistant/src/command/app/index.ts +++ b/assistant/src/command/app/index.ts @@ -9,14 +9,16 @@ const downloadCommand = new Command('download') .option('-i, --id ', '应用名称') .option('-t, --type ', '应用类型', 'web') .option('-r, --registry ', '应用源 https://kevisual.cn') + .option('-f --force', '强制覆盖') + .option('-y --yes', '覆盖的时候不提示') .action(async (options) => { - const { id, type } = options; + const { id, type, force, yes } = options; assistantConfig.checkMounted(); const registry = options.registry || assistantConfig.getRegistry(); // console.log('registry', registry); const app = new AppDownload(assistantConfig); if (id) { - await app.downloadApp({ id, type, registry }); + await app.downloadApp({ id, type, registry, force, yes }); } }); diff --git a/assistant/src/module/assistant/local-app-manager/assistant-app.ts b/assistant/src/module/assistant/local-app-manager/assistant-app.ts index d4995c0..6f06de6 100644 --- a/assistant/src/module/assistant/local-app-manager/assistant-app.ts +++ b/assistant/src/module/assistant/local-app-manager/assistant-app.ts @@ -4,10 +4,11 @@ import { parseIfJson } from '@/module/assistant/index.ts'; import path from 'node:path'; import fs from 'node:fs'; import glob from 'fast-glob'; +import type { App } from '@kevisual/router'; export class AssistantApp extends Manager { config: AssistantConfig; pagesPath: string; - constructor(config: AssistantConfig) { + constructor(config: AssistantConfig, mainApp?: App) { config.checkMounted(); const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps'); const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages'); @@ -16,6 +17,7 @@ export class AssistantApp extends Manager { super({ appsPath, configFilename: configFimename, + mainApp: mainApp, }); this.pagesPath = pagesPath; this.config = config; diff --git a/assistant/src/server.ts b/assistant/src/server.ts index 3b3e69d..f935d98 100644 --- a/assistant/src/server.ts +++ b/assistant/src/server.ts @@ -6,6 +6,7 @@ import getPort, { portNumbers } from 'get-port'; import { program } from 'commander'; import { spawnSync } from 'child_process'; import chalk from 'chalk'; +import { AssistantApp } from './lib.ts'; export const runServer = async (port?: number) => { let _port: number | undefined; if (port) { @@ -29,6 +30,12 @@ export const runServer = async (port?: number) => { }); app.server.on(proxyRoute); proxyWs(); + const manager = new AssistantApp(assistantConfig, app); + setTimeout(() => { + manager.load({ runtime: 'client' }).then(() => { + console.log('Assistant App Loaded'); + }); + }, 1000); return { app, port: _port, diff --git a/assistant/src/services/app/index.ts b/assistant/src/services/app/index.ts index 3a4e5d7..a71cca5 100644 --- a/assistant/src/services/app/index.ts +++ b/assistant/src/services/app/index.ts @@ -35,6 +35,8 @@ type DownloadAppOptions = { * 应用名称,默认为 user/app的 app */ appName?: string; + force?: boolean; + yes?: boolean; }; type DeleteAppOptions = { /** @@ -53,7 +55,7 @@ export class AppDownload { this.config = config; } async downloadApp(opts: DownloadAppOptions) { - const { id, type = 'web' } = opts; + const { id, type = 'web', force } = opts; const configDir = this.config.configDir; this.config?.checkMounted(); const appsDir = this.config.configPath?.appsDir; @@ -75,6 +77,12 @@ export class AppDownload { } else { throw new Error('应用类型错误,只能是 web 或 app'); } + if (force) { + args.push('-f'); + if (opts.yes) { + args.push('-y'); + } + } if (opts.registry) { args.push('-r', opts.registry); } diff --git a/src/command/app/front-app/index.ts b/src/command/app/front-app/index.ts index 5f1db84..1828acc 100644 --- a/src/command/app/front-app/index.ts +++ b/src/command/app/front-app/index.ts @@ -34,6 +34,8 @@ const downloadAppCommand = new Command('download') .option('-o, --output ', '下载 app serve client的包, 输出路径, 默认是当前目录') .option('-t, --type ', '下载 app serve client的包, 类型, app,或者web, 默认为web') .option('-r, --registry ', '下载 app serve client的包, 使用私有源') + .option('-f, --force ', '强制覆盖') + .option('-y, --yes ', '覆盖的时候不提示') .action(async (options) => { const id = options.id || ''; const output = options.output || ''; @@ -79,6 +81,8 @@ const downloadAppCommand = new Command('download') appDir: output, kevisualUrl: registry, appType: appType, + force: options.force, + yes: options.yes, }); if (result.code === 200) { console.log(chalk.green('下载成功', res.data?.user, res.data?.key)); diff --git a/src/module/download/install.ts b/src/module/download/install.ts index a629762..07e70ec 100644 --- a/src/module/download/install.ts +++ b/src/module/download/install.ts @@ -3,6 +3,10 @@ import fs from 'fs'; import { storage, baseURL } from '../query.ts'; import { chalk } from '../chalk.ts'; import { Result } from '@kevisual/query'; +import { fileIsExist } from '@/uitls/file.ts'; +import { glob } from 'fast-glob'; +import inquirer from 'inquirer'; + type DownloadTask = { downloadPath: string; downloadUrl: string; @@ -62,6 +66,34 @@ export const fetchLink = async (url: string, opts?: Options) => { content, }; }; +const checkDelete = async (opts?: { force?: boolean; dir?: string; yes?: boolean }) => { + const { force = false, dir = '', yes = false } = opts || {}; + if (force) { + try { + if (fileIsExist(dir)) { + const files = await glob(`${dir}/**/*`, { onlyFiles: true }); + const answers = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: `是否你需要删除 【${opts?.dir}】 目录下的文件. [${files.length}] 个?`, + when: () => files.length > 0 && !yes, // 当 username 为空时,提示用户输入 + }, + ]); + if (answers?.confirm || yes) { + fs.rmSync(dir, { recursive: true }); + console.log(chalk.green('删除成功', dir)); + } else { + console.log(chalk.red('取消删除', dir)); + } + } + } catch (error) { + console.error(error); + } finally { + fs.mkdirSync(dir, { recursive: true }); + } + } +}; type InstallAppOpts = { appDir?: string; kevisualUrl?: string; @@ -73,6 +105,11 @@ type InstallAppOpts = { * 是否是web, 下载到 web-config的下面 */ appType?: 'app' | 'web'; + /** + * 是否强制覆盖, 下载前删除已有的 + */ + force?: boolean; + yes?: boolean; }; export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { // const _app = demoData; @@ -84,6 +121,8 @@ export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { let hasPackage = false; const user = _app.user; const key = _app.key; + const downloadDirPath = appType === 'web' ? path.join(appDir, user, key) : path.join(appDir); + await checkDelete({ force: opts?.force, yes: opts?.yes, dir: downloadDirPath }); const packagePath = path.join(appDir, appType === 'app' ? 'package.json' : `${user}/${key}/package.json`); const downFiles = files .filter((file: any) => file?.path)