From ce9b43b4977c048837893279a7496bbc14156e47 Mon Sep 17 00:00:00 2001 From: xion Date: Sun, 9 Mar 2025 10:35:13 +0800 Subject: [PATCH] feat: add app download --- package.json | 2 +- src/command/app/index.ts | 88 ++++++++++++++++++++ src/command/download.ts | 51 ++++++++++++ src/index.ts | 2 + src/module/download/install.ts | 128 +++++++++++++++++++++++++++++ src/query/app-manager/query-app.ts | 16 ++++ 6 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/command/app/index.ts create mode 100644 src/command/download.ts create mode 100644 src/module/download/install.ts create mode 100644 src/query/app-manager/query-app.ts diff --git a/package.json b/package.json index 7f98b42..fc8433d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/envision-cli", - "version": "0.0.29-alpha.1", + "version": "0.0.29-alpha.2", "description": "envision command tools", "main": "dist/index.js", "type": "module", diff --git a/src/command/app/index.ts b/src/command/app/index.ts new file mode 100644 index 0000000..e9ca105 --- /dev/null +++ b/src/command/app/index.ts @@ -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 ', '下载 app serve client的包, id 或者user/key') + .option('-o, --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 ', '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); \ No newline at end of file diff --git a/src/command/download.ts b/src/command/download.ts new file mode 100644 index 0000000..7c9ce6d --- /dev/null +++ b/src/command/download.ts @@ -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 ', '下载 app serve client的包, id 或者user/key') + .option('-o, --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); diff --git a/src/index.ts b/src/index.ts index 08f3bed..ea1ecb3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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); diff --git a/src/module/download/install.ts b/src/module/download/install.ts new file mode 100644 index 0000000..57fd047 --- /dev/null +++ b/src/module/download/install.ts @@ -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, 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', + }; + } +}; diff --git a/src/query/app-manager/query-app.ts b/src/query/app-manager/query-app.ts new file mode 100644 index 0000000..deb1c41 --- /dev/null +++ b/src/query/app-manager/query-app.ts @@ -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, + }, + }); +};