"feat: 升级本地应用管理依赖,新增应用删除功能及强制覆盖下载选项"
This commit is contained in:
		| @@ -42,7 +42,7 @@ | |||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@kevisual/ai-center": "^0.0.3", |     "@kevisual/ai-center": "^0.0.3", | ||||||
|     "@kevisual/load": "^0.0.6", |     "@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/logger": "^0.0.3", | ||||||
|     "@kevisual/query": "0.0.18", |     "@kevisual/query": "0.0.18", | ||||||
|     "@kevisual/query-login": "0.0.5", |     "@kevisual/query-login": "0.0.5", | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								assistant/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								assistant/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -19,8 +19,8 @@ importers: | |||||||
|         specifier: ^0.0.6 |         specifier: ^0.0.6 | ||||||
|         version: 0.0.6 |         version: 0.0.6 | ||||||
|       '@kevisual/local-app-manager': |       '@kevisual/local-app-manager': | ||||||
|         specifier: ^0.1.17 |         specifier: ^0.1.18 | ||||||
|         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)) |         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': |       '@kevisual/logger': | ||||||
|         specifier: ^0.0.3 |         specifier: ^0.0.3 | ||||||
|         version: 0.0.3 |         version: 0.0.3 | ||||||
| @@ -382,8 +382,8 @@ packages: | |||||||
|   '@kevisual/load@0.0.6': |   '@kevisual/load@0.0.6': | ||||||
|     resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} |     resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} | ||||||
|  |  | ||||||
|   '@kevisual/local-app-manager@0.1.17': |   '@kevisual/local-app-manager@0.1.18': | ||||||
|     resolution: {integrity: sha512-0Ye+GwxPd9FwaICNJoG5avkScVZ9OnTtUfskFFA6UBiSJ7MT4ZBhS2dzwU4o2Yl6mV951M7rXN5Kbs08pYJWUg==} |     resolution: {integrity: sha512-uJbob3/iaqzPWTNMZ2VjOlSTIZNEmN3MXdqtXwR+BAMIMQdsBllueZNGWlqnUyOXdHk09Y8dQU38u7QGsIYkeA==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
|       '@kevisual/router': ^0.0.6 |       '@kevisual/router': ^0.0.6 | ||||||
|       '@kevisual/types': ^0.0.1 |       '@kevisual/types': ^0.0.1 | ||||||
| @@ -1782,7 +1782,7 @@ snapshots: | |||||||
|     dependencies: |     dependencies: | ||||||
|       eventemitter3: 5.0.1 |       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: |     dependencies: | ||||||
|       '@kevisual/router': 0.0.20 |       '@kevisual/router': 0.0.20 | ||||||
|       '@kevisual/types': 0.0.10 |       '@kevisual/types': 0.0.10 | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { AssistantApp } from '@/module/assistant/index.ts'; | import { AssistantApp } from '@/module/assistant/index.ts'; | ||||||
| import { program, Command, assistantConfig } from '@/program.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 管理本地的应用模块'); | const appManagerCommand = new Command('app-manager').alias('am').description('Manage Assistant Apps 管理本地的应用模块'); | ||||||
| program.addCommand(appManagerCommand); | program.addCommand(appManagerCommand); | ||||||
| @@ -38,18 +39,20 @@ appManagerCommand | |||||||
|     manager.start(appKey); |     manager.start(appKey); | ||||||
|     console.log('Start App:', 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 | appManagerCommand | ||||||
|   .command('stop') |   .command('stop') | ||||||
|   .argument('<app-key-name>', '应用的 key 名称') |   .argument('<app-key-name>', '应用的 key 名称') | ||||||
|   .action(async (appKey: string) => { |   .action(async (appKey: string) => { | ||||||
|     const manager = new AssistantApp(assistantConfig); |     await stop(appKey); | ||||||
|     try { |  | ||||||
|       await manager.loadConfig(); |  | ||||||
|       await manager.stop(appKey); |  | ||||||
|     } catch (error) { |  | ||||||
|       console.error(error); |  | ||||||
|     } |  | ||||||
|     console.log('Stop App:', appKey); |     console.log('Stop App:', appKey); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -67,6 +70,22 @@ appManagerCommand | |||||||
|     console.log('Restart App:', appKey); |     console.log('Restart App:', appKey); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | appManagerCommand | ||||||
|  |   .command('delete') | ||||||
|  |   .alias('del') | ||||||
|  |   .argument('<app-key-name>', '应用的 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') | const pageListCommand = new Command('page-list') | ||||||
|   .alias('pl') |   .alias('pl') | ||||||
|   .option('-a, --all', '列出前端页面的所有信息') |   .option('-a, --all', '列出前端页面的所有信息') | ||||||
|   | |||||||
| @@ -9,14 +9,16 @@ const downloadCommand = new Command('download') | |||||||
|   .option('-i, --id <id>', '应用名称') |   .option('-i, --id <id>', '应用名称') | ||||||
|   .option('-t, --type <type>', '应用类型', 'web') |   .option('-t, --type <type>', '应用类型', 'web') | ||||||
|   .option('-r, --registry <registry>', '应用源 https://kevisual.cn') |   .option('-r, --registry <registry>', '应用源 https://kevisual.cn') | ||||||
|  |   .option('-f --force', '强制覆盖') | ||||||
|  |   .option('-y --yes', '覆盖的时候不提示') | ||||||
|   .action(async (options) => { |   .action(async (options) => { | ||||||
|     const { id, type } = options; |     const { id, type, force, yes } = options; | ||||||
|     assistantConfig.checkMounted(); |     assistantConfig.checkMounted(); | ||||||
|     const registry = options.registry || assistantConfig.getRegistry(); |     const registry = options.registry || assistantConfig.getRegistry(); | ||||||
|     // console.log('registry', registry); |     // console.log('registry', registry); | ||||||
|     const app = new AppDownload(assistantConfig); |     const app = new AppDownload(assistantConfig); | ||||||
|     if (id) { |     if (id) { | ||||||
|       await app.downloadApp({ id, type, registry }); |       await app.downloadApp({ id, type, registry, force, yes }); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,10 +4,11 @@ import { parseIfJson } from '@/module/assistant/index.ts'; | |||||||
| import path from 'node:path'; | import path from 'node:path'; | ||||||
| import fs from 'node:fs'; | import fs from 'node:fs'; | ||||||
| import glob from 'fast-glob'; | import glob from 'fast-glob'; | ||||||
|  | import type { App } from '@kevisual/router'; | ||||||
| export class AssistantApp extends Manager { | export class AssistantApp extends Manager { | ||||||
|   config: AssistantConfig; |   config: AssistantConfig; | ||||||
|   pagesPath: string; |   pagesPath: string; | ||||||
|   constructor(config: AssistantConfig) { |   constructor(config: AssistantConfig, mainApp?: App) { | ||||||
|     config.checkMounted(); |     config.checkMounted(); | ||||||
|     const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps'); |     const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps'); | ||||||
|     const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages'); |     const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages'); | ||||||
| @@ -16,6 +17,7 @@ export class AssistantApp extends Manager { | |||||||
|     super({ |     super({ | ||||||
|       appsPath, |       appsPath, | ||||||
|       configFilename: configFimename, |       configFilename: configFimename, | ||||||
|  |       mainApp: mainApp, | ||||||
|     }); |     }); | ||||||
|     this.pagesPath = pagesPath; |     this.pagesPath = pagesPath; | ||||||
|     this.config = config; |     this.config = config; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import getPort, { portNumbers } from 'get-port'; | |||||||
| import { program } from 'commander'; | import { program } from 'commander'; | ||||||
| import { spawnSync } from 'child_process'; | import { spawnSync } from 'child_process'; | ||||||
| import chalk from 'chalk'; | import chalk from 'chalk'; | ||||||
|  | import { AssistantApp } from './lib.ts'; | ||||||
| export const runServer = async (port?: number) => { | export const runServer = async (port?: number) => { | ||||||
|   let _port: number | undefined; |   let _port: number | undefined; | ||||||
|   if (port) { |   if (port) { | ||||||
| @@ -29,6 +30,12 @@ export const runServer = async (port?: number) => { | |||||||
|   }); |   }); | ||||||
|   app.server.on(proxyRoute); |   app.server.on(proxyRoute); | ||||||
|   proxyWs(); |   proxyWs(); | ||||||
|  |   const manager = new AssistantApp(assistantConfig, app); | ||||||
|  |   setTimeout(() => { | ||||||
|  |     manager.load({ runtime: 'client' }).then(() => { | ||||||
|  |       console.log('Assistant App Loaded'); | ||||||
|  |     }); | ||||||
|  |   }, 1000); | ||||||
|   return { |   return { | ||||||
|     app, |     app, | ||||||
|     port: _port, |     port: _port, | ||||||
|   | |||||||
| @@ -35,6 +35,8 @@ type DownloadAppOptions = { | |||||||
|    * 应用名称,默认为 user/app的 app |    * 应用名称,默认为 user/app的 app | ||||||
|    */ |    */ | ||||||
|   appName?: string; |   appName?: string; | ||||||
|  |   force?: boolean; | ||||||
|  |   yes?: boolean; | ||||||
| }; | }; | ||||||
| type DeleteAppOptions = { | type DeleteAppOptions = { | ||||||
|   /** |   /** | ||||||
| @@ -53,7 +55,7 @@ export class AppDownload { | |||||||
|     this.config = config; |     this.config = config; | ||||||
|   } |   } | ||||||
|   async downloadApp(opts: DownloadAppOptions) { |   async downloadApp(opts: DownloadAppOptions) { | ||||||
|     const { id, type = 'web' } = opts; |     const { id, type = 'web', force } = opts; | ||||||
|     const configDir = this.config.configDir; |     const configDir = this.config.configDir; | ||||||
|     this.config?.checkMounted(); |     this.config?.checkMounted(); | ||||||
|     const appsDir = this.config.configPath?.appsDir; |     const appsDir = this.config.configPath?.appsDir; | ||||||
| @@ -75,6 +77,12 @@ export class AppDownload { | |||||||
|     } else { |     } else { | ||||||
|       throw new Error('应用类型错误,只能是 web 或 app'); |       throw new Error('应用类型错误,只能是 web 或 app'); | ||||||
|     } |     } | ||||||
|  |     if (force) { | ||||||
|  |       args.push('-f'); | ||||||
|  |       if (opts.yes) { | ||||||
|  |         args.push('-y'); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     if (opts.registry) { |     if (opts.registry) { | ||||||
|       args.push('-r', opts.registry); |       args.push('-r', opts.registry); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -34,6 +34,8 @@ const downloadAppCommand = new Command('download') | |||||||
|   .option('-o, --output <output>', '下载 app serve client的包, 输出路径, 默认是当前目录') |   .option('-o, --output <output>', '下载 app serve client的包, 输出路径, 默认是当前目录') | ||||||
|   .option('-t, --type <type>', '下载 app serve client的包, 类型, app,或者web, 默认为web') |   .option('-t, --type <type>', '下载 app serve client的包, 类型, app,或者web, 默认为web') | ||||||
|   .option('-r, --registry <registry>', '下载 app serve client的包, 使用私有源') |   .option('-r, --registry <registry>', '下载 app serve client的包, 使用私有源') | ||||||
|  |   .option('-f, --force ', '强制覆盖') | ||||||
|  |   .option('-y, --yes ', '覆盖的时候不提示') | ||||||
|   .action(async (options) => { |   .action(async (options) => { | ||||||
|     const id = options.id || ''; |     const id = options.id || ''; | ||||||
|     const output = options.output || ''; |     const output = options.output || ''; | ||||||
| @@ -79,6 +81,8 @@ const downloadAppCommand = new Command('download') | |||||||
|         appDir: output, |         appDir: output, | ||||||
|         kevisualUrl: registry, |         kevisualUrl: registry, | ||||||
|         appType: appType, |         appType: appType, | ||||||
|  |         force: options.force, | ||||||
|  |         yes: options.yes, | ||||||
|       }); |       }); | ||||||
|       if (result.code === 200) { |       if (result.code === 200) { | ||||||
|         console.log(chalk.green('下载成功', res.data?.user, res.data?.key)); |         console.log(chalk.green('下载成功', res.data?.user, res.data?.key)); | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ import fs from 'fs'; | |||||||
| import { storage, baseURL } from '../query.ts'; | import { storage, baseURL } from '../query.ts'; | ||||||
| import { chalk } from '../chalk.ts'; | import { chalk } from '../chalk.ts'; | ||||||
| import { Result } from '@kevisual/query'; | import { Result } from '@kevisual/query'; | ||||||
|  | import { fileIsExist } from '@/uitls/file.ts'; | ||||||
|  | import { glob } from 'fast-glob'; | ||||||
|  | import inquirer from 'inquirer'; | ||||||
|  |  | ||||||
| type DownloadTask = { | type DownloadTask = { | ||||||
|   downloadPath: string; |   downloadPath: string; | ||||||
|   downloadUrl: string; |   downloadUrl: string; | ||||||
| @@ -62,6 +66,34 @@ export const fetchLink = async (url: string, opts?: Options) => { | |||||||
|     content, |     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 = { | type InstallAppOpts = { | ||||||
|   appDir?: string; |   appDir?: string; | ||||||
|   kevisualUrl?: string; |   kevisualUrl?: string; | ||||||
| @@ -73,6 +105,11 @@ type InstallAppOpts = { | |||||||
|    * 是否是web, 下载到 web-config的下面 |    * 是否是web, 下载到 web-config的下面 | ||||||
|    */ |    */ | ||||||
|   appType?: 'app' | 'web'; |   appType?: 'app' | 'web'; | ||||||
|  |   /** | ||||||
|  |    * 是否强制覆盖, 下载前删除已有的 | ||||||
|  |    */ | ||||||
|  |   force?: boolean; | ||||||
|  |   yes?: boolean; | ||||||
| }; | }; | ||||||
| export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { | export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { | ||||||
|   // const _app = demoData; |   // const _app = demoData; | ||||||
| @@ -84,6 +121,8 @@ export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { | |||||||
|     let hasPackage = false; |     let hasPackage = false; | ||||||
|     const user = _app.user; |     const user = _app.user; | ||||||
|     const key = _app.key; |     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 packagePath = path.join(appDir, appType === 'app' ? 'package.json' : `${user}/${key}/package.json`); | ||||||
|     const downFiles = files |     const downFiles = files | ||||||
|       .filter((file: any) => file?.path) |       .filter((file: any) => file?.path) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user