feat: add silky cli tools

This commit is contained in:
熊潇 2025-04-27 22:16:09 +08:00
parent 75d181ef43
commit 6867de838e
42 changed files with 3867 additions and 251 deletions

View File

@ -9,3 +9,5 @@ assistant-app
.env*
!.env*example
libs

0
assistant/bin/assistant.js Normal file → Executable file
View File

View File

@ -0,0 +1,26 @@
import path from 'node:path';
import pkg from './package.json';
import fs from 'node:fs';
// bun run src/index.ts --
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
*
* @param {string} p
* @returns
*/
export const w = (p) => path.join(__dirname, p);
await Bun.build({
target: 'node',
format: 'esm',
entrypoints: [w('./src/lib.ts')],
outdir: w('./libs'),
naming: {
entry: 'assistant-lib.mjs',
},
external: ['pm2'],
define: {
ENVISION_VERSION: JSON.stringify(pkg.version),
},
env: 'ENVISION_*',
});

View File

@ -20,16 +20,27 @@
"scripts": {
"dev": "bun run src/run.ts ",
"dev:server": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/run-server.ts",
"build:lib": "bun run bun-lib.config.mjs",
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",
"build": "rimraf dist && bun run bun.config.mjs"
},
"bin": {
"ev-assistant": "bin/assistant.js",
"ev-asst": "bin/assistant.js"
},
"exports": {
".": {
"import": "./dist/assistant.mjs"
},
"./lib": {
"import": "./libs/assistant-lib.mjs",
"types": "./libs/assistant-lib.d.ts"
}
},
"devDependencies": {
"@kevisual/ai-center": "^0.0.3",
"@kevisual/load": "^0.0.6",
"@kevisual/local-app-manager": "^0.1.16",
"@kevisual/local-app-manager": "^0.1.17",
"@kevisual/query": "0.0.17",
"@kevisual/query-login": "0.0.5",
"@kevisual/router": "^0.0.13",
@ -54,6 +65,7 @@
"pino-pretty": "^13.0.0",
"send": "^1.2.0",
"supports-color": "^10.0.0",
"tslib": "^2.8.1",
"ws": "npm:@kevisual/ws",
"zustand": "^5.0.3"
},
@ -64,6 +76,7 @@
"access": "public"
},
"dependencies": {
"esbuild": "^0.25.3",
"pm2": "^6.0.5"
}
}

3123
assistant/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
packages:
- 'tasks/**'

View File

@ -0,0 +1,36 @@
import { program, Command, assistantConfig } from '@/program.ts';
import { AppDownload } from '@/services/app/index.ts';
const appManagerCommand = new Command('app').description('本地的应用模块的安装和下载, 分为 app 和 web 两种类型');
program.addCommand(appManagerCommand);
const downloadCommand = new Command('download')
.description('下载应用')
.option('-i, --id <id>', '应用名称')
.option('-t, --type <type>', '应用类型', 'web')
.option('-r, --registry <registry>', '应用源 https://kevisual.cn')
.action(async (options) => {
const { id, type } = options;
assistantConfig.checkMounted();
const registry = options.registry || assistantConfig.getRegistry();
// console.log('registry', registry);
const app = new AppDownload(assistantConfig);
if (id) {
await app.downloadApp({ id, type, registry });
}
});
appManagerCommand.addCommand(downloadCommand);
const deleteCommand = new Command('delete')
.description('删除应用')
.option('-i, --id <id>', '应用名称')
.option('-t, --type <type>', '应用类型', 'web')
.action(async (options) => {
const { id, type } = options;
const app = new AppDownload(assistantConfig);
if (id) {
await app.deleteApp({ id, type });
}
});
appManagerCommand.addCommand(deleteCommand);

View File

