@topogram/cli 0.3.51 → 0.3.52
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/ARCHITECTURE.md +4 -4
- package/CHANGELOG.md +11 -11
- package/package.json +1 -1
- package/src/adoption/plan.js +2 -2
- package/src/agent-ops/query-builders.js +42 -33
- package/src/cli.js +174 -129
- package/src/generator/adapters.d.ts +1 -0
- package/src/generator/adapters.js +64 -39
- package/src/generator/check.js +19 -12
- package/src/generator/context/diff.js +9 -9
- package/src/generator/context/domain-coverage.js +11 -10
- package/src/generator/context/domain-page.js +6 -6
- package/src/generator/context/shared.js +37 -21
- package/src/generator/context/slice.js +70 -65
- package/src/generator/index.js +12 -12
- package/src/generator/output.js +21 -20
- package/src/generator/registry.js +61 -49
- package/src/generator/runtime/app-bundle.js +15 -15
- package/src/generator/runtime/compile-check.js +7 -7
- package/src/generator/runtime/deployment.js +9 -9
- package/src/generator/runtime/environment.js +39 -39
- package/src/generator/runtime/runtime-check.js +5 -5
- package/src/generator/runtime/shared.js +40 -38
- package/src/generator/runtime/smoke.js +5 -5
- package/src/generator/surfaces/databases/contract.js +1 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +6 -5
- package/src/generator/surfaces/databases/postgres/drizzle.js +3 -2
- package/src/generator/surfaces/databases/postgres/prisma.js +3 -2
- package/src/generator/surfaces/databases/shared.js +3 -2
- package/src/generator/surfaces/databases/snapshot.js +1 -1
- package/src/generator/surfaces/databases/sqlite/prisma.js +3 -2
- package/src/generator/surfaces/native/swiftui-app.js +3 -3
- package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +1 -1
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +3 -3
- package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +3 -3
- package/src/generator/surfaces/services/persistence-wiring.js +3 -2
- package/src/generator/surfaces/services/server-contract.js +4 -4
- package/src/generator/surfaces/shared.js +2 -2
- package/src/generator/surfaces/web/design-intent.js +1 -1
- package/src/generator/surfaces/web/index.js +7 -7
- package/src/generator/surfaces/web/{react-components.js → react-widgets.js} +53 -53
- package/src/generator/surfaces/web/react.js +36 -36
- package/src/generator/surfaces/web/{sveltekit-components.js → sveltekit-widgets.js} +53 -53
- package/src/generator/surfaces/web/sveltekit.js +34 -34
- package/src/generator/surfaces/web/{ui-web-contract.js → ui-surface-contract.js} +8 -8
- package/src/generator/surfaces/web/vanilla.js +6 -6
- package/src/generator/{component-conformance.js → widget-conformance.js} +129 -128
- package/src/generator/widgets.js +40 -0
- package/src/generator-policy.js +10 -12
- package/src/import/core/runner.js +34 -34
- package/src/import/core/shared.js +1 -1
- package/src/import/extractors/ui/android-compose.js +1 -1
- package/src/import/extractors/ui/blazor.js +1 -1
- package/src/import/extractors/ui/razor-pages.js +1 -1
- package/src/import/extractors/ui/react-router.js +4 -4
- package/src/import/extractors/ui/sveltekit.js +4 -4
- package/src/import/extractors/ui/swiftui.js +1 -1
- package/src/import/extractors/ui/uikit.js +1 -1
- package/src/new-project.js +19 -18
- package/src/project-config.js +92 -42
- package/src/proofs/contract-audit.js +1 -1
- package/src/proofs/ios-parity.js +1 -1
- package/src/proofs/issues-parity.js +1 -1
- package/src/realization/backend/build-backend-runtime-realization.js +2 -2
- package/src/realization/ui/build-ui-shared-realization.js +33 -33
- package/src/realization/ui/build-web-realization.js +23 -20
- package/src/reconcile/journeys.js +1 -1
- package/src/resolver/index.js +148 -65
- package/src/validator/index.js +473 -423
- package/src/validator/kinds.js +36 -36
- package/src/validator/per-kind/{component.js → widget.js} +47 -47
- package/src/{component-behavior.js → widget-behavior.js} +3 -3
- package/src/workflows.js +39 -38
- package/template-helpers/react.js +4 -4
- package/template-helpers/sveltekit.js +4 -4
- package/src/generator/components.js +0 -39
- /package/src/resolver/enrich/{component.js → widget.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const BUNDLED_GENERATOR_ADAPTERS: any[];
|
|
2
2
|
export function getBundledGeneratorAdapter(generatorId: string): any;
|
|
3
3
|
export function resolveGeneratorForComponent(component: any, options?: { rootDir?: string | null; configDir?: string | null }): { manifest: any; adapter: any };
|
|
4
|
+
export function generateWithRuntimeGenerator(context: any): { files: Record<string, string>; artifacts?: any; diagnostics?: any[] };
|
|
4
5
|
export function generateWithComponentGenerator(context: any): { files: Record<string, string>; artifacts?: any; diagnostics?: any[] };
|
|
@@ -29,7 +29,7 @@ import { generateServerContract } from "./surfaces/services/server-contract.js";
|
|
|
29
29
|
import { generateStatelessServer } from "./surfaces/services/stateless.js";
|
|
30
30
|
import { generateReactApp } from "./surfaces/web/react.js";
|
|
31
31
|
import { generateSvelteKitApp } from "./surfaces/web/sveltekit.js";
|
|
32
|
-
import {
|
|
32
|
+
import { generateUiSurfaceContract } from "./surfaces/web/ui-surface-contract.js";
|
|
33
33
|
import { generateVanillaWebApp } from "./surfaces/web/vanilla.js";
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -40,7 +40,8 @@ import { generateVanillaWebApp } from "./surfaces/web/vanilla.js";
|
|
|
40
40
|
* @typedef {Object} GeneratorContext
|
|
41
41
|
* @property {Record<string, any>} graph
|
|
42
42
|
* @property {Record<string, any>} projection
|
|
43
|
-
* @property {Record<string, any>}
|
|
43
|
+
* @property {Record<string, any>} runtime
|
|
44
|
+
* @property {Record<string, any>} component Internal runtime alias.
|
|
44
45
|
* @property {Record<string, any>|null} [topology]
|
|
45
46
|
* @property {Record<string, any>} [contracts]
|
|
46
47
|
* @property {Record<string, any>|null} [implementation]
|
|
@@ -82,12 +83,21 @@ function requiredManifest(generatorId) {
|
|
|
82
83
|
return manifest;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {GeneratorContext} context
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function runtimeFor(context) {
|
|
91
|
+
return context.runtime || context.component || {};
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
/**
|
|
86
95
|
* @param {GeneratorContext} context
|
|
87
96
|
* @returns {string}
|
|
88
97
|
*/
|
|
89
98
|
function projectionIdFor(context) {
|
|
90
|
-
|
|
99
|
+
const runtime = runtimeFor(context);
|
|
100
|
+
return context.projection?.id || runtime.projection?.id || context.options?.projectionId;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
/**
|
|
@@ -97,12 +107,14 @@ function projectionIdFor(context) {
|
|
|
97
107
|
*/
|
|
98
108
|
function serverOptions(context, profile) {
|
|
99
109
|
const projectionId = projectionIdFor(context);
|
|
100
|
-
const
|
|
110
|
+
const runtime = runtimeFor(context);
|
|
111
|
+
const dbProjectionId = runtime.databaseComponent?.projection?.id || context.options?.dbProjectionId;
|
|
101
112
|
return {
|
|
102
113
|
...(context.options || {}),
|
|
103
114
|
projectionId,
|
|
104
115
|
dbProjectionId,
|
|
105
|
-
component:
|
|
116
|
+
component: runtime,
|
|
117
|
+
runtime,
|
|
106
118
|
topology: context.topology,
|
|
107
119
|
contracts: context.contracts,
|
|
108
120
|
implementation: context.implementation,
|
|
@@ -115,10 +127,12 @@ function serverOptions(context, profile) {
|
|
|
115
127
|
* @returns {Record<string, any>}
|
|
116
128
|
*/
|
|
117
129
|
function commonOptions(context) {
|
|
130
|
+
const runtime = runtimeFor(context);
|
|
118
131
|
return {
|
|
119
132
|
...(context.options || {}),
|
|
120
133
|
projectionId: projectionIdFor(context),
|
|
121
|
-
component:
|
|
134
|
+
component: runtime,
|
|
135
|
+
runtime,
|
|
122
136
|
topology: context.topology,
|
|
123
137
|
contracts: context.contracts,
|
|
124
138
|
implementation: context.implementation
|
|
@@ -131,26 +145,28 @@ function commonOptions(context) {
|
|
|
131
145
|
*/
|
|
132
146
|
function buildContractsForContext(context) {
|
|
133
147
|
const projectionId = projectionIdFor(context);
|
|
134
|
-
|
|
148
|
+
const runtime = runtimeFor(context);
|
|
149
|
+
const surface = runtime.type || runtime.kind;
|
|
150
|
+
if (surface === "web" || surface === "web_surface") {
|
|
135
151
|
return {
|
|
136
|
-
|
|
152
|
+
uiSurface: generateUiSurfaceContract(context.graph, { ...(context.options || {}), projectionId })
|
|
137
153
|
};
|
|
138
154
|
}
|
|
139
|
-
if (
|
|
155
|
+
if (surface === "api" || surface === "api_service") {
|
|
140
156
|
return {
|
|
141
157
|
server: generateServerContract(context.graph, { ...(context.options || {}), projectionId }),
|
|
142
158
|
api: generateApiContractGraph(context.graph, {})
|
|
143
159
|
};
|
|
144
160
|
}
|
|
145
|
-
if (
|
|
161
|
+
if (surface === "database") {
|
|
146
162
|
return {
|
|
147
163
|
db: generateDbContractGraph(context.graph, { ...(context.options || {}), projectionId }),
|
|
148
164
|
lifecyclePlan: generateDbLifecyclePlan(context.graph, { ...(context.options || {}), projectionId })
|
|
149
165
|
};
|
|
150
166
|
}
|
|
151
|
-
if (
|
|
167
|
+
if (surface === "native" || surface === "ios_surface" || surface === "android_surface") {
|
|
152
168
|
return {
|
|
153
|
-
|
|
169
|
+
uiSurface: generateUiSurfaceContract(context.graph, { ...(context.options || {}), projectionId })
|
|
154
170
|
};
|
|
155
171
|
}
|
|
156
172
|
return {};
|
|
@@ -161,7 +177,8 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
|
|
|
161
177
|
{
|
|
162
178
|
manifest: requiredManifest("topogram/hono"),
|
|
163
179
|
generate(context) {
|
|
164
|
-
|
|
180
|
+
const runtime = runtimeFor(context);
|
|
181
|
+
if (runtime && !runtime.databaseComponent) {
|
|
165
182
|
return fileResult(generateStatelessServer(context.graph, serverOptions(context, "hono")));
|
|
166
183
|
}
|
|
167
184
|
return fileResult(generateHonoServer(context.graph, serverOptions(context, "hono")));
|
|
@@ -170,7 +187,8 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
|
|
|
170
187
|
{
|
|
171
188
|
manifest: requiredManifest("topogram/express"),
|
|
172
189
|
generate(context) {
|
|
173
|
-
|
|
190
|
+
const runtime = runtimeFor(context);
|
|
191
|
+
if (runtime && !runtime.databaseComponent) {
|
|
174
192
|
return fileResult(generateStatelessServer(context.graph, serverOptions(context, "express")));
|
|
175
193
|
}
|
|
176
194
|
return fileResult(generateExpressServer(context.graph, serverOptions(context, "express")));
|
|
@@ -246,24 +264,24 @@ function selectPackageExport(moduleValue, exportName) {
|
|
|
246
264
|
|
|
247
265
|
/**
|
|
248
266
|
* @param {GeneratorManifest} manifest
|
|
249
|
-
* @param {Record<string, any>}
|
|
267
|
+
* @param {Record<string, any>} runtime
|
|
250
268
|
* @param {{ rootDir?: string|null, configDir?: string|null }} [options]
|
|
251
269
|
* @returns {GeneratorAdapter}
|
|
252
270
|
*/
|
|
253
|
-
function loadPackageGeneratorAdapter(manifest,
|
|
254
|
-
const packageName = manifest.package ||
|
|
271
|
+
function loadPackageGeneratorAdapter(manifest, runtime, options = {}) {
|
|
272
|
+
const packageName = manifest.package || runtime?.generator?.package;
|
|
255
273
|
if (!packageName) {
|
|
256
|
-
throw new Error(`
|
|
274
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator '${manifest.id}@${manifest.version}' is package-backed but does not declare a package.`);
|
|
257
275
|
}
|
|
258
276
|
const rootDir = options.configDir || options.rootDir || process.cwd();
|
|
259
277
|
const diagnostics = generatorPolicyDiagnosticsForBindings(
|
|
260
278
|
loadGeneratorPolicy(rootDir),
|
|
261
279
|
[{
|
|
262
|
-
componentId: String(
|
|
263
|
-
componentType: String(
|
|
264
|
-
projection: String(
|
|
265
|
-
generatorId: String(
|
|
266
|
-
version: String(
|
|
280
|
+
componentId: String(runtime?.id || "unknown"),
|
|
281
|
+
componentType: String(runtime?.kind || runtime?.type || manifest.surface || "unknown"),
|
|
282
|
+
projection: String(runtime?.projection?.id || runtime?.projection || "unknown"),
|
|
283
|
+
generatorId: String(runtime?.generator?.id || manifest.id),
|
|
284
|
+
version: String(runtime?.generator?.version || manifest.version),
|
|
267
285
|
packageName
|
|
268
286
|
}],
|
|
269
287
|
"generator-adapter"
|
|
@@ -281,15 +299,15 @@ function loadPackageGeneratorAdapter(manifest, component, options = {}) {
|
|
|
281
299
|
moduleValue = requireFromProject(rootDir)(packageName);
|
|
282
300
|
} catch (error) {
|
|
283
301
|
const installHint = packageGeneratorInstallHint(packageName);
|
|
284
|
-
throw new Error(`
|
|
302
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator package '${packageName}' could not be loaded from '${rootDir}': ${error instanceof Error ? error.message : String(error)}${installHint ? `. ${installHint}` : ""}`);
|
|
285
303
|
}
|
|
286
304
|
const adapter = selectPackageExport(moduleValue, manifest.export);
|
|
287
305
|
if (!adapter || typeof adapter.generate !== "function") {
|
|
288
|
-
throw new Error(`
|
|
306
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator package '${packageName}' must export an adapter with a generate(context) function.`);
|
|
289
307
|
}
|
|
290
308
|
const adapterManifest = adapter.manifest || manifest;
|
|
291
309
|
if (adapterManifest.id !== manifest.id || adapterManifest.version !== manifest.version) {
|
|
292
|
-
throw new Error(`
|
|
310
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator package '${packageName}' adapter manifest '${adapterManifest.id}@${adapterManifest.version}' does not match '${manifest.id}@${manifest.version}'.`);
|
|
293
311
|
}
|
|
294
312
|
return {
|
|
295
313
|
manifest,
|
|
@@ -300,35 +318,35 @@ function loadPackageGeneratorAdapter(manifest, component, options = {}) {
|
|
|
300
318
|
}
|
|
301
319
|
|
|
302
320
|
/**
|
|
303
|
-
* @param {Record<string, any>}
|
|
321
|
+
* @param {Record<string, any>} runtime
|
|
304
322
|
* @param {{ rootDir?: string|null, configDir?: string|null }} [options]
|
|
305
323
|
* @returns {{ manifest: GeneratorManifest, adapter: GeneratorAdapter }}
|
|
306
324
|
*/
|
|
307
|
-
export function resolveGeneratorForComponent(
|
|
308
|
-
const generatorId =
|
|
309
|
-
const resolved = resolveGeneratorManifestForBinding(
|
|
325
|
+
export function resolveGeneratorForComponent(runtime, options = {}) {
|
|
326
|
+
const generatorId = runtime?.generator?.id;
|
|
327
|
+
const resolved = resolveGeneratorManifestForBinding(runtime?.generator, options);
|
|
310
328
|
const manifest = resolved.manifest || getGeneratorManifest(generatorId);
|
|
311
329
|
if (!manifest) {
|
|
312
330
|
const detail = resolved.errors.length > 0 ? ` ${resolved.errors.join(" ")}` : "";
|
|
313
|
-
throw new Error(`
|
|
331
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' uses unknown generator '${generatorId || "unknown"}'.${detail}`);
|
|
314
332
|
}
|
|
315
333
|
if (manifest.planned) {
|
|
316
|
-
throw new Error(`
|
|
334
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' uses planned generator '${manifest.id}', which is not implemented yet.`);
|
|
317
335
|
}
|
|
318
|
-
if (
|
|
319
|
-
throw new Error(`
|
|
336
|
+
if (runtime.generator?.version !== manifest.version) {
|
|
337
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator '${manifest.id}' version '${runtime.generator?.version}' is unsupported; expected '${manifest.version}'.`);
|
|
320
338
|
}
|
|
321
339
|
const manifestValidation = validateGeneratorManifest(manifest);
|
|
322
340
|
if (!manifestValidation.ok) {
|
|
323
341
|
throw new Error(manifestValidation.errors.join("\n"));
|
|
324
342
|
}
|
|
325
343
|
if (manifest.source === "package") {
|
|
326
|
-
return { manifest, adapter: loadPackageGeneratorAdapter(manifest,
|
|
344
|
+
return { manifest, adapter: loadPackageGeneratorAdapter(manifest, runtime, options) };
|
|
327
345
|
}
|
|
328
346
|
const adapter = getBundledGeneratorAdapter(manifest.id);
|
|
329
347
|
if (!adapter) {
|
|
330
|
-
const installHint = packageGeneratorInstallHint(
|
|
331
|
-
throw new Error(`
|
|
348
|
+
const installHint = packageGeneratorInstallHint(runtime?.generator?.package || manifest.package);
|
|
349
|
+
throw new Error(`Runtime '${runtime?.id || "unknown"}' generator '${manifest.id}@${manifest.version}' is not available. Package-backed generators must be installed before generation.${installHint ? ` ${installHint}` : ""}`);
|
|
332
350
|
}
|
|
333
351
|
return { manifest, adapter };
|
|
334
352
|
}
|
|
@@ -337,13 +355,20 @@ export function resolveGeneratorForComponent(component, options = {}) {
|
|
|
337
355
|
* @param {Omit<GeneratorContext, "contracts"> & { contracts?: Record<string, any> }} context
|
|
338
356
|
* @returns {GeneratorResult}
|
|
339
357
|
*/
|
|
340
|
-
export function
|
|
341
|
-
const
|
|
358
|
+
export function generateWithRuntimeGenerator(context) {
|
|
359
|
+
const runtime = runtimeFor(context);
|
|
360
|
+
const { manifest, adapter } = resolveGeneratorForComponent(runtime, context.options || {});
|
|
342
361
|
const contracts = context.contracts || buildContractsForContext(context);
|
|
343
362
|
return adapter.generate({
|
|
344
363
|
...context,
|
|
364
|
+
runtime,
|
|
365
|
+
component: runtime,
|
|
345
366
|
projection: context.projection,
|
|
346
367
|
manifest,
|
|
347
368
|
contracts
|
|
348
369
|
});
|
|
349
370
|
}
|
|
371
|
+
|
|
372
|
+
export function generateWithComponentGenerator(context) {
|
|
373
|
+
return generateWithRuntimeGenerator(context);
|
|
374
|
+
}
|
package/src/generator/check.js
CHANGED
|
@@ -109,17 +109,17 @@ function loadInstalledAdapter(packageName, rootDir, manifest) {
|
|
|
109
109
|
* @returns {Record<string, any>}
|
|
110
110
|
*/
|
|
111
111
|
function smokeProjection(manifest) {
|
|
112
|
-
const
|
|
112
|
+
const type = manifest.projectionTypes[0] || "";
|
|
113
113
|
if (manifest.surface === "api") {
|
|
114
114
|
return {
|
|
115
115
|
id: "proj_generator_check_api",
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
type: "api_contract",
|
|
117
|
+
endpoints: [{ method: "GET", path: "/generator-check", capabilityId: "cap_generator_check", success: 200 }]
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
return {
|
|
121
121
|
id: `proj_generator_check_${manifest.surface}`,
|
|
122
|
-
|
|
122
|
+
type
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -131,8 +131,8 @@ function smokeProjection(manifest) {
|
|
|
131
131
|
function smokeContracts(manifest, projection) {
|
|
132
132
|
if (manifest.surface === "web" || manifest.surface === "native") {
|
|
133
133
|
return {
|
|
134
|
-
|
|
135
|
-
projection: { id: projection.id,
|
|
134
|
+
uiSurface: {
|
|
135
|
+
projection: { id: projection.id, type: projection.type },
|
|
136
136
|
appShell: { brand: "Generator Check" },
|
|
137
137
|
screens: [
|
|
138
138
|
{ id: "screen_generator_check_home", title: "Generator Check", route: "/" },
|
|
@@ -148,22 +148,22 @@ function smokeContracts(manifest, projection) {
|
|
|
148
148
|
if (manifest.surface === "api") {
|
|
149
149
|
return {
|
|
150
150
|
server: {
|
|
151
|
-
projection: { id: projection.id,
|
|
152
|
-
routes: projection.
|
|
151
|
+
projection: { id: projection.id, type: "api_contract" },
|
|
152
|
+
routes: projection.endpoints,
|
|
153
153
|
preconditions: [],
|
|
154
154
|
responses: []
|
|
155
155
|
},
|
|
156
|
-
api: { projections: [{ id: projection.id,
|
|
156
|
+
api: { projections: [{ id: projection.id, endpoints: projection.endpoints }] }
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
if (manifest.surface === "database") {
|
|
160
160
|
return {
|
|
161
161
|
db: {
|
|
162
|
-
projection: { id: projection.id,
|
|
162
|
+
projection: { id: projection.id, type: projection.type },
|
|
163
163
|
tables: []
|
|
164
164
|
},
|
|
165
165
|
lifecyclePlan: {
|
|
166
|
-
projection: { id: projection.id,
|
|
166
|
+
projection: { id: projection.id, type: projection.type },
|
|
167
167
|
migrations: [],
|
|
168
168
|
seeds: []
|
|
169
169
|
}
|
|
@@ -177,8 +177,15 @@ function smokeContracts(manifest, projection) {
|
|
|
177
177
|
* @returns {Record<string, any>}
|
|
178
178
|
*/
|
|
179
179
|
function smokeComponent(manifest) {
|
|
180
|
+
const runtimeKindBySurface = {
|
|
181
|
+
api: "api_service",
|
|
182
|
+
web: "web_surface",
|
|
183
|
+
native: "ios_surface",
|
|
184
|
+
database: "database"
|
|
185
|
+
};
|
|
180
186
|
return {
|
|
181
|
-
id: `
|
|
187
|
+
id: `runtime_generator_check_${manifest.surface}`,
|
|
188
|
+
kind: runtimeKindBySurface[manifest.surface] || manifest.surface,
|
|
182
189
|
type: manifest.surface,
|
|
183
190
|
projection: `proj_generator_check_${manifest.surface}`,
|
|
184
191
|
generator: {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
maintainedProofMetadata,
|
|
7
7
|
relatedCapabilitiesForEntity,
|
|
8
8
|
relatedProjectionsForCapability,
|
|
9
|
-
|
|
9
|
+
relatedProjectionsForWidget,
|
|
10
10
|
relatedProjectionsForEntity,
|
|
11
11
|
stableSortedStrings,
|
|
12
12
|
summarizeById,
|
|
@@ -79,7 +79,7 @@ function changedIdsForDiffSections(diff) {
|
|
|
79
79
|
"rules",
|
|
80
80
|
"workflows",
|
|
81
81
|
"projections",
|
|
82
|
-
"
|
|
82
|
+
"widgets",
|
|
83
83
|
"journeys",
|
|
84
84
|
"verifications",
|
|
85
85
|
"domains",
|
|
@@ -100,16 +100,16 @@ function collectAffectedProjectionIds(graph, baselineGraph, diff) {
|
|
|
100
100
|
const changedCapabilities = stableSortedStrings((diff.capabilities || []).map((entry) => entry.id));
|
|
101
101
|
const changedEntities = stableSortedStrings((diff.entities || []).map((entry) => entry.id));
|
|
102
102
|
const changedProjections = stableSortedStrings((diff.projections || []).map((entry) => entry.id));
|
|
103
|
-
const
|
|
103
|
+
const changedWidgetProjections = stableSortedStrings((diff.widgets || []).flatMap((entry) => {
|
|
104
104
|
if (entry.classification === "additive") {
|
|
105
|
-
return
|
|
105
|
+
return relatedProjectionsForWidget(graph, entry.id);
|
|
106
106
|
}
|
|
107
107
|
if (entry.classification === "removed") {
|
|
108
|
-
return
|
|
108
|
+
return relatedProjectionsForWidget(baselineGraph, entry.id);
|
|
109
109
|
}
|
|
110
110
|
return [
|
|
111
|
-
...
|
|
112
|
-
...
|
|
111
|
+
...relatedProjectionsForWidget(graph, entry.id),
|
|
112
|
+
...relatedProjectionsForWidget(baselineGraph, entry.id)
|
|
113
113
|
];
|
|
114
114
|
}));
|
|
115
115
|
|
|
@@ -117,7 +117,7 @@ function collectAffectedProjectionIds(graph, baselineGraph, diff) {
|
|
|
117
117
|
...changedProjections,
|
|
118
118
|
...changedCapabilities.flatMap((id) => relatedProjectionsForCapability(graph, id)),
|
|
119
119
|
...changedEntities.flatMap((id) => relatedProjectionsForEntity(graph, id)),
|
|
120
|
-
...
|
|
120
|
+
...changedWidgetProjections
|
|
121
121
|
]);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -183,7 +183,7 @@ export function generateContextDiff(graph, options = {}) {
|
|
|
183
183
|
rules: diffMaps(normalizeTargetMap(graph, "rule"), normalizeTargetMap(baselineGraph, "rule")),
|
|
184
184
|
workflows: diffMaps(normalizeTargetMap(graph, "workflow"), normalizeTargetMap(baselineGraph, "workflow")),
|
|
185
185
|
projections: diffMaps(normalizeTargetMap(graph, "projection"), normalizeTargetMap(baselineGraph, "projection")),
|
|
186
|
-
|
|
186
|
+
widgets: diffMaps(normalizeTargetMap(graph, "widget"), normalizeTargetMap(baselineGraph, "widget")),
|
|
187
187
|
journeys: diffMaps(normalizeTargetMap(graph, "journey"), normalizeTargetMap(baselineGraph, "journey")),
|
|
188
188
|
verifications: diffMaps(normalizeTargetMap(graph, "verification"), normalizeTargetMap(baselineGraph, "verification")),
|
|
189
189
|
domains: diffMaps(normalizeTargetMap(graph, "domain"), normalizeTargetMap(baselineGraph, "domain")),
|
|
@@ -9,14 +9,15 @@ import {
|
|
|
9
9
|
summarizeDomain
|
|
10
10
|
} from "./shared.js";
|
|
11
11
|
|
|
12
|
-
function
|
|
13
|
-
const
|
|
12
|
+
function projectionTypesFromProjections(projections) {
|
|
13
|
+
const projectionTypes = new Set();
|
|
14
14
|
for (const projection of projections) {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const projectionType = projection?.type || projection?.platform;
|
|
16
|
+
if (projectionType) {
|
|
17
|
+
projectionTypes.add(projectionType);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
return [...
|
|
20
|
+
return [...projectionTypes].sort();
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function generateDomainCoverage(graph, options = {}) {
|
|
@@ -45,17 +46,17 @@ export function generateDomainCoverage(graph, options = {}) {
|
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
+
const projectionTypes = projectionTypesFromProjections(projectionStatements);
|
|
49
50
|
const matrix = {};
|
|
50
51
|
for (const capabilityId of capabilities) {
|
|
51
52
|
matrix[capabilityId] = {};
|
|
52
|
-
for (const
|
|
53
|
+
for (const projectionType of projectionTypes) {
|
|
53
54
|
const realized = projectionStatements.some(
|
|
54
55
|
(projection) =>
|
|
55
|
-
projection.platform ===
|
|
56
|
+
(projection.type || projection.platform) === projectionType &&
|
|
56
57
|
(projection.realizes || []).some((entry) => entry.id === capabilityId)
|
|
57
58
|
);
|
|
58
|
-
matrix[capabilityId][
|
|
59
|
+
matrix[capabilityId][projectionType] = realized;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -64,7 +65,7 @@ export function generateDomainCoverage(graph, options = {}) {
|
|
|
64
65
|
version: 1,
|
|
65
66
|
focus: { kind: "domain", id: domain.id },
|
|
66
67
|
summary: summarizeDomain(domain),
|
|
67
|
-
|
|
68
|
+
projectionTypes,
|
|
68
69
|
capabilities,
|
|
69
70
|
entities,
|
|
70
71
|
rules,
|
|
@@ -94,13 +94,13 @@ export function generateDomainPage(graph, options = {}) {
|
|
|
94
94
|
lines.push(renderMembersSection(graph, "Verifications", relatedVerificationsForDomain(graph, domain.id), "verification"));
|
|
95
95
|
lines.push("");
|
|
96
96
|
|
|
97
|
-
if (coverage.
|
|
98
|
-
lines.push("## Per-
|
|
97
|
+
if (coverage.projectionTypes.length) {
|
|
98
|
+
lines.push("## Per-type coverage");
|
|
99
99
|
lines.push("");
|
|
100
|
-
const headers = ["Capability", ...coverage.
|
|
100
|
+
const headers = ["Capability", ...coverage.projectionTypes];
|
|
101
101
|
const rows = coverage.capabilities.map((capabilityId) => {
|
|
102
|
-
const cells = coverage.
|
|
103
|
-
coverage.coverage_matrix[capabilityId]?.[
|
|
102
|
+
const cells = coverage.projectionTypes.map((projectionType) =>
|
|
103
|
+
coverage.coverage_matrix[capabilityId]?.[projectionType] ? "✓" : "—"
|
|
104
104
|
);
|
|
105
105
|
return [`\`${capabilityId}\``, ...cells];
|
|
106
106
|
});
|
|
@@ -108,7 +108,7 @@ export function generateDomainPage(graph, options = {}) {
|
|
|
108
108
|
lines.push("");
|
|
109
109
|
lines.push(`Projections involved: ${coverage.projections.map((id) => `\`${id}\``).join(", ") || "none"}`);
|
|
110
110
|
} else {
|
|
111
|
-
lines.push("## Per-
|
|
111
|
+
lines.push("## Per-type coverage");
|
|
112
112
|
lines.push("");
|
|
113
113
|
lines.push("No projections currently realize capabilities in this domain.");
|
|
114
114
|
}
|
|
@@ -168,7 +168,7 @@ function summarizeStatement(statement) {
|
|
|
168
168
|
reviewBoundary: reviewBoundaryForEntityPolicy(statement),
|
|
169
169
|
ownership_boundary: defaultOwnershipBoundary()
|
|
170
170
|
};
|
|
171
|
-
case "
|
|
171
|
+
case "widget":
|
|
172
172
|
return summarizeComponent(statement);
|
|
173
173
|
case "shape":
|
|
174
174
|
return {
|
|
@@ -396,8 +396,12 @@ export function relatedProjectionsForShape(graph, shapeId) {
|
|
|
396
396
|
return stableSortedStrings([...directProjectionIds, ...viaCapabilities]);
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
export function widgetById(graph, widgetId) {
|
|
400
|
+
return (graph?.byKind?.widget || graph?.byKind?.component || []).find((widget) => widget.id === widgetId) || null;
|
|
401
|
+
}
|
|
402
|
+
|
|
399
403
|
export function componentById(graph, componentId) {
|
|
400
|
-
return (graph
|
|
404
|
+
return widgetById(graph, componentId);
|
|
401
405
|
}
|
|
402
406
|
|
|
403
407
|
function projectionById(graph, projectionId) {
|
|
@@ -430,36 +434,44 @@ function downstreamProjectionIds(graph, projectionIds) {
|
|
|
430
434
|
return stableSortedStrings([...visited]);
|
|
431
435
|
}
|
|
432
436
|
|
|
433
|
-
export function
|
|
437
|
+
export function relatedWidgetsForProjection(graph, projection) {
|
|
434
438
|
const directIds = (projection?.uiComponents || []).map((entry) => entry.component?.id).filter(Boolean);
|
|
435
439
|
const inheritedIds = realizedProjectionIds(projection)
|
|
436
440
|
.map((projectionId) => projectionById(graph, projectionId))
|
|
437
441
|
.filter(Boolean)
|
|
438
|
-
.flatMap((realizedProjection) =>
|
|
442
|
+
.flatMap((realizedProjection) => relatedWidgetsForProjection(graph, realizedProjection));
|
|
439
443
|
return stableSortedStrings([...directIds, ...inheritedIds]);
|
|
440
444
|
}
|
|
441
445
|
|
|
442
|
-
export function
|
|
443
|
-
|
|
446
|
+
export function relatedComponentsForProjection(graph, projection) {
|
|
447
|
+
return relatedWidgetsForProjection(graph, projection);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function relatedShapesForWidget(widget) {
|
|
451
|
+
if (!widget) return [];
|
|
444
452
|
const ids = [
|
|
445
|
-
...(
|
|
446
|
-
...(
|
|
453
|
+
...(widget.events || []).map((event) => event.shape?.id),
|
|
454
|
+
...(widget.lookups || [])
|
|
447
455
|
.filter((lookup) => lookup?.target?.kind === "shape" || String(lookup?.id || "").startsWith("shape_"))
|
|
448
456
|
.map((lookup) => lookup.id),
|
|
449
|
-
...(
|
|
457
|
+
...(widget.dependencies || [])
|
|
450
458
|
.filter((dependency) => referenceKind(dependency) === "shape")
|
|
451
459
|
.map((dependency) => dependency.id)
|
|
452
460
|
];
|
|
453
461
|
return stableSortedStrings(ids);
|
|
454
462
|
}
|
|
455
463
|
|
|
456
|
-
export function
|
|
457
|
-
|
|
458
|
-
|
|
464
|
+
export function relatedShapesForComponent(component) {
|
|
465
|
+
return relatedShapesForWidget(component);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function relatedProjectionsForWidget(graph, widgetId) {
|
|
469
|
+
const widget = widgetById(graph, widgetId);
|
|
470
|
+
if (!widget) return [];
|
|
459
471
|
const explicitProjectionIds = stableSortedStrings((graph?.byKind?.projection || [])
|
|
460
|
-
.filter((projection) => (projection.uiComponents || []).some((entry) => entry.component?.id ===
|
|
472
|
+
.filter((projection) => (projection.uiComponents || []).some((entry) => entry.component?.id === widgetId))
|
|
461
473
|
.map((projection) => projection.id));
|
|
462
|
-
const dependencyProjectionIds = stableSortedStrings((
|
|
474
|
+
const dependencyProjectionIds = stableSortedStrings((widget.dependencies || []).flatMap((dependency) => {
|
|
463
475
|
const kind = referenceKind(dependency);
|
|
464
476
|
if (kind === "projection") {
|
|
465
477
|
return [dependency.id];
|
|
@@ -475,18 +487,22 @@ export function relatedProjectionsForComponent(graph, componentId) {
|
|
|
475
487
|
}
|
|
476
488
|
return [];
|
|
477
489
|
}));
|
|
478
|
-
const
|
|
479
|
-
const
|
|
490
|
+
const widgetPatterns = new Set(widget.patterns || []);
|
|
491
|
+
const widgetRegions = new Set(widget.regions || []);
|
|
480
492
|
const viaUiRegions = stableSortedStrings((graph?.byKind?.projection || [])
|
|
481
493
|
.filter((projection) => (projection.uiScreenRegions || []).some((region) =>
|
|
482
|
-
(region.pattern &&
|
|
483
|
-
(region.region &&
|
|
494
|
+
(region.pattern && widgetPatterns.has(region.pattern)) ||
|
|
495
|
+
(region.region && widgetRegions.has(region.region))
|
|
484
496
|
))
|
|
485
497
|
.map((projection) => projection.id));
|
|
486
498
|
|
|
487
499
|
return downstreamProjectionIds(graph, stableSortedStrings([...explicitProjectionIds, ...dependencyProjectionIds, ...viaUiRegions]));
|
|
488
500
|
}
|
|
489
501
|
|
|
502
|
+
export function relatedProjectionsForComponent(graph, componentId) {
|
|
503
|
+
return relatedProjectionsForWidget(graph, componentId);
|
|
504
|
+
}
|
|
505
|
+
|
|
490
506
|
function referenceKind(reference) {
|
|
491
507
|
if (reference?.target?.kind) {
|
|
492
508
|
return reference.target.kind;
|
|
@@ -539,7 +555,7 @@ export function workspaceInventory(graph) {
|
|
|
539
555
|
journeys: stableSortedStrings((graph.docs || []).filter((doc) => doc.kind === "journey").map((doc) => doc.id)),
|
|
540
556
|
entities: stableSortedStrings((graph.byKind.entity || []).map((item) => item.id)),
|
|
541
557
|
projections: stableSortedStrings((graph.byKind.projection || []).map((item) => item.id)),
|
|
542
|
-
|
|
558
|
+
widgets: stableSortedStrings((graph.byKind.widget || graph.byKind.component || []).map((item) => item.id)),
|
|
543
559
|
verifications: stableSortedStrings((graph.byKind.verification || []).map((item) => item.id)),
|
|
544
560
|
domains: stableSortedStrings((graph.byKind.domain || []).map((item) => item.id)),
|
|
545
561
|
pitches: stableSortedStrings((graph.byKind.pitch || []).map((item) => item.id)),
|
|
@@ -556,7 +572,7 @@ export function ensureContextSelection(options = {}) {
|
|
|
556
572
|
options.capabilityId ? ["capability", options.capabilityId] : null,
|
|
557
573
|
options.workflowId ? ["workflow", options.workflowId] : null,
|
|
558
574
|
options.projectionId ? ["projection", options.projectionId] : null,
|
|
559
|
-
options.componentId ? ["
|
|
575
|
+
(options.widgetId || options.componentId) ? ["widget", options.widgetId || options.componentId] : null,
|
|
560
576
|
options.entityId ? ["entity", options.entityId] : null,
|
|
561
577
|
options.journeyId ? ["journey", options.journeyId] : null,
|
|
562
578
|
options.surfaceId ? ["surface", options.surfaceId] : null,
|
|
@@ -571,7 +587,7 @@ export function ensureContextSelection(options = {}) {
|
|
|
571
587
|
|
|
572
588
|
if (selectors.length !== 1) {
|
|
573
589
|
throw new Error(
|
|
574
|
-
"Context selection requires exactly one of --capability, --workflow, --projection, --
|
|
590
|
+
"Context selection requires exactly one of --capability, --workflow, --projection, --widget, --entity, --journey, --surface, --domain, --pitch, --requirement, --acceptance, --task, --bug, or --document"
|
|
575
591
|
);
|
|
576
592
|
}
|
|
577
593
|
|