feat: add app download

This commit is contained in:
熊潇 2025-03-09 10:35:13 +08:00
parent af18b39248
commit ce9b43b497
6 changed files with 286 additions and 1 deletions

View File

@ -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",

88
src/command/app/index.ts Normal file
View 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
View 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);

View File

@ -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);

View 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',
};
}
};

View 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,
},
});
};