@longzai-intelligence-records/config 0.0.1 → 0.0.3
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/package.json +11 -1
- package/CHANGELOG.md +0 -12
- package/lzi-builder.config.ts +0 -11
- package/oxlint.config.ts +0 -6
- package/src/__tests__/schema/lzi-records-config.schema.test.ts +0 -63
- package/src/index.ts +0 -13
- package/src/loader/load-records-config.utils.ts +0 -180
- package/src/scaffold/render-scaffold.renderer.ts +0 -47
- package/src/schema/lzi-records-config.schema.ts +0 -122
- package/src/validation/validate-cross-entries.ts +0 -85
- package/tsconfig/.cache/build.tsbuildinfo +0 -1
- package/tsconfig/.cache/node.tsbuildinfo +0 -1
- package/tsconfig/.cache/test.tsbuildinfo +0 -1
- package/tsconfig/app.json +0 -13
- package/tsconfig/build.json +0 -15
- package/tsconfig/node.json +0 -12
- package/tsconfig/test.json +0 -15
- package/tsconfig.json +0 -23
package/package.json
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longzai-intelligence-records/config",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Longzai Intelligence Records 配置",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"config",
|
|
7
|
+
"longzai-intelligence",
|
|
8
|
+
"records",
|
|
9
|
+
"schema"
|
|
10
|
+
],
|
|
4
11
|
"license": "UNLICENSED",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
5
15
|
"type": "module",
|
|
6
16
|
"main": "./dist/index.js",
|
|
7
17
|
"types": "./dist/index.d.ts",
|
package/CHANGELOG.md
DELETED
package/lzi-builder.config.ts
DELETED
package/oxlint.config.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { defineConfig, parseLziRecordsConfig } from '@/schema/lzi-records-config.schema';
|
|
4
|
-
import { validateLziRecordsConfig } from '@/validation/validate-cross-entries';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* lzi-records 配置 schema 解析与跨条目校验测试
|
|
8
|
-
*/
|
|
9
|
-
describe('lzi-records-config schema', () => {
|
|
10
|
-
test('最小合法配置解析且默认值落定', () => {
|
|
11
|
-
/**
|
|
12
|
-
* 最小配置
|
|
13
|
-
*/
|
|
14
|
-
const config = parseLziRecordsConfig(
|
|
15
|
-
defineConfig({
|
|
16
|
-
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/records' }],
|
|
17
|
-
}),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
expect(config.scopes).toHaveLength(1);
|
|
21
|
-
expect(config.lint.baseline).toEqual([]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('scope key 形态非法即抛', () => {
|
|
25
|
-
expect(() =>
|
|
26
|
-
parseLziRecordsConfig({
|
|
27
|
-
scopes: [{ key: 'Root', title: '全局', paths: ['.'], registryDir: 'docs/records' }],
|
|
28
|
-
}),
|
|
29
|
-
).toThrow('lzi-records 配置形态非法');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('scopes 空数组即抛(至少一个注册面)', () => {
|
|
33
|
-
expect(() => parseLziRecordsConfig({ scopes: [] })).toThrow();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('跨条目:注册表目录被两 scope 共用即抛', () => {
|
|
37
|
-
/**
|
|
38
|
-
* 共用目录配置
|
|
39
|
-
*/
|
|
40
|
-
const config = parseLziRecordsConfig({
|
|
41
|
-
scopes: [
|
|
42
|
-
{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/records' },
|
|
43
|
-
{ key: 'sub', title: '子面', paths: ['packages'], registryDir: 'docs/records' },
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(() => validateLziRecordsConfig(config)).toThrow(
|
|
48
|
-
'注册表目录「docs/records」被 root、sub 共用',
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('跨条目:基线豁免文件不在注册面内即抛', () => {
|
|
53
|
-
/**
|
|
54
|
-
* 带越界基线的配置
|
|
55
|
-
*/
|
|
56
|
-
const config = parseLziRecordsConfig({
|
|
57
|
-
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/records' }],
|
|
58
|
-
lint: { baseline: [{ file: 'docs/other/0001-x.md', rule: 'R-FILENAME', reason: '存量' }] },
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
expect(() => validateLziRecordsConfig(config)).toThrow('不在任何 scope 注册表目录内');
|
|
62
|
-
});
|
|
63
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 配置包入口
|
|
3
|
-
*
|
|
4
|
-
* 汇出:defineConfig、schema 与解析、跨条目校验、加载器、init 脚手架渲染。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export * from '@/loader/load-records-config.utils';
|
|
8
|
-
|
|
9
|
-
export * from '@/scaffold/render-scaffold.renderer';
|
|
10
|
-
|
|
11
|
-
export * from '@/schema/lzi-records-config.schema';
|
|
12
|
-
|
|
13
|
-
export * from '@/validation/validate-cross-entries';
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lzi-records 配置加载器
|
|
3
|
-
*
|
|
4
|
-
* 复用 shared-config 的 loadUnifiedConfig 统一加载:优先工具专属
|
|
5
|
-
* lzi-records.config.ts(cwd 向上查找),回退统一配置 lzi.config.ts 的 records
|
|
6
|
-
* 字段;命中后 zod 形态解析 + 跨条目语义校验,非法即抛(加载即失败)。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { loadUnifiedConfig } from '@longzai-intelligence-shared-config/core';
|
|
10
|
-
import { existsSync } from 'node:fs';
|
|
11
|
-
import { resolve } from 'node:path';
|
|
12
|
-
import { pathToFileURL } from 'node:url';
|
|
13
|
-
|
|
14
|
-
import { type LziRecordsConfig, parseLziRecordsConfig } from '@/schema/lzi-records-config.schema';
|
|
15
|
-
import { validateLziRecordsConfig } from '@/validation/validate-cross-entries';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 配置来源类型
|
|
19
|
-
*/
|
|
20
|
-
export type LziRecordsConfigSource = 'standalone' | 'unified' | 'explicit';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 加载成功结果
|
|
24
|
-
*/
|
|
25
|
-
export type LoadLziRecordsConfigSuccess = {
|
|
26
|
-
/**
|
|
27
|
-
* 加载成功标记
|
|
28
|
-
*/
|
|
29
|
-
ok: true;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 解析并校验后的配置(默认值全部落定)
|
|
33
|
-
*/
|
|
34
|
-
config: LziRecordsConfig;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 命中的配置文件绝对路径
|
|
38
|
-
*/
|
|
39
|
-
configFilePath: string;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 配置来源
|
|
43
|
-
*/
|
|
44
|
-
source: LziRecordsConfigSource;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 加载失败结果(未找到配置)
|
|
49
|
-
*/
|
|
50
|
-
export type LoadLziRecordsConfigFailure = {
|
|
51
|
-
/**
|
|
52
|
-
* 加载成功标记
|
|
53
|
-
*/
|
|
54
|
-
ok: false;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 失败原因
|
|
58
|
-
*/
|
|
59
|
-
reason: string;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 加载结果
|
|
64
|
-
*/
|
|
65
|
-
export type LoadLziRecordsConfigResult = LoadLziRecordsConfigSuccess | LoadLziRecordsConfigFailure;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 加载选项
|
|
69
|
-
*/
|
|
70
|
-
export type LoadLziRecordsConfigOptions = {
|
|
71
|
-
/**
|
|
72
|
-
* 查找起点目录(缺省 process.cwd())
|
|
73
|
-
*/
|
|
74
|
-
cwd?: string;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 显式配置文件路径(优先级最高,绕过向上查找)
|
|
78
|
-
*/
|
|
79
|
-
configPath?: string;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 解析 + 校验配置对象(形态 + 跨条目,非法即抛带路径上下文)
|
|
84
|
-
*
|
|
85
|
-
* @param input - 配置原始对象
|
|
86
|
-
* @param configFilePath - 配置文件路径(错误消息附注)
|
|
87
|
-
* @returns 解析并校验后的配置
|
|
88
|
-
* @throws {@link Error} 形态或跨条目违规
|
|
89
|
-
*/
|
|
90
|
-
function parseAndValidate(input: unknown, configFilePath: string): LziRecordsConfig {
|
|
91
|
-
try {
|
|
92
|
-
/**
|
|
93
|
-
* 形态解析 + 跨条目校验后的配置
|
|
94
|
-
*/
|
|
95
|
-
return validateLziRecordsConfig(parseLziRecordsConfig(input));
|
|
96
|
-
} catch (error) {
|
|
97
|
-
/**
|
|
98
|
-
* 原始错误消息
|
|
99
|
-
*/
|
|
100
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
101
|
-
|
|
102
|
-
throw new Error(`${message}(配置文件:${configFilePath})`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 加载 lzi-records 配置
|
|
108
|
-
*
|
|
109
|
-
* @param options - 加载选项
|
|
110
|
-
* @returns 加载结果:命中返回解析后配置;未找到返回失败(显式路径不存在也按失败处理)
|
|
111
|
-
* @throws {@link Error} 命中配置但形态/跨条目非法(加载即失败语义)
|
|
112
|
-
*/
|
|
113
|
-
export async function loadLziRecordsConfig(
|
|
114
|
-
options: LoadLziRecordsConfigOptions = {},
|
|
115
|
-
): Promise<LoadLziRecordsConfigResult> {
|
|
116
|
-
if (options.configPath !== undefined) {
|
|
117
|
-
/**
|
|
118
|
-
* 显式路径解析结果(相对 cwd 锚定)
|
|
119
|
-
*/
|
|
120
|
-
const resolved = resolve(options.cwd ?? process.cwd(), options.configPath);
|
|
121
|
-
|
|
122
|
-
if (!existsSync(resolved)) {
|
|
123
|
-
return {
|
|
124
|
-
ok: false,
|
|
125
|
-
reason: `未找到配置文件:${resolved}`,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
ok: true,
|
|
131
|
-
config: parseAndValidate(await importConfigModule(resolved), resolved),
|
|
132
|
-
configFilePath: resolved,
|
|
133
|
-
source: 'explicit',
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 统一加载结果(standalone 优先 > unified records 字段)
|
|
139
|
-
*/
|
|
140
|
-
const unified = await loadUnifiedConfig({
|
|
141
|
-
standaloneFile: 'lzi-records.config.ts',
|
|
142
|
-
unifiedField: 'records',
|
|
143
|
-
cwd: options.cwd,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (!unified.success) {
|
|
147
|
-
return {
|
|
148
|
-
ok: false,
|
|
149
|
-
reason:
|
|
150
|
-
'未找到 lzi-records.config.ts(且 lzi.config.ts 无 records 字段)——可先执行 lzi-records init',
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
ok: true,
|
|
156
|
-
config: parseAndValidate(unified.config, unified.configFilePath),
|
|
157
|
-
configFilePath: unified.configFilePath,
|
|
158
|
-
source: unified.source,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 按显式文件 URL 动态导入配置模块(测试与工具内部使用)
|
|
164
|
-
*
|
|
165
|
-
* @param configPath - 配置文件绝对路径
|
|
166
|
-
* @returns 配置模块 default 导出
|
|
167
|
-
* @throws {@link Error} 导入失败或无 default 导出
|
|
168
|
-
*/
|
|
169
|
-
export async function importConfigModule(configPath: string): Promise<unknown> {
|
|
170
|
-
/**
|
|
171
|
-
* 动态导入的配置模块
|
|
172
|
-
*/
|
|
173
|
-
const module = await import(pathToFileURL(configPath).href);
|
|
174
|
-
|
|
175
|
-
if (module.default === undefined) {
|
|
176
|
-
throw new Error(`配置文件缺少 default 导出:${configPath}`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return module.default;
|
|
180
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lzi-records 配置脚手架渲染(init 命令落盘)
|
|
3
|
-
*
|
|
4
|
-
* 渲染 lzi-records.config.ts 全文(单 scope 起步形态);幂等不覆盖由命令层守卫。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 脚手架渲染入参
|
|
9
|
-
*/
|
|
10
|
-
export type ConfigScaffoldInput = {
|
|
11
|
-
/**
|
|
12
|
-
* scope key(kebab-case)
|
|
13
|
-
*/
|
|
14
|
-
scopeKey: string;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* scope 人类可读名
|
|
18
|
-
*/
|
|
19
|
-
scopeTitle: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 注册表目录(posix 相对仓库根)
|
|
23
|
-
*/
|
|
24
|
-
registryDir: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 渲染 lzi-records.config.ts 脚手架全文
|
|
29
|
-
*
|
|
30
|
-
* @param input - 脚手架入参
|
|
31
|
-
* @returns 可直接落盘的配置文件全文
|
|
32
|
-
*/
|
|
33
|
-
export function renderConfigScaffold(input: ConfigScaffoldInput): string {
|
|
34
|
-
return [
|
|
35
|
-
"import { defineConfig } from '@longzai-intelligence-records/config';",
|
|
36
|
-
'',
|
|
37
|
-
'/**',
|
|
38
|
-
' * lzi-records 配置',
|
|
39
|
-
' */',
|
|
40
|
-
'export default defineConfig({',
|
|
41
|
-
' scopes: [',
|
|
42
|
-
` { key: '${input.scopeKey}', title: '${input.scopeTitle}', paths: ['.'], registryDir: '${input.registryDir}' },`,
|
|
43
|
-
' ],',
|
|
44
|
-
'});',
|
|
45
|
-
'',
|
|
46
|
-
].join('\n');
|
|
47
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lzi-records 配置 schema 与解析
|
|
3
|
-
*
|
|
4
|
-
* 单一真源 `lzi-records.config.ts` 的形态定义:zod 守形态、跨条目语义由
|
|
5
|
-
* validate-cross-entries 纯函数承担,非法配置在加载即失败。引擎零仓库知识——
|
|
6
|
-
* 一切仓库特有信息(scope 注册、豁免基线)只存在于本配置。
|
|
7
|
-
*
|
|
8
|
-
* records 定位(decisions/021):广义降级 fallback 通道,无语义分型——配置面
|
|
9
|
-
* 亦无类型维度,仅 scope 注册与 lint 豁免基线。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import * as z from 'zod';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* scope key 合法形态(kebab-case)
|
|
16
|
-
*/
|
|
17
|
-
export const SCOPE_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* init 缺省注册表目录
|
|
21
|
-
*/
|
|
22
|
-
export const DEFAULT_REGISTRY_DIR = 'docs/records';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 棘轮基线条目 schema(存量豁免逐条带理由登记——存量不追溯,豁免只许缩短)
|
|
26
|
-
*/
|
|
27
|
-
export const baselineEntrySchema = z.object({
|
|
28
|
-
file: z.string().min(1),
|
|
29
|
-
rule: z.string().min(1),
|
|
30
|
-
reason: z.string().min(1),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* scope 注册条目 schema
|
|
35
|
-
*/
|
|
36
|
-
const scopeSchema = z.object({
|
|
37
|
-
key: z.string().regex(SCOPE_KEY_PATTERN),
|
|
38
|
-
title: z.string().min(1),
|
|
39
|
-
paths: z.array(z.string().min(1)).min(1),
|
|
40
|
-
registryDir: z.string().min(1),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* scope 注册条目(书写形态)
|
|
45
|
-
*/
|
|
46
|
-
export type ScopeEntryInput = z.input<typeof scopeSchema>;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* scope 注册条目(解析后形态)
|
|
50
|
-
*/
|
|
51
|
-
export type ScopeEntry = z.output<typeof scopeSchema>;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* lint 节 schema(baseline 为豁免基线:存量违例逐条登记,只许缩短)
|
|
55
|
-
*/
|
|
56
|
-
const lintSchema = z.object({
|
|
57
|
-
baseline: z.array(baselineEntrySchema).default([]),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* lzi-records 配置根 schema
|
|
62
|
-
*/
|
|
63
|
-
export const lziRecordsConfigSchema = z.object({
|
|
64
|
-
scopes: z.array(scopeSchema).min(1),
|
|
65
|
-
lint: lintSchema.default({ baseline: [] }),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* lzi-records 配置(用户书写形态,可省略各默认值)
|
|
70
|
-
*/
|
|
71
|
-
export type LziRecordsConfigInput = z.input<typeof lziRecordsConfigSchema>;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* lzi-records 配置(解析后形态,默认值全部落定)
|
|
75
|
-
*/
|
|
76
|
-
export type LziRecordsConfig = z.output<typeof lziRecordsConfigSchema>;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 棘轮基线条目
|
|
80
|
-
*/
|
|
81
|
-
export type LintBaselineEntry = z.infer<typeof baselineEntrySchema>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 解析并校验配置(非法即抛,加载即失败)
|
|
85
|
-
*
|
|
86
|
-
* @param input - 配置原始对象(defineConfig 产物)
|
|
87
|
-
* @returns 默认值落定的配置
|
|
88
|
-
* @throws {@link Error} 形态违规(消息聚合全部违规项;跨条目语义校验由
|
|
89
|
-
* validateLziRecordsConfig 在加载链路上追加执行)
|
|
90
|
-
*/
|
|
91
|
-
export function parseLziRecordsConfig(input: unknown): LziRecordsConfig {
|
|
92
|
-
/**
|
|
93
|
-
* 形态解析结果
|
|
94
|
-
*/
|
|
95
|
-
const parsed = lziRecordsConfigSchema.safeParse(input);
|
|
96
|
-
|
|
97
|
-
if (!parsed.success) {
|
|
98
|
-
/**
|
|
99
|
-
* 形态违规明细(路径 + 消息)
|
|
100
|
-
*/
|
|
101
|
-
const details = parsed.error.issues
|
|
102
|
-
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
103
|
-
.join(';');
|
|
104
|
-
|
|
105
|
-
throw new Error(`lzi-records 配置形态非法:${details}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return parsed.data;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 定义 lzi-records 配置(identity 工厂)
|
|
113
|
-
*
|
|
114
|
-
* 类型透传,与 lzi-dev.config.ts / oxfmt.config.ts 等 workspace 配置惯例同构:
|
|
115
|
-
* 不在工厂层做任何合并——默认值由本模块 schema 解析层统一落定。
|
|
116
|
-
*
|
|
117
|
-
* @param config - 配置对象(可省略各默认值)
|
|
118
|
-
* @returns 原样返回传入的配置对象
|
|
119
|
-
*/
|
|
120
|
-
export function defineConfig(config: LziRecordsConfigInput): LziRecordsConfigInput {
|
|
121
|
-
return config;
|
|
122
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lzi-records 配置跨条目语义校验(纯函数,零 fs)
|
|
3
|
-
*
|
|
4
|
-
* 形态由 schema 承担;本模块承担条目间语义:scope key 唯一、注册表目录不共用
|
|
5
|
-
* (两 scope 共用同一 registryDir 会造成取号与 lint 双重歧义)、基线豁免文件
|
|
6
|
-
* 须落在某 scope 注册表内。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { LziRecordsConfig } from '@/schema/lzi-records-config.schema';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 校验并透传配置(语义非法即抛,消息聚合全部违规项)
|
|
13
|
-
*
|
|
14
|
-
* @param config - 形态解析后的配置
|
|
15
|
-
* @returns 原样透传(校验通过)
|
|
16
|
-
* @throws {@link Error} 跨条目语义违规
|
|
17
|
-
*/
|
|
18
|
-
export function validateLziRecordsConfig(config: LziRecordsConfig): LziRecordsConfig {
|
|
19
|
-
/**
|
|
20
|
-
* 违规消息列表
|
|
21
|
-
*/
|
|
22
|
-
const violations: string[] = [];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* scope key 重复检测(key → 出现次数)
|
|
26
|
-
*/
|
|
27
|
-
const keyCounts = new Map<string, number>();
|
|
28
|
-
|
|
29
|
-
for (const scope of config.scopes) {
|
|
30
|
-
keyCounts.set(scope.key, (keyCounts.get(scope.key) ?? 0) + 1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
for (const [key, count] of keyCounts) {
|
|
34
|
-
if (count > 1) {
|
|
35
|
-
violations.push(`scopes: key「${key}」重复登记 ${count} 次(scope key 必须唯一)`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 注册表目录重复检测(registryDir → 占用 scope key 列表)
|
|
41
|
-
*/
|
|
42
|
-
const dirOwners = new Map<string, string[]>();
|
|
43
|
-
|
|
44
|
-
for (const scope of config.scopes) {
|
|
45
|
-
/**
|
|
46
|
-
* 现占用列表
|
|
47
|
-
*/
|
|
48
|
-
const owners = dirOwners.get(scope.registryDir) ?? [];
|
|
49
|
-
|
|
50
|
-
owners.push(scope.key);
|
|
51
|
-
dirOwners.set(scope.registryDir, owners);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
for (const [dir, owners] of dirOwners) {
|
|
55
|
-
if (owners.length > 1) {
|
|
56
|
-
violations.push(
|
|
57
|
-
`scopes: 注册表目录「${dir}」被 ${owners.join('、')} 共用(一目录一 scope,否则取号与 lint 歧义)`,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 基线豁免文件落点检测(豁免文件须命中某 scope 的注册表目录内)
|
|
64
|
-
*/
|
|
65
|
-
for (const entry of config.lint.baseline) {
|
|
66
|
-
/**
|
|
67
|
-
* 命中标记(文件路径以某 registryDir 前缀开头)
|
|
68
|
-
*/
|
|
69
|
-
const covered = config.scopes.some(
|
|
70
|
-
(scope) => entry.file === scope.registryDir || entry.file.startsWith(`${scope.registryDir}/`),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (!covered) {
|
|
74
|
-
violations.push(
|
|
75
|
-
`lint.baseline: 豁免条目「${entry.file}」(规则 ${entry.rule})不在任何 scope 注册表目录内`,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (violations.length > 0) {
|
|
81
|
-
throw new Error(`lzi-records 配置跨条目语义非法:${violations.join(';')}`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return config;
|
|
85
|
-
}
|