@longzai-intelligence-issues/config 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 +12 -0
- package/README.md +23 -0
- package/dist/index.d.ts +400 -0
- package/dist/index.js +6 -0
- package/dist/rolldown-runtime-BqT_7tdF.js +1 -0
- package/lzi-builder.config.ts +14 -0
- package/lzi-bun.config.ts +8 -0
- package/oxlint.config.ts +3 -0
- package/package.json +40 -0
- package/src/__tests__/baseline/render-baseline.renderer.test.ts +142 -0
- package/src/__tests__/scaffold/render-scaffold.renderer.test.ts +137 -0
- package/src/__tests__/schema/lzi-issues-config.schema.test.ts +144 -0
- package/src/__tests__/validation/validate-cross-entries.test.ts +177 -0
- package/src/baseline/render-baseline.renderer.ts +120 -0
- package/src/index.ts +16 -0
- package/src/loader/load-issues-config.utils.ts +180 -0
- package/src/scaffold/render-scaffold.renderer.ts +420 -0
- package/src/schema/lzi-issues-config.schema.ts +277 -0
- package/src/validation/validate-cross-entries.ts +260 -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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
本项目的所有重要更改都将记录在此文件中。
|
|
4
|
+
|
|
5
|
+
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
6
|
+
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
7
|
+
|
|
8
|
+
## [0.0.1] - 2026-09-14
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- 新增 config 包:lzi-issues.config.ts 单一真源 schema(zod 形态+字段级默认)、跨条目语义校验、loadUnifiedConfig 加载封装(standalone>unified)、init 脚手架渲染(create/append)、棘轮基线再生渲染
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @longzai-intelligence-issues/config
|
|
2
|
+
|
|
3
|
+
lzi-issues 配置包,定义单一真源 `lzi-issues.config.ts` 的形态、校验与加载,并承载 init 脚手架渲染与棘轮基线再生渲染。
|
|
4
|
+
|
|
5
|
+
## 意图
|
|
6
|
+
|
|
7
|
+
引擎零仓库知识——一切仓库特有信息(scope 注册、扫描面、门禁正则、提示词开关)只存在于仓库根的 lzi-issues.config.ts;本包让这份配置具备「非法即加载失败」的强校验与跨包一致的消费入口,避免引擎携带任何仓库知识、也避免各工具自建查找逻辑。
|
|
8
|
+
|
|
9
|
+
## 定位
|
|
10
|
+
|
|
11
|
+
位于 `longzai-intelligence-issues` 项目 `packages/config`,是配置层。向下依赖 zod(形态校验)与 `@longzai-intelligence-shared-config/core` 的 loadUnifiedConfig(统一加载优先级链);向上被 packages/ledger(引擎消费配置类型)、apps/cli(命令加载入口)消费。
|
|
12
|
+
|
|
13
|
+
## 职能
|
|
14
|
+
|
|
15
|
+
- defineConfig(identity 工厂)与 zod schema:storage(五策略,默认 files)、scopes(key/paths/registryDir/保留号/slug 数字策略)、lint(基线/strictLegacy/规则包)、numbering(豁免/全局排除/审计根与 pending)、docs(扫描根/扩展白名单/排除/基线)、close(挂起词/重度与全量正则/阻塞门禁)、prompts 各节的字段级默认值。
|
|
16
|
+
- 跨条目语义校验(纯函数):scope key 与保留号查重、基线 (file, rule) 查重、豁免指向已登记 scope 与条目查重、glob 形态与查重、审计根相对路径形态——任一违规在加载即抛错。
|
|
17
|
+
- 加载封装:优先级 lzi-issues.config.ts(cwd 向上查找)> lzi.config.ts 的 issues 字段,支持 --config 显式路径;命中后统一走解析 + 跨条目校验。
|
|
18
|
+
- init 脚手架渲染:create 模式生成完整配置文件(defineConfig 范式 + 契约注释头),append 模式在既有 scopes 数组内做括号配平插入(不动其他节、key 冲突显式抛错)。
|
|
19
|
+
- 棘轮基线再生渲染:以解析值重建配置文件(棘轮治理契约头 + 生成段),供 lint / docs 的 --write-baseline 落盘。
|
|
20
|
+
|
|
21
|
+
## 实现情况
|
|
22
|
+
|
|
23
|
+
已实现 schema、跨条目校验、加载封装、脚手架与基线再生(30 个 bun test 用例,含生成文件动态导入回环解析用例);lint / typecheck / build / test 四道门禁通过。配置各节字段语义的速览亦可参考项目根 lzi-issues.config.ts 的契约注释头。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import "./rolldown-runtime-BqT_7tdF.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
//#region src/schema/lzi-issues-config.schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 存储策略全集(ADR-0048 多策略):files 为 Local First 默认
|
|
6
|
+
*/
|
|
7
|
+
declare const STORAGE_STRATEGIES: readonly ['files', 'sqlite', 'postgres', 'mysql', 'memory'];
|
|
8
|
+
/**
|
|
9
|
+
* 存储策略类型
|
|
10
|
+
*/
|
|
11
|
+
type StorageStrategy = (typeof STORAGE_STRATEGIES)[number];
|
|
12
|
+
/**
|
|
13
|
+
* 关闭门禁挂起词默认集(状态行命中任一即拒绝收口——诚实黄灯维持)
|
|
14
|
+
*/
|
|
15
|
+
declare const DEFAULT_PENDING_WORDS: readonly ['待补跑', '待复跑', '待低负载', '环境不具备', '挂起', '后补跑'];
|
|
16
|
+
/**
|
|
17
|
+
* audit 覆盖面「形似编号」筛选默认模式
|
|
18
|
+
*/
|
|
19
|
+
declare const DEFAULT_NUMBERISH_PATTERN = "^\\d{1,4}[-._]";
|
|
20
|
+
/**
|
|
21
|
+
* audit 默认审计根(docs 树)
|
|
22
|
+
*/
|
|
23
|
+
declare const DEFAULT_AUDIT_ROOTS: readonly ['docs'];
|
|
24
|
+
/**
|
|
25
|
+
* init 缺省注册表目录
|
|
26
|
+
*/
|
|
27
|
+
declare const DEFAULT_REGISTRY_DIR = "docs/issues";
|
|
28
|
+
/**
|
|
29
|
+
* sqlite 策略缺省库文件路径(统一收纳 .lzi/issues/ 命名空间)
|
|
30
|
+
*/
|
|
31
|
+
declare const DEFAULT_SQLITE_PATH = ".lzi/issues/issues.sqlite";
|
|
32
|
+
/**
|
|
33
|
+
* scope key 合法形态(kebab-case)
|
|
34
|
+
*/
|
|
35
|
+
declare const SCOPE_KEY_PATTERN: RegExp;
|
|
36
|
+
/**
|
|
37
|
+
* 编号形态(数字字符串,零填充原文)
|
|
38
|
+
*/
|
|
39
|
+
declare const NUMBER_PATTERN: RegExp;
|
|
40
|
+
/**
|
|
41
|
+
* 扩展名形态(如 .md)
|
|
42
|
+
*/
|
|
43
|
+
declare const EXTENSION_PATTERN: RegExp;
|
|
44
|
+
/**
|
|
45
|
+
* 棘轮基线条目 schema(豁免必须携带 issue 收编指针)
|
|
46
|
+
*/
|
|
47
|
+
declare const baselineEntrySchema: z.ZodObject<{
|
|
48
|
+
file: z.ZodString;
|
|
49
|
+
rule: z.ZodString;
|
|
50
|
+
issue: z.ZodString;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
/**
|
|
53
|
+
* scope 注册条目 schema
|
|
54
|
+
*/
|
|
55
|
+
declare const scopeSchema: z.ZodObject<{
|
|
56
|
+
key: z.ZodString;
|
|
57
|
+
title: z.ZodString;
|
|
58
|
+
paths: z.ZodArray<z.ZodString>;
|
|
59
|
+
registryDir: z.ZodString;
|
|
60
|
+
numbering: z.ZodDefault<z.ZodObject<{
|
|
61
|
+
reserved: z.ZodArray<z.ZodObject<{
|
|
62
|
+
number: z.ZodString;
|
|
63
|
+
reason: z.ZodString;
|
|
64
|
+
}, z.core.$strip>>;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
slugDigitPolicy: z.ZodOptional<z.ZodLiteral<"forbid">>;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
/**
|
|
69
|
+
* scope 注册条目(书写形态)
|
|
70
|
+
*/
|
|
71
|
+
type ScopeEntryInput = z.input<typeof scopeSchema>;
|
|
72
|
+
/**
|
|
73
|
+
* scope 注册条目(解析后形态)
|
|
74
|
+
*/
|
|
75
|
+
type ScopeEntry = z.output<typeof scopeSchema>;
|
|
76
|
+
/**
|
|
77
|
+
* lzi-issues 配置根 schema
|
|
78
|
+
*
|
|
79
|
+
* docs 节整体可选(未配置时 docs 引用检查命令显式拒绝,不设隐式扫描面);
|
|
80
|
+
* 其余节缺省时整体回退该节全默认值。
|
|
81
|
+
*/
|
|
82
|
+
declare const lziIssuesConfigSchema: z.ZodObject<{
|
|
83
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
84
|
+
storage: z.ZodDefault<z.ZodObject<{
|
|
85
|
+
strategy: z.ZodDefault<z.ZodEnum<{
|
|
86
|
+
files: "files";
|
|
87
|
+
memory: "memory";
|
|
88
|
+
mysql: "mysql";
|
|
89
|
+
postgres: "postgres";
|
|
90
|
+
sqlite: "sqlite";
|
|
91
|
+
}>>;
|
|
92
|
+
sqlitePath: z.ZodDefault<z.ZodString>;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
scopes: z.ZodArray<z.ZodObject<{
|
|
95
|
+
key: z.ZodString;
|
|
96
|
+
title: z.ZodString;
|
|
97
|
+
paths: z.ZodArray<z.ZodString>;
|
|
98
|
+
registryDir: z.ZodString;
|
|
99
|
+
numbering: z.ZodDefault<z.ZodObject<{
|
|
100
|
+
reserved: z.ZodArray<z.ZodObject<{
|
|
101
|
+
number: z.ZodString;
|
|
102
|
+
reason: z.ZodString;
|
|
103
|
+
}, z.core.$strip>>;
|
|
104
|
+
}, z.core.$strip>>;
|
|
105
|
+
slugDigitPolicy: z.ZodOptional<z.ZodLiteral<"forbid">>;
|
|
106
|
+
}, z.core.$strip>>;
|
|
107
|
+
lint: z.ZodDefault<z.ZodObject<{
|
|
108
|
+
baseline: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
109
|
+
file: z.ZodString;
|
|
110
|
+
rule: z.ZodString;
|
|
111
|
+
issue: z.ZodString;
|
|
112
|
+
}, z.core.$strip>>>;
|
|
113
|
+
strictLegacy: z.ZodDefault<z.ZodBoolean>;
|
|
114
|
+
rulePacks: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
numbering: z.ZodDefault<z.ZodObject<{
|
|
117
|
+
allowlist: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
118
|
+
scopeKey: z.ZodString;
|
|
119
|
+
number: z.ZodString;
|
|
120
|
+
files: z.ZodArray<z.ZodString>;
|
|
121
|
+
status: z.ZodEnum<{
|
|
122
|
+
待清偿: "待清偿";
|
|
123
|
+
维持现状: "维持现状";
|
|
124
|
+
}>;
|
|
125
|
+
reason: z.ZodString;
|
|
126
|
+
}, z.core.$strip>>>;
|
|
127
|
+
globalExclusions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
128
|
+
glob: z.ZodString;
|
|
129
|
+
reason: z.ZodString;
|
|
130
|
+
}, z.core.$strip>>>;
|
|
131
|
+
audit: z.ZodDefault<z.ZodObject<{
|
|
132
|
+
roots: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
133
|
+
numberishPattern: z.ZodDefault<z.ZodString>;
|
|
134
|
+
pending: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
135
|
+
glob: z.ZodString;
|
|
136
|
+
reason: z.ZodString;
|
|
137
|
+
}, z.core.$strip>>>;
|
|
138
|
+
}, z.core.$strip>>;
|
|
139
|
+
}, z.core.$strip>>;
|
|
140
|
+
slugDigitAllowlist: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
141
|
+
glob: z.ZodString;
|
|
142
|
+
reason: z.ZodString;
|
|
143
|
+
}, z.core.$strip>>>;
|
|
144
|
+
docs: z.ZodOptional<z.ZodObject<{
|
|
145
|
+
scanRoots: z.ZodArray<z.ZodString>;
|
|
146
|
+
includeExtensions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
147
|
+
exclusions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
148
|
+
glob: z.ZodString;
|
|
149
|
+
reason: z.ZodString;
|
|
150
|
+
}, z.core.$strip>>>;
|
|
151
|
+
baseline: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
152
|
+
file: z.ZodString;
|
|
153
|
+
rule: z.ZodString;
|
|
154
|
+
issue: z.ZodString;
|
|
155
|
+
}, z.core.$strip>>>;
|
|
156
|
+
}, z.core.$strip>>;
|
|
157
|
+
close: z.ZodDefault<z.ZodObject<{
|
|
158
|
+
pendingWords: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
159
|
+
heavyPattern: z.ZodOptional<z.ZodString>;
|
|
160
|
+
fullPattern: z.ZodOptional<z.ZodString>;
|
|
161
|
+
blockingMustBeGreen: z.ZodDefault<z.ZodBoolean>;
|
|
162
|
+
}, z.core.$strip>>;
|
|
163
|
+
prompts: z.ZodDefault<z.ZodObject<{
|
|
164
|
+
builtin: z.ZodDefault<z.ZodBoolean>;
|
|
165
|
+
extraDir: z.ZodOptional<z.ZodString>;
|
|
166
|
+
}, z.core.$strip>>;
|
|
167
|
+
}, z.core.$strip>;
|
|
168
|
+
/**
|
|
169
|
+
* lzi-issues 配置(用户书写形态,可省略各默认值)
|
|
170
|
+
*/
|
|
171
|
+
type LziIssuesConfigInput = z.input<typeof lziIssuesConfigSchema>;
|
|
172
|
+
/**
|
|
173
|
+
* lzi-issues 配置(解析后形态,默认值全部落定)
|
|
174
|
+
*/
|
|
175
|
+
type LziIssuesConfig = z.output<typeof lziIssuesConfigSchema>;
|
|
176
|
+
/**
|
|
177
|
+
* 棘轮基线条目
|
|
178
|
+
*/
|
|
179
|
+
type LintBaselineEntry = z.infer<typeof baselineEntrySchema>;
|
|
180
|
+
/**
|
|
181
|
+
* 解析并校验配置(非法即抛,加载即失败)
|
|
182
|
+
*
|
|
183
|
+
* @param input - 配置原始对象(defineConfig 产物或统一配置字段值)
|
|
184
|
+
* @returns 默认值落定的配置
|
|
185
|
+
* @throws {@link Error} 形态违规(消息聚合全部违规项;跨条目语义校验由
|
|
186
|
+
* validateLziIssuesConfig 在加载链路上追加执行)
|
|
187
|
+
*/
|
|
188
|
+
declare function parseLziIssuesConfig(input: unknown): LziIssuesConfig;
|
|
189
|
+
/**
|
|
190
|
+
* 定义 lzi-issues 配置(identity 工厂)
|
|
191
|
+
*
|
|
192
|
+
* 类型透传,与 lzi-dev.config.ts / oxfmt.config.ts 等 workspace 配置惯例同构:
|
|
193
|
+
* 不在工厂层做任何合并——默认值由本模块 schema 解析层统一落定。
|
|
194
|
+
*
|
|
195
|
+
* @param config - 配置对象(可省略各默认值)
|
|
196
|
+
* @returns 原样返回传入的配置对象
|
|
197
|
+
*/
|
|
198
|
+
declare function defineConfig(config: LziIssuesConfigInput): LziIssuesConfigInput;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/baseline/render-baseline.renderer.d.ts
|
|
201
|
+
/**
|
|
202
|
+
* 棘轮契约注释头(再生文件头)
|
|
203
|
+
*/
|
|
204
|
+
declare const RATCHET_CONTRACT_HEADER: string;
|
|
205
|
+
/**
|
|
206
|
+
* 基线再生目标节
|
|
207
|
+
*/
|
|
208
|
+
type BaselineSection = 'lint' | 'docs';
|
|
209
|
+
/**
|
|
210
|
+
* 基线条目排序与去重((file, rule) 唯一,file 升序、rule 次序)
|
|
211
|
+
*
|
|
212
|
+
* @param entries - 原始条目列表
|
|
213
|
+
* @returns 排序去重后的条目列表
|
|
214
|
+
*/
|
|
215
|
+
declare function deriveBaselineEntries(entries: readonly LintBaselineEntry[]): LintBaselineEntry[];
|
|
216
|
+
/**
|
|
217
|
+
* 渲染基线再生后的完整配置文件
|
|
218
|
+
*
|
|
219
|
+
* @param config - 当前解析后配置(其余节值原样保留)
|
|
220
|
+
* @param section - 再生目标节(lint / docs)
|
|
221
|
+
* @param baseline - 再生基线条目(通常为 deriveBaselineEntries 产物)
|
|
222
|
+
* @returns 完整配置文件文本(棘轮契约头 + defineConfig 体)
|
|
223
|
+
* @throws {@link Error} section 为 docs 但配置未登记 docs 节
|
|
224
|
+
*/
|
|
225
|
+
declare function renderBaselineRegeneratedConfig(config: LziIssuesConfig, section: BaselineSection, baseline: readonly LintBaselineEntry[]): string;
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/loader/load-issues-config.utils.d.ts
|
|
228
|
+
/**
|
|
229
|
+
* 配置来源类型
|
|
230
|
+
*/
|
|
231
|
+
type LziIssuesConfigSource = 'standalone' | 'unified' | 'explicit';
|
|
232
|
+
/**
|
|
233
|
+
* 加载成功结果
|
|
234
|
+
*/
|
|
235
|
+
type LoadLziIssuesConfigSuccess = {
|
|
236
|
+
/**
|
|
237
|
+
* 加载成功标记
|
|
238
|
+
*/
|
|
239
|
+
ok: true;
|
|
240
|
+
/**
|
|
241
|
+
* 解析并校验后的配置(默认值全部落定)
|
|
242
|
+
*/
|
|
243
|
+
config: LziIssuesConfig;
|
|
244
|
+
/**
|
|
245
|
+
* 命中的配置文件绝对路径
|
|
246
|
+
*/
|
|
247
|
+
configFilePath: string;
|
|
248
|
+
/**
|
|
249
|
+
* 配置来源
|
|
250
|
+
*/
|
|
251
|
+
source: LziIssuesConfigSource;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* 加载失败结果(未找到配置)
|
|
255
|
+
*/
|
|
256
|
+
type LoadLziIssuesConfigFailure = {
|
|
257
|
+
/**
|
|
258
|
+
* 加载成功标记
|
|
259
|
+
*/
|
|
260
|
+
ok: false;
|
|
261
|
+
/**
|
|
262
|
+
* 失败原因
|
|
263
|
+
*/
|
|
264
|
+
reason: string;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* 加载结果
|
|
268
|
+
*/
|
|
269
|
+
type LoadLziIssuesConfigResult = LoadLziIssuesConfigSuccess | LoadLziIssuesConfigFailure;
|
|
270
|
+
/**
|
|
271
|
+
* 加载选项
|
|
272
|
+
*/
|
|
273
|
+
type LoadLziIssuesConfigOptions = {
|
|
274
|
+
/**
|
|
275
|
+
* 查找起点目录(缺省 process.cwd())
|
|
276
|
+
*/
|
|
277
|
+
cwd?: string;
|
|
278
|
+
/**
|
|
279
|
+
* 显式配置文件路径(优先级最高,绕过向上查找)
|
|
280
|
+
*/
|
|
281
|
+
configPath?: string;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* 加载 lzi-issues 配置
|
|
285
|
+
*
|
|
286
|
+
* @param options - 加载选项
|
|
287
|
+
* @returns 加载结果:命中返回解析后配置;未找到返回失败(显式路径不存在也按失败处理)
|
|
288
|
+
* @throws {@link Error} 命中配置但形态/跨条目非法(加载即失败语义)
|
|
289
|
+
*/
|
|
290
|
+
declare function loadLziIssuesConfig(options?: LoadLziIssuesConfigOptions): Promise<LoadLziIssuesConfigResult>;
|
|
291
|
+
/**
|
|
292
|
+
* 按显式文件 URL 动态导入配置模块(测试与工具内部使用)
|
|
293
|
+
*
|
|
294
|
+
* @param configPath - 配置文件绝对路径
|
|
295
|
+
* @returns 配置模块 default 导出
|
|
296
|
+
* @throws {@link Error} 导入失败或无 default 导出
|
|
297
|
+
*/
|
|
298
|
+
declare function importConfigModule(configPath: string): Promise<unknown>;
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/scaffold/render-scaffold.renderer.d.ts
|
|
301
|
+
/**
|
|
302
|
+
* lzi-issues 配置脚手架渲染(init 命令载体)
|
|
303
|
+
*
|
|
304
|
+
* 结构化渲染而非手拼字符串:create 模式生成完整 config 文件(defineConfig
|
|
305
|
+
* 范式 + 契约注释头);append 模式在既有 scopes 数组内做括号配平插入,不动
|
|
306
|
+
* 其他节。序列化器与基线再生渲染共用(同构 TS 字面量输出)。
|
|
307
|
+
*/
|
|
308
|
+
/**
|
|
309
|
+
* scope 脚手架入参(init 命令旗标映射)
|
|
310
|
+
*/
|
|
311
|
+
type ScopeScaffoldInput = {
|
|
312
|
+
/**
|
|
313
|
+
* scope key(kebab-case)
|
|
314
|
+
*/
|
|
315
|
+
key: string;
|
|
316
|
+
/**
|
|
317
|
+
* scope 人类可读名
|
|
318
|
+
*/
|
|
319
|
+
title: string;
|
|
320
|
+
/**
|
|
321
|
+
* 覆盖路径 glob(缺省 ['.']——本目录树全部)
|
|
322
|
+
*/
|
|
323
|
+
paths?: string[];
|
|
324
|
+
/**
|
|
325
|
+
* 注册表目录(缺省 docs/issues)
|
|
326
|
+
*/
|
|
327
|
+
registryDir?: string;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* 配置文件契约注释头(create 模式)
|
|
331
|
+
*/
|
|
332
|
+
declare const CONFIG_SCAFFOLD_HEADER: string;
|
|
333
|
+
/**
|
|
334
|
+
* 序列化完整配置对象为 defineConfig 包裹的配置体文本(不含文件头注释)
|
|
335
|
+
*
|
|
336
|
+
* @param config - 配置对象(解析后或字面量输入形态)
|
|
337
|
+
* @returns 配置体文本(export default defineConfig({...});)
|
|
338
|
+
*/
|
|
339
|
+
declare function serializeConfigBody(config: Record<string, unknown>): string;
|
|
340
|
+
/**
|
|
341
|
+
* 渲染完整配置文件文本(create 模式)
|
|
342
|
+
*
|
|
343
|
+
* @param config - 配置对象
|
|
344
|
+
* @param header - 文件头注释(缺省 init 契约头)
|
|
345
|
+
* @returns 完整文件文本
|
|
346
|
+
*/
|
|
347
|
+
declare function renderConfigFile(config: Record<string, unknown>, header?: string): string;
|
|
348
|
+
/**
|
|
349
|
+
* 渲染 scope 条目字面量(4 空格基准缩进,append 模式插入单元)
|
|
350
|
+
*
|
|
351
|
+
* @param scope - scope 脚手架入参
|
|
352
|
+
* @returns scope 条目字面量文本
|
|
353
|
+
*/
|
|
354
|
+
declare function renderScopeEntry(scope: ScopeScaffoldInput): string;
|
|
355
|
+
/**
|
|
356
|
+
* 渲染 init 生成的完整配置文件(单 scope 起步)
|
|
357
|
+
*
|
|
358
|
+
* @param scope - 首个 scope 入参
|
|
359
|
+
* @returns 完整配置文件文本
|
|
360
|
+
*/
|
|
361
|
+
declare function renderConfigScaffoldFile(scope: ScopeScaffoldInput): string;
|
|
362
|
+
/**
|
|
363
|
+
* 在既有配置文本的 scopes 数组内追加 scope(append 模式,不动其他节)
|
|
364
|
+
*
|
|
365
|
+
* 仅依赖括号配平定位数组收口,在收口前插入条目;scope key 已存在时显式抛错。
|
|
366
|
+
* 插入缩进按本包渲染器规范(4 空格)——非规范缩进的文件插入后由 oxfmt 归一。
|
|
367
|
+
*
|
|
368
|
+
* @param configText - 既有配置文件全文
|
|
369
|
+
* @param scope - 待追加 scope 入参
|
|
370
|
+
* @returns 追加后的配置文件全文
|
|
371
|
+
* @throws {@link Error} key 已注册 / 无 scopes 数组 / 括号不配平
|
|
372
|
+
*/
|
|
373
|
+
declare function appendScopeToConfigText(configText: string, scope: ScopeScaffoldInput): string;
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/validation/validate-cross-entries.d.ts
|
|
376
|
+
/**
|
|
377
|
+
* 跨条目校验违规消息列表类型
|
|
378
|
+
*/
|
|
379
|
+
type CrossEntryViolations = string[];
|
|
380
|
+
/**
|
|
381
|
+
* 跨条目语义校验
|
|
382
|
+
*
|
|
383
|
+
* 校验面:scope key 查重、保留号逐 scope 查重、基线 (file,rule) 查重(lint 与
|
|
384
|
+
* docs 各自为界)、allowlist 指向已登记 scope 且条目查重、全部 glob 形态与
|
|
385
|
+
* 查重、audit.roots 与 registryDir 相对路径形态。
|
|
386
|
+
*
|
|
387
|
+
* @param config - 解析后配置
|
|
388
|
+
* @returns 违规消息列表(空列表 = 合法)
|
|
389
|
+
*/
|
|
390
|
+
declare function validateCrossEntries(config: LziIssuesConfig): CrossEntryViolations;
|
|
391
|
+
/**
|
|
392
|
+
* 校验配置并聚合抛错(加载链路统一入口)
|
|
393
|
+
*
|
|
394
|
+
* @param config - 解析后配置
|
|
395
|
+
* @returns 原样返回合法配置(链式便利)
|
|
396
|
+
* @throws {@link Error} 存在任何跨条目违规(消息聚合全部违规项)
|
|
397
|
+
*/
|
|
398
|
+
declare function validateLziIssuesConfig(config: LziIssuesConfig): LziIssuesConfig;
|
|
399
|
+
//#endregion
|
|
400
|
+
export { BaselineSection, CONFIG_SCAFFOLD_HEADER, CrossEntryViolations, DEFAULT_AUDIT_ROOTS, DEFAULT_NUMBERISH_PATTERN, DEFAULT_PENDING_WORDS, DEFAULT_REGISTRY_DIR, DEFAULT_SQLITE_PATH, EXTENSION_PATTERN, LintBaselineEntry, LoadLziIssuesConfigFailure, LoadLziIssuesConfigOptions, LoadLziIssuesConfigResult, LoadLziIssuesConfigSuccess, LziIssuesConfig, LziIssuesConfigInput, LziIssuesConfigSource, NUMBER_PATTERN, RATCHET_CONTRACT_HEADER, SCOPE_KEY_PATTERN, STORAGE_STRATEGIES, ScopeEntry, ScopeEntryInput, ScopeScaffoldInput, StorageStrategy, appendScopeToConfigText, baselineEntrySchema, defineConfig, deriveBaselineEntries, importConfigModule, loadLziIssuesConfig, lziIssuesConfigSchema, parseLziIssuesConfig, renderBaselineRegeneratedConfig, renderConfigFile, renderConfigScaffoldFile, renderScopeEntry, serializeConfigBody, validateCrossEntries, validateLziIssuesConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import"./rolldown-runtime-BqT_7tdF.js";import*as e from"zod";import{loadUnifiedConfig as t}from"@longzai-intelligence-shared-config/core";import{existsSync as n}from"node:fs";import{resolve as r}from"node:path";import{pathToFileURL as i}from"node:url";const a=[`files`,`sqlite`,`postgres`,`mysql`,`memory`],o=[`待补跑`,`待复跑`,`待低负载`,`环境不具备`,`挂起`,`后补跑`],s=`^\\d{1,4}[-._]`,c=[`docs`],l=`docs/issues`,u=`.lzi/issues/issues.sqlite`,d=/^[a-z][a-z0-9-]*$/,f=/^\d+$/,p=/^\.[A-Za-z0-9]+$/,m=e.object({number:e.string().regex(f),reason:e.string().min(1)}),h=e.object({file:e.string().min(1),rule:e.string().min(1),issue:e.string().min(1)}),g=e.object({glob:e.string().min(1),reason:e.string().min(1)}),_=e.object({key:e.string().regex(d),title:e.string().min(1),paths:e.array(e.string().min(1)).min(1),registryDir:e.string().min(1),numbering:e.object({reserved:e.array(m)}).default({reserved:[]}),slugDigitPolicy:e.literal(`forbid`).optional()}),v=e.object({strategy:e.enum(a).default(`files`),sqlitePath:e.string().min(1).default(u)}),y=e.object({baseline:e.array(h).default([]),strictLegacy:e.boolean().default(!1),rulePacks:e.array(e.string().min(1)).default([])}),b=e.object({scopeKey:e.string().min(1),number:e.string().regex(f),files:e.array(e.string().min(1)).min(2),status:e.enum([`待清偿`,`维持现状`]),reason:e.string().min(1)}),x=e.object({allowlist:e.array(b).default([]),globalExclusions:e.array(g).default([]),audit:e.object({roots:e.array(e.string().min(1)).min(1).default([...c]),numberishPattern:e.string().min(1).default(s),pending:e.array(g).default([])}).default({roots:[...c],numberishPattern:s,pending:[]})}),S=e.object({scanRoots:e.array(e.string().min(1)).min(1),includeExtensions:e.array(e.string().regex(p)).default([`.md`]),exclusions:e.array(g).default([]),baseline:e.array(h).default([])}),C=e.object({pendingWords:e.array(e.string().min(1)).default([...o]),heavyPattern:e.string().min(1).optional(),fullPattern:e.string().min(1).optional(),blockingMustBeGreen:e.boolean().default(!0)}),w=e.object({builtin:e.boolean().default(!0),extraDir:e.string().min(1).optional()}),T=e.object({schemaVersion:e.literal(1),storage:v.default({strategy:`files`,sqlitePath:u}),scopes:e.array(_).min(1),lint:y.default({baseline:[],strictLegacy:!1,rulePacks:[]}),numbering:x.default({allowlist:[],globalExclusions:[],audit:{roots:[...c],numberishPattern:s,pending:[]}}),slugDigitAllowlist:e.array(g).default([]),docs:S.optional(),close:C.default({pendingWords:[...o],blockingMustBeGreen:!0}),prompts:w.default({builtin:!0})});function E(e){let t=T.safeParse(e);if(!t.success){let e=t.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`).join(`;`);throw Error(`lzi-issues 配置形态非法:${e}`)}return t.data}function D(e){return e}const O=[`/**`," * lzi-issues 配置(单一真源)——由 `lzi-issues init` 生成。",` *`,` * 引擎零仓库知识:一切仓库特有信息(scope 注册 / 扫描面 / 门禁参数)只在本文件登记。`,` * 存储默认 files(Local First):issue 为注册表 markdown,派生数据统一 .lzi/issues/。`,` */`].join(`
|
|
2
|
+
`);function k(e){return`'${e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`).replace(/\n/g,`\\n`)}'`}function A(e,t){if(e.length===0)return`[]`;let n=` `.repeat(t+1),r=` `.repeat(t);return e.every(e=>typeof e==`object`&&!!e)?`[\n${e.map(e=>`${n}${N(e,t+1)},`).join(`
|
|
3
|
+
`)}\n${r}]`:`[${e.map(e=>N(e,t+1)).join(`, `)}]`}function j(e,t){let n=Object.keys(e).filter(t=>e[t]!==void 0);if(n.length===0)return`{}`;let r=` `.repeat(t+1),i=` `.repeat(t);return`{\n${n.map(n=>`${r}${n}: ${N(e[n],t+1)},`).join(`
|
|
4
|
+
`)}\n${i}}`}function M(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function N(e,t){return typeof e==`string`?k(e):Array.isArray(e)?A(e,t):M(e)?j(e,t):String(e)}function P(e){return`export default defineConfig(${N(e,0)});\n`}function F(e,t=O){return`import { defineConfig } from '@longzai-intelligence-issues/config';\n\n${t}\n${P(e)}`}function I(e){return N({key:e.key,title:e.title,paths:e.paths??[`.`],registryDir:e.registryDir??`docs/issues`},2).trimStart()}function L(e){return F({schemaVersion:1,scopes:[{key:e.key,title:e.title,paths:e.paths??[`.`],registryDir:e.registryDir??`docs/issues`}]})}function R(e,t){let n=e[t]??``,r={"[":`]`,"{":`}`,"(":`)`}[n];if(r===void 0)throw Error(`扫描起点不是开括号:位置 ${t}`);let i=0;for(let a=t;a<e.length;a+=1){let t=e[a]??``,o=e[a+1]??``;if(t===`/`&&o===`/`){if(a=e.indexOf(`
|
|
5
|
+
`,a),a===-1)break;continue}if(t===`/`&&o===`*`){if(a=e.indexOf(`*/`,a+2),a===-1)throw Error(`括号扫描遇未闭合块注释`);a+=1;continue}if(t===`'`||t===`"`||t==="`"){for(a+=1;a<e.length;){let n=e[a]??``;if(n===`\\`){a+=2;continue}if(n===t)break;a+=1}continue}if(t===n)i+=1;else if(t===r&&(--i,i===0))return a}throw Error(`括号不配平:scopes 数组未闭合`)}function z(e,t){if(RegExp(`key\\s*:\\s*'${t.key}'`).test(e))throw Error(`scope key「${t.key}」已注册,拒绝重复追加`);let n=/scopes\s*:\s*\[/.exec(e);if(n===null)throw Error(`配置文件缺少 scopes 数组,无法追加 scope`);let r=R(e,(n.index??0)+n[0].length-1),i=r-1;for(;i>=0&&/\s/.test(e[i]??``);)--i;let a=e[i]===`,`?`\n ${I(t)}`:`,\n ${I(t)}`;return`${e.slice(0,r)}${a}${e.slice(r)}`}const B=[`/**`," * lzi-issues 配置(单一真源)——由 `--write-baseline --issue <指针>` 再生。",` *`,` * 棘轮基线语义:基线按 (file, rule) 精确命中豁免存量违规;基线外新增违规仍红;`,` * 基线条目无对应现行违规计陈旧红——修复存量即须同步删除对应条目,棘轮只许缩短直至清零;`,` * 每条豁免必须携带 issue 收编指针(非空)。`,` *`,` * 注意:再生以解析值重建本文件,节内手工注释不保留——长篇说明请写入 docs/guides。`,` */`].join(`
|
|
6
|
+
`);function V(e){let t=new Map;for(let n of e){let e=`${n.file}::${n.rule}`;t.has(e)||t.set(e,{...n})}return[...t.values()].sort((e,t)=>e.file===t.file?e.rule.localeCompare(t.rule):e.file.localeCompare(t.file))}function H(e,t,n){let r=V(n);if(t===`lint`)return F({schemaVersion:e.schemaVersion,storage:e.storage,scopes:e.scopes,lint:{...e.lint,baseline:r},numbering:e.numbering,slugDigitAllowlist:e.slugDigitAllowlist,docs:e.docs,close:e.close,prompts:e.prompts},B);if(e.docs===void 0)throw Error(`配置未登记 docs 节,无法再生 docs 基线`);return F({schemaVersion:e.schemaVersion,storage:e.storage,scopes:e.scopes,lint:e.lint,numbering:e.numbering,slugDigitAllowlist:e.slugDigitAllowlist,docs:{...e.docs,baseline:r},close:e.close,prompts:e.prompts},B)}function U(e,t,n){(t.startsWith(`/`)||t.startsWith(`\\`))&&n.push(`${e}「${t}」不得为绝对路径`),t.includes(`\\`)&&n.push(`${e}「${t}」不得包含反斜杠(须为 posix 相对路径)`),t.split(`/`).includes(`..`)&&n.push(`${e}「${t}」不得包含 .. 上溯段`)}function W(e,t,n){t.includes(`\\`)&&n.push(`${e}「${t}」不得包含反斜杠`),t.startsWith(`/`)&&n.push(`${e}「${t}」不得以 / 起首(须为仓库相对 glob)`)}function G(e,t,n){let r=new Set;for(let i of t)r.has(i)&&n.push(`${e} 存在重复 glob「${i}」`),r.add(i)}function K(e){let t=[],n=new Set;e.scopes.forEach((e,r)=>{let i=`scopes[${r}](${e.key})`;n.has(e.key)&&t.push(`scope key「${e.key}」重复注册`),n.add(e.key),U(`${i}.registryDir`,e.registryDir,t);let a=new Set;for(let n of e.numbering.reserved)a.has(n.number)&&t.push(`${i} 保留号「${n.number}」重复登记`),a.add(n.number)});let r=new Set;for(let n of e.lint.baseline){let e=`${n.file}::${n.rule}`;r.has(e)&&t.push(`lint.baseline 条目(${n.file}, ${n.rule})重复`),r.add(e)}if(e.docs!==void 0){let n=new Set;for(let r of e.docs.baseline){let e=`${r.file}::${r.rule}`;n.has(e)&&t.push(`docs.baseline 条目(${r.file}, ${r.rule})重复`),n.add(e)}for(let n of e.docs.exclusions)W(`docs.exclusions`,n.glob,t);G(`docs.exclusions`,e.docs.exclusions.map(e=>e.glob),t);for(let n of e.docs.scanRoots)U(`docs.scanRoots`,n,t)}let i=new Set;for(let r of e.numbering.allowlist){n.has(r.scopeKey)||t.push(`numbering.allowlist 条目指向未登记 scope「${r.scopeKey}」(编号 ${r.number})`);let e=`${r.scopeKey}::${r.number}::${[...r.files].sort().join(`,`)}`;i.has(e)&&t.push(`numbering.allowlist 条目(${r.scopeKey} ${r.number})重复`),i.add(e)}for(let n of e.numbering.globalExclusions)W(`numbering.globalExclusions`,n.glob,t);G(`numbering.globalExclusions`,e.numbering.globalExclusions.map(e=>e.glob),t);for(let n of e.numbering.audit.pending)W(`numbering.audit.pending`,n.glob,t);G(`numbering.audit.pending`,e.numbering.audit.pending.map(e=>e.glob),t);for(let n of e.numbering.audit.roots)U(`numbering.audit.roots`,n,t);for(let n of e.slugDigitAllowlist)W(`slugDigitAllowlist`,n.glob,t);return G(`slugDigitAllowlist`,e.slugDigitAllowlist.map(e=>e.glob),t),t}function q(e){let t=K(e);if(t.length>0)throw Error(`lzi-issues 配置跨条目校验失败:${t.join(`;`)}`);return e}function J(e,t){try{return q(E(e))}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`${n}(配置文件:${t})`)}}async function Y(e={}){if(e.configPath!==void 0){let t=r(e.cwd??process.cwd(),e.configPath);return n(t)?{ok:!0,config:J(await X(t),t),configFilePath:t,source:`explicit`}:{ok:!1,reason:`未找到配置文件:${t}`}}let i=await t({standaloneFile:`lzi-issues.config.ts`,unifiedField:`issues`,cwd:e.cwd});return i.success?{ok:!0,config:J(i.config,i.configFilePath),configFilePath:i.configFilePath,source:i.source}:{ok:!1,reason:`未找到 lzi-issues.config.ts(且 lzi.config.ts 无 issues 字段)——可先执行 lzi-issues init`}}async function X(e){let t=await import(i(e).href);if(t.default===void 0)throw Error(`配置文件缺少 default 导出:${e}`);return t.default}export{O as CONFIG_SCAFFOLD_HEADER,c as DEFAULT_AUDIT_ROOTS,s as DEFAULT_NUMBERISH_PATTERN,o as DEFAULT_PENDING_WORDS,l as DEFAULT_REGISTRY_DIR,u as DEFAULT_SQLITE_PATH,p as EXTENSION_PATTERN,f as NUMBER_PATTERN,B as RATCHET_CONTRACT_HEADER,d as SCOPE_KEY_PATTERN,a as STORAGE_STRATEGIES,z as appendScopeToConfigText,h as baselineEntrySchema,D as defineConfig,V as deriveBaselineEntries,X as importConfigModule,Y as loadLziIssuesConfig,T as lziIssuesConfigSchema,E as parseLziIssuesConfig,H as renderBaselineRegeneratedConfig,F as renderConfigFile,L as renderConfigScaffoldFile,I as renderScopeEntry,P as serializeConfigBody,K as validateCrossEntries,q as validateLziIssuesConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"node:module";export{};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from '@longzai-intelligence-builder/esm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 配置包构建配置
|
|
5
|
+
*
|
|
6
|
+
* 单入口库包:schema/校验/加载/脚手架渲染/基线再生。加载器依赖
|
|
7
|
+
* node:fs / node:path / node:url(纯 Node 运行时),显式声明 platform: 'node'。
|
|
8
|
+
*/
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
entry: {
|
|
11
|
+
index: 'src/index.ts',
|
|
12
|
+
},
|
|
13
|
+
platform: 'node',
|
|
14
|
+
});
|
package/oxlint.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longzai-intelligence-issues/config",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "UNLICENSED",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "lzi-builder",
|
|
16
|
+
"build:prod": "NODE_ENV=production bun run build",
|
|
17
|
+
"prepublishOnly": "bun run build:prod",
|
|
18
|
+
"typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
|
|
19
|
+
"typecheck:app": "lzi-tsgo typecheck tsconfig/app.json",
|
|
20
|
+
"typecheck:node": "lzi-tsgo typecheck tsconfig/node.json",
|
|
21
|
+
"typecheck:test": "lzi-tsgo typecheck tsconfig/test.json",
|
|
22
|
+
"lint": "oxlint && oxfmt --check",
|
|
23
|
+
"lint:fix": "oxlint --fix && oxfmt",
|
|
24
|
+
"test": "lzi-bun-cli test",
|
|
25
|
+
"test:watch": "lzi-bun-cli test --watch",
|
|
26
|
+
"test:coverage": "lzi-bun-cli test --coverage",
|
|
27
|
+
"clean": "lzi-dev-cli clean"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@longzai-intelligence-shared-config/core": "^0.0.5",
|
|
31
|
+
"zod": "^4.4.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@longzai-intelligence-builder/cli": "^0.2.16",
|
|
35
|
+
"@longzai-intelligence-tsgo/cli": "^0.0.1",
|
|
36
|
+
"@types/bun": "latest",
|
|
37
|
+
"@typescript/native-preview": "beta",
|
|
38
|
+
"oxlint": "^1.77.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
deriveBaselineEntries,
|
|
8
|
+
RATCHET_CONTRACT_HEADER,
|
|
9
|
+
renderBaselineRegeneratedConfig,
|
|
10
|
+
} from '@/baseline/render-baseline.renderer';
|
|
11
|
+
import { parseLziIssuesConfig } from '@/schema/lzi-issues-config.schema';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 基线再生回环用的基准配置
|
|
15
|
+
*/
|
|
16
|
+
const BASE_CONFIG = parseLziIssuesConfig({
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/issues' }],
|
|
19
|
+
docs: { scanRoots: ['docs'] },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 动态导入序号(每次唯一文件名,规避 bun 模块 URL 缓存返回陈旧模块)
|
|
24
|
+
*/
|
|
25
|
+
let importSequence = 0;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 动态导入再生文本并解析(包导入改写为包内别名)
|
|
29
|
+
*
|
|
30
|
+
* @param generated - 再生的配置文件全文
|
|
31
|
+
* @returns 解析后配置
|
|
32
|
+
*/
|
|
33
|
+
async function importAndParse(generated: string) {
|
|
34
|
+
/**
|
|
35
|
+
* 临时目录(包内)
|
|
36
|
+
*/
|
|
37
|
+
const tmpDir = join(import.meta.dir, '__tmp-baseline__');
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 临时文件路径(序号唯一化)
|
|
41
|
+
*/
|
|
42
|
+
const tmpFile = join(tmpDir, `gen-${(importSequence += 1)}.config.ts`);
|
|
43
|
+
|
|
44
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
/**
|
|
48
|
+
* 可导入文本
|
|
49
|
+
*/
|
|
50
|
+
const importable = generated.replace(
|
|
51
|
+
"'@longzai-intelligence-issues/config'",
|
|
52
|
+
"'@/schema/lzi-issues-config.schema'",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
writeFileSync(tmpFile, importable);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 动态导入的配置模块
|
|
59
|
+
*/
|
|
60
|
+
const module = (await import(pathToFileURL(tmpFile).href)) as { default: unknown };
|
|
61
|
+
|
|
62
|
+
return parseLziIssuesConfig(module.default);
|
|
63
|
+
} finally {
|
|
64
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('deriveBaselineEntries', () => {
|
|
69
|
+
test('按 (file, rule) 去重并按 file、rule 排序', () => {
|
|
70
|
+
const entries = deriveBaselineEntries([
|
|
71
|
+
{ file: 'b.md', rule: 'y', issue: '0002' },
|
|
72
|
+
{ file: 'a.md', rule: 'z', issue: '0001' },
|
|
73
|
+
{ file: 'a.md', rule: 'z', issue: '0009' },
|
|
74
|
+
{ file: 'a.md', rule: 'x', issue: '0003' },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
expect(entries).toEqual([
|
|
78
|
+
{ file: 'a.md', rule: 'x', issue: '0003' },
|
|
79
|
+
{ file: 'a.md', rule: 'z', issue: '0001' },
|
|
80
|
+
{ file: 'b.md', rule: 'y', issue: '0002' },
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('空入参产出空列表', () => {
|
|
85
|
+
expect(deriveBaselineEntries([])).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('renderBaselineRegeneratedConfig', () => {
|
|
90
|
+
test('lint 节再生:契约头 + 条目在场 + 其余节保留 + 回环合法', async () => {
|
|
91
|
+
const text = renderBaselineRegeneratedConfig(BASE_CONFIG, 'lint', [
|
|
92
|
+
{ file: 'docs/issues/0002-a.md', rule: 'yellow-no-pending-reason', issue: '0003' },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
expect(text.includes('棘轮只许缩短')).toBe(true);
|
|
96
|
+
expect(text.includes(RATCHET_CONTRACT_HEADER)).toBe(true);
|
|
97
|
+
expect(text.includes("rule: 'yellow-no-pending-reason'")).toBe(true);
|
|
98
|
+
expect(text.includes("registryDir: 'docs/issues'")).toBe(true);
|
|
99
|
+
|
|
100
|
+
const config = await importAndParse(text);
|
|
101
|
+
|
|
102
|
+
expect(config.lint.baseline).toEqual([
|
|
103
|
+
{ file: 'docs/issues/0002-a.md', rule: 'yellow-no-pending-reason', issue: '0003' },
|
|
104
|
+
]);
|
|
105
|
+
expect(config.scopes[0]?.key).toBe('root');
|
|
106
|
+
expect(config.docs?.scanRoots).toEqual(['docs']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('docs 节再生:docs.baseline 替换且 lint 节不动', async () => {
|
|
110
|
+
const configWithLintBaseline = {
|
|
111
|
+
...BASE_CONFIG,
|
|
112
|
+
lint: {
|
|
113
|
+
...BASE_CONFIG.lint,
|
|
114
|
+
baseline: [{ file: 'x.md', rule: 'r', issue: '0001' }],
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const text = renderBaselineRegeneratedConfig(configWithLintBaseline, 'docs', [
|
|
119
|
+
{ file: 'README.md', rule: 'doc-ref-unknown', issue: '0004' },
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const config = await importAndParse(text);
|
|
123
|
+
|
|
124
|
+
expect(config.docs?.baseline).toEqual([
|
|
125
|
+
{ file: 'README.md', rule: 'doc-ref-unknown', issue: '0004' },
|
|
126
|
+
]);
|
|
127
|
+
expect(config.lint.baseline).toEqual([{ file: 'x.md', rule: 'r', issue: '0001' }]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('未登记 docs 节时 docs 再生显式抛错', () => {
|
|
131
|
+
expect(() =>
|
|
132
|
+
renderBaselineRegeneratedConfig(
|
|
133
|
+
parseLziIssuesConfig({
|
|
134
|
+
schemaVersion: 1,
|
|
135
|
+
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/issues' }],
|
|
136
|
+
}),
|
|
137
|
+
'docs',
|
|
138
|
+
[],
|
|
139
|
+
),
|
|
140
|
+
).toThrow('docs 节');
|
|
141
|
+
});
|
|
142
|
+
});
|