feat: add app download
This commit is contained in:
		
							
								
								
									
										88
									
								
								src/command/app/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/command/app/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| /** | ||||
|  * 下载 app serve client的包的命令 | ||||
|  */ | ||||
|  | ||||
| import { chalk } from '@/module/chalk.ts'; | ||||
| import { program, Command } from '../../program.ts'; | ||||
| import { queryApp } from '../../query/app-manager/query-app.ts'; | ||||
| import { installApp, uninstallApp } from '@/module/download/install.ts'; | ||||
| export const appCommand = new Command('app').description('app 命令').action(() => { | ||||
|   console.log('app'); | ||||
| }); | ||||
|  | ||||
| program.addCommand(appCommand); | ||||
|  | ||||
| const downloadAppCommand = new Command('download') | ||||
|   .description('下载 app serve client的包') | ||||
|   .option('-i, --id <id>', '下载 app serve client的包, id 或者user/key') | ||||
|   .option('-o, --output <output>', '下载 app serve client的包, 输出路径') | ||||
|   .action(async (options) => { | ||||
|     const id = options.id || ''; | ||||
|     if (!id) { | ||||
|       console.error(chalk.red('id is required')); | ||||
|       return; | ||||
|     } | ||||
|     const [user, key] = id.split('/'); | ||||
|     const data: any = {}; | ||||
|     if (user && key) { | ||||
|       data.user = user; | ||||
|       data.key = key; | ||||
|     } else { | ||||
|       data.id = id; | ||||
|     } | ||||
|     const res = await queryApp(data); | ||||
|     if (res.code === 200) { | ||||
|       const app = res.data; | ||||
|       const result = await installApp(app, { | ||||
|         appDir: '', | ||||
|         // kevisualUrl: 'https://kevisual.cn', | ||||
|         kevisualUrl: 'https://kevisual.xiongxiao.me', | ||||
|       }); | ||||
|       if (result.code === 200) { | ||||
|         console.log(chalk.green('下载成功', res.data?.user, res.data?.key)); | ||||
|       } else { | ||||
|         console.error(chalk.red(result.message || '下载失败')); | ||||
|       } | ||||
|     } else { | ||||
|       console.error(chalk.red(res.message || '下载失败')); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| const uninstallAppCommand = new Command('uninstall') | ||||
|   .alias('remove') | ||||
|   .description('卸载 app serve client的包') | ||||
|   .option('-i, --id <id>', 'user/key') | ||||
|   .action(async (options) => { | ||||
|     const id = options.id || ''; | ||||
|     if (!id) { | ||||
|       console.error(chalk.red('id is required')); | ||||
|       return; | ||||
|     } | ||||
|     const [user, key] = id.split('/'); | ||||
|     const data: any = {}; | ||||
|     if (user && key) { | ||||
|       data.user = user; | ||||
|       data.key = key; | ||||
|     } else { | ||||
|       console.error(chalk.red('id is required')); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const result = await uninstallApp( | ||||
|       { | ||||
|         user, | ||||
|         key, | ||||
|       }, | ||||
|       { | ||||
|         appDir: '', | ||||
|       }, | ||||
|     ); | ||||
|     if (result.code === 200) { | ||||
|       console.log(chalk.green('卸载成功', user, key)); | ||||
|     } else { | ||||
|       console.error(chalk.red(result.message || '卸载失败')); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| appCommand.addCommand(downloadAppCommand); | ||||
| appCommand.addCommand(uninstallAppCommand); | ||||
							
								
								
									
										51
									
								
								src/command/download.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/command/download.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| /** | ||||
|  * 下载 app serve client的包的命令 | ||||
|  */ | ||||
|  | ||||
| import { chalk } from '@/module/chalk.ts'; | ||||
| import { program, Command } from '../program.ts'; | ||||
| import { queryApp } from '../query/app-manager/query-app.ts'; | ||||
| import { installApp } from '@/module/download/install.ts'; | ||||
| export const downloadCommand = new Command('download').description('下载 app serve client的包').action(() => { | ||||
|   console.log('download'); | ||||
| }); | ||||
|  | ||||
| program.addCommand(downloadCommand); | ||||
|  | ||||
| const downloadAppCommand = new Command('app') | ||||
|   .description('下载 app serve client的包') | ||||
|   .option('-i, --id <id>', '下载 app serve client的包, id 或者user/key') | ||||
|   .option('-o, --output <output>', '下载 app serve client的包, 输出路径') | ||||
|   .action(async (options) => { | ||||
|     const id = options.id || ''; | ||||
|     if (!id) { | ||||
|       console.error(chalk.red('id is required')); | ||||
|       return; | ||||
|     } | ||||
|     const [user, key] = id.split('/'); | ||||
|     const data: any = {}; | ||||
|     if (user && key) { | ||||
|       data.user = user; | ||||
|       data.key = key; | ||||
|     } else { | ||||
|       data.id = id; | ||||
|     } | ||||
|     const res = await queryApp(data); | ||||
|     if (res.code === 200) { | ||||
|       const app = res.data; | ||||
|       const result = await installApp(app, { | ||||
|         appDir: '', | ||||
|         // kevisualUrl: 'https://kevisual.cn', | ||||
|         kevisualUrl: 'https://kevisual.xiongxiao.me', | ||||
|       }); | ||||
|       if (result.code === 200) { | ||||
|         console.log(chalk.green('下载成功', res.data?.user, res.data?.key)); | ||||
|       } else { | ||||
|         console.error(chalk.red(result.message || '下载失败')); | ||||
|       } | ||||
|     } else { | ||||
|       console.error(chalk.red(res.message || '下载失败')); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| downloadCommand.addCommand(downloadAppCommand); | ||||
| @@ -12,6 +12,8 @@ import './command/npm.ts'; | ||||
| import './command/publish.ts'; | ||||
| import './command/init.ts'; | ||||
| import './command/proxy.ts'; | ||||
| // import './command/download.ts'; | ||||
| import './command/app/index.ts'; | ||||
|  | ||||
| // program.parse(process.argv); | ||||
|  | ||||
|   | ||||
							
								
								
									
										128
									
								
								src/module/download/install.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/module/download/install.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| import path from 'path'; | ||||
| import fs from 'fs'; | ||||
|  | ||||
| type DownloadTask = { | ||||
|   downloadPath: string; | ||||
|   downloadUrl: string; | ||||
|   user: string; | ||||
|   key: string; | ||||
|   version: string; | ||||
| }; | ||||
| export type Package = { | ||||
|   id: string; | ||||
|   name?: string; | ||||
|   version?: string; | ||||
|   description?: string; | ||||
|   title?: string; | ||||
|   user?: string; | ||||
|   key?: string; | ||||
|   [key: string]: any; | ||||
| }; | ||||
| type InstallAppOpts = { | ||||
|   appDir?: string; | ||||
|   kevisualUrl?: string; | ||||
|   /** | ||||
|    * 是否是客户端, 下载到 assistant-config的下面 | ||||
|    */ | ||||
|   isClient?: boolean; | ||||
| }; | ||||
| export const installApp = async (app: Package, opts: InstallAppOpts = {}) => { | ||||
|   // const _app = demoData; | ||||
|   const { appDir = '', kevisualUrl = 'https://kevisual.cn' } = opts; | ||||
|   const _app = app; | ||||
|   try { | ||||
|     let files = _app.data.files || []; | ||||
|     const version = _app.version; | ||||
|     const user = _app.user; | ||||
|     const key = _app.key; | ||||
|  | ||||
|     const downFiles = files.map((file: any) => { | ||||
|       const noVersionPath = file.path.replace(`/${version}`, ''); | ||||
|       return { | ||||
|         ...file, | ||||
|         downloadPath: path.join(appDir, noVersionPath), | ||||
|         downloadUrl: `${kevisualUrl}/${noVersionPath}`, | ||||
|       }; | ||||
|     }); | ||||
|     const downloadTasks: DownloadTask[] = downFiles as any; | ||||
|     for (const file of downloadTasks) { | ||||
|       const downloadPath = file.downloadPath; | ||||
|       const downloadUrl = file.downloadUrl; | ||||
|       const dir = path.dirname(downloadPath); | ||||
|       if (!fs.existsSync(dir)) { | ||||
|         fs.mkdirSync(dir, { recursive: true }); | ||||
|       } | ||||
|       const res = await fetch(downloadUrl); | ||||
|       const blob = await res.blob(); | ||||
|       fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); | ||||
|     } | ||||
|     let indexHtml = files.find((file: any) => file.name === 'index.html'); | ||||
|     if (!indexHtml) { | ||||
|       files.push({ | ||||
|         name: 'index.html', | ||||
|         path: `${user}/${key}/index.html`, | ||||
|       }); | ||||
|       fs.writeFileSync(path.join(appDir, `${user}/${key}/index.html`), JSON.stringify(app, null, 2)); | ||||
|     } | ||||
|     _app.data.files = files; | ||||
|     return { | ||||
|       code: 200, | ||||
|       data: _app, | ||||
|       message: 'Install app success', | ||||
|     }; | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|     return { | ||||
|       code: 500, | ||||
|       message: 'Install app failed', | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| export const checkAppDir = (appDir: string) => { | ||||
|   const files = fs.readdirSync(appDir); | ||||
|   if (files.length === 0) { | ||||
|     fs.rmSync(appDir, { recursive: true }); | ||||
|   } | ||||
| }; | ||||
| export const checkFileExists = (path: string) => { | ||||
|   try { | ||||
|     fs.accessSync(path); | ||||
|     return true; | ||||
|   } catch (error) { | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
| type UninstallAppOpts = { | ||||
|   appDir?: string; | ||||
| }; | ||||
| export const uninstallApp = async (app: Partial<Package>, opts: UninstallAppOpts = {}) => { | ||||
|   const { appDir = '' } = opts; | ||||
|   try { | ||||
|     const { user, key } = app; | ||||
|     const keyDir = path.join(appDir, user, key); | ||||
|     const parentDir = path.join(appDir, user); | ||||
|     if (!checkFileExists(appDir) || !checkFileExists(keyDir)) { | ||||
|       return { | ||||
|         code: 200, | ||||
|         message: 'uninstall app success', | ||||
|       }; | ||||
|     } | ||||
|     try { | ||||
|       // 删除appDir和文件 | ||||
|       fs.rmSync(keyDir, { recursive: true }); | ||||
|     } catch (error) { | ||||
|       console.error(error); | ||||
|     } | ||||
|     checkAppDir(parentDir); | ||||
|     return { | ||||
|       code: 200, | ||||
|       message: 'Uninstall app success', | ||||
|     }; | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|     return { | ||||
|       code: 500, | ||||
|       message: 'Uninstall app failed', | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										16
									
								
								src/query/app-manager/query-app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/query/app-manager/query-app.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { query } from '@/module/query.ts'; | ||||
|  | ||||
| type QueryAppParams = { | ||||
|   id?: string; | ||||
|   user?: string; | ||||
|   key?: string; | ||||
| }; | ||||
| export const queryApp = async (params: QueryAppParams) => { | ||||
|   return await query.post({ | ||||
|     path: 'app', | ||||
|     key: 'getApp', | ||||
|     data: { | ||||
|       ...params, | ||||
|     }, | ||||
|   }); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user