@longzai-intelligence-specs/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/dist/index.d.ts +201 -0
- package/dist/index.js +2 -0
- package/dist/rolldown-runtime-BqT_7tdF.js +1 -0
- package/lzi-builder.config.ts +11 -0
- package/oxlint.config.ts +6 -0
- package/package.json +35 -0
- package/src/__tests__/schema/lzi-specs-config.schema.test.ts +63 -0
- package/src/index.ts +13 -0
- package/src/loader/load-specs-config.utils.ts +180 -0
- package/src/scaffold/render-scaffold.renderer.ts +47 -0
- package/src/schema/lzi-specs-config.schema.ts +122 -0
- package/src/validation/validate-cross-entries.ts +85 -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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lzi-specs 配置 schema 与解析
|
|
3
|
+
*
|
|
4
|
+
* 单一真源 `lzi-specs.config.ts` 的形态定义:zod 守形态、跨条目语义由
|
|
5
|
+
* validate-cross-entries 纯函数承担,非法配置在加载即失败。引擎零仓库知识——
|
|
6
|
+
* 一切仓库特有信息(scope 注册、豁免基线)只存在于本配置。
|
|
7
|
+
*
|
|
8
|
+
* specs 无编号取号(目录形态 `spec-YYYYMMDD-slug/`,日期即键),故配置面无
|
|
9
|
+
* numbering 维度;check 的豁免基线用于存量命名/缺件违例的棘轮收编。
|
|
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/specs';
|
|
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
|
+
* check 节 schema(baseline 为豁免基线:存量命名/缺件违例逐条登记,只许缩短)
|
|
55
|
+
*/
|
|
56
|
+
const checkSchema = z.object({
|
|
57
|
+
baseline: z.array(baselineEntrySchema).default([]),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* lzi-specs 配置根 schema
|
|
62
|
+
*/
|
|
63
|
+
export const lziSpecsConfigSchema = z.object({
|
|
64
|
+
scopes: z.array(scopeSchema).min(1),
|
|
65
|
+
check: checkSchema.default({ baseline: [] }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* lzi-specs 配置(用户书写形态,可省略各默认值)
|
|
70
|
+
*/
|
|
71
|
+
export type LziSpecsConfigInput = z.input<typeof lziSpecsConfigSchema>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* lzi-specs 配置(解析后形态,默认值全部落定)
|
|
75
|
+
*/
|
|
76
|
+
export type LziSpecsConfig = z.output<typeof lziSpecsConfigSchema>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 棘轮基线条目
|
|
80
|
+
*/
|
|
81
|
+
export type CheckBaselineEntry = z.infer<typeof baselineEntrySchema>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 解析并校验配置(非法即抛,加载即失败)
|
|
85
|
+
*
|
|
86
|
+
* @param input - 配置原始对象(defineConfig 产物)
|
|
87
|
+
* @returns 默认值落定的配置
|
|
88
|
+
* @throws {@link Error} 形态违规(消息聚合全部违规项;跨条目语义校验由
|
|
89
|
+
* validateLziSpecsConfig 在加载链路上追加执行)
|
|
90
|
+
*/
|
|
91
|
+
export function parseLziSpecsConfig(input: unknown): LziSpecsConfig {
|
|
92
|
+
/**
|
|
93
|
+
* 形态解析结果
|
|
94
|
+
*/
|
|
95
|
+
const parsed = lziSpecsConfigSchema.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-specs 配置形态非法:${details}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return parsed.data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 定义 lzi-specs 配置(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: LziSpecsConfigInput): LziSpecsConfigInput {
|
|
121
|
+
return config;
|
|
122
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lzi-specs 配置跨条目语义校验(纯函数,零 fs)
|
|
3
|
+
*
|
|
4
|
+
* 形态由 schema 承担;本模块承担条目间语义:scope key 唯一、注册表目录不共用
|
|
5
|
+
* (两 scope 共用同一 registryDir 会造成识别与 check 双重歧义)、基线豁免文件
|
|
6
|
+
* 须落在某 scope 注册表内。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { LziSpecsConfig } from '@/schema/lzi-specs-config.schema';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 校验并透传配置(语义非法即抛,消息聚合全部违规项)
|
|
13
|
+
*
|
|
14
|
+
* @param config - 形态解析后的配置
|
|
15
|
+
* @returns 原样透传(校验通过)
|
|
16
|
+
* @throws {@link Error} 跨条目语义违规
|
|
17
|
+
*/
|
|
18
|
+
export function validateLziSpecsConfig(config: LziSpecsConfig): LziSpecsConfig {
|
|
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,否则识别与 check 歧义)`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 基线豁免文件落点检测(豁免文件须命中某 scope 的注册表目录内)
|
|
64
|
+
*/
|
|
65
|
+
for (const entry of config.check.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
|
+
`check.baseline: 豁免条目「${entry.file}」(规则 ${entry.rule})不在任何 scope 注册表目录内`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (violations.length > 0) {
|
|
81
|
+
throw new Error(`lzi-specs 配置跨条目语义非法:${violations.join(';')}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return config;
|
|
85
|
+
}
|