chore: 添加 commander 模块及相关命令行工具功能
This commit is contained in:
@@ -14,3 +14,5 @@ await buildWithBun({ naming: 'router-simple', entry: 'src/router-simple.ts', dts
|
|||||||
await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true, external });
|
await buildWithBun({ naming: 'opencode', entry: 'src/opencode.ts', dts: true, external });
|
||||||
|
|
||||||
await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true, external });
|
await buildWithBun({ naming: 'ws', entry: 'src/ws.ts', dts: true, external });
|
||||||
|
|
||||||
|
await buildWithBun({ naming: 'commander', entry: 'src/commander.ts', dts: true, external });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.0.86",
|
"version": "0.0.87",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
|
"commander": "^14.0.3",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
"hono": "^4.12.5",
|
"hono": "^4.12.5",
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/router.js",
|
".": "./dist/router.js",
|
||||||
"./browser": "./dist/router-browser.js",
|
"./browser": "./dist/router-browser.js",
|
||||||
|
"./commander": "./dist/commander.js",
|
||||||
"./simple": "./dist/router-simple.js",
|
"./simple": "./dist/router-simple.js",
|
||||||
"./opencode": "./dist/opencode.js",
|
"./opencode": "./dist/opencode.js",
|
||||||
"./skill": "./dist/app.js",
|
"./skill": "./dist/app.js",
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -51,6 +51,9 @@ importers:
|
|||||||
'@types/xml2js':
|
'@types/xml2js':
|
||||||
specifier: ^0.4.14
|
specifier: ^0.4.14
|
||||||
version: 0.4.14
|
version: 0.4.14
|
||||||
|
commander:
|
||||||
|
specifier: ^14.0.3
|
||||||
|
version: 14.0.3
|
||||||
eventemitter3:
|
eventemitter3:
|
||||||
specifier: ^5.0.4
|
specifier: ^5.0.4
|
||||||
version: 5.0.4
|
version: 5.0.4
|
||||||
@@ -405,6 +408,10 @@ packages:
|
|||||||
bun-types@1.3.10:
|
bun-types@1.3.10:
|
||||||
resolution: {integrity: sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg==}
|
resolution: {integrity: sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg==}
|
||||||
|
|
||||||
|
commander@14.0.3:
|
||||||
|
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
commondir@1.0.1:
|
commondir@1.0.1:
|
||||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||||
|
|
||||||
@@ -938,6 +945,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.3.5
|
'@types/node': 25.3.5
|
||||||
|
|
||||||
|
commander@14.0.3: {}
|
||||||
|
|
||||||
commondir@1.0.1: {}
|
commondir@1.0.1: {}
|
||||||
|
|
||||||
cookie@1.1.1: {}
|
cookie@1.1.1: {}
|
||||||
|
|||||||
115
src/commander.ts
Normal file
115
src/commander.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { program } from 'commander';
|
||||||
|
import { App } from './app.ts';
|
||||||
|
|
||||||
|
export const groupByPath = (routes: App['routes']) => {
|
||||||
|
return routes.reduce((acc, route) => {
|
||||||
|
const path = route.path || 'default';
|
||||||
|
if (!acc[path]) {
|
||||||
|
acc[path] = [];
|
||||||
|
}
|
||||||
|
acc[path].push(route);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, typeof routes>);
|
||||||
|
}
|
||||||
|
export const parseArgs = (args: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(args);
|
||||||
|
} catch {
|
||||||
|
// 尝试解析 a=b b=c 格式
|
||||||
|
const result: Record<string, string> = {};
|
||||||
|
const pairs = args.match(/(\S+?)=(\S+)/g);
|
||||||
|
if (pairs && pairs.length > 0) {
|
||||||
|
for (const pair of pairs) {
|
||||||
|
const idx = pair.indexOf('=');
|
||||||
|
const key = pair.slice(0, idx);
|
||||||
|
const value = pair.slice(idx + 1);
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new Error('Invalid arguments: expected JSON or key=value pairs (e.g. a=b c=d)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const parseDescription = (route: App['routes'][number]) => {
|
||||||
|
let desc = '';
|
||||||
|
if (route.metadata?.skill) {
|
||||||
|
desc += `\n\t=====${route.metadata.skill}=====\n`;
|
||||||
|
}
|
||||||
|
let hasSummary = false;
|
||||||
|
if (route.metadata?.summary) {
|
||||||
|
desc += `\t${route.metadata.summary}`;
|
||||||
|
hasSummary = true;
|
||||||
|
}
|
||||||
|
if (route.metadata?.args) {
|
||||||
|
const argsLines = Object.entries(route.metadata.args).map(([key, schema]: [string, any]) => {
|
||||||
|
const defType: string = schema?._def?.type ?? schema?.type ?? '';
|
||||||
|
const isOptional = defType === 'optional';
|
||||||
|
const innerType: string = isOptional
|
||||||
|
? (schema?._def?.innerType?.type ?? schema?._def?.innerType?._def?.type ?? '')
|
||||||
|
: defType;
|
||||||
|
const description: string =
|
||||||
|
schema?.description ??
|
||||||
|
schema?._def?.description ??
|
||||||
|
'';
|
||||||
|
const optionalMark = isOptional ? '?' : '';
|
||||||
|
const descPart = description ? ` ${description}` : '';
|
||||||
|
return `\t - ${key}${optionalMark}: ${innerType}${descPart}`;
|
||||||
|
});
|
||||||
|
desc += '\n\targs:\n' + argsLines.join('\n');
|
||||||
|
}
|
||||||
|
if (route.description && !hasSummary) {
|
||||||
|
desc += `\t - ${route.description}`;
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
export const createCommand = (opts: { app: App, program: typeof program }) => {
|
||||||
|
const { app, program } = opts;
|
||||||
|
const routes = app.routes;
|
||||||
|
|
||||||
|
|
||||||
|
const groupRoutes = groupByPath(routes);
|
||||||
|
for (const path in groupRoutes) {
|
||||||
|
const routeList = groupRoutes[path];
|
||||||
|
const keys = routeList.map(route => route.key).filter(Boolean);
|
||||||
|
const subProgram = program.command(path).description(`路由《${path}》 ${keys.length > 0 ? ': ' + keys.join(', ') : ''}`);
|
||||||
|
routeList.forEach(route => {
|
||||||
|
if (!route.key) return;
|
||||||
|
const description = parseDescription(route);
|
||||||
|
subProgram.command(route.key)
|
||||||
|
.description(description || '')
|
||||||
|
.option('--args <args>', 'JSON字符串参数,传递给命令执行')
|
||||||
|
.action(async (options) => {
|
||||||
|
const output = (data: any) => {
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
||||||
|
} else {
|
||||||
|
process.stdout.write(String(data) + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const args = options.args ? parseArgs(options.args) : {};
|
||||||
|
// 这里可以添加实际的命令执行逻辑,例如调用对应的路由处理函数
|
||||||
|
const res = await app.run({ path, key: route.key, payload: args }, { appId: app.appId });
|
||||||
|
if (res.code === 200) {
|
||||||
|
output(res.data);
|
||||||
|
} else {
|
||||||
|
output(`Error: ${res.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
output(`Execution error: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
export const parse = (opts: { app: App, description?: string, parse?: boolean }) => {
|
||||||
|
const { app, description, parse } = opts;
|
||||||
|
program.description(description || 'Router 命令行工具');
|
||||||
|
createCommand({ app: app as App, program });
|
||||||
|
if (parse) {
|
||||||
|
program.parse(process.argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user