@longzai-intelligence-issues/ledger 0.0.1
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/CHANGELOG.md +18 -0
- package/README.md +26 -0
- package/dist/index.d.ts +2123 -0
- package/dist/index.js +78 -0
- package/dist/rolldown-runtime-BqT_7tdF.js +1 -0
- package/lzi-builder.config.ts +15 -0
- package/lzi-bun.config.ts +8 -0
- package/oxlint.config.ts +3 -0
- package/package.json +39 -0
- package/src/__tests__/close/verify-close.commands.test.ts +560 -0
- package/src/__tests__/docs-refs/docs-refs.commands.test.ts +213 -0
- package/src/__tests__/fs/mini-glob.utils.test.ts +71 -0
- package/src/__tests__/fs/walk-surface.utils.test.ts +170 -0
- package/src/__tests__/health/health.commands.test.ts +131 -0
- package/src/__tests__/lint/lint.core.test.ts +332 -0
- package/src/__tests__/numbering/numbering.commands.test.ts +194 -0
- package/src/__tests__/numbering/numbering.core.test.ts +318 -0
- package/src/__tests__/parser/guide-fixture.utils.test.ts +45 -0
- package/src/__tests__/parser/registry.parser.test.ts +316 -0
- package/src/__tests__/prompts/prompts.commands.test.ts +47 -0
- package/src/__tests__/template/issue-template.renderer.test.ts +133 -0
- package/src/close/evidence-reader.utils.ts +201 -0
- package/src/close/green-flip.commands.ts +199 -0
- package/src/close/verify-close.commands.ts +676 -0
- package/src/docs-refs/docs-refs.commands.ts +693 -0
- package/src/freeze/freeze.commands.ts +261 -0
- package/src/fs/mini-glob.utils.ts +216 -0
- package/src/fs/walk-surface.utils.ts +298 -0
- package/src/health/health.commands.ts +327 -0
- package/src/index.ts +46 -0
- package/src/lint/lint-baseline.commands.ts +147 -0
- package/src/lint/lint.core.ts +584 -0
- package/src/normalize/normalize-header.commands.ts +361 -0
- package/src/numbering/numbering.commands.ts +375 -0
- package/src/numbering/numbering.core.ts +685 -0
- package/src/parser/format.utils.ts +279 -0
- package/src/parser/guide-fixture.utils.ts +117 -0
- package/src/parser/registry.parser.ts +679 -0
- package/src/prompts/prompts.commands.ts +211 -0
- package/src/template/issue-template.renderer.ts +351 -0
- package/src/triage/triage.classify.ts +93 -0
- package/src/vault/vault.commands.ts +434 -0
- package/tsconfig/.cache/build.tsbuildinfo +1 -0
- package/tsconfig/.cache/node.tsbuildinfo +1 -0
- package/tsconfig/.cache/test.tsbuildinfo +1 -0
- package/tsconfig/app.json +13 -0
- package/tsconfig/build.json +15 -0
- package/tsconfig/node.json +12 -0
- package/tsconfig/test.json +15 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* freeze / unfreeze(裁定语硬门)
|
|
3
|
+
*
|
|
4
|
+
* 冻结/解冻均属人类裁定事项:--reason/--note 必须含「用户裁定」,工具拒绝形式
|
|
5
|
+
* 冻结/解冻;状态行含 `|`(复合元信息行)拒绝整行破坏性替换;幂等(重复
|
|
6
|
+
* freeze 更新理由);解冻仅对已冻结案生效,恢复 🔴。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
STATUS_LINE_LOCATE_PATTERN,
|
|
14
|
+
STATUS_LINE_PREFIX_SOURCE,
|
|
15
|
+
STATUS_LINE_SCAN_LINES,
|
|
16
|
+
} from '@/parser/format.utils';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 裁定语硬门关键词
|
|
20
|
+
*/
|
|
21
|
+
export const ADJUDICATION_KEYWORD = '用户裁定';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* freeze 命令入参
|
|
25
|
+
*/
|
|
26
|
+
export type FreezeIssueInput = {
|
|
27
|
+
/**
|
|
28
|
+
* 仓库根绝对路径
|
|
29
|
+
*/
|
|
30
|
+
root: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* issue 文件仓库相对路径
|
|
34
|
+
*/
|
|
35
|
+
file: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 裁定理由(须含「用户裁定」)
|
|
39
|
+
*/
|
|
40
|
+
reason: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* unfreeze 命令入参
|
|
45
|
+
*/
|
|
46
|
+
export type UnfreezeIssueInput = {
|
|
47
|
+
/**
|
|
48
|
+
* 仓库根绝对路径
|
|
49
|
+
*/
|
|
50
|
+
root: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* issue 文件仓库相对路径
|
|
54
|
+
*/
|
|
55
|
+
file: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 解冻注记(须含「用户裁定」)
|
|
59
|
+
*/
|
|
60
|
+
note: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* freeze / unfreeze 结果
|
|
65
|
+
*/
|
|
66
|
+
export type FreezeResult = {
|
|
67
|
+
/**
|
|
68
|
+
* 操作成功标记
|
|
69
|
+
*/
|
|
70
|
+
ok: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 结果说明
|
|
74
|
+
*/
|
|
75
|
+
message: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 幂等标记
|
|
79
|
+
*/
|
|
80
|
+
idempotent: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 状态行定位结果
|
|
85
|
+
*/
|
|
86
|
+
type StatusLineLocation = {
|
|
87
|
+
/**
|
|
88
|
+
* 状态行文本
|
|
89
|
+
*/
|
|
90
|
+
line: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 行下标
|
|
94
|
+
*/
|
|
95
|
+
index: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 定位可写状态行(头部窗口内首个命中)
|
|
100
|
+
*
|
|
101
|
+
* @param raw - 文档全文
|
|
102
|
+
* @returns 状态行文本与行下标;未命中为 null
|
|
103
|
+
*/
|
|
104
|
+
function locateStatusLine(raw: string): StatusLineLocation | null {
|
|
105
|
+
/**
|
|
106
|
+
* 文档行数组
|
|
107
|
+
*/
|
|
108
|
+
const lines = raw.split(/\r?\n/);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 扫描窗口上界
|
|
112
|
+
*/
|
|
113
|
+
const limit = Math.min(lines.length, STATUS_LINE_SCAN_LINES);
|
|
114
|
+
|
|
115
|
+
for (let index = 0; index < limit; index += 1) {
|
|
116
|
+
if (STATUS_LINE_LOCATE_PATTERN.test(lines[index] ?? '')) {
|
|
117
|
+
return { line: lines[index] ?? '', index };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 执行冻结(幂等:重复调用更新理由)
|
|
126
|
+
*
|
|
127
|
+
* @param input - 命令入参
|
|
128
|
+
* @param input.root - 仓库根绝对路径
|
|
129
|
+
* @param input.file - issue 文件仓库相对路径
|
|
130
|
+
* @param input.reason - 裁定理由(须含「用户裁定」)
|
|
131
|
+
* @returns 操作结果
|
|
132
|
+
*/
|
|
133
|
+
export function freezeIssue(input: FreezeIssueInput): FreezeResult {
|
|
134
|
+
if (!input.reason.includes(ADJUDICATION_KEYWORD)) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
message: `冻结属人类裁定事项:--reason 必须含「${ADJUDICATION_KEYWORD}」——工具拒绝形式冻结`,
|
|
138
|
+
idempotent: false,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return writeFrozenStatus(input.root, input.file, input.reason, 'freeze');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 执行解冻(仅对已冻结案生效)
|
|
147
|
+
*
|
|
148
|
+
* @param input - 命令入参
|
|
149
|
+
* @param input.root - 仓库根绝对路径
|
|
150
|
+
* @param input.file - issue 文件仓库相对路径
|
|
151
|
+
* @param input.note - 解冻注记(须含「用户裁定」)
|
|
152
|
+
* @returns 操作结果
|
|
153
|
+
*/
|
|
154
|
+
export function unfreezeIssue(input: UnfreezeIssueInput): FreezeResult {
|
|
155
|
+
if (!input.note.includes(ADJUDICATION_KEYWORD)) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
message: `解冻属人类裁定事项:--note 必须含「${ADJUDICATION_KEYWORD}」——工具拒绝形式解冻`,
|
|
159
|
+
idempotent: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return writeFrozenStatus(input.root, input.file, input.note, 'unfreeze');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 冻结/解冻状态行写入核
|
|
168
|
+
*
|
|
169
|
+
* @param root - 仓库根绝对路径
|
|
170
|
+
* @param file - issue 文件仓库相对路径
|
|
171
|
+
* @param noteText - 理由/注记文本
|
|
172
|
+
* @param mode - 操作模式
|
|
173
|
+
* @returns 操作结果
|
|
174
|
+
*/
|
|
175
|
+
function writeFrozenStatus(
|
|
176
|
+
root: string,
|
|
177
|
+
file: string,
|
|
178
|
+
noteText: string,
|
|
179
|
+
mode: 'freeze' | 'unfreeze',
|
|
180
|
+
): FreezeResult {
|
|
181
|
+
/**
|
|
182
|
+
* 文档全文
|
|
183
|
+
*/
|
|
184
|
+
let raw = '';
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
raw = readFileSync(join(root, file), 'utf8');
|
|
188
|
+
} catch {
|
|
189
|
+
return { ok: false, message: '读档失败(文件不存在或不可读)', idempotent: false };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 状态行定位结果
|
|
194
|
+
*/
|
|
195
|
+
const located = locateStatusLine(raw);
|
|
196
|
+
|
|
197
|
+
if (located === null) {
|
|
198
|
+
return { ok: false, message: '未定位到状态行——冻结/解冻拒绝写入', idempotent: false };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (located.line.includes('|')) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
message: '状态行为复合元信息行(含 |)——拒绝整行破坏性替换,请先经 normalize-header 迁移',
|
|
205
|
+
idempotent: false,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 已冻结判定
|
|
211
|
+
*/
|
|
212
|
+
const alreadyFrozen = located.line.includes('已冻结');
|
|
213
|
+
|
|
214
|
+
if (mode === 'unfreeze' && !alreadyFrozen) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
message: `非冻结案拒绝解冻(当前状态行:${located.line})`,
|
|
218
|
+
idempotent: false,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 目标状态行
|
|
224
|
+
*/
|
|
225
|
+
const targetLine =
|
|
226
|
+
mode === 'freeze'
|
|
227
|
+
? `**状态**: ⚪ 已冻结(${noteText}——不推进、不处置、无验证判据;如需推进须先取得「解冻」的显式裁定)`
|
|
228
|
+
: `**状态**: 🔴 待处理(${noteText}——冻结解除,处置方向待重新评估)`;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 幂等判定(同模式同理由原文即目标)
|
|
232
|
+
*/
|
|
233
|
+
if (located.line === targetLine) {
|
|
234
|
+
return { ok: true, message: '状态行已为目标形态——幂等零变更', idempotent: true };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 前缀保留改写(「状态…冒号」前缀原形态 + 空格规范)
|
|
239
|
+
*/
|
|
240
|
+
const prefixMatch = new RegExp(`^(${STATUS_LINE_PREFIX_SOURCE})\\s*`).exec(located.line);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 改写后状态行
|
|
244
|
+
*/
|
|
245
|
+
const rewrittenLine = `${prefixMatch?.[1] ?? '**状态**:'} ${targetLine.replace(/^\*\*状态\*\*:\s*/, '')}`;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 文档行数组
|
|
249
|
+
*/
|
|
250
|
+
const lines = raw.split(/\r?\n/);
|
|
251
|
+
|
|
252
|
+
lines[located.index] = rewrittenLine;
|
|
253
|
+
|
|
254
|
+
writeFileSync(join(root, file), lines.join('\n'));
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
ok: true,
|
|
258
|
+
message: mode === 'freeze' ? '已冻结(⚪)' : '已解冻恢复 🔴',
|
|
259
|
+
idempotent: false,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自研 mini-glob 编译器(零第三方依赖)
|
|
3
|
+
*
|
|
4
|
+
* 语义(apex numbering-guard 同款):单星→段内任意;双星后随分隔→任意层级
|
|
5
|
+
* 目录;双星非后随分隔→任意字符;`?`→段内单字符;`{a,b}`→段内多选一(候选
|
|
6
|
+
* 项可含通配);字符类 `[` 与裸大括号显式抛错(不静默误配);整串锚定匹配。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* mini-glob 编译错误
|
|
11
|
+
*/
|
|
12
|
+
export class MiniGlobError extends Error {
|
|
13
|
+
/**
|
|
14
|
+
* 构造编译错误
|
|
15
|
+
*
|
|
16
|
+
* @param pattern - 涉事 glob 模式
|
|
17
|
+
* @param reason - 违规原因
|
|
18
|
+
*/
|
|
19
|
+
constructor(pattern: string, reason: string) {
|
|
20
|
+
super(`glob 模式「${pattern}」非法:${reason}`);
|
|
21
|
+
this.name = 'MiniGlobError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 正则特殊字符(转义表)
|
|
27
|
+
*/
|
|
28
|
+
const REGEX_SPECIALS = new Set(['.', '^', '$', '+', '(', ')', '|', '\\', ']']);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 编译结果缓存(glob → RegExp)
|
|
32
|
+
*/
|
|
33
|
+
const compiledCache = new Map<string, RegExp>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 转义正则特殊字符
|
|
37
|
+
*
|
|
38
|
+
* @param char - 待转义单字符
|
|
39
|
+
* @returns 转义后文本
|
|
40
|
+
*/
|
|
41
|
+
function escapeChar(char: string): string {
|
|
42
|
+
return REGEX_SPECIALS.has(char) ? `\\${char}` : char;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 翻译不含大括号的片段(`*`/`?`/字符类抛错/字面转义)
|
|
47
|
+
*
|
|
48
|
+
* @param pattern - 完整 glob(错误消息用)
|
|
49
|
+
* @param segment - 待翻译片段
|
|
50
|
+
* @returns 正则片段
|
|
51
|
+
* @throws {@link MiniGlobError} 片段含字符类 `[`
|
|
52
|
+
*/
|
|
53
|
+
function translatePlainSegment(pattern: string, segment: string): string {
|
|
54
|
+
/**
|
|
55
|
+
* 输出收集器
|
|
56
|
+
*/
|
|
57
|
+
let out = '';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 扫描下标
|
|
61
|
+
*/
|
|
62
|
+
let index = 0;
|
|
63
|
+
|
|
64
|
+
while (index < segment.length) {
|
|
65
|
+
/**
|
|
66
|
+
* 当前字符
|
|
67
|
+
*/
|
|
68
|
+
const char = segment[index] ?? '';
|
|
69
|
+
|
|
70
|
+
if (char === '[') {
|
|
71
|
+
throw new MiniGlobError(pattern, '不支持字符类 [(与既有生态对齐,显式抛错不静默误配)');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (char === '*') {
|
|
75
|
+
/**
|
|
76
|
+
* 连续星号长度
|
|
77
|
+
*/
|
|
78
|
+
let stars = 0;
|
|
79
|
+
|
|
80
|
+
while ((segment[index + stars] ?? '') === '*') {
|
|
81
|
+
stars += 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (stars >= 2 && (segment[index + stars] ?? '') === '/') {
|
|
85
|
+
out += '(?:.*/)?';
|
|
86
|
+
index += stars + 1;
|
|
87
|
+
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (stars >= 2) {
|
|
92
|
+
out += '[\\s\\S]*';
|
|
93
|
+
index += stars;
|
|
94
|
+
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
out += '[^/]*';
|
|
99
|
+
index += 1;
|
|
100
|
+
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (char === '?') {
|
|
105
|
+
out += '[^/]';
|
|
106
|
+
index += 1;
|
|
107
|
+
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
out += escapeChar(char);
|
|
112
|
+
index += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 翻译片段(含大括号多选一展开)
|
|
120
|
+
*
|
|
121
|
+
* @param pattern - 完整 glob
|
|
122
|
+
* @param segment - 待翻译片段
|
|
123
|
+
* @returns 正则片段
|
|
124
|
+
* @throws {@link MiniGlobError} 大括号未闭合 / 裸 `}` / 字符类 `[`
|
|
125
|
+
*/
|
|
126
|
+
function translateSegment(pattern: string, segment: string): string {
|
|
127
|
+
/**
|
|
128
|
+
* 首个大括号位置
|
|
129
|
+
*/
|
|
130
|
+
const braceStart = segment.indexOf('{');
|
|
131
|
+
|
|
132
|
+
if (braceStart === -1) {
|
|
133
|
+
return translatePlainSegment(pattern, segment);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 配对闭括号位置
|
|
138
|
+
*/
|
|
139
|
+
const braceEnd = segment.indexOf('}', braceStart + 1);
|
|
140
|
+
|
|
141
|
+
if (braceEnd === -1) {
|
|
142
|
+
throw new MiniGlobError(pattern, '大括号未闭合');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 大括号前的字面前缀
|
|
147
|
+
*/
|
|
148
|
+
const prefix = segment.slice(0, braceStart);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 多选一候选内容(含大括号即嵌套形态,显式拒收)
|
|
152
|
+
*/
|
|
153
|
+
const braceContent = segment.slice(braceStart + 1, braceEnd);
|
|
154
|
+
|
|
155
|
+
if (braceContent.includes('{')) {
|
|
156
|
+
throw new MiniGlobError(pattern, '不支持嵌套大括号');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 多选一候选列表
|
|
161
|
+
*/
|
|
162
|
+
const candidates = braceContent.split(',');
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 大括号后的剩余部分
|
|
166
|
+
*/
|
|
167
|
+
const rest = segment.slice(braceEnd + 1);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 逐候选翻译结果(候选 + 剩余部分合并翻译,支持嵌套前后缀)
|
|
171
|
+
*/
|
|
172
|
+
const translated = candidates.map((candidate) =>
|
|
173
|
+
translateSegment(pattern, `${prefix}${candidate}${rest}`),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return `(?:${translated.join('|')})`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 编译 glob 模式为整串锚定正则
|
|
181
|
+
*
|
|
182
|
+
* @param pattern - glob 模式
|
|
183
|
+
* @returns 整串 `^…$` 正则
|
|
184
|
+
* @throws {@link MiniGlobError} 模式含不支持语法
|
|
185
|
+
*/
|
|
186
|
+
export function compileMiniGlob(pattern: string): RegExp {
|
|
187
|
+
/**
|
|
188
|
+
* 缓存命中结果
|
|
189
|
+
*/
|
|
190
|
+
const cached = compiledCache.get(pattern);
|
|
191
|
+
|
|
192
|
+
if (cached !== undefined) {
|
|
193
|
+
return cached;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 编译产物(^…$ 锚定)
|
|
198
|
+
*/
|
|
199
|
+
const compiled = new RegExp(`^${translateSegment(pattern, pattern)}$`);
|
|
200
|
+
|
|
201
|
+
compiledCache.set(pattern, compiled);
|
|
202
|
+
|
|
203
|
+
return compiled;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 判定候选路径是否命中 glob
|
|
208
|
+
*
|
|
209
|
+
* @param pattern - glob 模式
|
|
210
|
+
* @param candidate - 候选 posix 相对路径
|
|
211
|
+
* @returns 是否命中
|
|
212
|
+
* @throws {@link MiniGlobError} 模式含不支持语法
|
|
213
|
+
*/
|
|
214
|
+
export function matchMiniGlob(pattern: string, candidate: string): boolean {
|
|
215
|
+
return compileMiniGlob(pattern).test(candidate);
|
|
216
|
+
}
|