@kevisual/router 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/router-browser.d.ts +10 -3
- package/dist/router-browser.js +17439 -530
- package/dist/router-sign.d.ts +1 -0
- package/dist/router-sign.js +15 -7
- package/dist/router.d.ts +10 -35
- package/dist/router.js +17446 -597
- package/package.json +22 -9
- package/readme.md +14 -0
- package/src/app-browser.ts +5 -0
- package/src/app.ts +7 -1
- package/src/auto/call-sock.ts +151 -0
- package/src/auto/listen/cleanup.ts +102 -0
- package/src/auto/listen/run-check.ts +50 -0
- package/src/auto/listen-sock.ts +245 -0
- package/src/auto/load-ts.ts +37 -0
- package/src/auto/runtime.ts +19 -0
- package/src/auto/utils/glob.ts +83 -0
- package/src/browser.ts +3 -1
- package/src/index.ts +3 -3
- package/src/route.ts +8 -3
- package/src/sign.ts +22 -9
- package/src/test/static.ts +22 -0
- package/src/utils/is-engine.ts +15 -0
- package/src/validator/index.ts +3 -1
- package/src/connect.ts +0 -67
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getRuntime } from './runtime.ts';
|
|
2
|
+
import { glob } from './utils/glob.ts';
|
|
3
|
+
type GlobOptions = {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getMatchFiles = async (match: string = './*.ts', { cwd = process.cwd() }: GlobOptions = {}): Promise<string[]> => {
|
|
8
|
+
const runtime = getRuntime();
|
|
9
|
+
if (runtime.isNode) {
|
|
10
|
+
console.error(`Node.js is not supported`);
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
if (runtime.isDeno) {
|
|
14
|
+
// Deno 环境下
|
|
15
|
+
return await glob(match);
|
|
16
|
+
}
|
|
17
|
+
if (runtime.isBun) {
|
|
18
|
+
// Bun 环境下
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
const { Glob } = await import('bun');
|
|
21
|
+
const path = await import('path');
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
const glob = new Glob(match, { cwd, absolute: true, onlyFiles: true });
|
|
24
|
+
const files: string[] = [];
|
|
25
|
+
for await (const file of glob.scan('.')) {
|
|
26
|
+
files.push(path.join(cwd, file));
|
|
27
|
+
}
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
return Array.from(files);
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const loadTS = async (match: string = './*.ts', { cwd = process.cwd() }: GlobOptions = {}): Promise<any[]> => {
|
|
35
|
+
const files = await getMatchFiles(match, { cwd });
|
|
36
|
+
return Promise.all(files.map((file) => import(file)));
|
|
37
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type RuntimeEngine = 'node' | 'deno' | 'bun';
|
|
2
|
+
|
|
3
|
+
type Runtime = {
|
|
4
|
+
isNode?: boolean;
|
|
5
|
+
isDeno?: boolean;
|
|
6
|
+
isBun?: boolean;
|
|
7
|
+
engine: RuntimeEngine;
|
|
8
|
+
};
|
|
9
|
+
export const getRuntime = (): Runtime => {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
if (typeof Deno !== 'undefined') {
|
|
12
|
+
return { isDeno: true, engine: 'deno' };
|
|
13
|
+
}
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
if (typeof Bun !== 'undefined') {
|
|
16
|
+
return { isBun: true, engine: 'bun' };
|
|
17
|
+
}
|
|
18
|
+
return { isNode: true, engine: 'node' };
|
|
19
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
type GlobOptions = {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const glob = async (match: string = './*.ts', { cwd = process.cwd() }: GlobOptions = {}) => {
|
|
6
|
+
const fs = await import('node:fs');
|
|
7
|
+
const path = await import('node:path');
|
|
8
|
+
|
|
9
|
+
// 将 glob 模式转换为正则表达式
|
|
10
|
+
const globToRegex = (pattern: string): RegExp => {
|
|
11
|
+
const escaped = pattern
|
|
12
|
+
.replace(/\./g, '\\.')
|
|
13
|
+
.replace(/\*\*/g, '__DOUBLE_STAR__') // 临时替换 **
|
|
14
|
+
.replace(/\*/g, '[^/]*') // * 匹配除 / 外的任意字符
|
|
15
|
+
.replace(/__DOUBLE_STAR__/g, '.*') // ** 匹配任意字符包括 /
|
|
16
|
+
.replace(/\?/g, '[^/]'); // ? 匹配除 / 外的单个字符
|
|
17
|
+
return new RegExp(`^${escaped}$`);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 递归读取目录
|
|
21
|
+
const readDirRecursive = async (dir: string): Promise<string[]> => {
|
|
22
|
+
const files: string[] = [];
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const fullPath = path.join(dir, entry.name);
|
|
29
|
+
|
|
30
|
+
if (entry.isFile()) {
|
|
31
|
+
files.push(fullPath);
|
|
32
|
+
} else if (entry.isDirectory()) {
|
|
33
|
+
// 递归搜索子目录
|
|
34
|
+
const subFiles = await readDirRecursive(fullPath);
|
|
35
|
+
files.push(...subFiles);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// 忽略无法访问的目录
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return files;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 解析模式是否包含递归搜索
|
|
46
|
+
const hasRecursive = match.includes('**');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
let allFiles: string[] = [];
|
|
50
|
+
|
|
51
|
+
if (hasRecursive) {
|
|
52
|
+
// 处理递归模式
|
|
53
|
+
const basePath = match.split('**')[0];
|
|
54
|
+
const startDir = path.resolve(cwd, basePath || '.');
|
|
55
|
+
allFiles = await readDirRecursive(startDir);
|
|
56
|
+
} else {
|
|
57
|
+
// 处理非递归模式
|
|
58
|
+
const dir = path.resolve(cwd, path.dirname(match));
|
|
59
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
60
|
+
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.isFile()) {
|
|
63
|
+
allFiles.push(path.join(dir, entry.name));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 创建相对于 cwd 的匹配模式
|
|
69
|
+
const normalizedMatch = path.resolve(cwd, match);
|
|
70
|
+
const regex = globToRegex(normalizedMatch);
|
|
71
|
+
|
|
72
|
+
// 过滤匹配的文件
|
|
73
|
+
const matchedFiles = allFiles.filter(file => {
|
|
74
|
+
const normalizedFile = path.resolve(file);
|
|
75
|
+
return regex.test(normalizedFile);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return matchedFiles;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`Error in glob pattern "${match}":`, error);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
};
|
package/src/browser.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { Route, QueryRouter, QueryRouterServer } from './route.ts';
|
|
2
2
|
|
|
3
|
-
export { Rule, Schema
|
|
3
|
+
export type { Rule, Schema } from './validator/index.ts';
|
|
4
|
+
|
|
5
|
+
export { createSchema } from './validator/index.ts';
|
|
4
6
|
|
|
5
7
|
export type { RouteContext, RouteOpts } from './route.ts';
|
|
6
8
|
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { Route, QueryRouter, QueryRouterServer } from './route.ts';
|
|
2
|
-
export { Connect, QueryConnect } from './connect.ts';
|
|
3
2
|
|
|
4
3
|
export type { RouteContext, RouteOpts, RouteMiddleware } from './route.ts';
|
|
5
4
|
|
|
@@ -11,8 +10,9 @@ export { Server, handleServer } from './server/index.ts';
|
|
|
11
10
|
*/
|
|
12
11
|
export { CustomError } from './result/error.ts';
|
|
13
12
|
|
|
14
|
-
export {
|
|
15
|
-
|
|
13
|
+
export { createSchema } from './validator/index.ts';
|
|
14
|
+
export type { Rule } from './validator/rule.ts';
|
|
15
|
+
export type { Schema } from 'zod';
|
|
16
16
|
export { App } from './app.ts';
|
|
17
17
|
|
|
18
18
|
export * from './router-define.ts';
|
package/src/route.ts
CHANGED
|
@@ -54,7 +54,7 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
|
|
54
54
|
queryRoute?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) => Promise<any>;
|
|
55
55
|
index?: number;
|
|
56
56
|
throw?: (code?: number | string, message?: string, tips?: string) => void;
|
|
57
|
-
/**
|
|
57
|
+
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */
|
|
58
58
|
needSerialize?: boolean;
|
|
59
59
|
} & T;
|
|
60
60
|
export type SimpleObject = Record<string, any>;
|
|
@@ -603,7 +603,12 @@ export class QueryRouter {
|
|
|
603
603
|
message: res.message,
|
|
604
604
|
};
|
|
605
605
|
}
|
|
606
|
-
|
|
606
|
+
/**
|
|
607
|
+
* 设置上下文
|
|
608
|
+
* @description 这里的上下文是为了在handle函数中使用
|
|
609
|
+
* @param ctx
|
|
610
|
+
*/
|
|
611
|
+
setContext(ctx: RouteContext) {
|
|
607
612
|
this.context = ctx;
|
|
608
613
|
}
|
|
609
614
|
getList(): RouteInfo[] {
|
|
@@ -647,7 +652,7 @@ export class QueryRouter {
|
|
|
647
652
|
throw(...args: any[]) {
|
|
648
653
|
throw new CustomError(...args);
|
|
649
654
|
}
|
|
650
|
-
hasRoute(path: string, key
|
|
655
|
+
hasRoute(path: string, key: string = '') {
|
|
651
656
|
return this.routes.find((r) => r.path === path && r.key === key);
|
|
652
657
|
}
|
|
653
658
|
}
|
package/src/sign.ts
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import { generate } from 'selfsigned';
|
|
2
2
|
|
|
3
|
-
type Attributes = {
|
|
3
|
+
export type Attributes = {
|
|
4
4
|
name: string;
|
|
5
5
|
value: string;
|
|
6
6
|
};
|
|
7
|
-
type AltNames = {
|
|
7
|
+
export type AltNames = {
|
|
8
8
|
type: number;
|
|
9
9
|
value?: string;
|
|
10
10
|
ip?: string;
|
|
11
11
|
};
|
|
12
12
|
export const createCert = (attrs: Attributes[] = [], altNames: AltNames[] = []) => {
|
|
13
13
|
let attributes = [
|
|
14
|
-
{ name: 'commonName', value: '*' }, // 通配符域名
|
|
15
14
|
{ name: 'countryName', value: 'CN' }, // 国家代码
|
|
16
15
|
{ name: 'stateOrProvinceName', value: 'ZheJiang' }, // 州名
|
|
17
|
-
{ name: 'localityName', value: '
|
|
18
|
-
{ name: 'organizationName', value: '
|
|
19
|
-
{ name: 'organizationalUnitName', value: '
|
|
16
|
+
{ name: 'localityName', value: 'HangZhou' }, // 城市名
|
|
17
|
+
{ name: 'organizationName', value: 'kevisual' }, // 组织名
|
|
18
|
+
{ name: 'organizationalUnitName', value: 'kevisual' }, // 组织单位
|
|
20
19
|
...attrs,
|
|
21
20
|
];
|
|
22
21
|
// attribute 根据name去重复, 后面的覆盖前面的
|
|
23
|
-
attributes =
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
attributes = Object.values(
|
|
23
|
+
attributes.reduce(
|
|
24
|
+
(acc, attr) => ({
|
|
25
|
+
...acc,
|
|
26
|
+
[attr.name]: attr,
|
|
27
|
+
}),
|
|
28
|
+
{} as Record<string, Attributes>,
|
|
29
|
+
),
|
|
30
|
+
);
|
|
26
31
|
|
|
27
32
|
const options = {
|
|
28
33
|
days: 365, // 证书有效期(天)
|
|
@@ -32,6 +37,14 @@ export const createCert = (attrs: Attributes[] = [], altNames: AltNames[] = [])
|
|
|
32
37
|
altNames: [
|
|
33
38
|
{ type: 2, value: '*' }, // DNS 名称
|
|
34
39
|
{ type: 2, value: 'localhost' }, // DNS
|
|
40
|
+
{
|
|
41
|
+
type: 2,
|
|
42
|
+
value: '[::1]',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 7,
|
|
46
|
+
ip: 'fe80::1',
|
|
47
|
+
},
|
|
35
48
|
{ type: 7, ip: '127.0.0.1' }, // IP 地址
|
|
36
49
|
...altNames,
|
|
37
50
|
],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts';
|
|
2
|
+
initProxy({
|
|
3
|
+
pagesDir: './demo',
|
|
4
|
+
watch: true,
|
|
5
|
+
});
|
|
6
|
+
import { App } from '../app.ts';
|
|
7
|
+
|
|
8
|
+
const app = new App();
|
|
9
|
+
app
|
|
10
|
+
.route({
|
|
11
|
+
path: 'a',
|
|
12
|
+
})
|
|
13
|
+
.define(async (ctx) => {
|
|
14
|
+
ctx.body = '1';
|
|
15
|
+
})
|
|
16
|
+
.addTo(app);
|
|
17
|
+
|
|
18
|
+
app.listen(2233, () => {
|
|
19
|
+
console.log('Server is running on http://localhost:2233');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.onServerRequest(proxyRoute);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
2
|
+
export const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof document.createElement === 'function';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
export const isDeno = typeof Deno !== 'undefined' && typeof Deno.version === 'object' && typeof Deno.version.deno === 'string';
|
|
5
|
+
|
|
6
|
+
export const getEngine = () => {
|
|
7
|
+
if (isNode) {
|
|
8
|
+
return 'node';
|
|
9
|
+
} else if (isBrowser) {
|
|
10
|
+
return 'browser';
|
|
11
|
+
} else if (isDeno) {
|
|
12
|
+
return 'deno';
|
|
13
|
+
}
|
|
14
|
+
return 'unknown';
|
|
15
|
+
};
|
package/src/validator/index.ts
CHANGED
package/src/connect.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { nanoid } from 'nanoid';
|
|
2
|
-
import { RouteContext } from './route.ts';
|
|
3
|
-
|
|
4
|
-
export class Connect {
|
|
5
|
-
path: string;
|
|
6
|
-
key?: string;
|
|
7
|
-
_fn?: (ctx?: RouteContext) => Promise<RouteContext>;
|
|
8
|
-
description?: string;
|
|
9
|
-
connects: { path: string; key?: string }[];
|
|
10
|
-
share = false;
|
|
11
|
-
|
|
12
|
-
constructor(path: string) {
|
|
13
|
-
this.path = path;
|
|
14
|
-
this.key = nanoid();
|
|
15
|
-
}
|
|
16
|
-
use(path: string) {
|
|
17
|
-
this.connects.push({ path });
|
|
18
|
-
}
|
|
19
|
-
useList(paths: string[]) {
|
|
20
|
-
paths.forEach((path) => {
|
|
21
|
-
this.connects.push({ path });
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
useConnect(connect: Connect) {
|
|
25
|
-
this.connects.push({ path: connect.path, key: connect.key });
|
|
26
|
-
}
|
|
27
|
-
useConnectList(connects: Connect[]) {
|
|
28
|
-
connects.forEach((connect) => {
|
|
29
|
-
this.connects.push({ path: connect.path, key: connect.key });
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
getPathList() {
|
|
33
|
-
return this.connects.map((c) => c.path).filter(Boolean);
|
|
34
|
-
}
|
|
35
|
-
set fn(fn: (ctx?: RouteContext) => Promise<RouteContext>) {
|
|
36
|
-
this._fn = fn;
|
|
37
|
-
}
|
|
38
|
-
get fn() {
|
|
39
|
-
return this._fn;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export class QueryConnect {
|
|
43
|
-
connects: Connect[];
|
|
44
|
-
constructor() {
|
|
45
|
-
this.connects = [];
|
|
46
|
-
}
|
|
47
|
-
add(connect: Connect) {
|
|
48
|
-
const has = this.connects.find((c) => c.path === connect.path && c.key === connect.key);
|
|
49
|
-
if (has) {
|
|
50
|
-
// remove the old connect
|
|
51
|
-
console.log('[replace connect]:', connect.path, connect.key);
|
|
52
|
-
this.connects = this.connects.filter((c) => c.path !== connect.path && c.key !== connect.key);
|
|
53
|
-
}
|
|
54
|
-
this.connects.push(connect);
|
|
55
|
-
}
|
|
56
|
-
remove(connect: Connect) {
|
|
57
|
-
this.connects = this.connects.filter((c) => c.path !== connect.path && c.key !== connect.key);
|
|
58
|
-
}
|
|
59
|
-
getList() {
|
|
60
|
-
return this.connects.map((c) => {
|
|
61
|
-
return {
|
|
62
|
-
path: c.path,
|
|
63
|
-
key: c.key,
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|