@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
package/CHANGELOG.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import "./rolldown-runtime-BqT_7tdF.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
//#region src/schema/lzi-specs-config.schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* scope key 合法形态(kebab-case)
|
|
6
|
+
*/
|
|
7
|
+
declare const SCOPE_KEY_PATTERN: RegExp;
|
|
8
|
+
/**
|
|
9
|
+
* init 缺省注册表目录
|
|
10
|
+
*/
|
|
11
|
+
declare const DEFAULT_REGISTRY_DIR = "docs/specs";
|
|
12
|
+
/**
|
|
13
|
+
* 棘轮基线条目 schema(存量豁免逐条带理由登记——存量不追溯,豁免只许缩短)
|
|
14
|
+
*/
|
|
15
|
+
declare const baselineEntrySchema: z.ZodObject<{
|
|
16
|
+
file: z.ZodString;
|
|
17
|
+
rule: z.ZodString;
|
|
18
|
+
reason: z.ZodString;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
/**
|
|
21
|
+
* scope 注册条目 schema
|
|
22
|
+
*/
|
|
23
|
+
declare const scopeSchema: z.ZodObject<{
|
|
24
|
+
key: z.ZodString;
|
|
25
|
+
title: z.ZodString;
|
|
26
|
+
paths: z.ZodArray<z.ZodString>;
|
|
27
|
+
registryDir: z.ZodString;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
/**
|
|
30
|
+
* scope 注册条目(书写形态)
|
|
31
|
+
*/
|
|
32
|
+
type ScopeEntryInput = z.input<typeof scopeSchema>;
|
|
33
|
+
/**
|
|
34
|
+
* scope 注册条目(解析后形态)
|
|
35
|
+
*/
|
|
36
|
+
type ScopeEntry = z.output<typeof scopeSchema>;
|
|
37
|
+
/**
|
|
38
|
+
* lzi-specs 配置根 schema
|
|
39
|
+
*/
|
|
40
|
+
declare const lziSpecsConfigSchema: z.ZodObject<{
|
|
41
|
+
scopes: z.ZodArray<z.ZodObject<{
|
|
42
|
+
key: z.ZodString;
|
|
43
|
+
title: z.ZodString;
|
|
44
|
+
paths: z.ZodArray<z.ZodString>;
|
|
45
|
+
registryDir: z.ZodString;
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
|
+
check: z.ZodDefault<z.ZodObject<{
|
|
48
|
+
baseline: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
49
|
+
file: z.ZodString;
|
|
50
|
+
rule: z.ZodString;
|
|
51
|
+
reason: z.ZodString;
|
|
52
|
+
}, z.core.$strip>>>;
|
|
53
|
+
}, z.core.$strip>>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
/**
|
|
56
|
+
* lzi-specs 配置(用户书写形态,可省略各默认值)
|
|
57
|
+
*/
|
|
58
|
+
type LziSpecsConfigInput = z.input<typeof lziSpecsConfigSchema>;
|
|
59
|
+
/**
|
|
60
|
+
* lzi-specs 配置(解析后形态,默认值全部落定)
|
|
61
|
+
*/
|
|
62
|
+
type LziSpecsConfig = z.output<typeof lziSpecsConfigSchema>;
|
|
63
|
+
/**
|
|
64
|
+
* 棘轮基线条目
|
|
65
|
+
*/
|
|
66
|
+
type CheckBaselineEntry = z.infer<typeof baselineEntrySchema>;
|
|
67
|
+
/**
|
|
68
|
+
* 解析并校验配置(非法即抛,加载即失败)
|
|
69
|
+
*
|
|
70
|
+
* @param input - 配置原始对象(defineConfig 产物)
|
|
71
|
+
* @returns 默认值落定的配置
|
|
72
|
+
* @throws {@link Error} 形态违规(消息聚合全部违规项;跨条目语义校验由
|
|
73
|
+
* validateLziSpecsConfig 在加载链路上追加执行)
|
|
74
|
+
*/
|
|
75
|
+
declare function parseLziSpecsConfig(input: unknown): LziSpecsConfig;
|
|
76
|
+
/**
|
|
77
|
+
* 定义 lzi-specs 配置(identity 工厂)
|
|
78
|
+
*
|
|
79
|
+
* 类型透传,与 lzi-dev.config.ts / oxfmt.config.ts 等 workspace 配置惯例同构:
|
|
80
|
+
* 不在工厂层做任何合并——默认值由本模块 schema 解析层统一落定。
|
|
81
|
+
*
|
|
82
|
+
* @param config - 配置对象(可省略各默认值)
|
|
83
|
+
* @returns 原样返回传入的配置对象
|
|
84
|
+
*/
|
|
85
|
+
declare function defineConfig(config: LziSpecsConfigInput): LziSpecsConfigInput;
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/loader/load-specs-config.utils.d.ts
|
|
88
|
+
/**
|
|
89
|
+
* 配置来源类型
|
|
90
|
+
*/
|
|
91
|
+
type LziSpecsConfigSource = 'standalone' | 'unified' | 'explicit';
|
|
92
|
+
/**
|
|
93
|
+
* 加载成功结果
|
|
94
|
+
*/
|
|
95
|
+
type LoadLziSpecsConfigSuccess = {
|
|
96
|
+
/**
|
|
97
|
+
* 加载成功标记
|
|
98
|
+
*/
|
|
99
|
+
ok: true;
|
|
100
|
+
/**
|
|
101
|
+
* 解析并校验后的配置(默认值全部落定)
|
|
102
|
+
*/
|
|
103
|
+
config: LziSpecsConfig;
|
|
104
|
+
/**
|
|
105
|
+
* 命中的配置文件绝对路径
|
|
106
|
+
*/
|
|
107
|
+
configFilePath: string;
|
|
108
|
+
/**
|
|
109
|
+
* 配置来源
|
|
110
|
+
*/
|
|
111
|
+
source: LziSpecsConfigSource;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* 加载失败结果(未找到配置)
|
|
115
|
+
*/
|
|
116
|
+
type LoadLziSpecsConfigFailure = {
|
|
117
|
+
/**
|
|
118
|
+
* 加载成功标记
|
|
119
|
+
*/
|
|
120
|
+
ok: false;
|
|
121
|
+
/**
|
|
122
|
+
* 失败原因
|
|
123
|
+
*/
|
|
124
|
+
reason: string;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* 加载结果
|
|
128
|
+
*/
|
|
129
|
+
type LoadLziSpecsConfigResult = LoadLziSpecsConfigSuccess | LoadLziSpecsConfigFailure;
|
|
130
|
+
/**
|
|
131
|
+
* 加载选项
|
|
132
|
+
*/
|
|
133
|
+
type LoadLziSpecsConfigOptions = {
|
|
134
|
+
/**
|
|
135
|
+
* 查找起点目录(缺省 process.cwd())
|
|
136
|
+
*/
|
|
137
|
+
cwd?: string;
|
|
138
|
+
/**
|
|
139
|
+
* 显式配置文件路径(优先级最高,绕过向上查找)
|
|
140
|
+
*/
|
|
141
|
+
configPath?: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* 加载 lzi-specs 配置
|
|
145
|
+
*
|
|
146
|
+
* @param options - 加载选项
|
|
147
|
+
* @returns 加载结果:命中返回解析后配置;未找到返回失败(显式路径不存在也按失败处理)
|
|
148
|
+
* @throws {@link Error} 命中配置但形态/跨条目非法(加载即失败语义)
|
|
149
|
+
*/
|
|
150
|
+
declare function loadLziSpecsConfig(options?: LoadLziSpecsConfigOptions): Promise<LoadLziSpecsConfigResult>;
|
|
151
|
+
/**
|
|
152
|
+
* 按显式文件 URL 动态导入配置模块(测试与工具内部使用)
|
|
153
|
+
*
|
|
154
|
+
* @param configPath - 配置文件绝对路径
|
|
155
|
+
* @returns 配置模块 default 导出
|
|
156
|
+
* @throws {@link Error} 导入失败或无 default 导出
|
|
157
|
+
*/
|
|
158
|
+
declare function importConfigModule(configPath: string): Promise<unknown>;
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/scaffold/render-scaffold.renderer.d.ts
|
|
161
|
+
/**
|
|
162
|
+
* lzi-specs 配置脚手架渲染(init 命令落盘)
|
|
163
|
+
*
|
|
164
|
+
* 渲染 lzi-specs.config.ts 全文(单 scope 起步形态);幂等不覆盖由命令层守卫。
|
|
165
|
+
*/
|
|
166
|
+
/**
|
|
167
|
+
* 脚手架渲染入参
|
|
168
|
+
*/
|
|
169
|
+
type ConfigScaffoldInput = {
|
|
170
|
+
/**
|
|
171
|
+
* scope key(kebab-case)
|
|
172
|
+
*/
|
|
173
|
+
scopeKey: string;
|
|
174
|
+
/**
|
|
175
|
+
* scope 人类可读名
|
|
176
|
+
*/
|
|
177
|
+
scopeTitle: string;
|
|
178
|
+
/**
|
|
179
|
+
* 注册表目录(posix 相对仓库根)
|
|
180
|
+
*/
|
|
181
|
+
registryDir: string;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* 渲染 lzi-specs.config.ts 脚手架全文
|
|
185
|
+
*
|
|
186
|
+
* @param input - 脚手架入参
|
|
187
|
+
* @returns 可直接落盘的配置文件全文
|
|
188
|
+
*/
|
|
189
|
+
declare function renderConfigScaffold(input: ConfigScaffoldInput): string;
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/validation/validate-cross-entries.d.ts
|
|
192
|
+
/**
|
|
193
|
+
* 校验并透传配置(语义非法即抛,消息聚合全部违规项)
|
|
194
|
+
*
|
|
195
|
+
* @param config - 形态解析后的配置
|
|
196
|
+
* @returns 原样透传(校验通过)
|
|
197
|
+
* @throws {@link Error} 跨条目语义违规
|
|
198
|
+
*/
|
|
199
|
+
declare function validateLziSpecsConfig(config: LziSpecsConfig): LziSpecsConfig;
|
|
200
|
+
//#endregion
|
|
201
|
+
export { CheckBaselineEntry, ConfigScaffoldInput, DEFAULT_REGISTRY_DIR, LoadLziSpecsConfigFailure, LoadLziSpecsConfigOptions, LoadLziSpecsConfigResult, LoadLziSpecsConfigSuccess, LziSpecsConfig, LziSpecsConfigInput, LziSpecsConfigSource, SCOPE_KEY_PATTERN, ScopeEntry, ScopeEntryInput, baselineEntrySchema, defineConfig, importConfigModule, loadLziSpecsConfig, lziSpecsConfigSchema, parseLziSpecsConfig, renderConfigScaffold, validateLziSpecsConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./rolldown-runtime-BqT_7tdF.js";import{loadUnifiedConfig as e}from"@longzai-intelligence-shared-config/core";import{existsSync as t}from"node:fs";import{resolve as n}from"node:path";import{pathToFileURL as r}from"node:url";import*as i from"zod";const a=/^[a-z][a-z0-9-]*$/,o=`docs/specs`,s=i.object({file:i.string().min(1),rule:i.string().min(1),reason:i.string().min(1)}),c=i.object({key:i.string().regex(a),title:i.string().min(1),paths:i.array(i.string().min(1)).min(1),registryDir:i.string().min(1)}),l=i.object({baseline:i.array(s).default([])}),u=i.object({scopes:i.array(c).min(1),check:l.default({baseline:[]})});function d(e){let t=u.safeParse(e);if(!t.success){let e=t.error.issues.map(e=>`${e.path.join(`.`)}: ${e.message}`).join(`;`);throw Error(`lzi-specs 配置形态非法:${e}`)}return t.data}function f(e){return e}function p(e){let t=[],n=/* @__PURE__ */ new Map;for(let t of e.scopes)n.set(t.key,(n.get(t.key)??0)+1);for(let[e,r]of n)r>1&&t.push(`scopes: key「${e}」重复登记 ${r} 次(scope key 必须唯一)`);let r=/* @__PURE__ */ new Map;for(let t of e.scopes){let e=r.get(t.registryDir)??[];e.push(t.key),r.set(t.registryDir,e)}for(let[e,n]of r)n.length>1&&t.push(`scopes: 注册表目录「${e}」被 ${n.join(`、`)} 共用(一目录一 scope,否则识别与 check 歧义)`);for(let n of e.check.baseline)e.scopes.some(e=>n.file===e.registryDir||n.file.startsWith(`${e.registryDir}/`))||t.push(`check.baseline: 豁免条目「${n.file}」(规则 ${n.rule})不在任何 scope 注册表目录内`);if(t.length>0)throw Error(`lzi-specs 配置跨条目语义非法:${t.join(`;`)}`);return e}function m(e,t){try{return p(d(e))}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`${n}(配置文件:${t})`)}}async function h(r={}){if(r.configPath!==void 0){let e=n(r.cwd??process.cwd(),r.configPath);return t(e)?{ok:!0,config:m(await g(e),e),configFilePath:e,source:`explicit`}:{ok:!1,reason:`未找到配置文件:${e}`}}let i=await e({standaloneFile:`lzi-specs.config.ts`,unifiedField:`specs`,cwd:r.cwd});return i.success?{ok:!0,config:m(i.config,i.configFilePath),configFilePath:i.configFilePath,source:i.source}:{ok:!1,reason:`未找到 lzi-specs.config.ts(且 lzi.config.ts 无 specs 字段)——可先执行 lzi-specs init`}}async function g(e){let t=await import(r(e).href);if(t.default===void 0)throw Error(`配置文件缺少 default 导出:${e}`);return t.default}function _(e){return[`import { defineConfig } from '@longzai-intelligence-specs/config';`,``,`/**`,` * lzi-specs 配置`,` */`,`export default defineConfig({`,` scopes: [`,` { key: '${e.scopeKey}', title: '${e.scopeTitle}', paths: ['.'], registryDir: '${e.registryDir}' },`,` ],`,`});`,``].join(`
|
|
2
|
+
`)}export{o as DEFAULT_REGISTRY_DIR,a as SCOPE_KEY_PATTERN,s as baselineEntrySchema,f as defineConfig,g as importConfigModule,h as loadLziSpecsConfig,u as lziSpecsConfigSchema,d as parseLziSpecsConfig,_ as renderConfigScaffold,p as validateLziSpecsConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"node:module";export{};
|
package/oxlint.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longzai-intelligence-specs/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": "bun test",
|
|
25
|
+
"test:watch": "bun test --watch",
|
|
26
|
+
"test:coverage": "bun test --coverage"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@longzai-intelligence-shared-config/core": "^0.0.5",
|
|
30
|
+
"zod": "^4.4.3"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "latest"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { defineConfig, parseLziSpecsConfig } from '@/schema/lzi-specs-config.schema';
|
|
4
|
+
import { validateLziSpecsConfig } from '@/validation/validate-cross-entries';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* lzi-specs 配置 schema 解析与跨条目校验测试
|
|
8
|
+
*/
|
|
9
|
+
describe('lzi-specs-config schema', () => {
|
|
10
|
+
test('最小合法配置解析且默认值落定', () => {
|
|
11
|
+
/**
|
|
12
|
+
* 最小配置
|
|
13
|
+
*/
|
|
14
|
+
const config = parseLziSpecsConfig(
|
|
15
|
+
defineConfig({
|
|
16
|
+
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/specs' }],
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(config.scopes).toHaveLength(1);
|
|
21
|
+
expect(config.check.baseline).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('scope key 形态非法即抛', () => {
|
|
25
|
+
expect(() =>
|
|
26
|
+
parseLziSpecsConfig({
|
|
27
|
+
scopes: [{ key: 'Root', title: '全局', paths: ['.'], registryDir: 'docs/specs' }],
|
|
28
|
+
}),
|
|
29
|
+
).toThrow('lzi-specs 配置形态非法');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('scopes 空数组即抛(至少一个注册面)', () => {
|
|
33
|
+
expect(() => parseLziSpecsConfig({ scopes: [] })).toThrow();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('跨条目:注册表目录被两 scope 共用即抛', () => {
|
|
37
|
+
/**
|
|
38
|
+
* 共用目录配置
|
|
39
|
+
*/
|
|
40
|
+
const config = parseLziSpecsConfig({
|
|
41
|
+
scopes: [
|
|
42
|
+
{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/specs' },
|
|
43
|
+
{ key: 'sub', title: '子面', paths: ['packages'], registryDir: 'docs/specs' },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(() => validateLziSpecsConfig(config)).toThrow(
|
|
48
|
+
'注册表目录「docs/specs」被 root、sub 共用',
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('跨条目:基线豁免文件不在注册面内即抛', () => {
|
|
53
|
+
/**
|
|
54
|
+
* 带越界基线的配置
|
|
55
|
+
*/
|
|
56
|
+
const config = parseLziSpecsConfig({
|
|
57
|
+
scopes: [{ key: 'root', title: '全局', paths: ['.'], registryDir: 'docs/specs' }],
|
|
58
|
+
check: { baseline: [{ file: 'docs/other/0001-x.md', rule: 'R-FILENAME', reason: '存量' }] },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(() => validateLziSpecsConfig(config)).toThrow('不在任何 scope 注册表目录内');
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置包入口
|
|
3
|
+
*
|
|
4
|
+
* 汇出:defineConfig、schema 与解析、跨条目校验、加载器、init 脚手架渲染。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from '@/loader/load-specs-config.utils';
|
|
8
|
+
|
|
9
|
+
export * from '@/scaffold/render-scaffold.renderer';
|
|
10
|
+
|
|
11
|
+
export * from '@/schema/lzi-specs-config.schema';
|
|
12
|
+
|
|
13
|
+
export * from '@/validation/validate-cross-entries';
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lzi-specs 配置加载器
|
|
3
|
+
*
|
|
4
|
+
* 复用 shared-config 的 loadUnifiedConfig 统一加载:优先工具专属
|
|
5
|
+
* lzi-specs.config.ts(cwd 向上查找),回退统一配置 lzi.config.ts 的 specs
|
|
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 LziSpecsConfig, parseLziSpecsConfig } from '@/schema/lzi-specs-config.schema';
|
|
15
|
+
import { validateLziSpecsConfig } from '@/validation/validate-cross-entries';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 配置来源类型
|
|
19
|
+
*/
|
|
20
|
+
export type LziSpecsConfigSource = 'standalone' | 'unified' | 'explicit';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 加载成功结果
|
|
24
|
+
*/
|
|
25
|
+
export type LoadLziSpecsConfigSuccess = {
|
|
26
|
+
/**
|
|
27
|
+
* 加载成功标记
|
|
28
|
+
*/
|
|
29
|
+
ok: true;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 解析并校验后的配置(默认值全部落定)
|
|
33
|
+
*/
|
|
34
|
+
config: LziSpecsConfig;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 命中的配置文件绝对路径
|
|
38
|
+
*/
|
|
39
|
+
configFilePath: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 配置来源
|
|
43
|
+
*/
|
|
44
|
+
source: LziSpecsConfigSource;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 加载失败结果(未找到配置)
|
|
49
|
+
*/
|
|
50
|
+
export type LoadLziSpecsConfigFailure = {
|
|
51
|
+
/**
|
|
52
|
+
* 加载成功标记
|
|
53
|
+
*/
|
|
54
|
+
ok: false;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 失败原因
|
|
58
|
+
*/
|
|
59
|
+
reason: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 加载结果
|
|
64
|
+
*/
|
|
65
|
+
export type LoadLziSpecsConfigResult = LoadLziSpecsConfigSuccess | LoadLziSpecsConfigFailure;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 加载选项
|
|
69
|
+
*/
|
|
70
|
+
export type LoadLziSpecsConfigOptions = {
|
|
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): LziSpecsConfig {
|
|
91
|
+
try {
|
|
92
|
+
/**
|
|
93
|
+
* 形态解析 + 跨条目校验后的配置
|
|
94
|
+
*/
|
|
95
|
+
return validateLziSpecsConfig(parseLziSpecsConfig(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-specs 配置
|
|
108
|
+
*
|
|
109
|
+
* @param options - 加载选项
|
|
110
|
+
* @returns 加载结果:命中返回解析后配置;未找到返回失败(显式路径不存在也按失败处理)
|
|
111
|
+
* @throws {@link Error} 命中配置但形态/跨条目非法(加载即失败语义)
|
|
112
|
+
*/
|
|
113
|
+
export async function loadLziSpecsConfig(
|
|
114
|
+
options: LoadLziSpecsConfigOptions = {},
|
|
115
|
+
): Promise<LoadLziSpecsConfigResult> {
|
|
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 specs 字段)
|
|
139
|
+
*/
|
|
140
|
+
const unified = await loadUnifiedConfig({
|
|
141
|
+
standaloneFile: 'lzi-specs.config.ts',
|
|
142
|
+
unifiedField: 'specs',
|
|
143
|
+
cwd: options.cwd,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!unified.success) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
reason:
|
|
150
|
+
'未找到 lzi-specs.config.ts(且 lzi.config.ts 无 specs 字段)——可先执行 lzi-specs 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
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lzi-specs 配置脚手架渲染(init 命令落盘)
|
|
3
|
+
*
|
|
4
|
+
* 渲染 lzi-specs.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-specs.config.ts 脚手架全文
|
|
29
|
+
*
|
|
30
|
+
* @param input - 脚手架入参
|
|
31
|
+
* @returns 可直接落盘的配置文件全文
|
|
32
|
+
*/
|
|
33
|
+
export function renderConfigScaffold(input: ConfigScaffoldInput): string {
|
|
34
|
+
return [
|
|
35
|
+
"import { defineConfig } from '@longzai-intelligence-specs/config';",
|
|
36
|
+
'',
|
|
37
|
+
'/**',
|
|
38
|
+
' * lzi-specs 配置',
|
|
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
|
+
}
|