Compare commits
4 Commits
151586f98c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce92c8ffc | ||
|
|
e0fe1bb476 | ||
|
|
bd787c55a2 | ||
|
|
648fe7ad33 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.1.2",
|
"version": "0.1.6",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"@kevisual/query": "^0.0.53",
|
"@kevisual/query": "^0.0.53",
|
||||||
"@kevisual/remote-app": "^0.0.7",
|
"@kevisual/remote-app": "^0.0.7",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.2.26",
|
"@opencode-ai/plugin": "^1.2.27",
|
||||||
"@types/bun": "^1.3.10",
|
"@types/bun": "^1.3.10",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
"hono": "^4.12.8",
|
"hono": "^4.12.8",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.7",
|
||||||
"path-to-regexp": "^8.3.0",
|
"path-to-regexp": "^8.3.0",
|
||||||
"send": "^1.2.1",
|
"send": "^1.2.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
|||||||
126
src/commander.ts
126
src/commander.ts
@@ -1,6 +1,7 @@
|
|||||||
import { Command, program } from 'commander';
|
import { Command, program } from 'commander';
|
||||||
import { App, QueryRouterServer } from './app.ts';
|
import { App } from './app.ts';
|
||||||
import { RemoteApp } from '@kevisual/remote-app'
|
import { RemoteApp } from '@kevisual/remote-app'
|
||||||
|
import z from 'zod';
|
||||||
export const groupByPath = (routes: App['routes']) => {
|
export const groupByPath = (routes: App['routes']) => {
|
||||||
return routes.reduce((acc, route) => {
|
return routes.reduce((acc, route) => {
|
||||||
const path = route.path || 'default';
|
const path = route.path || 'default';
|
||||||
@@ -124,9 +125,10 @@ export const parse = async (opts: {
|
|||||||
token?: string,
|
token?: string,
|
||||||
username?: string,
|
username?: string,
|
||||||
id?: string,
|
id?: string,
|
||||||
}
|
},
|
||||||
|
exitOnEnd?: boolean,
|
||||||
}) => {
|
}) => {
|
||||||
const { description, parse = true, version } = opts;
|
const { description, parse = true, version, exitOnEnd = true } = opts;
|
||||||
const app = opts.app as App;
|
const app = opts.app as App;
|
||||||
const _program = opts.program || program;
|
const _program = opts.program || program;
|
||||||
_program.description(description || 'Router 命令行工具');
|
_program.description(description || 'Router 命令行工具');
|
||||||
@@ -134,25 +136,8 @@ export const parse = async (opts: {
|
|||||||
_program.version(version);
|
_program.version(version);
|
||||||
}
|
}
|
||||||
app.createRouteList();
|
app.createRouteList();
|
||||||
app.route({
|
|
||||||
path: 'cli',
|
|
||||||
key: 'list'
|
|
||||||
}).define(async () => {
|
|
||||||
const routes = app.routes.map(route => {
|
|
||||||
return {
|
|
||||||
path: route.path,
|
|
||||||
key: route.key,
|
|
||||||
description: route?.metadata?.summary || route.description || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// 输出为表格格式
|
|
||||||
const table = routes.map(route => {
|
|
||||||
return `${route.path} ${route.key} - ${route.description}`;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
console.log(table);
|
|
||||||
}).addTo(app, { overwrite: false })
|
|
||||||
|
|
||||||
|
createCliList(app);
|
||||||
createCommand({ app: app as App, program: _program });
|
createCommand({ app: app as App, program: _program });
|
||||||
|
|
||||||
if (opts.remote) {
|
if (opts.remote) {
|
||||||
@@ -167,9 +152,104 @@ export const parse = async (opts: {
|
|||||||
remoteApp.listenProxy();
|
remoteApp.listenProxy();
|
||||||
console.log('已连接到远程应用,正在监听命令...');
|
console.log('已连接到远程应用,正在监听命令...');
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (parse) {
|
if (parse) {
|
||||||
_program.parse(process.argv);
|
await _program.parseAsync(process.argv);
|
||||||
|
if (exitOnEnd) {
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCliList = (app: App) => {
|
||||||
|
app.route({
|
||||||
|
path: 'cli',
|
||||||
|
key: 'list',
|
||||||
|
description: '列出所有可用的命令',
|
||||||
|
metadata: {
|
||||||
|
summary: '列出所有可用的命令',
|
||||||
|
args: {
|
||||||
|
q: z.string().optional().describe('查询关键词,支持模糊匹配命令'),
|
||||||
|
path: z.string().optional().describe('按路径前缀过滤,如 user、admin'),
|
||||||
|
tags: z.string().optional().describe('按标签过滤,多个标签用逗号分隔'),
|
||||||
|
sort: z.enum(['key', 'path', 'name']).optional().describe('排序方式'),
|
||||||
|
limit: z.number().optional().describe('限制返回数量'),
|
||||||
|
offset: z.number().optional().describe('偏移量,用于分页'),
|
||||||
|
format: z.enum(['table', 'simple', 'json']).optional().describe('输出格式'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const { q, path: pathFilter, tags, sort, limit, offset, format } = ctx.query as any;
|
||||||
|
let routes = app.routes.map(route => {
|
||||||
|
return {
|
||||||
|
path: route.path,
|
||||||
|
key: route.key,
|
||||||
|
description: route?.metadata?.summary || route.description || '',
|
||||||
|
tags: route?.metadata?.tags || [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路径过滤
|
||||||
|
if (pathFilter) {
|
||||||
|
routes = routes.filter(route => route.path.startsWith(pathFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签过滤
|
||||||
|
if (tags) {
|
||||||
|
const tagList = tags.split(',').map((t: string) => t.trim().toLowerCase()).filter(Boolean);
|
||||||
|
if (tagList.length > 0) {
|
||||||
|
routes = routes.filter(route => {
|
||||||
|
const routeTags = Array.isArray(route.tags) ? route.tags.map((t: unknown) => String(t).toLowerCase()) : [];
|
||||||
|
return tagList.some((tag: string) => routeTags.includes(tag));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键词过滤
|
||||||
|
if (q) {
|
||||||
|
const keyword = q.toLowerCase();
|
||||||
|
routes = routes.filter(route => {
|
||||||
|
return route.path.toLowerCase().includes(keyword) ||
|
||||||
|
route.key.toLowerCase().includes(keyword) ||
|
||||||
|
route.description.toLowerCase().includes(keyword);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
if (sort) {
|
||||||
|
routes.sort((a, b) => {
|
||||||
|
if (sort === 'path') return a.path.localeCompare(b.path);
|
||||||
|
if (sort === 'key') return a.key.localeCompare(b.key);
|
||||||
|
return a.key.localeCompare(b.key); // name 默认为 key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const total = routes.length;
|
||||||
|
const start = offset || 0;
|
||||||
|
const end = limit ? start + limit : undefined;
|
||||||
|
routes = routes.slice(start, end);
|
||||||
|
|
||||||
|
// 输出
|
||||||
|
const outputFormat = format || 'table';
|
||||||
|
if (outputFormat === 'json') {
|
||||||
|
console.log(JSON.stringify({ total, offset: start, limit, routes }, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputFormat === 'simple') {
|
||||||
|
routes.forEach(route => {
|
||||||
|
console.log(`${route.path} ${route.key}`);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// table 格式
|
||||||
|
const table = routes.map(route => {
|
||||||
|
return `${route.path} ${route.key} - ${route.description}`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
console.log(table);
|
||||||
|
}).addTo(app, { overwrite: false })
|
||||||
|
}
|
||||||
41
src/route.ts
41
src/route.ts
@@ -434,7 +434,7 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|||||||
console.error('=====debug====:', e);
|
console.error('=====debug====:', e);
|
||||||
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
|
console.error('=====debug====:[path:key]:', `${route.path}-${route.key}`);
|
||||||
}
|
}
|
||||||
if (e instanceof CustomError) {
|
if (e instanceof CustomError || e?.code) {
|
||||||
ctx.code = e.code;
|
ctx.code = e.code;
|
||||||
ctx.message = e.message;
|
ctx.message = e.message;
|
||||||
} else {
|
} else {
|
||||||
@@ -782,6 +782,45 @@ export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Qu
|
|||||||
const { path, key, id } = api as any;
|
const { path, key, id } = api as any;
|
||||||
return this.run({ path, key, id, payload }, ctx);
|
return this.run({ path, key, id, payload }, ctx);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 创建认证相关的中间件,默认是 auth, auth-admin, auth-can 三个中间件
|
||||||
|
* @param fun 认证函数,接收 RouteContext 和认证类型
|
||||||
|
*/
|
||||||
|
async createAuth(fun: (ctx: RouteContext<C>, type?: 'auth' | 'auth-admin' | 'auth-can') => any) {
|
||||||
|
this.route({
|
||||||
|
path: 'auth',
|
||||||
|
key: 'auth',
|
||||||
|
id: 'auth',
|
||||||
|
description: 'token验证',
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
path: 'auth-admin',
|
||||||
|
key: 'auth-admin',
|
||||||
|
id: 'auth-admin',
|
||||||
|
description: 'admin token验证',
|
||||||
|
middleware: ['auth']
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth-admin');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
path: 'auth-can',
|
||||||
|
key: 'auth-can',
|
||||||
|
id: 'auth-can',
|
||||||
|
description: '权限验证'
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
if (fun) {
|
||||||
|
await fun(ctx, 'auth-can');
|
||||||
|
}
|
||||||
|
}).addTo(this, { overwrite: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user