@ -2,6 +2,7 @@ import { program, runProgram } from '@/program.ts';
import './command/config-manager/index.ts';
import './command/app-manager/index.ts';
import './command/asst-server/index.ts';
import './command/app/index.ts';
/**
*

2
assistant/src/lib.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './module/assistant/index.ts';
export { AssistantInit } from './services/init/index.ts';

View File

@ -60,8 +60,9 @@ export const initConfig = (configRootPath: string) => {
};
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
type AssistantConfigData = {
pageApi?: string; // https://kevisual.silkyai.cn
export type AssistantConfigData = {
pageApi?: string; // https://kevisual.cn
registry?: string; // https://kevisual.cn
proxy?: ProxyInfo[];
apiProxyList?: ProxyInfo[];
description?: string;
@ -135,6 +136,10 @@ export class AssistantConfig {
}
return this.getConfig();
}
getRegistry() {
const config = this.getCacheAssistantConfig();
return config?.registry || config?.pageApi;
}
/**
* assistant-config.json
* @param config
@ -211,20 +216,24 @@ export class AssistantConfig {
* pem: 证书目录
* @param configDir
*/
static detectConfigDir(configDir?: string) {
static detectConfigDir(configDir?: string, deep = 3) {
const checkConfigDir = path.resolve(configDir || process.env.ASSISTANT_CONFIG_DIR || process.cwd());
const configPath = path.join(checkConfigDir, 'assistant-app');
if (checkFileExists(configPath)) {
return path.join(checkConfigDir);
}
if (deep >= 2) {
const lastConfigPath = path.join(checkConfigDir, '..', 'assistant-app');
if (checkFileExists(lastConfigPath)) {
return path.join(checkConfigDir, '..');
}
}
if (deep >= 3) {
const lastConfigPath2 = path.join(checkConfigDir, '../..', 'assistant-app');
if (checkFileExists(lastConfigPath2)) {
return path.join(checkConfigDir, '../..');
}
}
// 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录
return checkConfigDir;
}

View File

@ -1,4 +1,3 @@
export * from './install/index.ts';
export * from './config/index.ts';
export * from './file/index.ts';

View File

@ -1,121 +0,0 @@
import path from 'node:path';
import fs from 'node:fs';
import { checkFileExists } from '../file/index.ts';
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的下面
*/
};
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 });
}
};
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

View File

@ -1 +1,124 @@
// app downlaod
import { checkFileExists, AssistantConfig } from '@/module/assistant/index.ts';
import path from 'path';
import fs from 'fs';
import inquirer from 'inquirer';
import { spawnSync } from 'child_process';
export const runCommand = (command: string, args: string[]) => {
const result = spawnSync(command, args, {
stdio: 'inherit',
shell: true,
});
if (result.error) {
console.error('Error executing command:', result.error);
throw result.error;
}
if (result.status !== 0) {
console.error('Command failed with status:', result.status);
throw new Error(`Command failed with status: ${result.status}`);
}
return result;
};
export type appType = 'web' | 'app';
type DownloadAppOptions = {
/**
*
* @type {string}
* @example user/app
* @description
*/
id: string;
type?: appType;
registry?: string;
/**
* , user/app的 app
*/
appName?: string;
};
type DeleteAppOptions = {
/**
*
* @type {string}
* @example user/app
* @description
*/
id: string;
type?: appType;
appName?: string;
};
export class AppDownload {
config: AssistantConfig;
constructor(config: AssistantConfig) {
this.config = config;
}
async downloadApp(opts: DownloadAppOptions) {
const { id, type = 'web' } = opts;
const configDir = this.config.configDir;
this.config?.checkMounted();
const appsDir = this.config.configPath?.appsDir;
const pageDir = this.config.configPath?.pageDir;
if (!id) {
throw new Error('应用名称不能为空');
}
const command = 'ev';
const args = ['app', 'download'];
args.push('-i', id);
if (type) {
args.push('-t', type);
}
const appName = opts?.appName || id.split('/').pop();
if (type === 'web') {
args.push('-o', pageDir);
} else if (type === 'app') {
args.push('-o', path.join(appsDir, appName));
} else {
throw new Error('应用类型错误,只能是 web 或 app');
}
if (opts.registry) {
args.push('-r', opts.registry);
}
return runCommand(command, args);
}
async confirm(message?: string) {
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: message || '是否继续删除应用?',
default: false,
},
]);
return confirm;
}
async deleteApp(opts: DeleteAppOptions) {
const { id, type = 'web' } = opts;
const appName = opts?.appName || id.split('/').pop();
this.config?.checkMounted();
const appsDir = this.config.configPath?.appsDir;
const pageDir = this.config.configPath?.pageDir;
if (!id) {
throw new Error('应用名称不能为空');
}
let deletePath = '';
let isDelete = false;
if (type === 'web') {
// 直接删除路径就行
const pagePath = path.join(pageDir, id);
deletePath = pagePath;
} else if (type === 'app') {
const appPath = path.join(appsDir, appName);
deletePath = appPath;
}
if (deletePath && checkFileExists(deletePath)) {
const confirm = await this.confirm(`是否删除 ${deletePath} 应用?`);
if (!confirm) {
console.log('取消删除应用');
return;
}
fs.rmSync(deletePath, { recursive: true });
isDelete = true;
console.log(`删除应用成功: ${deletePath}`);
}
}
}

