tcdona_unilib 1.0.10 → 1.0.12
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/.prettierrc.json +6 -0
- package/animejs.ts +13 -1
- package/ast-grep.ts +42 -0
- package/big.ts +2 -2
- package/clipboardy.ts +1 -1
- package/dayjs.ts +2 -2
- package/dotenvx.ts +15 -2
- package/eff/eff.delay.ts +21 -0
- package/eff/eff.ts +226 -0
- package/eff/effcan.ts +286 -0
- package/eff/throwable.ts +11 -0
- package/effector.ts +33 -2
- package/es-toolkit.ts +1 -7
- package/exome.ts +10 -2
- package/hono.ts +18 -2
- package/hotkeys-js.ts +3 -2
- package/idmeta.json +42 -0
- package/inquirer.ts +1 -1
- package/koka/async.ts +4 -0
- package/koka/ctx.ts +4 -0
- package/koka/err.ts +4 -0
- package/koka/gen.ts +4 -0
- package/koka/index.ts +2 -0
- package/koka/opt.ts +4 -0
- package/koka/result.ts +10 -0
- package/koka/task.ts +4 -0
- package/koka-domain.ts +18 -0
- package/koka.ts +34 -0
- package/magic-regexp.ts +28 -2
- package/marked.ts +28 -2
- package/nanoid.ts +7 -1
- package/nanostores.ts +31 -2
- package/neverthrow.ts +2 -2
- package/package.json +15 -15
- package/pathe.ts +16 -1
- package/pinyin-pro.ts +16 -2
- package/prettier.ts +20 -2
- package/staticMeta/enum.api.ts +111 -82
- package/staticMeta/err.ts +64 -0
- package/staticMeta/file.yml.ts +12 -12
- package/staticMeta/md.html2md.ts +38 -0
- package/staticMeta/md.md2html.ts +203 -0
- package/staticMeta/path.init.ts +12 -12
- package/staticMeta/string.nanoid.ts +7 -7
- package/staticMeta/url.ts +57 -54
- package/tinypool.ts +29 -2
- package/turndown.ts +1 -1
- package/vite.ts +2 -2
- package/viteplugin/md.plugin.dist.d.ts +18 -0
- package/viteplugin/md.plugin.dist.js +1133 -0
- package/viteplugin/md.plugin.ts +22 -0
- package/viteplugin/vite-env.d.ts +6 -0
- package/viteplugin/vite.config.ts +62 -0
- package/vue.ts +2 -12
- package/zod.ts +19 -2
- package/zx.ts +25 -1
- package/@ast-grep.ts +0 -18
- package/comctx.ts +0 -2
- package/hono/cors.ts +0 -1
- package/hono/logger.ts +0 -1
- package/hono/timeout.ts +0 -1
- package/staticMeta/ast.scan.ts +0 -131
- package/staticMeta/ast.ts +0 -259
- package/staticMeta/eff.delay.ts +0 -17
- package/staticMeta/eff.ts +0 -203
- package/staticMeta/iduniq.ts +0 -320
- package/staticMeta/idupdate.ts +0 -374
- package/staticMeta/pkg.json.ts +0 -138
- package/staticMeta/project.ts +0 -98
- package/staticMeta/sync.ts +0 -296
- package/tcproject.ts +0 -60
package/staticMeta/eff.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Eff 框架:基于 Result 的异步任务管理
|
|
3
|
-
* - root/create:创建异步任务
|
|
4
|
-
* - all/any/race/allSettled:并行或竞速执行多个任务
|
|
5
|
-
* - Signal 传播:支持任务取消和自动清理
|
|
6
|
-
*/
|
|
7
|
-
import type { Result } from 'neverthrow';
|
|
8
|
-
import { err, ok, ResultAsync } from 'neverthrow';
|
|
9
|
-
|
|
10
|
-
export interface EffContext { signal: AbortSignal }
|
|
11
|
-
|
|
12
|
-
type EffExecutor<T, E> = (ctx: EffContext) => PromiseLike<Result<T, E>>;
|
|
13
|
-
type NonEmptyArray<T> = readonly [T, ...T[]];
|
|
14
|
-
|
|
15
|
-
const ABORT_REASON = { SETTLED: 'settled', COMPLETED: 'completed' } as const;
|
|
16
|
-
const noop = () => {};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 将父 signal abort 传播到子 controller,返回解绑函数
|
|
20
|
-
*/
|
|
21
|
-
const linkSignal = (
|
|
22
|
-
parentSignal: AbortSignal,
|
|
23
|
-
controller: AbortController,
|
|
24
|
-
): (() => void) => {
|
|
25
|
-
if (parentSignal.aborted) {
|
|
26
|
-
controller.abort(parentSignal.reason);
|
|
27
|
-
return noop;
|
|
28
|
-
}
|
|
29
|
-
const handler = () => controller.abort(parentSignal.reason);
|
|
30
|
-
parentSignal.addEventListener('abort', handler, { once: true });
|
|
31
|
-
return () => parentSignal.removeEventListener('abort', handler);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 并行运行多个任务,通过回调函数允许调用方控制何时 settle
|
|
36
|
-
*/
|
|
37
|
-
const runAll = <T, E>(
|
|
38
|
-
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
39
|
-
parentSignal: AbortSignal,
|
|
40
|
-
onResult: (
|
|
41
|
-
result: Result<T, E>,
|
|
42
|
-
index: number,
|
|
43
|
-
settle: (r: Result<unknown, unknown>) => void,
|
|
44
|
-
context: {
|
|
45
|
-
resultCount: number;
|
|
46
|
-
okValues: T[];
|
|
47
|
-
errValues: E[];
|
|
48
|
-
allResults: Result<T, E>[];
|
|
49
|
-
},
|
|
50
|
-
) => void,
|
|
51
|
-
): ResultAsync<unknown, unknown> =>
|
|
52
|
-
Eff.create(({ signal }) => {
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const childSignal = controller.signal;
|
|
55
|
-
const unlink = linkSignal(signal, controller);
|
|
56
|
-
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
let done = false;
|
|
59
|
-
const context = {
|
|
60
|
-
resultCount: 0,
|
|
61
|
-
okValues: [] as T[],
|
|
62
|
-
errValues: [] as E[],
|
|
63
|
-
allResults: [] as Result<T, E>[],
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const settle = (r: Result<unknown, unknown>) => {
|
|
67
|
-
if (done) return;
|
|
68
|
-
done = true;
|
|
69
|
-
unlink();
|
|
70
|
-
controller.abort(ABORT_REASON.SETTLED);
|
|
71
|
-
resolve(r);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
if (signal.aborted) {
|
|
75
|
-
settle(ok([]));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let remaining = executors.length;
|
|
80
|
-
|
|
81
|
-
executors.forEach((executor, i) => {
|
|
82
|
-
Eff.create(executor, childSignal).then((r) => {
|
|
83
|
-
if (done) return;
|
|
84
|
-
remaining--;
|
|
85
|
-
onResult(r, i, settle, context);
|
|
86
|
-
if (remaining === 0) settle(ok(context.allResults));
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}, parentSignal);
|
|
91
|
-
|
|
92
|
-
export class Eff {
|
|
93
|
-
/**
|
|
94
|
-
* 创建根级任务(无父 signal)
|
|
95
|
-
*/
|
|
96
|
-
static root<T, E>(executor: EffExecutor<T, E>): ResultAsync<T, E> {
|
|
97
|
-
const controller = new AbortController();
|
|
98
|
-
const promise: Promise<Result<T, E>> = Promise.resolve()
|
|
99
|
-
.then(() => executor({ signal: controller.signal }))
|
|
100
|
-
.catch((e) => err(e as E))
|
|
101
|
-
.finally(() => controller.abort(ABORT_REASON.COMPLETED));
|
|
102
|
-
return new ResultAsync(promise);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 创建子任务(继承父 signal,支持取消传播)
|
|
107
|
-
*/
|
|
108
|
-
static create<T, E>(
|
|
109
|
-
executor: EffExecutor<T, E>,
|
|
110
|
-
parentSignal: AbortSignal,
|
|
111
|
-
): ResultAsync<T, E> {
|
|
112
|
-
const controller = new AbortController();
|
|
113
|
-
const unlink = linkSignal(parentSignal, controller);
|
|
114
|
-
|
|
115
|
-
const promise: Promise<Result<T, E>> = Promise.resolve()
|
|
116
|
-
.then(() => executor({ signal: controller.signal }))
|
|
117
|
-
.catch((e) => err(e as E))
|
|
118
|
-
.finally(() => {
|
|
119
|
-
unlink();
|
|
120
|
-
controller.abort(ABORT_REASON.COMPLETED);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return new ResultAsync(promise);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 全部成功才成功,任一失败立即失败并 abort 其他
|
|
128
|
-
*/
|
|
129
|
-
static all<T, E>(
|
|
130
|
-
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
131
|
-
parentSignal: AbortSignal,
|
|
132
|
-
): ResultAsync<T[], E> {
|
|
133
|
-
return runAll<T, E>(
|
|
134
|
-
executors,
|
|
135
|
-
parentSignal,
|
|
136
|
-
(result, index, settle, context) => {
|
|
137
|
-
result.match(
|
|
138
|
-
(v) => {
|
|
139
|
-
context.okValues[index] = v;
|
|
140
|
-
context.resultCount++;
|
|
141
|
-
if (context.resultCount === executors.length) {
|
|
142
|
-
settle(ok(context.okValues));
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
(e) => settle(err(e)),
|
|
146
|
-
);
|
|
147
|
-
},
|
|
148
|
-
) as ResultAsync<T[], E>;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* 任一成功立即成功并 abort 其他,全部失败才失败
|
|
153
|
-
*/
|
|
154
|
-
static any<T, E>(
|
|
155
|
-
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
156
|
-
parentSignal: AbortSignal,
|
|
157
|
-
): ResultAsync<T, E[]> {
|
|
158
|
-
return runAll<T, E>(
|
|
159
|
-
executors,
|
|
160
|
-
parentSignal,
|
|
161
|
-
(result, index, settle, context) => {
|
|
162
|
-
result.match(
|
|
163
|
-
(v) => settle(ok(v)),
|
|
164
|
-
(e) => {
|
|
165
|
-
context.errValues[index] = e;
|
|
166
|
-
context.resultCount++;
|
|
167
|
-
if (context.resultCount === executors.length) {
|
|
168
|
-
settle(err(context.errValues));
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
);
|
|
172
|
-
},
|
|
173
|
-
) as ResultAsync<T, E[]>;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 任一完成立即返回其结果并 abort 其他
|
|
178
|
-
*/
|
|
179
|
-
static race<T, E>(
|
|
180
|
-
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
181
|
-
parentSignal: AbortSignal,
|
|
182
|
-
): ResultAsync<T, E> {
|
|
183
|
-
return runAll<T, E>(executors, parentSignal, (result, _index, settle) => {
|
|
184
|
-
settle(result);
|
|
185
|
-
}) as ResultAsync<T, E>;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 等待全部完成,收集所有结果(不因某个失败而 abort)
|
|
190
|
-
*/
|
|
191
|
-
static allSettled<T, E>(
|
|
192
|
-
executors: NonEmptyArray<EffExecutor<T, E>>,
|
|
193
|
-
parentSignal: AbortSignal,
|
|
194
|
-
): ResultAsync<Result<T, E>[], never> {
|
|
195
|
-
return runAll<T, E>(
|
|
196
|
-
executors,
|
|
197
|
-
parentSignal,
|
|
198
|
-
(result, index, _settle, context) => {
|
|
199
|
-
context.allResults[index] = result;
|
|
200
|
-
},
|
|
201
|
-
) as ResultAsync<Result<T, E>[], never>;
|
|
202
|
-
}
|
|
203
|
-
}
|
package/staticMeta/iduniq.ts
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fileId 调用规范检查:
|
|
3
|
-
* - 检查 fileInit 导入是否被重命名
|
|
4
|
-
* - 检查 fileInit 返回值是否命名为 fileId
|
|
5
|
-
* - 检查 fileId 调用中的 key 是否重复
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { err, ok } from 'neverthrow';
|
|
9
|
-
import type { EffContext } from './eff.js';
|
|
10
|
-
import { Eff } from './eff.js';
|
|
11
|
-
import type { Result, ResultAsync } from 'neverthrow';
|
|
12
|
-
import { fileInit } from './enum.api.js';
|
|
13
|
-
import { parseFileAsync, scanTypeScriptFilesAsync } from './ast.scan.js';
|
|
14
|
-
import { extractKeyFromCall, findAllByKind, findFunctionCalls } from './ast.js';
|
|
15
|
-
import type { AstNode } from './ast.js';
|
|
16
|
-
|
|
17
|
-
type NonEmptyArray<T> = readonly [T, ...T[]];
|
|
18
|
-
|
|
19
|
-
// ==================== 日志配置 ====================
|
|
20
|
-
|
|
21
|
-
const fileId = fileInit('staticMeta/iduniq.ts');
|
|
22
|
-
|
|
23
|
-
// ==================== 类型定义 ====================
|
|
24
|
-
|
|
25
|
-
interface KeyUsage {
|
|
26
|
-
readonly key: string;
|
|
27
|
-
readonly tags?: string[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface CheckIssue {
|
|
31
|
-
readonly filePath: string;
|
|
32
|
-
readonly type: 'import-violation' | 'variable-name-violation' | 'duplicate';
|
|
33
|
-
readonly details: string;
|
|
34
|
-
readonly keys?: readonly {
|
|
35
|
-
key: string;
|
|
36
|
-
count: number;
|
|
37
|
-
}[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ==================== 导入规范检查 ====================
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 检查 fileInit 导入是否被 as 重命名(违反规范)
|
|
44
|
-
*/
|
|
45
|
-
function checkImportConvention(root: AstNode): Result<boolean, CheckIssue> {
|
|
46
|
-
const importStatements = findAllByKind(root, 'import_statement');
|
|
47
|
-
|
|
48
|
-
for (const stmt of importStatements) {
|
|
49
|
-
const specifiers = findAllByKind(stmt, 'import_specifier');
|
|
50
|
-
for (const spec of specifiers) {
|
|
51
|
-
const children = spec.children();
|
|
52
|
-
// import_specifier 的结构: identifier | (identifier 'as' identifier)
|
|
53
|
-
if (children.length >= 1) {
|
|
54
|
-
const firstName = children[0].text();
|
|
55
|
-
if (firstName === 'fileInit') {
|
|
56
|
-
// 检查是否被 as 重命名
|
|
57
|
-
if (children.length > 1) {
|
|
58
|
-
const asNode = children.find((c: AstNode) => c.text() === 'as');
|
|
59
|
-
if (asNode) {
|
|
60
|
-
return err({
|
|
61
|
-
filePath: '',
|
|
62
|
-
type: 'import-violation',
|
|
63
|
-
details: `fileInit 被重命名为 ${children[children.length - 1].text()},违反导入规范`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return ok(true);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ==================== 变量名规范检查 ====================
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 检查 fileInit() 返回值是否被命名为 fileId(违反规范则报错)
|
|
79
|
-
*/
|
|
80
|
-
function checkVariableNameConvention(root: AstNode): Result<boolean, CheckIssue> {
|
|
81
|
-
// 查找所有变量声明
|
|
82
|
-
const varDeclarations = findAllByKind(root, 'variable_declarator');
|
|
83
|
-
|
|
84
|
-
for (const decl of varDeclarations) {
|
|
85
|
-
const children = decl.children();
|
|
86
|
-
if (children.length < 2) continue;
|
|
87
|
-
|
|
88
|
-
// 检查初始化值是否是 fileInit 调用
|
|
89
|
-
const initPart = children[children.length - 1];
|
|
90
|
-
if (initPart?.kind() === 'call_expression') {
|
|
91
|
-
const callChildren = initPart.children();
|
|
92
|
-
if (callChildren.length > 0 && callChildren[0].text() === 'fileInit') {
|
|
93
|
-
// 获取变量名
|
|
94
|
-
const varName = children[0].text();
|
|
95
|
-
if (varName !== 'fileId') {
|
|
96
|
-
return err({
|
|
97
|
-
filePath: '',
|
|
98
|
-
type: 'variable-name-violation',
|
|
99
|
-
details: `fileInit 的返回值被命名为 ${varName},应该命名为 fileId`,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return ok(true);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ==================== Key 唯一性检查 ====================
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 提取代码中所有 fileId 调用的 key 和 tags
|
|
113
|
-
*/
|
|
114
|
-
function collectFileIdUsages(root: AstNode): KeyUsage[] {
|
|
115
|
-
const usages: KeyUsage[] = [];
|
|
116
|
-
const allCalls = findFunctionCalls(root, 'fileId');
|
|
117
|
-
|
|
118
|
-
for (const call of allCalls) {
|
|
119
|
-
const usage = extractKeyFromCall(call, 'fileId');
|
|
120
|
-
if (usage) {
|
|
121
|
-
usages.push(usage);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return usages;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ==================== 文件检测 ====================
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 检查单个文件:验证导入规范、变量名规范、key 唯一性
|
|
132
|
-
*/
|
|
133
|
-
function checkFile(filePath: string, root: AstNode): Result<CheckIssue | null, Error> {
|
|
134
|
-
// 步骤 1:导入约束检查
|
|
135
|
-
const importCheck = checkImportConvention(root);
|
|
136
|
-
if (importCheck.isErr()) {
|
|
137
|
-
return ok({
|
|
138
|
-
...importCheck.error,
|
|
139
|
-
filePath,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 步骤 2:变量名约束检查
|
|
144
|
-
const varNameCheck = checkVariableNameConvention(root);
|
|
145
|
-
if (varNameCheck.isErr()) {
|
|
146
|
-
return ok({
|
|
147
|
-
...varNameCheck.error,
|
|
148
|
-
filePath,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 步骤 3:Key 唯一性检查
|
|
153
|
-
const idUsages = collectFileIdUsages(root);
|
|
154
|
-
if (idUsages.length === 0) {
|
|
155
|
-
return ok(null);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const keyCountMap = new Map<string, number>();
|
|
159
|
-
for (const usage of idUsages) {
|
|
160
|
-
const count = keyCountMap.get(usage.key) ?? 0;
|
|
161
|
-
keyCountMap.set(usage.key, count + 1);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const duplicateKeys = Array.from(keyCountMap.entries()).filter(
|
|
165
|
-
([, count]) => count > 1,
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
if (duplicateKeys.length > 0) {
|
|
169
|
-
return ok({
|
|
170
|
-
filePath,
|
|
171
|
-
type: 'duplicate',
|
|
172
|
-
details: 'Key 存在重复',
|
|
173
|
-
keys: duplicateKeys.map(([key, count]) => ({ key, count })),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return ok(null);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ==================== 批量检测 ====================
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* 并行检测所有文件(使用 Eff.allSettled)
|
|
184
|
-
*/
|
|
185
|
-
function checkAllFiles(allFiles: string[], parentSignal: AbortSignal): ResultAsync<CheckIssue[], Error> {
|
|
186
|
-
if (allFiles.length === 0) {
|
|
187
|
-
return Eff.root(() => Promise.resolve(ok([])));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 直接传递 executor 函数给 Eff.allSettled,而不是 Eff.root 的返回值
|
|
191
|
-
const executors = allFiles.map((file) =>
|
|
192
|
-
async ({ signal }: EffContext) => {
|
|
193
|
-
if (signal.aborted) return err(new Error('任务已取消'));
|
|
194
|
-
|
|
195
|
-
fileId('checkProgress').info(`[正在扫描] ${file}`);
|
|
196
|
-
|
|
197
|
-
const parseResult = await parseFileAsync(file, signal);
|
|
198
|
-
if (parseResult.isErr()) {
|
|
199
|
-
return ok(null as CheckIssue | null);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const { root } = parseResult.value;
|
|
203
|
-
const checkResult = checkFile(file, root);
|
|
204
|
-
if (checkResult.isErr()) {
|
|
205
|
-
return ok(null as CheckIssue | null);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return ok(checkResult.value);
|
|
209
|
-
},
|
|
210
|
-
) as unknown as NonEmptyArray<(ctx: EffContext) => Promise<Result<CheckIssue | null, Error>>>;
|
|
211
|
-
|
|
212
|
-
// 使用 Eff.allSettled 并行执行,支持 signal 传播
|
|
213
|
-
return Eff.allSettled(executors, parentSignal).map((results) => {
|
|
214
|
-
const issues: CheckIssue[] = [];
|
|
215
|
-
for (const result of results) {
|
|
216
|
-
if (result.isOk() && result.value !== null) {
|
|
217
|
-
issues.push(result.value);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return issues;
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ==================== 输出格式 ====================
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* 分类并格式化输出检测结果
|
|
228
|
-
*/
|
|
229
|
-
function printAllIssues(issues: CheckIssue[]): void {
|
|
230
|
-
if (issues.length === 0) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
fileId('summaryHeader').info(
|
|
235
|
-
`\n${'='.repeat(60)}\n检测结果汇总\n${'='.repeat(60)}`,
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
// 分类统计
|
|
239
|
-
const importViolations = issues.filter((i) => i.type === 'import-violation');
|
|
240
|
-
const varNameViolations = issues.filter((i) => i.type === 'variable-name-violation');
|
|
241
|
-
const duplicateIssues = issues.filter((i) => i.type === 'duplicate');
|
|
242
|
-
|
|
243
|
-
// 导入约束违反
|
|
244
|
-
if (importViolations.length > 0) {
|
|
245
|
-
fileId('importViolationsHeader').error(`\n❌ 导入约束违反 (${importViolations.length} 个)`);
|
|
246
|
-
for (const issue of importViolations) {
|
|
247
|
-
fileId('importViolationsPath').error(` ${issue.filePath}`);
|
|
248
|
-
fileId('importViolationsDetail').error(` └─ ${issue.details}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// 变量名约束违反
|
|
253
|
-
if (varNameViolations.length > 0) {
|
|
254
|
-
fileId('varNameViolationsHeader').error(`\n❌ 变量名约束违反 (${varNameViolations.length} 个)`);
|
|
255
|
-
for (const issue of varNameViolations) {
|
|
256
|
-
fileId('varNameViolationsPath').error(` ${issue.filePath}`);
|
|
257
|
-
fileId('varNameViolationsDetail').error(` └─ ${issue.details}`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Key 重复
|
|
262
|
-
if (duplicateIssues.length > 0) {
|
|
263
|
-
fileId('duplicateKeysHeader').error(`\n❌ 重复的 Key (${duplicateIssues.length} 个)`);
|
|
264
|
-
for (const issue of duplicateIssues) {
|
|
265
|
-
fileId('duplicateKeysPath').error(` ${issue.filePath}`);
|
|
266
|
-
if (issue.keys) {
|
|
267
|
-
for (const k of issue.keys) {
|
|
268
|
-
fileId('duplicateKeysItem').error(` ├─ ${k.key} (${k.count} 次)`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
fileId('summaryFooter').info(`${'='.repeat(60)}\n`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ==================== 主函数 ====================
|
|
278
|
-
|
|
279
|
-
export const tz = () => {
|
|
280
|
-
return Eff.root(async ({ signal }) => {
|
|
281
|
-
try {
|
|
282
|
-
const allFiles = await scanTypeScriptFilesAsync();
|
|
283
|
-
fileId('checkStart').info(`开始检测 kv2 keys... (共 ${allFiles.length} 个文件)\n`);
|
|
284
|
-
|
|
285
|
-
// 传递 signal 以支持取消传播
|
|
286
|
-
const issuesResult = await checkAllFiles(allFiles, signal);
|
|
287
|
-
|
|
288
|
-
if (issuesResult.isErr()) {
|
|
289
|
-
return err(issuesResult.error);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const issues = issuesResult.value;
|
|
293
|
-
printAllIssues(issues);
|
|
294
|
-
|
|
295
|
-
const issueCount = issues.length;
|
|
296
|
-
if (issueCount > 0) {
|
|
297
|
-
fileId('checkCompleteFail').error(
|
|
298
|
-
`\n❌ 检测完成: 发现 ${issueCount} 个问题`,
|
|
299
|
-
);
|
|
300
|
-
process.exit(1);
|
|
301
|
-
} else {
|
|
302
|
-
fileId('checkCompleteSuccess').info(
|
|
303
|
-
'\n✅ 检测完成: 无问题',
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return ok(undefined);
|
|
308
|
-
} catch (e) {
|
|
309
|
-
return err(e instanceof Error ? e : new Error(String(e)));
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
tz().match(
|
|
315
|
-
() => { /* success */ },
|
|
316
|
-
(err) => {
|
|
317
|
-
fileId('scriptFailed').error(`脚本执行失败: ${err.message}`);
|
|
318
|
-
process.exit(1);
|
|
319
|
-
},
|
|
320
|
-
);
|