@tbox.cn/app-toolkit 0.1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/chunk-VV3ERZXS.js +1 -0
- package/dist/contracts-resolver-QM4FBQQV.js +1 -0
- package/dist/index.d.ts +1909 -0
- package/dist/index.js +8 -0
- package/package.json +47 -0
- package/src/assembly/app-manifest.ts +132 -0
- package/src/assembly/provider-catalog.ts +167 -0
- package/src/assembly/walk-manifests.ts +461 -0
- package/src/core/contracts-expected.ts +31 -0
- package/src/core/contracts-resolver.ts +274 -0
- package/src/core/credential-format.ts +98 -0
- package/src/core/credential-mask.ts +19 -0
- package/src/core/env-expansion.ts +51 -0
- package/src/core/errors.ts +46 -0
- package/src/core/file-cache.ts +65 -0
- package/src/core/fskit.ts +21 -0
- package/src/core/module-schema.ts +139 -0
- package/src/dto.ts +298 -0
- package/src/factory.ts +297 -0
- package/src/index.ts +137 -0
- package/src/integrations/credentials.ts +153 -0
- package/src/integrations/mock-bindings.ts +55 -0
- package/src/integrations/predicate-io.ts +199 -0
- package/src/integrations/predicate.ts +598 -0
- package/src/integrations/read.ts +53 -0
- package/src/integrations/write.ts +448 -0
- package/src/views/app.ts +28 -0
- package/src/views/context.ts +70 -0
- package/src/views/modules.ts +48 -0
- package/src/views/providers.ts +165 -0
- package/src/views/service-detail.ts +32 -0
- package/src/views/service-resolutions.ts +323 -0
- package/tests/contracts-resolver.test.ts +203 -0
- package/tests/demo-app.ts +146 -0
- package/tests/dev-manifest-catalog.test.ts +114 -0
- package/tests/error-name-safety.test.ts +26 -0
- package/tests/file-cache.test.ts +242 -0
- package/tests/import-layers.test.ts +111 -0
- package/tests/module-schema.test.ts +84 -0
- package/tests/naming-alignment.test.ts +50 -0
- package/tests/views.test.ts +427 -0
- package/tests/write-core.test.ts +188 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +29 -0
package/src/factory.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { writeAtomic } from './core/fskit.js';
|
|
3
|
+
import { walkManifests as internalsWalk } from './assembly/walk-manifests.js';
|
|
4
|
+
import type { FileCache } from './core/file-cache.js';
|
|
5
|
+
import { createFileCache } from './core/file-cache.js';
|
|
6
|
+
import { resolveContractsForApp, invalidateContractsResolver } from './core/contracts-resolver.js';
|
|
7
|
+
import type { ResolvedContracts } from './core/contracts-resolver.js';
|
|
8
|
+
import { parseModuleDescriptor } from './core/module-schema.js';
|
|
9
|
+
import { loadProviderDetailView, loadProviderServiceDetailView, loadProvidersView } from './views/providers.js';
|
|
10
|
+
import { loadProviderCatalog, loadProviderCatalogRaw } from './assembly/provider-catalog.js';
|
|
11
|
+
import { readIntegrationsStrict, readIntegrationsConfig } from './integrations/read.js';
|
|
12
|
+
import { createWriteCore, type SaveResult, type WriteOptions } from './integrations/write.js';
|
|
13
|
+
import { ensureMockBindings } from './integrations/mock-bindings.js';
|
|
14
|
+
import { evaluateIntegrationServices, type IntegrationServiceIssue, type PredicateContracts } from './integrations/predicate.js';
|
|
15
|
+
import { buildPredicateAssembly } from './integrations/predicate-io.js';
|
|
16
|
+
import { AppToolkitError, contractsNotResolvedMessage } from './core/errors.js';
|
|
17
|
+
import { loadViewSnapshotSync } from './views/context.js';
|
|
18
|
+
import { loadModulesView, loadModuleDetailView } from './views/modules.js';
|
|
19
|
+
import { loadAppView } from './views/app.js';
|
|
20
|
+
import { loadServiceDetailView } from './views/service-detail.js';
|
|
21
|
+
import { loadServiceResolutionsView, loadServiceResolutionDetail } from './views/service-resolutions.js';
|
|
22
|
+
import type { WalkManifestsResult } from './assembly/walk-manifests.js';
|
|
23
|
+
import type {
|
|
24
|
+
AppIntegrationNode,
|
|
25
|
+
AppView,
|
|
26
|
+
ModuleDetailView,
|
|
27
|
+
ModuleIntegrationNode,
|
|
28
|
+
ProviderDetail,
|
|
29
|
+
ProviderServiceDetail,
|
|
30
|
+
ProvidersView,
|
|
31
|
+
ServiceDetail,
|
|
32
|
+
ServiceIntegrationNode,
|
|
33
|
+
ServiceResolutionDetail,
|
|
34
|
+
ServiceResolutionsView,
|
|
35
|
+
ModulesView,
|
|
36
|
+
} from './dto.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* createAppToolkit(C4 工厂):22 方法 = 9 读(HTTP 直通)+ 4 写(HTTP 直通)+ 9 迁移面(Internals)。
|
|
40
|
+
* AppToolkit = AppToolkitRead & AppToolkitWrite & AppToolkitInternals——
|
|
41
|
+
* Read+Write = HTTP 直通面 additive 冻结(L1 端点↔方法 1:1,L3 命名镜像:Integration=节点级 /
|
|
42
|
+
* Config=文件级);Internals = CLI/doctor/桥消费(自由演进)。
|
|
43
|
+
*
|
|
44
|
+
* **门控断言单点(policy at edge)**:求值(2)+ 写(4)+ ensureMockBindings / writeIntegrationsConfig /
|
|
45
|
+
* evaluateIntegrationServices 入口 assertContractsUsable——strictValidation === false → 503
|
|
46
|
+
* CONTRACTS_NOT_RESOLVED(message 三分支);静态方法不门控(永不 503——铁律 #3);
|
|
47
|
+
* integrations/ views/ 机制层保持纯函数。
|
|
48
|
+
*
|
|
49
|
+
* 并发:进程内写串行(per-factory promise chain)+ 原子写 + 写后本实例缓存即刻失效(D26)。
|
|
50
|
+
* onApplied(D7):toolkit 判定「需要重启」(实际落盘且非 dryRun/no-diff)→ 调用一次 +
|
|
51
|
+
* restartScheduled 回填——通知闭包非控制,执行者恒为宿主 RunManager;CLI 不传恒 false。
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
export interface AppToolkitOptions {
|
|
55
|
+
/** 写成功(实际落盘且非 dryRun/no-diff)→ 调用一次(宿主闭包内防抖/守卫);CLI 不传恒 false */
|
|
56
|
+
onApplied?: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** ── HTTP 直通面(9 读;L1 镜像律:方法名 = load + URL 资源段)── */
|
|
60
|
+
export interface AppToolkitRead {
|
|
61
|
+
loadModules(): Promise<ModulesView>;
|
|
62
|
+
loadModule(moduleId: string): Promise<ModuleDetailView>;
|
|
63
|
+
loadApp(): Promise<AppView>;
|
|
64
|
+
loadService(service: string): Promise<ServiceDetail>;
|
|
65
|
+
loadServiceResolutions(services?: string[]): Promise<ServiceResolutionsView>;
|
|
66
|
+
loadServiceResolution(service: string): Promise<ServiceResolutionDetail>;
|
|
67
|
+
loadProviders(): Promise<ProvidersView>;
|
|
68
|
+
loadProvider(provider: string): Promise<ProviderDetail>;
|
|
69
|
+
loadProviderService(provider: string, service: string): Promise<ProviderServiceDetail>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** ── HTTP 直通面(4 写;body ≡ 文件节点全量替换;DELETE 无 dryRun/无 options)── */
|
|
73
|
+
export interface AppToolkitWrite {
|
|
74
|
+
writeAppIntegration(
|
|
75
|
+
node: AppIntegrationNode,
|
|
76
|
+
o?: { dryRun?: boolean; credentials?: { type: string; values: Record<string, string> }; credentialsByInstance?: Record<string, { type: string; values: Record<string, string> }> },
|
|
77
|
+
): Promise<SaveResult>;
|
|
78
|
+
writeModuleIntegration(moduleId: string, node: ModuleIntegrationNode, o?: { dryRun?: boolean }): Promise<SaveResult>;
|
|
79
|
+
writeServiceIntegration(
|
|
80
|
+
service: string,
|
|
81
|
+
node: ServiceIntegrationNode,
|
|
82
|
+
o?: { dryRun?: boolean; credentials?: { type: string; values: Record<string, string> }; credentialsByInstance?: Record<string, { type: string; values: Record<string, string> }> },
|
|
83
|
+
): Promise<SaveResult>;
|
|
84
|
+
deleteServiceIntegration(service: string): Promise<SaveResult>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** ── Internals(9,非 HTTP 面;L3 宾语消歧:Integration=节点级 / Config=文件级)── */
|
|
88
|
+
export interface AppToolkitInternals {
|
|
89
|
+
/** 逃生舱:正常路径无需调用(Freshness Contract 自动);测试/宿主特殊场景 */
|
|
90
|
+
invalidate(): void;
|
|
91
|
+
resolveContracts(): Promise<ResolvedContracts>;
|
|
92
|
+
/** 四产物(枚举零缓存——契约机制载体) */
|
|
93
|
+
walkManifests(): WalkManifestsResult;
|
|
94
|
+
parseModuleDescriptor(raw: unknown): ReturnType<typeof parseModuleDescriptor>;
|
|
95
|
+
/** 富形态(≠ 包级桥源 loadProviderCatalog,双形态有意) */
|
|
96
|
+
loadProviderCatalogRaw(): ReturnType<typeof loadProviderCatalogRaw>;
|
|
97
|
+
/** 文件级读(doctor/migrate;宽松透传——strict 校验归门控面) */
|
|
98
|
+
readIntegrationsConfig(): Promise<Record<string, unknown> | null>;
|
|
99
|
+
/** 文件级整写(provider-migrate;门控同款) */
|
|
100
|
+
writeIntegrationsConfig(config: Record<string, unknown>): Promise<void>;
|
|
101
|
+
ensureMockBindings(): Promise<ReturnType<typeof ensureMockBindings>>;
|
|
102
|
+
/** 谓词单源(contracts 注入签名;结构化输出) */
|
|
103
|
+
evaluateIntegrationServices(input: Omit<Parameters<typeof evaluateIntegrationServices>[0], 'contracts'>): Promise<IntegrationServiceIssue[]>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type AppToolkit = AppToolkitRead & AppToolkitWrite & AppToolkitInternals;
|
|
107
|
+
|
|
108
|
+
export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}): AppToolkit {
|
|
109
|
+
const cache: FileCache = createFileCache();
|
|
110
|
+
let writeChain: Promise<unknown> = Promise.resolve();
|
|
111
|
+
|
|
112
|
+
const viewCtx = { appDir, cache };
|
|
113
|
+
|
|
114
|
+
/** 门控断言(policy at edge)——strictValidation false → 503(message 三分支 N1) */
|
|
115
|
+
function assertContractsUsable(contracts: ResolvedContracts): void {
|
|
116
|
+
if (!contracts.strictValidation) {
|
|
117
|
+
throw new AppToolkitError('CONTRACTS_NOT_RESOLVED', 503, contractsNotResolvedMessage(contracts));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 快照(同步——调用方已 await resolveContracts) */
|
|
122
|
+
function snapshot(contracts: ResolvedContracts) {
|
|
123
|
+
return loadViewSnapshotSync(viewCtx, contracts);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 谓词全量 issues(求值轴/写后复查共用——contracts 注入 + buildPredicateAssembly 单源装配,FX-2b:
|
|
127
|
+
* 与 CLI doctor 规则同装配面——「面板 issues ≡ doctor issues」机制保证) */
|
|
128
|
+
async function predicateIssues(): Promise<IntegrationServiceIssue[]> {
|
|
129
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
130
|
+
const snap = snapshot(contracts);
|
|
131
|
+
const config = snap.integrations;
|
|
132
|
+
if (config === null) return [];
|
|
133
|
+
const predicateContracts: PredicateContracts = {
|
|
134
|
+
resolveEffectiveService: assertFn(contracts.module.resolveEffectiveService, 'resolveEffectiveService'),
|
|
135
|
+
buildSupplyLookup: assertFn(contracts.module.buildSupplyLookup, 'buildSupplyLookup'),
|
|
136
|
+
moduleDomainOf: assertFn(contracts.module.moduleDomainOf, 'moduleDomainOf'),
|
|
137
|
+
normalizeInstances: assertFn(contracts.module.normalizeInstances, 'normalizeInstances'),
|
|
138
|
+
RESOURCE_TYPE_IDS: (contracts.module.RESOURCE_TYPE_IDS ?? []) as readonly string[],
|
|
139
|
+
};
|
|
140
|
+
const assembly = buildPredicateAssembly(appDir, snap.walk, config, process.env);
|
|
141
|
+
return evaluateIntegrationServices({
|
|
142
|
+
contracts: predicateContracts,
|
|
143
|
+
integrations: config as never,
|
|
144
|
+
supplies: predicateContracts.buildSupplyLookup(snap.catalog as never) as never,
|
|
145
|
+
...assembly,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function assertFn<T>(fn: unknown, name: string): T {
|
|
150
|
+
if (typeof fn !== 'function') throw new AppToolkitError('CONTRACTS_NOT_RESOLVED', 503, `contracts 求值面 ${name} 缺席(形态异常)`);
|
|
151
|
+
return fn as T;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 写串行队列(进程内 last-writer-wins;链式保证单飞) */
|
|
155
|
+
function enqueue<T>(op: () => Promise<T> | T): Promise<T> {
|
|
156
|
+
const next = writeChain.then(op, op);
|
|
157
|
+
writeChain = next.catch(() => undefined);
|
|
158
|
+
return next;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const read = {
|
|
162
|
+
async loadModules() {
|
|
163
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
164
|
+
return loadModulesView(snapshot(contracts));
|
|
165
|
+
},
|
|
166
|
+
async loadModule(moduleId: string) {
|
|
167
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
168
|
+
return loadModuleDetailView(snapshot(contracts), moduleId);
|
|
169
|
+
},
|
|
170
|
+
async loadApp() {
|
|
171
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
172
|
+
return loadAppView(snapshot(contracts));
|
|
173
|
+
},
|
|
174
|
+
async loadService(service: string) {
|
|
175
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
176
|
+
return loadServiceDetailView(snapshot(contracts), service);
|
|
177
|
+
},
|
|
178
|
+
async loadServiceResolutions(services?: string[]) {
|
|
179
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
180
|
+
assertContractsUsable(contracts);
|
|
181
|
+
const issues = await predicateIssues();
|
|
182
|
+
return loadServiceResolutionsView(snapshot(contracts), issues, services);
|
|
183
|
+
},
|
|
184
|
+
async loadServiceResolution(service: string) {
|
|
185
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
186
|
+
assertContractsUsable(contracts);
|
|
187
|
+
const issues = await predicateIssues();
|
|
188
|
+
return loadServiceResolutionDetail(service, snapshot(contracts), issues);
|
|
189
|
+
},
|
|
190
|
+
async loadProviders() {
|
|
191
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
192
|
+
return loadProvidersView(snapshot(contracts));
|
|
193
|
+
},
|
|
194
|
+
async loadProvider(provider: string) {
|
|
195
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
196
|
+
return loadProviderDetailView(snapshot(contracts), provider);
|
|
197
|
+
},
|
|
198
|
+
async loadProviderService(provider: string, service: string) {
|
|
199
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
200
|
+
return loadProviderServiceDetailView(snapshot(contracts), provider, service);
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const core = createWriteCore(appDir, {
|
|
205
|
+
onApplied: options.onApplied,
|
|
206
|
+
cache,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const write = {
|
|
210
|
+
async writeAppIntegration(node: AppIntegrationNode, o?: WriteOptions) {
|
|
211
|
+
await core.warmup();
|
|
212
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
213
|
+
assertContractsUsable(contracts);
|
|
214
|
+
return enqueue(() => core.applyApp(node as Record<string, unknown>, o));
|
|
215
|
+
},
|
|
216
|
+
async writeModuleIntegration(moduleId: string, node: ModuleIntegrationNode, o?: WriteOptions) {
|
|
217
|
+
await core.warmup();
|
|
218
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
219
|
+
assertContractsUsable(contracts);
|
|
220
|
+
return enqueue(() => core.applyModule(moduleId, node as Record<string, unknown>, o));
|
|
221
|
+
},
|
|
222
|
+
async writeServiceIntegration(service: string, node: ServiceIntegrationNode, o?: WriteOptions) {
|
|
223
|
+
await core.warmup();
|
|
224
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
225
|
+
assertContractsUsable(contracts);
|
|
226
|
+
return enqueue(() => core.applyService(service, node as Record<string, unknown>, o));
|
|
227
|
+
},
|
|
228
|
+
async deleteServiceIntegration(service: string) {
|
|
229
|
+
await core.warmup();
|
|
230
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
231
|
+
assertContractsUsable(contracts);
|
|
232
|
+
return enqueue(() => core.deleteService(service));
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const internals: AppToolkitInternals = {
|
|
237
|
+
invalidate() {
|
|
238
|
+
cache.invalidate();
|
|
239
|
+
invalidateContractsResolver(appDir);
|
|
240
|
+
},
|
|
241
|
+
async resolveContracts() {
|
|
242
|
+
return resolveContractsForApp(appDir);
|
|
243
|
+
},
|
|
244
|
+
walkManifests() {
|
|
245
|
+
return loadProviderCatalogRawWalk(appDir);
|
|
246
|
+
},
|
|
247
|
+
parseModuleDescriptor(raw: unknown) {
|
|
248
|
+
return parseModuleDescriptor(raw);
|
|
249
|
+
},
|
|
250
|
+
loadProviderCatalogRaw() {
|
|
251
|
+
return loadProviderCatalogRaw(appDir);
|
|
252
|
+
},
|
|
253
|
+
async readIntegrationsConfig() {
|
|
254
|
+
return readIntegrationsConfig(appDir, cache);
|
|
255
|
+
},
|
|
256
|
+
async writeIntegrationsConfig(config: Record<string, unknown>) {
|
|
257
|
+
await core.warmup();
|
|
258
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
259
|
+
assertContractsUsable(contracts);
|
|
260
|
+
return enqueue(() => coreWriteIntegrationsConfig(appDir, config, cache));
|
|
261
|
+
},
|
|
262
|
+
async ensureMockBindings() {
|
|
263
|
+
await core.warmup();
|
|
264
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
265
|
+
assertContractsUsable(contracts);
|
|
266
|
+
return enqueue(() => ensureMockBindings(appDir));
|
|
267
|
+
},
|
|
268
|
+
async evaluateIntegrationServices(input) {
|
|
269
|
+
await core.warmup();
|
|
270
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
271
|
+
assertContractsUsable(contracts);
|
|
272
|
+
const predicateContracts: PredicateContracts = {
|
|
273
|
+
resolveEffectiveService: assertFn(contracts.module.resolveEffectiveService, 'resolveEffectiveService'),
|
|
274
|
+
buildSupplyLookup: assertFn(contracts.module.buildSupplyLookup, 'buildSupplyLookup'),
|
|
275
|
+
moduleDomainOf: assertFn(contracts.module.moduleDomainOf, 'moduleDomainOf'),
|
|
276
|
+
normalizeInstances: assertFn(contracts.module.normalizeInstances, 'normalizeInstances'),
|
|
277
|
+
RESOURCE_TYPE_IDS: (contracts.module.RESOURCE_TYPE_IDS ?? []) as readonly string[],
|
|
278
|
+
};
|
|
279
|
+
return evaluateIntegrationServices({ ...input, contracts: predicateContracts });
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
function loadProviderCatalogRawWalk(dir: string): WalkManifestsResult {
|
|
284
|
+
// walkManifests 直通(Internals 面——枚举零缓存契约载体;文件内容经本实例 cache,FX-3/F7)
|
|
285
|
+
return internalsWalk(dir, cache);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return { ...read, ...write, ...internals };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function coreWriteIntegrationsConfig(appDir: string, config: Record<string, unknown>, cache: FileCache): void {
|
|
292
|
+
const file = path.join(appDir, 'config', 'integrations.json');
|
|
293
|
+
writeAtomic(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
294
|
+
cache.invalidate([file]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export { loadProviderCatalog, loadProviderServiceDetailView, readIntegrationsStrict };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// app-toolkit 公开面聚合导出。
|
|
2
|
+
// C2(core 层):core 七件 + Freshness Contract。C3/C4 依次落 assembly/integrations/views/工厂/DTO。
|
|
3
|
+
// 公开面冻结纪律:本文件导出清单即 DTO 单源契约(additive-only,L2——agtcodingbox import type 消费)。
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
parseModuleDescriptor,
|
|
7
|
+
moduleDescriptorSchema,
|
|
8
|
+
SAFE_NAME_PATTERN,
|
|
9
|
+
pageContribSchema,
|
|
10
|
+
tabContribSchema,
|
|
11
|
+
moduleDependencySchema,
|
|
12
|
+
providerSlotContribSchema,
|
|
13
|
+
serviceNeedContribSchema,
|
|
14
|
+
resourceNeedContribSchema,
|
|
15
|
+
} from './core/module-schema.js';
|
|
16
|
+
export type {
|
|
17
|
+
ModuleDescriptor,
|
|
18
|
+
CardContrib,
|
|
19
|
+
ProviderSlotContrib,
|
|
20
|
+
ServiceNeedContrib,
|
|
21
|
+
ResourceNeedContrib,
|
|
22
|
+
ParseModuleResult,
|
|
23
|
+
} from './core/module-schema.js';
|
|
24
|
+
|
|
25
|
+
export { expandEnvVars } from './core/env-expansion.js';
|
|
26
|
+
export type { EnvExpansionResult } from './core/env-expansion.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
deriveStem,
|
|
30
|
+
stemToRef,
|
|
31
|
+
stemToFilePath,
|
|
32
|
+
readCredentialFile,
|
|
33
|
+
writeCredentialFile,
|
|
34
|
+
maskCredentialValues,
|
|
35
|
+
} from './core/credential-format.js';
|
|
36
|
+
export type { CredentialFile, StemInput } from './core/credential-format.js';
|
|
37
|
+
|
|
38
|
+
export { writeAtomic, ensureDir } from './core/fskit.js';
|
|
39
|
+
|
|
40
|
+
export { createFileCache } from './core/file-cache.js';
|
|
41
|
+
export type { FileCache } from './core/file-cache.js';
|
|
42
|
+
|
|
43
|
+
export { resolveContractsForApp, invalidateContractsResolver } from './core/contracts-resolver.js';
|
|
44
|
+
export type { ResolvedContracts } from './core/contracts-resolver.js';
|
|
45
|
+
|
|
46
|
+
export type { EvaluationExports, ResolvedModule } from './core/contracts-expected.js';
|
|
47
|
+
|
|
48
|
+
// ── integrations 谓词单源(迁移面;doctor 第 19 条与工厂 evaluateIntegrationServices 共用)──
|
|
49
|
+
export { evaluateIntegrationServices, isControlledInlineProvider, checkInstanceKeys } from './integrations/predicate.js';
|
|
50
|
+
export type {
|
|
51
|
+
IntegrationServicePredicateInput,
|
|
52
|
+
IntegrationServiceIssue,
|
|
53
|
+
PredicateContracts,
|
|
54
|
+
SupplyLookup,
|
|
55
|
+
} from './integrations/predicate.js';
|
|
56
|
+
// ── 谓词装配单源(FX-2b/F1:CLI doctor 规则与 toolkit 工厂同装配面——双通道 walkManifests)──
|
|
57
|
+
export { buildPredicateAssembly, refToEnvKey } from './integrations/predicate-io.js';
|
|
58
|
+
export type { PredicateAssembly, PredicateIoInputs } from './integrations/predicate-io.js';
|
|
59
|
+
|
|
60
|
+
// ── assembly 层(目录知识——walkManifests 四产物 + catalog 双形态 + 组合单体装配)──
|
|
61
|
+
export { walkManifests } from './assembly/walk-manifests.js';
|
|
62
|
+
export type { WalkManifestsResult, WalkModuleInfo, WalkCatalog, WalkProviderEntry } from './assembly/walk-manifests.js';
|
|
63
|
+
export { readAppManifest, writeAppManifest, manifestPath } from './assembly/app-manifest.js';
|
|
64
|
+
export type { AppManifest, NpmModuleEntry, ModuleMode } from './assembly/app-manifest.js';
|
|
65
|
+
export { loadProviderCatalog, loadProviderCatalogRaw } from './assembly/provider-catalog.js';
|
|
66
|
+
export type { BridgeProviderCatalog, ProviderServiceSchemas, ProviderServiceSchemasResult } from './assembly/provider-catalog.js';
|
|
67
|
+
|
|
68
|
+
// ── integrations 层(领域——读/写核心/凭据双工件/mock 绑定)──
|
|
69
|
+
export { readIntegrationsConfig, readIntegrationsStrict } from './integrations/read.js';
|
|
70
|
+
export type { ReadIntegrationsResult } from './integrations/read.js';
|
|
71
|
+
export { createWriteCore, deepEqual } from './integrations/write.js';
|
|
72
|
+
export type { SaveResult, WriteOptions, WriteCoreOptions, WriteCore } from './integrations/write.js';
|
|
73
|
+
export { ensureMockBindings } from './integrations/mock-bindings.js';
|
|
74
|
+
export type { MockBindingsResult } from './integrations/mock-bindings.js';
|
|
75
|
+
export { reconcileOrphanCredentials, credentialArtifactExists } from './integrations/credentials.js';
|
|
76
|
+
export type { AppliedCredential, CredentialInput, OrphanIssue, PlannedCredential } from './integrations/credentials.js';
|
|
77
|
+
|
|
78
|
+
// ── 错误面(领域 6 码——AppToolkitError;name 字面量约定见 errors.ts)──
|
|
79
|
+
export { AppToolkitError, contractsNotResolvedMessage } from './core/errors.js';
|
|
80
|
+
export type { AppToolkitErrorCode } from './core/errors.js';
|
|
81
|
+
|
|
82
|
+
// ── views(五投影——静态四轴 + 求值轴;铁律 #3 静态/求值分离由投影结构承载)──
|
|
83
|
+
export { loadModulesView, loadModuleDetailView, moduleDomainKeyOf } from './views/modules.js';
|
|
84
|
+
export { loadAppView } from './views/app.js';
|
|
85
|
+
export { loadServiceDetailView } from './views/service-detail.js';
|
|
86
|
+
export { loadProvidersView, loadProviderDetailView, loadProviderServiceDetailView } from './views/providers.js';
|
|
87
|
+
export { loadServiceResolutionsView, loadServiceResolutionDetail, resolveServiceSummary } from './views/service-resolutions.js';
|
|
88
|
+
export type { ViewContext, ViewSnapshot } from './views/context.js';
|
|
89
|
+
|
|
90
|
+
// ── 工厂(22 方法 = 9 读 + 4 写 + 9 Internals;门控 policy at edge;写串行)──
|
|
91
|
+
export { createAppToolkit } from './factory.js';
|
|
92
|
+
export type { AppToolkit, AppToolkitRead, AppToolkitWrite, AppToolkitInternals, AppToolkitOptions } from './factory.js';
|
|
93
|
+
|
|
94
|
+
// ── DTO(api.md §3 字段级单源——28 类型;agtcodingbox/tbox-ai-coding import type 消费)──
|
|
95
|
+
export type {
|
|
96
|
+
ServiceStatus,
|
|
97
|
+
BindingLayer,
|
|
98
|
+
ModulesView,
|
|
99
|
+
ModuleDeclaration,
|
|
100
|
+
ServiceDemand,
|
|
101
|
+
ModuleDetailView,
|
|
102
|
+
AppView,
|
|
103
|
+
ServiceDetail,
|
|
104
|
+
ServiceResolutionsView,
|
|
105
|
+
ServiceResolutionEntry,
|
|
106
|
+
ServiceResolution,
|
|
107
|
+
ServiceResolutionDetail,
|
|
108
|
+
AppIntegrationNode,
|
|
109
|
+
ModuleIntegrationNode,
|
|
110
|
+
ServiceIntegrationNode,
|
|
111
|
+
ServiceInstanceNode,
|
|
112
|
+
InlineVendorDeclaration,
|
|
113
|
+
CredentialValues,
|
|
114
|
+
ResolvedInstance,
|
|
115
|
+
ProvidersView,
|
|
116
|
+
ProviderEntry,
|
|
117
|
+
ProviderDetail,
|
|
118
|
+
ProviderServiceDetail,
|
|
119
|
+
EffectiveBinding,
|
|
120
|
+
CredentialEcho,
|
|
121
|
+
ContractsResolution,
|
|
122
|
+
IntegrationIssue,
|
|
123
|
+
} from './dto.js';
|
|
124
|
+
export { APP_INTEGRATION_NODE_KEYS } from './dto.js';
|
|
125
|
+
export {
|
|
126
|
+
readAppManifestFull,
|
|
127
|
+
upsertNpmModule,
|
|
128
|
+
removeNpmModule,
|
|
129
|
+
upsertMiniappContainer,
|
|
130
|
+
updateNpmModule,
|
|
131
|
+
isValidModuleId,
|
|
132
|
+
assertValidModuleId,
|
|
133
|
+
} from './assembly/app-manifest.js';
|
|
134
|
+
export type { MiniappContainerEntry, AppManifestFull } from './assembly/app-manifest.js';
|
|
135
|
+
export { collectDeclaredModules, extractModuleIdFromSource } from './assembly/walk-manifests.js';
|
|
136
|
+
export type { DeclaredModuleInfo } from './assembly/walk-manifests.js';
|
|
137
|
+
export { resolveContractsForApp as resolveContractsForAppExport } from './core/contracts-resolver.js';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { deriveStem, readCredentialFile, stemToFilePath, writeCredentialFile } from '../core/credential-format.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 凭据双工件·写端(C3-4 credentials;FX-1c 拆分——F3 定序):
|
|
7
|
+
* credentials / credentialsByInstance(body 唯一非文件字段)→ **纯派生 plan(零 I/O)**
|
|
8
|
+
* → 消费方(write.ts)ref 回填节点(先于 no-diff 判定)→ 严格校验通过后 `writeCredentialArtifacts`
|
|
9
|
+
* 落盘(dryRun → 零写盘 + existsSync 预览)。同 stem 已在场 → 重写 + overwrote:true。
|
|
10
|
+
* 孤儿凭据 = **写后对账**(扫描文件全量 credentialRef 引用 vs 凭据目录全集 →
|
|
11
|
+
* ORPHANED_CREDENTIAL warning——统一覆盖 DELETE 与 PUT 替换两类来源,只报告不自动清理,D9)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface CredentialInput {
|
|
15
|
+
type: string;
|
|
16
|
+
values: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AppliedCredential {
|
|
20
|
+
stem: string;
|
|
21
|
+
ref: string;
|
|
22
|
+
overwrote: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 凭据工件计划项(纯派生产物——stem/ref 确定性;input 供落盘/校验消费) */
|
|
26
|
+
export interface PlannedCredential {
|
|
27
|
+
/** 实例级(credentialsByInstance)时在场;应用/服务级根凭据缺席 */
|
|
28
|
+
instanceId?: string;
|
|
29
|
+
stem: string;
|
|
30
|
+
ref: string;
|
|
31
|
+
input: CredentialInput;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface OrphanIssue {
|
|
35
|
+
severity: 'warning';
|
|
36
|
+
code: 'ORPHANED_CREDENTIAL';
|
|
37
|
+
service?: string;
|
|
38
|
+
instance?: string;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 应用级凭据计划(app 写路径):stem = `${provider}-global` / `${provider}-${instanceId}-global`——纯派生零 I/O */
|
|
43
|
+
export function planAppCredentialArtifacts(
|
|
44
|
+
provider: string,
|
|
45
|
+
credentials?: CredentialInput,
|
|
46
|
+
credentialsByInstance?: Record<string, CredentialInput>,
|
|
47
|
+
): PlannedCredential[] {
|
|
48
|
+
const plan: PlannedCredential[] = [];
|
|
49
|
+
if (credentials) {
|
|
50
|
+
const stem = deriveStem({ provider });
|
|
51
|
+
plan.push({ stem, ref: `secret://${stem}`, input: credentials });
|
|
52
|
+
}
|
|
53
|
+
for (const [instanceId, input] of Object.entries(credentialsByInstance ?? {})) {
|
|
54
|
+
const stem = deriveStem({ provider, instanceId });
|
|
55
|
+
plan.push({ instanceId, stem, ref: `secret://${stem}`, input });
|
|
56
|
+
}
|
|
57
|
+
return plan;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 服务级凭据计划(service 写路径):stem = `${provider}-${service}` / `${provider}-${instanceId}-${domain}`——纯派生零 I/O */
|
|
61
|
+
export function planServiceCredentialArtifacts(
|
|
62
|
+
provider: string,
|
|
63
|
+
service: string,
|
|
64
|
+
domain: string,
|
|
65
|
+
credentials?: CredentialInput,
|
|
66
|
+
credentialsByInstance?: Record<string, CredentialInput>,
|
|
67
|
+
): PlannedCredential[] {
|
|
68
|
+
const plan: PlannedCredential[] = [];
|
|
69
|
+
if (credentials) {
|
|
70
|
+
const stem = deriveStem({ provider, service });
|
|
71
|
+
plan.push({ stem, ref: `secret://${stem}`, input: credentials });
|
|
72
|
+
}
|
|
73
|
+
for (const [instanceId, input] of Object.entries(credentialsByInstance ?? {})) {
|
|
74
|
+
const stem = deriveStem({ provider, instanceId, domain });
|
|
75
|
+
plan.push({ instanceId, stem, ref: `secret://${stem}`, input });
|
|
76
|
+
}
|
|
77
|
+
return plan;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 凭据工件落盘(F3 第 8 步——消费方保证已于严格校验通过后调用);
|
|
81
|
+
* dryRun → 零写盘,overwrote = existsSync 预览(「将创建工件」语义)。 */
|
|
82
|
+
export function writeCredentialArtifacts(
|
|
83
|
+
appDir: string,
|
|
84
|
+
plan: readonly PlannedCredential[],
|
|
85
|
+
dryRun: boolean,
|
|
86
|
+
): AppliedCredential[] {
|
|
87
|
+
return plan.map((p) => ({
|
|
88
|
+
stem: p.stem,
|
|
89
|
+
ref: p.ref,
|
|
90
|
+
overwrote: dryRun
|
|
91
|
+
? existsSync(join(appDir, stemToFilePath(p.stem)))
|
|
92
|
+
: writeCredentialFile(appDir, p.stem, p.input),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 凭据工件在场探测(dryRun 预览与对账共用) */
|
|
97
|
+
export function credentialArtifactExists(appDir: string, stem: string): boolean {
|
|
98
|
+
return existsSync(join(appDir, stemToFilePath(stem)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 全文件 credentialRef 引用收集(integrations.json 深扫描——secret:// 前缀值) */
|
|
102
|
+
function collectRefs(value: unknown, out: Set<string>): void {
|
|
103
|
+
if (typeof value === 'string') {
|
|
104
|
+
if (value.startsWith('secret://')) out.add(value.slice('secret://'.length));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
for (const v of value) collectRefs(v, out);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (value && typeof value === 'object') {
|
|
112
|
+
for (const v of Object.values(value)) collectRefs(v, out);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 孤儿凭据写后对账(D9):凭据目录全集 − integrations.json 全量引用 = 孤儿(只报告不清理) */
|
|
117
|
+
export function reconcileOrphanCredentials(appDir: string): OrphanIssue[] {
|
|
118
|
+
const credentialsDir = join(appDir, 'config', 'credentials');
|
|
119
|
+
const referenced = new Set<string>();
|
|
120
|
+
const integrationsFile = join(appDir, 'config', 'integrations.json');
|
|
121
|
+
if (existsSync(integrationsFile)) {
|
|
122
|
+
try {
|
|
123
|
+
collectRefs(JSON.parse(readFileSync(integrationsFile, 'utf8')), referenced);
|
|
124
|
+
} catch {
|
|
125
|
+
/* 坏文件 → 引用集为空(孤儿对账按现状目录全集报告) */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const issues: OrphanIssue[] = [];
|
|
129
|
+
if (!existsSync(credentialsDir)) return issues;
|
|
130
|
+
let stems: string[] = [];
|
|
131
|
+
try {
|
|
132
|
+
stems = readdirSync(credentialsDir).filter((f) => f.endsWith('.json')).map((f) => f.slice(0, -'.json'.length));
|
|
133
|
+
} catch {
|
|
134
|
+
return issues;
|
|
135
|
+
}
|
|
136
|
+
for (const stem of stems.sort()) {
|
|
137
|
+
if (referenced.has(stem)) continue;
|
|
138
|
+
// 掩码读侧验证:孤儿报告只提文件位(零真值——CredentialEcho 红线同源)
|
|
139
|
+
const content = readCredentialFile(appDir, stem);
|
|
140
|
+
void content;
|
|
141
|
+
issues.push({
|
|
142
|
+
severity: 'warning',
|
|
143
|
+
code: 'ORPHANED_CREDENTIAL',
|
|
144
|
+
message: `config/credentials/${stem}.json 不再被引用(凭据文件保留,可手动清理)`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return issues;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** stem → 文件相对路径(对账测试用透出) */
|
|
151
|
+
export function credentialFilePath(stem: string): string {
|
|
152
|
+
return stemToFilePath(stem);
|
|
153
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { walkManifests } from '../assembly/walk-manifests.js';
|
|
4
|
+
import { writeAtomic } from '../core/fskit.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* mock 槽绑定生成器(自 CLI mock-bindings.ts 迁入;provider 拆包 F3 收敛 · B 方案;integrations v4 零拓扑形态):
|
|
8
|
+
* 应用内 config/integrations.json 缺席时,从已装模块 manifest 的 local:true 槽派生全槽 mock 绑定。
|
|
9
|
+
*
|
|
10
|
+
* 语义边界(零改动迁移):
|
|
11
|
+
* - 缺席即跳过、永不覆盖(在场文件 = 用户/素材真源,生成器零侵入);
|
|
12
|
+
* - 只绑 local:true 槽(真厂商 provider 永不自动绑定);
|
|
13
|
+
* - sync 不触发(add = 新事实进入体系,sync = 状态重放)。
|
|
14
|
+
* 迁移顺带修复:writeFileSync → writeAtomic(原子写;H5)。
|
|
15
|
+
* 模块发现改 walkManifests(单源——CLI listLocalModules 并入)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface MockBindingsResult {
|
|
19
|
+
generated: boolean;
|
|
20
|
+
reason: 'in-place' | 'no-local-slots' | 'generated';
|
|
21
|
+
/** 本次生成(或在场文件阻断时的空集)涉及的 local 槽服务名 */
|
|
22
|
+
services: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ensureMockBindings(appDir: string): MockBindingsResult {
|
|
26
|
+
const file = join(appDir, 'config', 'integrations.json');
|
|
27
|
+
if (existsSync(file)) {
|
|
28
|
+
return { generated: false, reason: 'in-place', services: [] };
|
|
29
|
+
}
|
|
30
|
+
const slots: Array<{ service: string; provider: string; implementation: string }> = [];
|
|
31
|
+
for (const mod of walkManifests(appDir).modules) {
|
|
32
|
+
for (const slot of mod.descriptor.contributes.providers?.slots ?? []) {
|
|
33
|
+
if (slot.local !== true) continue;
|
|
34
|
+
slots.push({ service: slot.service, provider: slot.provider, implementation: slot.implementation });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (slots.length === 0) {
|
|
38
|
+
return { generated: false, reason: 'no-local-slots', services: [] };
|
|
39
|
+
}
|
|
40
|
+
const services: Record<string, unknown> = {};
|
|
41
|
+
const vendors = new Set(slots.map((s) => s.provider));
|
|
42
|
+
if (vendors.size === 1) {
|
|
43
|
+
// 单一 local 厂商 → terse:root.provider 继承 + 空槽对象(instances 缺席 = 通配激活)
|
|
44
|
+
const [vendor] = vendors;
|
|
45
|
+
for (const s of slots) services[s.service] = {};
|
|
46
|
+
writeAtomic(file, `${JSON.stringify({ provider: vendor, services }, null, 2)}\n`);
|
|
47
|
+
} else {
|
|
48
|
+
// 异构兜底:逐槽双字段(显式绑定组——无 root 继承位;同样零拓扑)
|
|
49
|
+
for (const s of slots) {
|
|
50
|
+
services[s.service] = { provider: s.provider, implementation: s.implementation };
|
|
51
|
+
}
|
|
52
|
+
writeAtomic(file, `${JSON.stringify({ services }, null, 2)}\n`);
|
|
53
|
+
}
|
|
54
|
+
return { generated: true, reason: 'generated', services: slots.map((s) => s.service).sort() };
|
|
55
|
+
}
|