@tbox.cn/app-toolkit 0.4.0 → 0.5.0
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/dist/chunk-WUULQDOV.js +1 -0
- package/dist/contracts-resolver-PS2UH2G6.js +1 -0
- package/dist/index.d.ts +315 -144
- package/dist/index.js +9 -9
- package/package.json +2 -2
- package/src/assembly/provider-catalog.ts +1 -1
- package/src/assembly/resolve-key.ts +75 -0
- package/src/assembly/walk-manifests.ts +138 -85
- package/src/core/contracts-expected.ts +2 -2
- package/src/core/contracts-resolver.ts +12 -1
- package/src/core/module-schema.ts +87 -14
- package/src/dto.ts +48 -33
- package/src/factory.ts +62 -18
- package/src/index.ts +14 -7
- package/src/integrations/mock-bindings.ts +1 -1
- package/src/integrations/module-binding.ts +60 -0
- package/src/integrations/predicate-io.ts +26 -5
- package/src/integrations/predicate.ts +97 -57
- package/src/integrations/prune-bindings.ts +33 -14
- package/src/integrations/write.ts +43 -27
- package/src/views/app.ts +4 -4
- package/src/views/context.ts +11 -12
- package/src/views/integration-schemas.ts +61 -17
- package/src/views/modules.ts +29 -23
- package/src/views/providers.ts +5 -5
- package/src/views/service-detail.ts +1 -1
- package/src/views/service-resolutions.ts +14 -13
- package/tests/credentials-view.test.ts +2 -2
- package/tests/demo-app.ts +11 -6
- package/tests/dev-manifest-catalog.test.ts +14 -14
- package/tests/file-cache.test.ts +5 -5
- package/tests/module-schema.test.ts +106 -20
- package/tests/resolve-key.test.ts +154 -0
- package/tests/vendor-schema-keywords.test.ts +74 -0
- package/tests/views.test.ts +296 -166
- package/tests/write-core.test.ts +83 -0
- package/dist/chunk-KPD3LUH4.js +0 -1
- package/dist/contracts-resolver-E4SECBOG.js +0 -1
- package/src/integrations/domain-binding.ts +0 -81
|
@@ -9,7 +9,7 @@ import { readAppManifestFull, type NpmModuleEntry, type AppManifestFull } from '
|
|
|
9
9
|
*
|
|
10
10
|
* manifest 候选路径探测(packages/<id>/tbox.module.json 优先 → node_modules/<pkg>/tbox.module.json;
|
|
11
11
|
* tbox-app dev 真身经 .tbox-dev.local/dev-manifest.json redirects 独立 catalog 通道直达)一次遍历同时产出:
|
|
12
|
-
* 1. modules:已装模块清单(declared;demand meta
|
|
12
|
+
* 1. modules:已装模块清单(declared;demand meta name/description 透传)
|
|
13
13
|
* 2. catalog(富形态):providers 嵌套(vendor→impl→entry,per-service {implementation, credentialType}
|
|
14
14
|
* 保真——同厂商异构/多实现合法,D25/H1)+ 需求侧聚合
|
|
15
15
|
* 3. serviceSchemas:per-service schema 复合键表——键 `${provider}\u0000${service}`(H2,同一服务
|
|
@@ -20,10 +20,12 @@ import { readAppManifestFull, type NpmModuleEntry, type AppManifestFull } from '
|
|
|
20
20
|
* ③ 候选自身 {service-slots.json, src/service-slots.json}(local/codegen 全量副本——
|
|
21
21
|
* mall 生成应用主形态,不依赖 install);零缓存枚举,与 manifest 探测同机制;
|
|
22
22
|
* 多行业 contracts-* 天然扩展 F8);缺席 → [](退化 demanded ∪ configured)
|
|
23
|
-
* 5. slotMeta:词汇 meta 节(per-service {
|
|
23
|
+
* 5. slotMeta:词汇 meta 节(per-service {name,description} 展示元数据——消费端兜底链
|
|
24
24
|
* 词汇 > demand > id,views/context resolveServiceDisplay 单源);多包发现序首声明胜出
|
|
25
25
|
* (与 catalog addSlots 同语义);contracts-mall 词汇锁保证 mall 域全量覆盖;缺席 → {}
|
|
26
|
-
* 6.
|
|
26
|
+
* 6. ownership:service → moduleId 归属表(v6——declare.owns 聚合;install id 键控;
|
|
27
|
+
* 双 owner 冲突 → declarationIssues OWNERSHIP_CONFLICT + 先声明胜出)
|
|
28
|
+
* 7. declarationIssues:声明面诊断(OWNERSHIP_CONFLICT / OLD_MANIFEST_KEY——P3 通道)
|
|
27
29
|
*
|
|
28
30
|
* **约束(D26;FX-3/F7 边界)**:目录枚举零缓存——readdir / node_modules 候选探测 / `.tbox/app.json` 读取 /
|
|
29
31
|
* dev-manifest 读取每次执行(枚举输入保持 fresh——agent 装卸模块下一笔即见);manifest/service-slots
|
|
@@ -32,6 +34,11 @@ import { readAppManifestFull, type NpmModuleEntry, type AppManifestFull } from '
|
|
|
32
34
|
* 宽松解析语义(坏文件静默 + 重复键首声明保留)零改动。
|
|
33
35
|
*/
|
|
34
36
|
|
|
37
|
+
/** 声明面诊断(v6 P3 通道——OWNERSHIP_CONFLICT / OLD_MANIFEST_KEY;error 级) */
|
|
38
|
+
export type DeclarationIssue =
|
|
39
|
+
| { severity: 'error'; code: 'OWNERSHIP_CONFLICT'; message: string; module?: string }
|
|
40
|
+
| { severity: 'error'; code: 'OLD_MANIFEST_KEY'; message: string; module?: string };
|
|
41
|
+
|
|
35
42
|
export interface WalkManifestsResult {
|
|
36
43
|
/** declared 产物(install id + descriptor;demand meta 透传) */
|
|
37
44
|
modules: WalkModuleInfo[];
|
|
@@ -41,15 +48,18 @@ export interface WalkManifestsResult {
|
|
|
41
48
|
serviceSchemas: Record<string, { configSchema?: string; credentialSchema?: string; packageDir: string }>;
|
|
42
49
|
/** 服务词汇 = 所有携带 service-slots.json 的已装契约包 `.slots` 键并集(缺席 → []) */
|
|
43
50
|
vocabulary: string[];
|
|
44
|
-
/** 词汇 meta 节:per-service {
|
|
51
|
+
/** 词汇 meta 节:per-service {name,description} 展示元数据(多包首声明胜出;缺席 → {}) */
|
|
45
52
|
slotMeta: Record<string, SlotMeta>;
|
|
46
|
-
/**
|
|
47
|
-
|
|
53
|
+
/** v6 服务归属表:service → moduleId(install id 键控——integrations modules 节键同轴;
|
|
54
|
+
* 双 owner 冲突 → declarationIssues + 先声明胜出;dev 通道以 manifest id 参与归属) */
|
|
55
|
+
ownership: Record<string, string>;
|
|
56
|
+
/** 声明面诊断(OWNERSHIP_CONFLICT / OLD_MANIFEST_KEY) */
|
|
57
|
+
declarationIssues: DeclarationIssue[];
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
/** 槽位展示元数据(service-slots.json meta
|
|
60
|
+
/** 槽位展示元数据(service-slots.json meta 节元素——宽松提取,字段皆可选;v7 词汇统一 title→name) */
|
|
51
61
|
export interface SlotMeta {
|
|
52
|
-
|
|
62
|
+
name?: string;
|
|
53
63
|
description?: string;
|
|
54
64
|
}
|
|
55
65
|
|
|
@@ -69,11 +79,11 @@ function parseSlotsFile(raw: string): SlotsFile | null {
|
|
|
69
79
|
if (parsed.meta && typeof parsed.meta === 'object' && !Array.isArray(parsed.meta)) {
|
|
70
80
|
for (const [svc, entry] of Object.entries(parsed.meta as Record<string, unknown>)) {
|
|
71
81
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
72
|
-
const {
|
|
82
|
+
const { name, description } = entry as { name?: unknown; description?: unknown };
|
|
73
83
|
const m: SlotMeta = {};
|
|
74
|
-
if (typeof
|
|
84
|
+
if (typeof name === 'string' && name.length > 0) m.name = name;
|
|
75
85
|
if (typeof description === 'string' && description.length > 0) m.description = description;
|
|
76
|
-
if (m.
|
|
86
|
+
if (m.name !== undefined || m.description !== undefined) meta[svc] = m;
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
return { slots, meta };
|
|
@@ -94,15 +104,15 @@ export interface WalkModuleInfo {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
/** 富形态供给条目:per-service {implementation, credentialType} 恒数组保真(H1);
|
|
97
|
-
*
|
|
98
|
-
* 首声明胜出,与 owner 同语义;B1 walk
|
|
107
|
+
* name/description = 厂商展示元数据(strict manifest descriptor 透传——多包同厂商
|
|
108
|
+
* 首声明胜出,与 owner 同语义;B1 walk 层传播;v7 词汇统一 title→name) */
|
|
99
109
|
export interface WalkProviderEntry {
|
|
100
110
|
provider: string;
|
|
101
111
|
implementation: string;
|
|
102
112
|
credentialType: string;
|
|
103
113
|
owner: string;
|
|
104
|
-
/** 厂商展示名(manifest
|
|
105
|
-
|
|
114
|
+
/** 厂商展示名(manifest name——多包首声明;缺席 = undefined) */
|
|
115
|
+
name?: string;
|
|
106
116
|
description?: string;
|
|
107
117
|
local?: boolean;
|
|
108
118
|
configSchema?: string;
|
|
@@ -117,8 +127,8 @@ export interface WalkCatalog {
|
|
|
117
127
|
{
|
|
118
128
|
optional: boolean;
|
|
119
129
|
defaults: Array<{ provider: string; implementation: string }>;
|
|
120
|
-
/** demand meta(
|
|
121
|
-
|
|
130
|
+
/** demand meta(name/description 首声明保留——模块 manifest demand 侧 additive;v7 title→name) */
|
|
131
|
+
name?: string;
|
|
122
132
|
description?: string;
|
|
123
133
|
owners: string[];
|
|
124
134
|
}
|
|
@@ -126,25 +136,28 @@ export interface WalkCatalog {
|
|
|
126
136
|
credentialTypes: Record<string, { owner: string; packageDir: string; credentialSchema?: string }>;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
/** loose 允许的
|
|
130
|
-
interface
|
|
139
|
+
/** loose 允许的 declare 局部结构(本地声明——解跨模块类型身份耦合,TS2719 防线) */
|
|
140
|
+
interface LooseDeclare {
|
|
131
141
|
providers?: { slots?: ProviderSlotContrib[] };
|
|
132
|
-
|
|
142
|
+
consumes?: Array<{
|
|
133
143
|
service: string;
|
|
134
144
|
optional?: boolean;
|
|
135
145
|
defaults?: Array<{ provider: string; implementation: string }>;
|
|
136
|
-
|
|
146
|
+
name?: string;
|
|
137
147
|
description?: string;
|
|
138
148
|
}>;
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
/** manifest 读取结果(判别联合——strict = 完整 descriptor;loose =
|
|
151
|
+
/** manifest 读取结果(判别联合——strict = 完整 descriptor;loose = 部分形态宽容提取;
|
|
152
|
+
* legacy = 旧键(v6 声明面 / v7 词汇统一,parseModuleDescriptor legacy 标志透传)——
|
|
153
|
+
* 拒 loose 静默兜底,定向 issue)。 */
|
|
142
154
|
type ReadManifestResult =
|
|
143
|
-
| { kind: 'strict';
|
|
144
|
-
| { kind: 'loose';
|
|
155
|
+
| { kind: 'strict'; id: string; version: string; descriptor: ModuleDescriptor; declare: ModuleDescriptor['declare'] }
|
|
156
|
+
| { kind: 'loose'; id?: string; declare: LooseDeclare }
|
|
157
|
+
| { kind: 'legacy'; detail: string };
|
|
145
158
|
|
|
146
|
-
/** 读单文件 manifest(坏文件 → null;宽松——strict
|
|
147
|
-
*
|
|
159
|
+
/** 读单文件 manifest(坏文件 → null;宽松——strict 解析失败但为新形态 declare → loose 兜底;
|
|
160
|
+
* 旧键 → legacy(定向 issue,静默兜底防线——单源 = parseModuleDescriptor legacy 标志)。
|
|
148
161
|
* FX-3(F7 边界):**文件内容**经 file-cache(stat 指纹——外部写下一笔自愈);目录枚举仍零缓存。 */
|
|
149
162
|
function readManifestFile(file: string, cache?: FileCache): ReadManifestResult | null {
|
|
150
163
|
const parse = (raw: string): ReadManifestResult | null => {
|
|
@@ -152,17 +165,19 @@ function readManifestFile(file: string, cache?: FileCache): ReadManifestResult |
|
|
|
152
165
|
const parsedRaw: unknown = JSON.parse(raw);
|
|
153
166
|
const parsed = parseModuleDescriptor(parsedRaw);
|
|
154
167
|
if (parsed.ok) {
|
|
155
|
-
return { kind: 'strict',
|
|
168
|
+
return { kind: 'strict', id: parsed.value.id, version: parsed.value.version, descriptor: parsed.value, declare: parsed.value.declare };
|
|
156
169
|
}
|
|
157
|
-
//
|
|
158
|
-
if (
|
|
159
|
-
|
|
170
|
+
// 旧键(v6/v7):parseModuleDescriptor 已定向检测(legacy 标志)——拒 loose 静默兜底
|
|
171
|
+
if (parsed.legacy) return { kind: 'legacy', detail: parsed.errors.join('; ') };
|
|
172
|
+
// loose 兜底:strict 拒(部分形态 manifest——如仅 declare)但有新形态 declare → 宽松提取
|
|
173
|
+
if (parsedRaw && typeof parsedRaw === 'object' && 'declare' in parsedRaw) {
|
|
174
|
+
const obj = parsedRaw as { id?: unknown; declare?: LooseDeclare };
|
|
160
175
|
return {
|
|
161
176
|
kind: 'loose',
|
|
162
|
-
...(typeof obj.
|
|
163
|
-
|
|
164
|
-
providers:
|
|
165
|
-
|
|
177
|
+
...(typeof obj.id === 'string' ? { id: obj.id } : {}),
|
|
178
|
+
declare: {
|
|
179
|
+
providers: obj.declare?.providers,
|
|
180
|
+
consumes: obj.declare?.consumes,
|
|
166
181
|
},
|
|
167
182
|
};
|
|
168
183
|
}
|
|
@@ -283,10 +298,9 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
283
298
|
const modules: WalkModuleInfo[] = [];
|
|
284
299
|
const catalog: WalkCatalog = { providers: {}, services: {}, credentialTypes: {} };
|
|
285
300
|
const serviceSchemas: WalkManifestsResult['serviceSchemas'] = {};
|
|
286
|
-
const
|
|
301
|
+
const declarationIssues: WalkManifestsResult['declarationIssues'] = [];
|
|
302
|
+
const ownership: Record<string, string> = {};
|
|
287
303
|
const seen = new Set<string>();
|
|
288
|
-
/** 域键 → 首个声明模块(P13 冲突检测) */
|
|
289
|
-
const domainOwners = new Map<string, string>();
|
|
290
304
|
|
|
291
305
|
const addSlots = (slots: readonly ProviderSlotContrib[], owner: string, packageDir: string, modMeta?: SlotMeta): void => {
|
|
292
306
|
for (const slot of slots) {
|
|
@@ -301,7 +315,7 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
301
315
|
implementation: slot.implementation,
|
|
302
316
|
credentialType: slot.credentialType,
|
|
303
317
|
owner,
|
|
304
|
-
...(modMeta?.
|
|
318
|
+
...(modMeta?.name ? { name: modMeta.name } : {}),
|
|
305
319
|
...(modMeta?.description ? { description: modMeta.description } : {}),
|
|
306
320
|
...(slot.local ? { local: true } : {}),
|
|
307
321
|
...(slot.configSchema ? { configSchema: slot.configSchema } : {}),
|
|
@@ -328,10 +342,48 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
328
342
|
|
|
329
343
|
/** 厂商展示元数据(strict manifest descriptor 透传——loose 形态无 descriptor → undefined) */
|
|
330
344
|
const modMetaOf = (manifest: ReadManifestResult): SlotMeta | undefined =>
|
|
331
|
-
manifest.kind === 'strict' ? {
|
|
345
|
+
manifest.kind === 'strict' ? { name: manifest.descriptor.name, description: manifest.descriptor.description } : undefined;
|
|
346
|
+
|
|
347
|
+
/** v6 归属聚合:declare.owns → ownership 表(先声明胜出;冲突 → OWNERSHIP_CONFLICT)。
|
|
348
|
+
* owner 键 = install id(integrations modules 节键同轴——npmModules 通道 = entry.id、
|
|
349
|
+
* packages/dev 通道 = manifest id)。 */
|
|
350
|
+
const addOwns = (services: readonly string[], owner: string): void => {
|
|
351
|
+
for (const service of services) {
|
|
352
|
+
const first = ownership[service];
|
|
353
|
+
if (first === undefined) {
|
|
354
|
+
ownership[service] = owner;
|
|
355
|
+
} else if (first !== owner) {
|
|
356
|
+
declarationIssues.push({
|
|
357
|
+
severity: 'error',
|
|
358
|
+
code: 'OWNERSHIP_CONFLICT',
|
|
359
|
+
module: owner,
|
|
360
|
+
message: `服务 ${service} 归属冲突:${first} 与 ${owner} 均声明 owns(先声明 ${first} 胜出)——修正 manifest declare.owns 互斥`,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
332
365
|
|
|
333
|
-
|
|
334
|
-
|
|
366
|
+
/** 旧键 legacy manifest → 定向 issue(N1:拒 loose 静默兜底)。 */
|
|
367
|
+
const pushLegacyIssue = (manifest: ReadManifestResult, module: string): void => {
|
|
368
|
+
if (manifest.kind !== 'legacy') return;
|
|
369
|
+
declarationIssues.push({
|
|
370
|
+
severity: 'error',
|
|
371
|
+
code: 'OLD_MANIFEST_KEY',
|
|
372
|
+
module,
|
|
373
|
+
message: `${module}:${manifest.detail}`,
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/** declare 提取(strict/loose 双形态统一视图;legacy → null) */
|
|
378
|
+
const declareOf = (manifest: ReadManifestResult): LooseDeclare | null =>
|
|
379
|
+
manifest.kind === 'strict'
|
|
380
|
+
? manifest.declare
|
|
381
|
+
: manifest.kind === 'loose'
|
|
382
|
+
? manifest.declare
|
|
383
|
+
: null;
|
|
384
|
+
|
|
385
|
+
const addServiceConsume = (
|
|
386
|
+
need: { service: string; optional?: boolean; defaults?: Array<{ provider: string; implementation: string }>; name?: string; description?: string },
|
|
335
387
|
owner: string,
|
|
336
388
|
): void => {
|
|
337
389
|
const optional = need.optional !== false;
|
|
@@ -353,22 +405,10 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
353
405
|
catalog.services[need.service] = {
|
|
354
406
|
optional,
|
|
355
407
|
defaults: [...defaults],
|
|
356
|
-
...(need.
|
|
408
|
+
...(need.name ? { name: need.name } : {}),
|
|
357
409
|
...(need.description ? { description: need.description } : {}),
|
|
358
410
|
owners: [owner],
|
|
359
411
|
};
|
|
360
|
-
// P13 域键冲突检测(首声明占用;跨模块冲突 → issue)
|
|
361
|
-
const domain = need.service.includes('.') ? need.service.slice(0, need.service.indexOf('.')) : need.service;
|
|
362
|
-
const firstOwner = domainOwners.get(domain);
|
|
363
|
-
if (firstOwner === undefined) domainOwners.set(domain, owner);
|
|
364
|
-
else if (firstOwner !== owner) {
|
|
365
|
-
domainKeyIssues.push({
|
|
366
|
-
severity: 'warning',
|
|
367
|
-
code: 'DOMAIN_KEY_CONFLICT',
|
|
368
|
-
module: owner,
|
|
369
|
-
message: `域键 ${domain} 被多模块声明(${firstOwner} 与 ${owner})——domains 换算歧义(P13)`,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
412
|
};
|
|
373
413
|
|
|
374
414
|
// 通道 1:npmModules 登记(packages/<id> 本地优先 → node_modules/<pkg>)
|
|
@@ -377,36 +417,46 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
377
417
|
const local = join(appDir, 'packages', entry.id);
|
|
378
418
|
const nm = join(appDir, 'node_modules', ...entry.package.split('/'));
|
|
379
419
|
const manifest = readManifestFile(join(local, 'tbox.module.json'), cache) ?? readManifestFile(join(nm, 'tbox.module.json'), cache);
|
|
420
|
+
if (manifest?.kind === 'legacy') {
|
|
421
|
+
pushLegacyIssue(manifest, entry.id);
|
|
422
|
+
seen.add(entry.id); // 防重复报
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
380
425
|
if (!manifest) continue;
|
|
381
426
|
seen.add(entry.id);
|
|
382
427
|
const dir = existsSync(join(local, 'tbox.module.json')) ? local : nm;
|
|
383
|
-
const
|
|
428
|
+
const declare = declareOf(manifest);
|
|
384
429
|
// loose 形态(部分 manifest)不进 modules 清单(descriptor 缺名不可装配化)——仅 catalog 聚合(旧语义等价)
|
|
385
430
|
if (manifest.kind === 'strict') {
|
|
386
431
|
modules.push({ id: entry.id, dir, pkg: entry.mode === 'sdk' ? entry.package : `@app/${entry.id}`, descriptor: manifest.descriptor });
|
|
387
432
|
}
|
|
388
|
-
const slots =
|
|
433
|
+
const slots = declare?.providers?.slots ?? [];
|
|
389
434
|
if (slots.length) addSlots(slots, entry.package, dir, modMetaOf(manifest));
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
435
|
+
const consumes = declare?.consumes ?? [];
|
|
436
|
+
for (const need of consumes) addServiceConsume(need, entry.package);
|
|
437
|
+
// v6 归属:owner 键 = install id(integrations modules 节同轴)
|
|
438
|
+
const owns = manifest.kind === 'strict' ? manifest.descriptor.declare.owns : [];
|
|
439
|
+
if (owns.length) addOwns(owns.map((o) => o.service), entry.id);
|
|
396
440
|
}
|
|
397
441
|
|
|
398
442
|
// 通道 1.5:tbox-app dev manifest 真身(catalog-only——聚合宽/声明窄不变量:dev 模块
|
|
399
|
-
// 永不进 modules 清单,防 sync 污染受跟踪装配文件;登记真源优先(seen
|
|
443
|
+
// 永不进 modules 清单,防 sync 污染受跟踪装配文件;登记真源优先(seen 按模块名去重))。
|
|
444
|
+
// v6:归属以 manifest id 参与(dev 会话的 modules 集成位键同轴——N3 聚合宽/声明窄)。
|
|
400
445
|
for (const { pkg, dir } of readDevManifestRedirects(appDir)) {
|
|
401
446
|
const manifest = readManifestFile(join(dir, 'tbox.module.json'), cache);
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (slots.length) addSlots(slots, pkg, dir, modMetaOf(manifest));
|
|
406
|
-
const needs = manifest.contributes.services ?? [];
|
|
407
|
-
if (needs.length) {
|
|
408
|
-
for (const need of needs) addServiceNeed(need, pkg);
|
|
447
|
+
if (manifest?.kind === 'legacy') {
|
|
448
|
+
pushLegacyIssue(manifest, pkg);
|
|
449
|
+
continue;
|
|
409
450
|
}
|
|
451
|
+
if (!manifest?.id || seen.has(manifest.id)) continue;
|
|
452
|
+
seen.add(manifest.id);
|
|
453
|
+
const declare = declareOf(manifest);
|
|
454
|
+
const slots = declare?.providers?.slots ?? [];
|
|
455
|
+
if (slots.length) addSlots(slots, pkg, dir, modMetaOf(manifest));
|
|
456
|
+
const consumes = declare?.consumes ?? [];
|
|
457
|
+
for (const need of consumes) addServiceConsume(need, pkg);
|
|
458
|
+
const owns = manifest.kind === 'strict' ? manifest.descriptor.declare.owns : [];
|
|
459
|
+
if (owns.length) addOwns(owns.map((o) => o.service), manifest.id);
|
|
410
460
|
}
|
|
411
461
|
|
|
412
462
|
// 通道 2:packages/* 约定发现(本地模块——npmModules 未登记形态,如手工放入的本地模块)
|
|
@@ -422,22 +472,25 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
422
472
|
if (seen.has(name)) continue;
|
|
423
473
|
const dir = join(packagesDir, name);
|
|
424
474
|
const manifest = readManifestFile(join(dir, 'tbox.module.json'), cache);
|
|
475
|
+
if (manifest?.kind === 'legacy') {
|
|
476
|
+
pushLegacyIssue(manifest, name);
|
|
477
|
+
seen.add(name);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
425
480
|
if (!manifest) continue;
|
|
426
|
-
const
|
|
427
|
-
if (seen.has(
|
|
428
|
-
seen.add(
|
|
429
|
-
const
|
|
481
|
+
const modId = manifest.id ?? name;
|
|
482
|
+
if (seen.has(modId)) continue;
|
|
483
|
+
seen.add(modId);
|
|
484
|
+
const declare = declareOf(manifest);
|
|
430
485
|
if (manifest.kind === 'strict') {
|
|
431
|
-
modules.push({ id:
|
|
432
|
-
}
|
|
433
|
-
const slots = contributes.providers?.slots ?? [];
|
|
434
|
-
if (slots.length) addSlots(slots, `@app/${modName}`, dir, modMetaOf(manifest));
|
|
435
|
-
const needs = contributes.services ?? [];
|
|
436
|
-
if (needs.length) {
|
|
437
|
-
for (const need of needs) {
|
|
438
|
-
addServiceNeed(need, `@app/${modName}`);
|
|
439
|
-
}
|
|
486
|
+
modules.push({ id: modId, dir, pkg: `@app/${modId}`, descriptor: manifest.descriptor });
|
|
440
487
|
}
|
|
488
|
+
const slots = declare?.providers?.slots ?? [];
|
|
489
|
+
if (slots.length) addSlots(slots, `@app/${modId}`, dir, modMetaOf(manifest));
|
|
490
|
+
const consumes = declare?.consumes ?? [];
|
|
491
|
+
for (const need of consumes) addServiceConsume(need, `@app/${modId}`);
|
|
492
|
+
const owns = manifest.kind === 'strict' ? manifest.descriptor.declare.owns : [];
|
|
493
|
+
if (owns.length) addOwns(owns.map((o) => o.service), modId);
|
|
441
494
|
}
|
|
442
495
|
}
|
|
443
496
|
|
|
@@ -452,7 +505,7 @@ export function walkManifests(appDir: string, cache?: FileCache): WalkManifestsR
|
|
|
452
505
|
}
|
|
453
506
|
}
|
|
454
507
|
|
|
455
|
-
return { modules, catalog, serviceSchemas, vocabulary: [...vocabulary].sort(), slotMeta,
|
|
508
|
+
return { modules, catalog, serviceSchemas, vocabulary: [...vocabulary].sort(), slotMeta, ownership, declarationIssues };
|
|
456
509
|
}
|
|
457
510
|
|
|
458
511
|
// ── CLI collectDeclared 迁入(C5——声明集合 + 双端入口探测 + declaration.moduleId 提取)──
|
|
@@ -514,7 +567,7 @@ export function collectDeclaredModules(appDir: string): DeclaredModuleInfo[] {
|
|
|
514
567
|
for (const mod of walk.modules) {
|
|
515
568
|
const mode = modeById.get(mod.id) ?? 'local';
|
|
516
569
|
const hasServerEntry = probeModuleObjectExport(mod.dir, 'server', 'serverModule');
|
|
517
|
-
const c = mod.descriptor.
|
|
570
|
+
const c = mod.descriptor.declare;
|
|
518
571
|
declared.push({
|
|
519
572
|
id: mod.id,
|
|
520
573
|
pkg: mode === 'sdk' ? mod.pkg : `@app/${mod.id}`,
|
|
@@ -13,11 +13,11 @@ import type {
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
export interface EvaluationExports {
|
|
16
|
-
/** 求值单源(TIER0;v3 显式激活 vs v4
|
|
16
|
+
/** 求值单源(TIER0;v3 显式激活 vs v4 通配语义随应用版本;v6 第五参 ownerOf) */
|
|
17
17
|
resolveEffectiveService: typeof resolveEffectiveService;
|
|
18
18
|
/** 供给查表派生(谓词 #12 与视图供给轴共用输入) */
|
|
19
19
|
buildSupplyLookup: typeof buildSupplyLookup;
|
|
20
|
-
/** service →
|
|
20
|
+
/** service → 首点段域前缀(v6 deprecated——级联键控退役,仅词汇组织消费) */
|
|
21
21
|
moduleDomainOf: typeof moduleDomainOf;
|
|
22
22
|
/** 部署实例注册表归一(默认实例委托——null → '*' 通配口径) */
|
|
23
23
|
normalizeInstances: typeof normalizeInstances;
|
|
@@ -38,13 +38,16 @@ export interface ResolvedContracts {
|
|
|
38
38
|
evaluation: boolean;
|
|
39
39
|
/** 严格 zod 面可用(parseIntegrationsConfig 在场——≥0.9 门控判据) */
|
|
40
40
|
strictValidation: boolean;
|
|
41
|
+
/** v6 形状标记在场(OWNERSHIP_BINDING_V6——modules[owner] 绑定形状支持;标记门 P1:
|
|
42
|
+
* v5 contracts + v6 toolkit 偏斜 → 503 第四分支,防静默忽略 ownerOf) */
|
|
43
|
+
ownershipBindingV6: boolean;
|
|
41
44
|
/** 动态 import 产物(命名导出子集——Partial<EvaluationExports>) */
|
|
42
45
|
module: ResolvedModule;
|
|
43
46
|
/** 解析诊断(N1 构建指引 message 三分支的输入:未安装 / 版本 <0.9 / 源码态未构建) */
|
|
44
47
|
notes: string[];
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
/** 求值面必需具名导出(在场 → evaluation: true
|
|
50
|
+
/** 求值面必需具名导出(在场 → evaluation: true;moduleDomainOf v6 deprecated 但仍在发布面) */
|
|
48
51
|
const EVALUATION_EXPORT_NAMES = [
|
|
49
52
|
'resolveEffectiveService',
|
|
50
53
|
'buildSupplyLookup',
|
|
@@ -56,10 +59,14 @@ const EVALUATION_EXPORT_NAMES = [
|
|
|
56
59
|
/** 严格校验面具名导出(在场 → strictValidation: true;0.9+) */
|
|
57
60
|
const STRICT_EXPORT_NAME = 'parseIntegrationsConfig';
|
|
58
61
|
|
|
62
|
+
/** v6 形状标记导出(在场 → ownershipBindingV6: true;判据先例 = STRICT_EXPORT_NAME 出口在场性) */
|
|
63
|
+
const OWNERSHIP_MARKER_NAME = 'OWNERSHIP_BINDING_V6';
|
|
64
|
+
|
|
59
65
|
const UNRESOLVED: ResolvedContracts = {
|
|
60
66
|
version: null,
|
|
61
67
|
evaluation: false,
|
|
62
68
|
strictValidation: false,
|
|
69
|
+
ownershipBindingV6: false,
|
|
63
70
|
module: {},
|
|
64
71
|
notes: [],
|
|
65
72
|
};
|
|
@@ -203,10 +210,14 @@ async function probeCandidate(
|
|
|
203
210
|
const mod = (await import(pathToFileURL(probeFile).href + versionQuery)) as Record<string, unknown>;
|
|
204
211
|
const evaluation = EVALUATION_EXPORT_NAMES.every((name) => mod[name] !== undefined);
|
|
205
212
|
const strictValidation = mod[STRICT_EXPORT_NAME] !== undefined;
|
|
213
|
+
// v6 形状标记(出口在场性——判据先例 STRICT_EXPORT_NAME);缺席时不在此 push note
|
|
214
|
+
// (503 文案归 factory assertContractsUsable 门控单点——P1)
|
|
215
|
+
const ownershipBindingV6 = mod[OWNERSHIP_MARKER_NAME] === true;
|
|
206
216
|
return {
|
|
207
217
|
version,
|
|
208
218
|
evaluation,
|
|
209
219
|
strictValidation,
|
|
220
|
+
ownershipBindingV6,
|
|
210
221
|
module: mod as ResolvedModule,
|
|
211
222
|
notes: evaluation
|
|
212
223
|
? []
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* tbox.module.json schema v1(001 §3.6 字段名,003 §4.1 schema v1
|
|
4
|
+
* tbox.module.json schema v1(001 §3.6 字段名,003 §4.1 schema v1 定版;v6 声明面翻转)。
|
|
5
5
|
* app-toolkit 单一校验源(自 CLI module-schema.ts 迁入;CLI 经 re-export 消费)。
|
|
6
6
|
*
|
|
7
7
|
* 迁移改造(ai-build C2-5):
|
|
8
8
|
* - resourceNeedContribSchema 的 type 由 `z.enum(RESOURCE_TYPE_IDS)` 放宽为 `z.string()`——
|
|
9
9
|
* 词汇校验移谓词、经 contracts 动态解析取 RESOURCE_TYPE_IDS(比静态 enum 更忠实应用版本);
|
|
10
|
-
* - demand meta additive:
|
|
10
|
+
* - demand meta additive:serviceConsumeContribSchema 增 name?/description?(服务展示元数据,
|
|
11
11
|
* manifest demand 侧 additive 扩展,catalog 聚合顺带携带——api.md ServiceDemand 对齐);
|
|
12
12
|
* - 安全钉子(H9b):provider 名与 instance id 进凭据 stem 派生,字符集 pattern
|
|
13
13
|
* `/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/` 为第一道防线(stem 派生处另有断言防线——纵深防御)。
|
|
14
|
+
*
|
|
15
|
+
* v6 声明面翻转(integrations v6 所有权模型):
|
|
16
|
+
* - `contributes` → `declare`(声明/行为分离——owns/consumes 语义对仗);
|
|
17
|
+
* - `declare.services` → `declare.consumes`(无向"服务" → 消费关系,多对多);
|
|
18
|
+
* - += `declare.owns`:薄数组(strictObject 单键——互斥事实无元数据;元数据恒在
|
|
19
|
+
* consumes 防双数组漂移)。服务归属唯一真源;双 owner = walk OWNERSHIP_CONFLICT;
|
|
20
|
+
* - 旧键 `contributes` / `declare.services`:parseModuleDescriptor 定向检测(N1——
|
|
21
|
+
* 报错含三路径升级指引,拒 loose 静默兜底)。
|
|
22
|
+
*
|
|
23
|
+
* v7 词汇统一(标识/展示名分轴,对齐实例轴 DeploymentInstance {id, name}):
|
|
24
|
+
* - 根键 `name` → `id`(标识键:唯一、寻址);根键 `title` → `name`(展示名:中文、可选);
|
|
25
|
+
* - `declare.consumes[].title` → `name`(demand meta 同步跟随);
|
|
26
|
+
* - 旧键 parseModuleDescriptor 定向检测(legacy? 标志——walk 据此拒 loose 兜底、
|
|
27
|
+
* CLI sync 据此拒装配改写)。
|
|
14
28
|
*/
|
|
15
29
|
|
|
16
30
|
/** 输入字符集(安全钉子①):provider 名 / instanceId 恒经此 pattern(进凭据 stem 派生的输入) */
|
|
@@ -39,7 +53,7 @@ export const moduleDependencySchema = z.object({
|
|
|
39
53
|
required: z.boolean().default(true),
|
|
40
54
|
});
|
|
41
55
|
|
|
42
|
-
/** provider 供给槽声明(integrations v2:
|
|
56
|
+
/** provider 供给槽声明(integrations v2:declare.providers.slots 元素;
|
|
43
57
|
* provider = 厂商名(catalog 嵌套第一轴)、implementation = 'id@version'(第二轴)) */
|
|
44
58
|
export const providerSlotContribSchema = z.object({
|
|
45
59
|
service: z.string(),
|
|
@@ -51,8 +65,8 @@ export const providerSlotContribSchema = z.object({
|
|
|
51
65
|
credentialSchema: z.string().optional(),
|
|
52
66
|
});
|
|
53
67
|
|
|
54
|
-
/**
|
|
55
|
-
export const
|
|
68
|
+
/** 服务消费槽声明(declare.consumes 元素;业务模块声明消费——catalog 需求侧聚合源) */
|
|
69
|
+
export const serviceConsumeContribSchema = z.object({
|
|
56
70
|
service: z.string(),
|
|
57
71
|
optional: z.boolean().default(true),
|
|
58
72
|
defaults: z
|
|
@@ -64,11 +78,19 @@ export const serviceNeedContribSchema = z.object({
|
|
|
64
78
|
)
|
|
65
79
|
.default([]),
|
|
66
80
|
// demand meta additive(api.md ServiceDemand):服务展示元数据,缺席 UI 回退 service id
|
|
67
|
-
title
|
|
81
|
+
// (v7 词汇统一:title → name)
|
|
82
|
+
name: z.string().optional(),
|
|
68
83
|
description: z.string().optional(),
|
|
69
84
|
});
|
|
70
85
|
|
|
71
|
-
/**
|
|
86
|
+
/** 服务归属声明(declare.owns 元素;v6 薄形状——互斥事实无元数据,strict 单键防双数组漂移) */
|
|
87
|
+
export const serviceOwnContribSchema = z
|
|
88
|
+
.object({
|
|
89
|
+
service: z.string(),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
|
|
93
|
+
/** 资源需求声明(declare.resources 元素;type 放宽为 string——词汇校验移谓词经 resolver 取词表) */
|
|
72
94
|
export const resourceNeedContribSchema = z.object({
|
|
73
95
|
id: z.string(),
|
|
74
96
|
type: z.string(),
|
|
@@ -77,9 +99,10 @@ export const resourceNeedContribSchema = z.object({
|
|
|
77
99
|
export const moduleDescriptorSchema = z
|
|
78
100
|
.object({
|
|
79
101
|
schemaVersion: z.number().default(1),
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
102
|
+
// v7 词汇统一(标识/展示名分轴):id = 标识键(唯一、寻址,键 = install id 规约形态);
|
|
103
|
+
// name = 展示名(中文、可选)——回退归视图层(name ?? id),schema 不灌值
|
|
104
|
+
id: z.string(),
|
|
105
|
+
name: z.string().optional(),
|
|
83
106
|
description: z.string().optional(),
|
|
84
107
|
version: z.string().default('0.0.0'),
|
|
85
108
|
kind: z.enum(['platform', 'business', 'third-party']).default('business'),
|
|
@@ -94,7 +117,7 @@ export const moduleDescriptorSchema = z
|
|
|
94
117
|
defaultMode: z.enum(['hybrid', 'sdk', 'codegen', 'local']).default('codegen'),
|
|
95
118
|
})
|
|
96
119
|
.default({}),
|
|
97
|
-
|
|
120
|
+
declare: z
|
|
98
121
|
.object({
|
|
99
122
|
handlers: z.array(z.string()).default([]),
|
|
100
123
|
tools: z.array(z.string()).default([]),
|
|
@@ -107,7 +130,8 @@ export const moduleDescriptorSchema = z
|
|
|
107
130
|
slots: z.array(providerSlotContribSchema).default([]),
|
|
108
131
|
})
|
|
109
132
|
.default({}),
|
|
110
|
-
|
|
133
|
+
owns: z.array(serviceOwnContribSchema).default([]),
|
|
134
|
+
consumes: z.array(serviceConsumeContribSchema).default([]),
|
|
111
135
|
resources: z.array(resourceNeedContribSchema).default([]),
|
|
112
136
|
})
|
|
113
137
|
.default({}),
|
|
@@ -123,14 +147,63 @@ export const moduleDescriptorSchema = z
|
|
|
123
147
|
export type ModuleDescriptor = z.infer<typeof moduleDescriptorSchema>;
|
|
124
148
|
export type CardContrib = z.infer<typeof cardContribSchema>;
|
|
125
149
|
export type ProviderSlotContrib = z.infer<typeof providerSlotContribSchema>;
|
|
126
|
-
export type
|
|
150
|
+
export type ServiceConsumeContrib = z.infer<typeof serviceConsumeContribSchema>;
|
|
151
|
+
export type ServiceOwnContrib = z.infer<typeof serviceOwnContribSchema>;
|
|
127
152
|
export type ResourceNeedContrib = z.infer<typeof resourceNeedContribSchema>;
|
|
128
153
|
|
|
129
154
|
export type ParseModuleResult =
|
|
130
155
|
| { ok: true; value: ModuleDescriptor }
|
|
131
|
-
| { ok: false; errors: string[] };
|
|
156
|
+
| { ok: false; errors: string[]; legacy?: true };
|
|
157
|
+
|
|
158
|
+
/** 旧键检测(N1):命中 → 定向错误(含三路径升级指引),不走 loose 静默兜底。
|
|
159
|
+
* v6 分支:声明面翻转键(contributes / declare.services);v7 分支:词汇统一翻转键
|
|
160
|
+
* (标识 `name`→`id`、展示名 `title`→`name`)。doctor descriptor-schema 规则以错误文本
|
|
161
|
+
* 「已更名」嗅探透传 OLD_MANIFEST_KEY——新增分支消息必须保持该词。 */
|
|
162
|
+
function legacyManifestKeys(raw: unknown): string[] {
|
|
163
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return [];
|
|
164
|
+
const errors: string[] = [];
|
|
165
|
+
const obj = raw as Record<string, unknown>;
|
|
166
|
+
if ('contributes' in obj) {
|
|
167
|
+
errors.push(
|
|
168
|
+
'(root): manifest 旧键 `contributes` 已更名 `declare`(v6 声明面)——' +
|
|
169
|
+
'codegen 副本经 `module update --source` 重新生成 / sdk 模式升级模块包 / 手改两键',
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const declare = obj.declare;
|
|
173
|
+
if (declare && typeof declare === 'object' && !Array.isArray(declare)
|
|
174
|
+
&& 'services' in (declare as Record<string, unknown>)) {
|
|
175
|
+
errors.push(
|
|
176
|
+
'declare: manifest 旧键 `declare.services` 已更名 `declare.consumes`(消费关系定向化)——' +
|
|
177
|
+
'同上三路径升级',
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
// v7 词汇统一(标识/展示名分轴):`name`→`id`(标识)、`title`→`name`(展示)。
|
|
181
|
+
// 判据以 `id` 在场性消解同名键歧义:无 id 有 name = v6 旧态;有 id 有 title = 半迁移。
|
|
182
|
+
if (!('id' in obj) && 'name' in obj) {
|
|
183
|
+
errors.push(
|
|
184
|
+
'(root): manifest 标识键 `name` 已更名 `id`(v7 词汇统一)——' +
|
|
185
|
+
'codegen 副本经 `module update --source` 重新生成 / sdk 模式升级模块包 / 手改两键',
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const declareObj = declare && typeof declare === 'object' && !Array.isArray(declare)
|
|
189
|
+
? (declare as Record<string, unknown>)
|
|
190
|
+
: undefined;
|
|
191
|
+
const consumeHasTitle = Array.isArray(declareObj?.consumes)
|
|
192
|
+
&& (declareObj.consumes as unknown[]).some(
|
|
193
|
+
(c) => c !== null && typeof c === 'object' && 'title' in (c as Record<string, unknown>),
|
|
194
|
+
);
|
|
195
|
+
if ('title' in obj || consumeHasTitle) {
|
|
196
|
+
errors.push(
|
|
197
|
+
'(root): manifest 展示名键 `title` 已更名 `name`(v7 词汇统一;declare.consumes[].title 同步改 name)——'
|
|
198
|
+
+ '同上三路径升级',
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
132
203
|
|
|
133
204
|
export function parseModuleDescriptor(raw: unknown): ParseModuleResult {
|
|
205
|
+
const legacy = legacyManifestKeys(raw);
|
|
206
|
+
if (legacy.length > 0) return { ok: false, errors: legacy, legacy: true };
|
|
134
207
|
const result = moduleDescriptorSchema.safeParse(raw);
|
|
135
208
|
if (result.success) return { ok: true, value: result.data };
|
|
136
209
|
return {
|