View File

@ -1,6 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { checkFileExists, AssistantConfig } from '@/module/assistant/index.ts';
import { checkFileExists, AssistantConfig, AssistantConfigData } from '@/module/assistant/index.ts';
import { chalk } from '@/module/chalk.ts';
import { HttpsPem } from '@/module/assistant/https/sign.ts';
export type AssistantInitOptions = {
@ -66,13 +66,16 @@ export class AssistantInit extends AssistantConfig {
const assistantPath = this.configPath?.configPath;
// 创建助手配置文件 assistant-config.json
if (!checkFileExists(assistantPath, true)) {
this.setConfig({
this.setConfig(this.getDefaultInitAssistantConfig());
console.log(chalk.green('助手配置文件assistant-config.json创建成功'));
}
}
protected getDefaultInitAssistantConfig() {
return {
description: '助手配置文件',
home: '/root/center',
proxy: [],
apiProxyList: [],
});
console.log(chalk.green('助手配置文件assistant-config.json创建成功'));
}
} as AssistantConfigData;
}
}

View File

@ -2,7 +2,7 @@ import { execSync } from 'node:child_process';
export const TaskCommandType = ['npm-install'] as const;
export type TaskCommand = {
key?: string;
key?: any;
/**
*
*/
@ -30,10 +30,38 @@ export type TaskCommand = {
*
*/
beforeCheck?: string;
tags?: string[];
meta?: any;
};
type RunTaskResult = {
code?: number;
message?: string;
data?: any;
output?: string;
task?: TaskCommand;
};
type TaskCommandOptions = {
isDebug?: boolean;
isLog?: boolean;
};
export class TasksCommand {
tasks: Map<string, TaskCommand> = new Map();
constructor() {}
tasks: Map<string | number, TaskCommand> = new Map();
isDebug: boolean = false;
isLog: boolean = false;
constructor(opts?: TaskCommandOptions) {
this.isDebug = opts?.isDebug ?? false;
this.isLog = opts?.isLog ?? false;
}
log(...args: any[]) {
if (this.isLog) {
console.log(...args);
}
}
debug(...args: any[]) {
if (this.isDebug) {
console.log(...args);
}
}
addTask(task: TaskCommand, run?: boolean) {
const key = task?.key || task?.description;
if (!key) {
@ -45,12 +73,17 @@ export class TasksCommand {
}
return this;
}
getTask(name: string) {
getTask(name: string | number) {
return this.tasks.get(name);
}
runTask(name: string) {
const task = this.getTask(name);
const end = (data?: { code: number; [key: string]: any }) => {
runTask(taskName: string | number | TaskCommand): RunTaskResult {
let task: TaskCommand | undefined;
if (typeof taskName === 'string' || typeof taskName === 'number') {
task = this.getTask(taskName);
} else if (typeof taskName === 'object') {
task = taskName;
}
const end = (data?: RunTaskResult) => {
return {
...data,
task,
@ -59,7 +92,8 @@ export class TasksCommand {
if (!task) {
return {
code: 500,
message: `没有找到 ${name} 这个任务`,
message: `没有找到这个任务`,
task: task,
};
}
let { command, before, after, afterCheck, beforeCheck, type } = task;
@ -68,8 +102,9 @@ export class TasksCommand {
}
if (before) {
const res = this.runCommand(before);
console.log('before', res, beforeCheck, this.checkForContainue(res, beforeCheck));
if (!this.checkForContainue(res, beforeCheck)) {
const checkForContainue = this.checkForContainue(res, beforeCheck);
this.debug('运行前检测', { runCommandResult: res, beforeCheck, checkForContainue });
if (!checkForContainue) {
return end({
code: 200,
message: `当前任务不需要执行, ${command}`,
@ -77,12 +112,14 @@ export class TasksCommand {
}
}
const res = this.runCommand(command);
console.log('runCommand', res);
this.debug(`执行任务:${command}`, { runCommandResult: res });
if (res.code !== 200) {
this.debug('当前任务执行失败:', { runCommandResult: res });
return end(res);
}
let checkText = res.data || '';
if (after) {
this.debug('执行后检测是否成功:', { after });
const res = this.runCommand(after);
if (res.code !== 200) {
return end(res);
@ -91,10 +128,11 @@ export class TasksCommand {
}
if (afterCheck) {
const isSuccess = checkText?.includes?.(afterCheck);
this.debug('执行后检测是否成功:', { afterCheck, checkText, isSuccess });
return end({
code: isSuccess ? 200 : 500,
output: res.data,
check: afterCheck,
data: afterCheck,
message: isSuccess ? `当前任务执行成功, ${command}` : `当前任务执行失败, ${command}`,
});
}
@ -139,4 +177,16 @@ export class TasksCommand {
};
}
}
getTasksArray() {
const tasks = Array.from(this.tasks.values());
return tasks;
}
/**
*
* @param tag
*/
getTasksArrayByTag(tag?: string) {
const tasks = Array.from(this.tasks.values());
return tasks.filter((task) => !tag || task.tags?.includes(tag) || task.meta?.tags?.includes(tag) || task.key === tag);
}
}

View File

@ -1,11 +1,12 @@
{
"name": "@kevisual/task-command",
"version": "0.0.2",
"version": "0.0.7",
"description": "",
"types": "mod.d.ts",
"scripts": {
"dts": "dts -i mod.ts -o mod.d.ts -d .",
"build": "bun run bun.config.mjs"
"build": "bun run bun.config.mjs",
"postbuild": "dts -i mod.ts -o mod.d.ts -d ."
},
"keywords": [],
"publishConfig": {

View File

@ -0,0 +1,2 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@ -0,0 +1,4 @@
#!/usr/bin/env node
import { runParser } from '../dist/silky.mjs';
runParser(process.argv);

View File

@ -0,0 +1,26 @@
import path from 'node:path';
import pkg from './package.json';
import fs from 'node:fs';
// bun run src/index.ts --
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
*
* @param {string} p
* @returns
*/
export const w = (p) => path.join(__dirname, p);
await Bun.build({
target: 'node',
format: 'esm',
entrypoints: [w('./src/index.ts')],
outdir: w('./dist'),
naming: {
entry: 'silky.mjs',
},
external: ['pm2'],
define: {
ENVISION_VERSION: JSON.stringify(pkg.version),
},
env: 'ENVISION_*',
});

View File

@ -0,0 +1,40 @@
{
"name": "@kevisual/silky-cli",
"version": "0.0.3",
"description": "",
"main": "index.js",
"scripts": {
"dev": "bun run src/run.ts ",
"build": "bun run bun.config.mjs"
},
"bin": {
"silky": "./bin/silky.js"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.9.0",
"type": "module",
"publishConfig": {
"access": "public"
},
"files": [
"bin",
"dist"
],
"devDependencies": {
"@kevisual/assistant-cli": "workspace:*",
"@kevisual/task-command": "^0.0.7",
"chalk": "^5.4.1",
"commander": "^13.1.0",
"cross-env": "^7.0.3",
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"inquirer": "^12.6.0",
"lodash-es": "^4.17.21",
"nanoid": "^5.1.5"
},
"dependencies": {
"pm2": "^6.0.5"
}
}

View File

@ -0,0 +1,2 @@
import './init.ts'
import './init-env.ts'

View File

@ -0,0 +1,68 @@
import { program, Command } from '@/program.ts';
import { TaskKey, silkyCommand } from '@/mdoules/talkshow.ts';
const description = `初始化运行环境,安装
1. @kevisual/cli
2. pm2
deno
bun
`;
const isDev = process.env.NODE_ENV === 'development';
const testEnv = new Command('init-env')
.description(description)
.option('-a --all')
.action((options) => {
let tag = options.all ? 'env' : 'must-env';
silkyCommand.isDebug = isDev ?? false;
const envTask = silkyCommand.getTasksArrayByTag(tag);
for (const value of envTask) {
try {
const res = silkyCommand.runTask(value);
if (res.code === 200) {
console.log('执行结果:', res.code, res.message);
} else {
console.log('执行结果错误:', res.task?.description, res.message);
}
} catch (error) {
console.log('error', error);
continue;
}
}
});
program.addCommand(testEnv);
const appDownloadDesc = `下载安装talkshow的应用模块
1. root/talkshow-admin
2. root/talkshow-code-center
3. root/center
`;
const appDownloadTask = new Command('init-talkshow')
.description(appDownloadDesc)
.option('-a --all', '下载所有应用模块或者只下载talkshow相关的')
.action((options) => {
//
let tag = options.all ? 'app' : 'talkshow';
const appTask = silkyCommand.getTasksArrayByTag(tag);
silkyCommand.isDebug = true;
for (const value of appTask) {
try {
const res = silkyCommand.runTask(value);
if (res.code === 200) {
console.log('执行结果:', res.task?.description, res.code);
} else {
console.log('执行结果错误:', res.task?.description, res.message);
}
} catch (error) {
console.log('error', error);
continue;
}
}
});
program.addCommand(appDownloadTask);

View File

@ -0,0 +1,25 @@
import { program, Command } from '@/program.ts';
import path from 'node:path';
import { AssistantInit } from '@/services/init/index.ts';
type InitCommandOptions = {
path?: string;
};
const Init = new Command('init')
.description('初始化一个助手客户端,生成配置文件。')
.option('-p --path <path>', '助手路径,默认为执行命令的目录,如果助手路径不存在则创建。')
.action((opts: InitCommandOptions) => {
// 如果path参数存在检测path是否是相对路径如果是相对路径则转换为绝对路径
if (opts.path && !opts.path.startsWith('/')) {
opts.path = path.join(process.cwd(), opts.path);
} else if (opts.path) {
opts.path = path.resolve(opts.path);
}
const configDir = AssistantInit.detectConfigDir(opts.path);
const assistantInit = new AssistantInit({
path: configDir,
});
assistantInit.init();
});
program.addCommand(Init);

View File

@ -0,0 +1,7 @@
import { AssistantConfig } from '@kevisual/assistant-cli/lib';
export const configDir = AssistantConfig.detectConfigDir(null, 1);
export const assistantConfig = new AssistantConfig({
configDir,
init: false,
});

View File

@ -0,0 +1,19 @@
import { program, runProgram } from '@/program.ts';
/**
*
* args[0] , example: node
* args[1] , example: index.ts
* @param argv
*/
export const runParser = async (argv: string[]) => {
// program.parse(process.argv);
// console.log('argv', argv);
try {
program.parse(argv);
} catch (error) {
console.error('执行错误:', error.message);
}
};
export { runProgram };

View File

@ -0,0 +1,116 @@
import { TasksCommand } from '@kevisual/task-command/mod.ts';
export const silkyCommand = new TasksCommand();
export const enum TaskKey {
ev = 1,
pm2,
deno,
bun,
silkyInit,
appCenter,
appTalkshowAdmin,
appTalkshow,
}
export const init = {
description: '安装依赖',
key: TaskKey.ev,
command: 'npm install -g @kevisual/cli --registry=https://registry.npmmirror.com',
type: 'npm-install',
before: 'ev -v',
beforeCheck: '.',
tags: ['env', 'must-env'],
};
silkyCommand.addTask(init);
const init1 = {
description: '安装 pm2',
type: 'npm-install',
key: TaskKey.pm2,
command: 'npm install -g pm2 --registry=https://registry.npmmirror.com',
before: 'pm2 -v',
beforeCheck: '.',
tags: ['env', 'must-env'],
};
silkyCommand.addTask(init1);
const init2 = {
description: '安装 deno',
command: 'npm install -g deno --registry=https://registry.npmmirror.com',
key: TaskKey.deno,
type: 'npm-install',
before: 'deno -v',
beforeCheck: 'deno',
tags: ['env'],
};
silkyCommand.addTask(init2);
const init3 = {
description: '安装 bun',
type: 'npm-install',
key: TaskKey.bun,
command: 'npm install -g bun --registry=https://registry.npmmirror.com',
before: 'bun -v',
beforeCheck: '.',
tags: ['env'],
};
silkyCommand.addTask(init3);
// ===========================
// 安装talkshow的程序到本地
const talkInit = {
description: '初始化一个助手客户端,生成配置文件。',
key: TaskKey.silkyInit,
command: 'silky init',
};
silkyCommand.addTask(talkInit);
const task1 = {
description: '下载前端应用 root/center 应用',
key: TaskKey.appCenter,
command: 'asst app download -i root/center',
tags: ['app', 'root'],
};
silkyCommand.addTask(task1);
const task2 = {
description: '下载前端应用 root/talkshow-admin 应用',
key: TaskKey.appTalkshowAdmin,
command: 'asst app download -i root/talkshow-admin',
tags: ['app', 'talkshow'],
};
silkyCommand.addTask(task2);
const task3 = {
description: '安装后端应用 root/talkshow-code-center 应用',
key: TaskKey.appTalkshow,
command: 'asst app download -i root/talkshow-code-center -t app',
tags: ['app', 'talkshow'],
};
silkyCommand.addTask(task3);
// ===========================
const testTask = {
description: '测试',
command: 'npm i -g nodemon --registry=https://registry.npmmirror.com',
type: 'npm-install',
before: 'nodemon -v',
beforeCheck: '.',
key: 'test-task',
};
silkyCommand.addTask(testTask);
const runTask1 = async () => {
const tasksCommand = new TasksCommand();
tasksCommand.addTask(init2);
const res = tasksCommand.runTask(init2.description);
console.log(res);
};
// runTask1();
const runTestTask = async () => {
const tasksCommand = new TasksCommand();
tasksCommand.addTask(testTask);
const res = tasksCommand.runTask(testTask);
console.log(res);
return res;
};
// runTestTask();

View File

@ -0,0 +1,24 @@
import { program, Command } from 'commander';
import fs from 'fs';
import './command/check/index.ts';
// 将多个子命令加入主程序中
let version = '0.0.1';
try {
// @ts-ignore
if (ENVISION_VERSION) version = ENVISION_VERSION;
} catch (e) {}
// @ts-ignore
program.name('silky').description('A CLI tool with silky').version(version, '-v, --version', 'output the current version');
export { program, Command };
/**
*
*
* @param args
*/
export const runProgram = (args: string[]) => {
const [_app, _command] = process.argv;
program.parse([_app, _command, ...args]);
};

View File

@ -0,0 +1,6 @@
import { runParser } from './index.ts';
/**
* test run parser
*/
runParser(process.argv);

View File

@ -0,0 +1,12 @@
import { AssistantInit as BaseAssistantInit } from '@kevisual/assistant-cli/lib';
export class AssistantInit extends BaseAssistantInit {
constructor(options: { path?: string }) {
super(options);
}
protected getDefaultInitAssistantConfig() {
const config = super.getDefaultInitAssistantConfig();
config.registry = 'https://kevisual.silkyai.cn';
return config;
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "@kevisual/types/json/backend.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
},
},
"include": [
"src/**/*",
],
}

View File

@ -1,74 +0,0 @@
import { TasksCommand } from 'https://esm.xiongxiao.me/@kevisual/task-command/mod.ts';
// import { TasksCommand } from '../../task-command/mod.ts';
const init = {
description: '安装依赖',
command: 'npm install -g @kevisual/cli --registry=https://registry.npmmirror.com',
afterCheck: 'added',
};
const init1 = {
description: '安装 pm2',
type: 'npm-install',
command: 'npm install -g pm2 --registry=https://registry.npmmirror.com',
before: 'pm2 -v',
};
const init2 = {
description: '安装 deno',
command: 'npm install -g deno --registry=https://registry.npmmirror.com',
type: 'npm-install',
before: 'deno -v',
beforeCheck: 'deno',
};
const init3 = {
description: '安装 bun',
type: 'npm-install',
command: 'npm install -g bun --registry=https://registry.npmmirror.com',
beforeCheck: 'bun -v',
};
// ===========================
// 安装talkshow的程序到本地
const talk = {
description: '设置默认的 base registry 地址 为 https://kevisual.silkyai.cn',
command: 'ev base -s https://kevisual.silkyai.cn',
};
const talkInit = {
description: '初始化一个助手客户端,生成配置文件。',
command: 'asst init',
};
const task1 = {
description: '下载前端应用 root/center 应用',
command: 'ev app download -i root/center -o assistant-app/page',
};
const task2 = {
description: '下载前端应用 root/talkshow-admin 应用',
command: 'ev app download -i root/talkshow-admin -o assistant-app/page',
};
const task3 = {
description: '安装后端应用 root/talkshow-code-center 应用',
command: 'ev app download -i root/talkshow-code-center -t app -o assistant-app/apps/talkshow-code-center',
};
// ===========================
const runTask1 = async () => {
const tasksCommand = new TasksCommand();
tasksCommand.addTask(init2);
const res = tasksCommand.runTask(init2.description);
console.log(res);
};
// runTask1();
const runTestTask = async () => {
const tasksCommand = new TasksCommand();
const task = {
description: 'test',
command: 'npm i -g rollup --registry=https://registry.npmmirror.com',
type: 'npm-install',
};
tasksCommand.addTask(task);
const res = tasksCommand.runTask(task.description);
console.log(res);
return res;
};
runTestTask();

View File

@ -2,6 +2,10 @@
"extends": "@kevisual/types/json/backend.json",
"compilerOptions": {
"baseUrl": ".",
"module": "NodeNext",
"outDir": "./libs",
"allowImportingTsExtensions": true,
"noEmit": true,
"paths": {
"@/*": [
"src/*"

0
bin/assistant.js Normal file → Executable file
View File

View File

@ -27,7 +27,8 @@
"scripts": {
"dev": "bun run src/run.ts ",
"build": "rimraf dist && bun run bun.config.mjs",
"dts": "dts-bundle-generator --external-inlines=@types/jsonwebtoken src/index.ts -o dist/index.d.ts "
"dts": "dts-bundle-generator --external-inlines=@types/jsonwebtoken src/index.ts -o dist/index.d.ts ",
"build:all": "rimraf dist && bun run bun.config.mjs && bun run assistant/bun.config.mjs"
},
"keywords": [
"kevisual",

10
pnpm-lock.yaml generated
View File

@ -92,8 +92,8 @@ importers:
specifier: ^0.0.6
version: 0.0.6
'@kevisual/local-app-manager':
specifier: ^0.1.16
version: 0.1.16(@kevisual/router@0.0.13)(@kevisual/types@0.0.7)(@kevisual/use-config@1.0.11(dotenv@16.5.0))(pm2@6.0.5(supports-color@10.0.0))
specifier: ^0.1.17
version: 0.1.17(@kevisual/router@0.0.13)(@kevisual/types@0.0.7)(@kevisual/use-config@1.0.11(dotenv@16.5.0))(pm2@6.0.5(supports-color@10.0.0))
'@kevisual/query':
specifier: 0.0.17
version: 0.0.17(@kevisual/ws@8.0.0)(encoding@0.1.13)
@ -535,8 +535,8 @@ packages:
'@kevisual/load@0.0.6':
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
'@kevisual/local-app-manager@0.1.16':
resolution: {integrity: sha512-PiXjL6bFuWyyEgzCaM6omDm7cFYeUMv7SFJ+Axg88RAamqpSHaIhRxvj2wfd+HAh089a16o8oRF9V62cWaURDQ==}
'@kevisual/local-app-manager@0.1.17':
resolution: {integrity: sha512-0Ye+GwxPd9FwaICNJoG5avkScVZ9OnTtUfskFFA6UBiSJ7MT4ZBhS2dzwU4o2Yl6mV951M7rXN5Kbs08pYJWUg==}
peerDependencies:
'@kevisual/router': ^0.0.6
'@kevisual/types': ^0.0.1
@ -2623,7 +2623,7 @@ snapshots:
dependencies:
eventemitter3: 5.0.1
'@kevisual/local-app-manager@0.1.16(@kevisual/router@0.0.13)(@kevisual/types@0.0.7)(@kevisual/use-config@1.0.11(dotenv@16.5.0))(pm2@6.0.5(supports-color@10.0.0))':
'@kevisual/local-app-manager@0.1.17(@kevisual/router@0.0.13)(@kevisual/types@0.0.7)(@kevisual/use-config@1.0.11(dotenv@16.5.0))(pm2@6.0.5(supports-color@10.0.0))':
dependencies:
'@kevisual/router': 0.0.13
'@kevisual/types': 0.0.7

View File

@ -9,6 +9,9 @@ import { checkAppDir, installApp, uninstallApp } from '@/module/download/install
import { fileIsExist } from '@/uitls/file.ts';
import fs from 'fs';
import { getConfig } from '@/module/get-config.ts';
import path from 'path';
import inquirer from 'inquirer';
import { baseURL, getUrl } from '@/module/query.ts';
export const appCommand = new Command('app').description('app 命令').action(() => {
console.log('app');
});
@ -55,7 +58,6 @@ const downloadAppCommand = new Command('download')
} else {
data.id = id;
}
const res = await queryApp(data);
let registry = 'https://kevisual.cn';
if (options?.registry) {
registry = new URL(options.registry).origin;
@ -63,6 +65,8 @@ const downloadAppCommand = new Command('download')
const config = getConfig();
registry = new URL(config.baseURL).origin;
}
const res = await queryApp(data, { url: getUrl(registry) });
console.log('registry', registry, data);
if (res.code === 200) {
const app = res.data;
let appType: 'app' | 'web' = 'web';
@ -88,9 +92,36 @@ const downloadAppCommand = new Command('download')
const uninstallAppCommand = new Command('uninstall')
.alias('remove')
.description('卸载 app serve client的包')
.description('卸载 app serve client的包。 手动删除更简单。')
.option('-i, --id <id>', 'user/key')
.option('-t, --type <type>', 'app或者web 默认为web', 'web')
.option('-p, --path <path>', '删除的路径, 如果存在,则优先执行,不会去判断 id 和 type 。')
.action(async (options) => {
if (options.path) {
const _path = path.resolve(options.path);
try {
const checkPath = fileIsExist(_path);
if (!checkPath) {
console.error(chalk.red('path is error, 请输入正确的路径'));
} else {
const answer = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `确定要删除 ${_path} 吗?`,
default: false,
},
]);
if (answer.confirm) {
fs.rmSync(_path, { recursive: true });
console.log(chalk.green('删除成功', _path));
}
}
} catch (e) {
console.error(chalk.red('删除失败', e));
}
return;
}
const id = options.id || '';
if (!id) {
console.error(chalk.red('id is required'));
@ -102,7 +133,7 @@ const uninstallAppCommand = new Command('uninstall')
data.user = user;
data.key = key;
} else {
console.error(chalk.red('id is required'));
console.error(chalk.red('id is required user/key'));
return;
}
@ -113,6 +144,7 @@ const uninstallAppCommand = new Command('uninstall')
},
{
appDir: '',
type: options.type,
},
);
if (result.code === 200) {

View File

@ -105,11 +105,17 @@ export const installApp = async (app: Package, opts: InstallAppOpts = {}) => {
};
}
};
/**
*
* @param appDir
*/
export const checkAppDir = (appDir: string) => {
try {
const files = fs.readdirSync(appDir);
if (files.length === 0) {
fs.rmSync(appDir, { recursive: true });
}
} catch (error) {}
};
export const checkFileExists = (path: string) => {
try {
@ -121,9 +127,10 @@ export const checkFileExists = (path: string) => {
};
type UninstallAppOpts = {
appDir?: string;
type?: 'app' | 'web';
};
export const uninstallApp = async (app: Partial<Package>, opts: UninstallAppOpts = {}) => {
const { appDir = '' } = opts;
const { appDir = '', type = 'web' } = opts;
try {
const { user, key } = app;
const keyDir = path.join(appDir, user, key);
@ -140,7 +147,7 @@ export const uninstallApp = async (app: Partial<Package>, opts: UninstallAppOpts
} catch (error) {
console.error(error);
}
checkAppDir(parentDir);
type === 'web' && checkAppDir(parentDir);
return {
code: 200,
message: 'Uninstall app success',

View File

@ -45,3 +45,12 @@ export const queryLogin = new QueryLoginNode({
// console.log('onLoad');
},
});
/**
*
* @param url
* @returns
*/
export const getUrl = (url: string) => {
return new URL('/api/router', url).href;
};

View File

@ -5,12 +5,15 @@ type QueryAppParams = {
user?: string;
key?: string;
};
export const queryApp = async (params: QueryAppParams) => {
return await query.post({
export const queryApp = async (params: QueryAppParams, opts?: any) => {
return await query.post(
{
path: 'app',
key: 'getApp',
data: {
...params,
},
});
},
opts,
);
};