"feat: 升级本地应用管理依赖,新增应用删除功能及强制覆盖下载选项"
This commit is contained in:
parent
035ddc248c
commit
e9eedcd1bd
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user