@tbox.cn/app-toolkit 0.2.0 → 0.4.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/index.d.ts +181 -33
- package/dist/index.js +8 -7
- package/package.json +2 -2
- package/src/assembly/app-manifest.ts +6 -0
- package/src/assembly/walk-manifests.ts +95 -28
- package/src/core/env-expansion.ts +14 -12
- package/src/core/module-schema.ts +3 -0
- package/src/dto.ts +59 -4
- package/src/factory.ts +58 -16
- package/src/index.ts +8 -2
- package/src/integrations/credentials.ts +1 -1
- package/src/integrations/evaluate-app.ts +41 -0
- package/src/integrations/predicate.ts +2 -1
- package/src/integrations/prune-bindings.ts +186 -0
- package/src/integrations/write.ts +15 -11
- package/src/views/app.ts +6 -2
- package/src/views/context.ts +20 -0
- package/src/views/integration-schemas.ts +197 -24
- package/src/views/modules.ts +42 -13
- package/src/views/providers.ts +39 -21
- package/src/views/service-detail.ts +5 -3
- package/tests/demo-app.ts +27 -3
- package/tests/dev-manifest-catalog.test.ts +81 -1
- package/tests/file-cache.test.ts +3 -1
- package/tests/module-schema.test.ts +15 -0
- package/tests/vendor-schema-keywords.test.ts +12 -0
- package/tests/views.test.ts +1098 -74
- package/tests/write-core.test.ts +26 -4
package/tests/demo-app.ts
CHANGED
|
@@ -50,6 +50,11 @@ export interface FakeContractsOptions {
|
|
|
50
50
|
resolveBody?: string;
|
|
51
51
|
/** service-slots.json 词汇(P9 身份判据/求值键集;缺省不物化 = 词汇空) */
|
|
52
52
|
slots?: string[];
|
|
53
|
+
/** 词汇 meta 节(per-service {title,description}——B1 展示名链 fixture) */
|
|
54
|
+
meta?: Record<string, { title?: string; description?: string }>;
|
|
55
|
+
/** service-slots.json 落位:'root' = 包根(缺省——夹具/旧布局形态);
|
|
56
|
+
* 'src' = 包内 src/(发布包 src 附带 / shim 链接形态——B0 探测跳 ②) */
|
|
57
|
+
slotsLayout?: 'root' | 'src';
|
|
53
58
|
/** zod 形拒绝 fixture(A1 T9/T10):parseIntegrationsConfig 恒抛带 issues 的结构化错误
|
|
54
59
|
* (结构性 ZodError 形——issues[].path 供 fields 断言;仅在专属 fixture 上使用) */
|
|
55
60
|
zodRejectPaths?: string[][];
|
|
@@ -58,7 +63,7 @@ export interface FakeContractsOptions {
|
|
|
58
63
|
/** 假 contracts 包:package.json exports 形态逐字段复刻发布面(types+import 双条件、无 require/default)
|
|
59
64
|
* ——resolver 不经 exports 解析(包根直探),该形态为 E4 类错误的机械防线。 */
|
|
60
65
|
export function writeFakeContractsPackage(packageRoot: string, opts: FakeContractsOptions = {}): void {
|
|
61
|
-
const { version = '0.9.0', withStrict = true, withDist = true, brokenDist = false, missingEvaluationExports = false, rejectMarker, resolveBody, slots, zodRejectPaths } = opts;
|
|
66
|
+
const { version = '0.9.0', withStrict = true, withDist = true, brokenDist = false, missingEvaluationExports = false, rejectMarker, resolveBody, slots, meta, slotsLayout = 'root', zodRejectPaths } = opts;
|
|
62
67
|
const strictBody = withStrict
|
|
63
68
|
? `export function parseIntegrationsConfig(raw) { ${
|
|
64
69
|
rejectMarker ? `if (JSON.stringify(raw).includes(${JSON.stringify(rejectMarker)})) throw new Error("fake strict reject"); ` : ''
|
|
@@ -113,12 +118,31 @@ export function writeFakeContractsPackage(packageRoot: string, opts: FakeContrac
|
|
|
113
118
|
mkdirSync(join(packageRoot, 'src'), { recursive: true });
|
|
114
119
|
writeFileSync(join(packageRoot, 'src', 'runtime.ts'), exportsBody);
|
|
115
120
|
}
|
|
116
|
-
// service-slots.json(P9
|
|
121
|
+
// service-slots.json(P9 词汇源——探测三跳发现位;缺省不物化 = 词汇空;
|
|
122
|
+
// slotsLayout 控制包根/src 落位——B0 探测跳①/②)
|
|
117
123
|
if (slots && slots.length > 0) {
|
|
118
|
-
|
|
124
|
+
const slotsFile = slotsLayout === 'src' ? join(packageRoot, 'src', 'service-slots.json') : join(packageRoot, 'service-slots.json');
|
|
125
|
+
mkdirSync(dirname(slotsFile), { recursive: true });
|
|
126
|
+
writeFileSync(slotsFile, `${JSON.stringify(meta ? { slots, meta } : { slots }, null, 2)}\n`);
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
/**
|
|
131
|
+
* 候选自身 src 词汇包(B0 探测跳 ③——local/codegen 全量副本形态):
|
|
132
|
+
* 落 <appDir>/packages/<name>/src/service-slots.json(无 package.json/node_modules——
|
|
133
|
+
* mall 生成应用主形态:stage rewriteExportsToSrc 展开后永驻 packages/*,不依赖 install)。
|
|
134
|
+
*/
|
|
135
|
+
export function writeSelfSrcContractPackage(
|
|
136
|
+
appDir: string,
|
|
137
|
+
name: string,
|
|
138
|
+
content: { slots: string[]; meta?: Record<string, { title?: string; description?: string }> },
|
|
139
|
+
): string {
|
|
140
|
+
const dir = join(appDir, 'packages', name, 'src');
|
|
141
|
+
mkdirSync(dir, { recursive: true });
|
|
142
|
+
writeFileSync(join(dir, 'service-slots.json'), `${JSON.stringify(content, null, 2)}\n`);
|
|
143
|
+
return join(appDir, 'packages', name);
|
|
144
|
+
}
|
|
145
|
+
|
|
122
146
|
export interface LinkOptions {
|
|
123
147
|
/** 候选子目录:''(appDir 根)/ 'apps/server' / 'apps/client' / 'packages/<id>' */
|
|
124
148
|
subdir: string;
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, afterEach } from 'vitest';
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { walkManifests } from '../src/assembly/walk-manifests.js';
|
|
5
|
-
import { createTempApp, type TempApp } from './demo-app.js';
|
|
5
|
+
import { createTempApp, linkContracts, writeFakeContractsPackage, writeSelfSrcContractPackage, type TempApp } from './demo-app.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* dev-manifest catalog 通道(通道 1.5)单测:聚合宽/声明窄不变量锁定。
|
|
@@ -112,3 +112,83 @@ describe('walkManifests · dev-manifest catalog 通道(聚合宽/声明窄)'
|
|
|
112
112
|
expect(walk.modules).toHaveLength(0);
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 词汇探测三跳(B0 回归锁):词汇源三真实布局全覆盖——
|
|
118
|
+
* ① nm 包根(夹具/旧布局)② nm src/(发布包 src 附带 + 模板 shim 链接)③ 候选自身 src/
|
|
119
|
+
* (local/codegen 全量副本——mall 生成应用主形态,不依赖 install)。
|
|
120
|
+
* 修复前探测只扫包根 → 三形态全 miss(vocabulary 恒空)。
|
|
121
|
+
*/
|
|
122
|
+
describe('walkManifests · 词汇探测三跳 + slotMeta(B0)', () => {
|
|
123
|
+
it('P1: nm 包根布局(跳①)→ vocabulary 命中;slotMeta 透传', () => {
|
|
124
|
+
app = createTempApp('tbox-probe-');
|
|
125
|
+
const store = join(app.appDir, '_store-a');
|
|
126
|
+
writeFakeContractsPackage(store, {
|
|
127
|
+
slots: ['auth.alipay-login', 'mall.info'],
|
|
128
|
+
meta: { 'auth.alipay-login': { title: '支付宝登录', description: '登录换码(code2Session)' } },
|
|
129
|
+
});
|
|
130
|
+
linkContracts(app.appDir, store, { subdir: 'apps/server' });
|
|
131
|
+
|
|
132
|
+
const walk = walkManifests(app.appDir);
|
|
133
|
+
expect(walk.vocabulary).toEqual(['auth.alipay-login', 'mall.info']);
|
|
134
|
+
expect(walk.slotMeta['auth.alipay-login']).toEqual({ title: '支付宝登录', description: '登录换码(code2Session)' });
|
|
135
|
+
expect(walk.slotMeta['mall.info']).toBeUndefined(); // meta 缺席槽不入表
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('P2: nm src 布局(跳②——发布包 src 附带 / shim 链接形态)→ 命中', () => {
|
|
139
|
+
app = createTempApp('tbox-probe-');
|
|
140
|
+
const store = join(app.appDir, '_store-b');
|
|
141
|
+
writeFakeContractsPackage(store, { slots: ['parking.query'], slotsLayout: 'src' });
|
|
142
|
+
linkContracts(app.appDir, store, { subdir: 'apps/server' });
|
|
143
|
+
|
|
144
|
+
const walk = walkManifests(app.appDir);
|
|
145
|
+
expect(walk.vocabulary).toEqual(['parking.query']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('P3: 候选自身 src 布局(跳③——local/codegen 全量副本,无 node_modules 链接)→ 命中', () => {
|
|
149
|
+
app = createTempApp('tbox-probe-');
|
|
150
|
+
writeSelfSrcContractPackage(app.appDir, 'contracts-mall', {
|
|
151
|
+
slots: ['member.account', 'parking.payment'],
|
|
152
|
+
meta: { 'member.account': { title: '会员账户' } },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const walk = walkManifests(app.appDir);
|
|
156
|
+
expect(walk.vocabulary).toEqual(['member.account', 'parking.payment']);
|
|
157
|
+
expect(walk.slotMeta['member.account']).toEqual({ title: '会员账户' });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('P4: 多包同槽 meta → 发现序首声明胜出(apps/* 先于 packages/* 候选序)', () => {
|
|
161
|
+
app = createTempApp('tbox-probe-');
|
|
162
|
+
const storeA = join(app.appDir, '_store-first');
|
|
163
|
+
writeFakeContractsPackage(storeA, { slots: ['x.y'], meta: { 'x.y': { title: '甲' } } });
|
|
164
|
+
linkContracts(app.appDir, storeA, { subdir: 'apps/server' });
|
|
165
|
+
const storeB = join(app.appDir, '_store-second');
|
|
166
|
+
writeFakeContractsPackage(storeB, { slots: ['x.y'], meta: { 'x.y': { title: '乙' } } });
|
|
167
|
+
linkContracts(app.appDir, storeB, { subdir: 'packages/mock' });
|
|
168
|
+
|
|
169
|
+
const walk = walkManifests(app.appDir);
|
|
170
|
+
expect(walk.slotMeta['x.y']).toEqual({ title: '甲' });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('P5: 坏 meta 条目(非对象/非 string/空串)→ 宽松跳过,slots 正常', () => {
|
|
174
|
+
app = createTempApp('tbox-probe-');
|
|
175
|
+
const store = join(app.appDir, '_store-bad');
|
|
176
|
+
writeFakeContractsPackage(store, { slots: ['a.b'] });
|
|
177
|
+
writeFileSync(
|
|
178
|
+
join(store, 'service-slots.json'),
|
|
179
|
+
JSON.stringify({ slots: ['a.b', 'c.d', 'e.f'], meta: { 'a.b': '字符串', 'c.d': { title: 42 }, 'e.f': { title: '甲', description: '' } } }),
|
|
180
|
+
);
|
|
181
|
+
linkContracts(app.appDir, store, { subdir: 'apps/server' });
|
|
182
|
+
|
|
183
|
+
const walk = walkManifests(app.appDir);
|
|
184
|
+
expect(walk.vocabulary).toEqual(['a.b', 'c.d', 'e.f']);
|
|
185
|
+
expect(walk.slotMeta).toEqual({ 'e.f': { title: '甲' } }); // 空串 description 不入表(与 title 守卫对称)
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('P6: 无契约包(退化态)→ vocabulary 空 + slotMeta 空(P9 退化路径语义锁)', () => {
|
|
189
|
+
app = createTempApp('tbox-probe-');
|
|
190
|
+
const walk = walkManifests(app.appDir);
|
|
191
|
+
expect(walk.vocabulary).toEqual([]);
|
|
192
|
+
expect(walk.slotMeta).toEqual({});
|
|
193
|
+
});
|
|
194
|
+
});
|
package/tests/file-cache.test.ts
CHANGED
|
@@ -180,7 +180,9 @@ describe('Freshness Contract W3/W7 — agent 动线矩阵(工厂方法级;FX
|
|
|
180
180
|
}),
|
|
181
181
|
);
|
|
182
182
|
const modules = await tk.loadModules();
|
|
183
|
-
|
|
183
|
+
// provider 家族包不在清单(2026-09-16 bug-fix)——排除本身随目录新增即时生效;
|
|
184
|
+
// loadModules 新鲜度由下方 case c(module-sdk-y)承载,供给轴新鲜度见下两行
|
|
185
|
+
expect(modules.modules.map((m) => m.id)).not.toContain('provider-x');
|
|
184
186
|
const providers = await tk.loadProviders();
|
|
185
187
|
expect(providers.providers.map((p) => p.provider)).toContain('joycity');
|
|
186
188
|
|
|
@@ -81,4 +81,19 @@ describe('module-schema(descriptor zod 单源)', () => {
|
|
|
81
81
|
expect(parsed.data.contributes.resources).toEqual([]);
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
it('B1 模块 meta additive:根级 title/description 保真;缺席仍合法(回退归视图层)', () => {
|
|
86
|
+
const withMeta = parseModuleDescriptor({ ...BASE_MANIFEST, title: '停车缴费', description: '车辆查询与绑定、停车费用查询、缴费支付与开票' });
|
|
87
|
+
expect(withMeta.ok).toBe(true);
|
|
88
|
+
if (withMeta.ok) {
|
|
89
|
+
expect(withMeta.value.title).toBe('停车缴费');
|
|
90
|
+
expect(withMeta.value.description).toContain('停车费用查询');
|
|
91
|
+
}
|
|
92
|
+
const withoutMeta = moduleDescriptorSchema.safeParse(BASE_MANIFEST);
|
|
93
|
+
expect(withoutMeta.success).toBe(true);
|
|
94
|
+
if (withoutMeta.success) {
|
|
95
|
+
expect(withoutMeta.data.title).toBeUndefined();
|
|
96
|
+
expect(withoutMeta.data.description).toBeUndefined();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
84
99
|
});
|
|
@@ -98,4 +98,16 @@ describe('vendor schema 关键词矩阵 ⊆ 渲染器支持集(D32 表单引
|
|
|
98
98
|
expect(used.has(k), `基线关键词 ${k} 未被提取到——提取器或目录布局变化,人工核对`).toBe(true);
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
it('B5 展示名 tripwire:全文件 properties.*.title 必在(新字段裸键名结构性不可上线)', () => {
|
|
103
|
+
const files = schemaFiles();
|
|
104
|
+
expect(files.length).toBeGreaterThan(5);
|
|
105
|
+
for (const { pkg, rel } of files) {
|
|
106
|
+
const doc = JSON.parse(readFileSync(rel, 'utf8')) as { properties?: Record<string, unknown> };
|
|
107
|
+
for (const [key, sub] of Object.entries(doc.properties ?? {})) {
|
|
108
|
+
const hasTitle = !!sub && typeof sub === 'object' && typeof (sub as { title?: unknown }).title === 'string';
|
|
109
|
+
expect(hasTitle, `${pkg} 字段 "${key}" 缺 title——集成配置表单展示名必填(B5 防线;纯名词,长尾注记移 description)`).toBe(true);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
101
113
|
});
|