@topogram/cli 0.3.53 → 0.3.55
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/package.json +1 -1
- package/src/adoption/review-groups.js +3 -3
- package/src/agent-ops/query-builders.js +34 -34
- package/src/archive/schema.js +1 -1
- package/src/cli.js +177 -22
- package/src/generator/adapters.js +2 -2
- package/src/generator/context/domain-coverage.js +2 -2
- package/src/generator/context/shared.js +6 -22
- package/src/generator/context/slice.js +10 -10
- package/src/generator/docs.js +1 -1
- package/src/generator/registry.js +1 -1
- package/src/generator/runtime/app-bundle.js +7 -5
- package/src/generator/runtime/compile-check.js +3 -1
- package/src/generator/runtime/deployment.js +3 -1
- package/src/generator/runtime/environment.js +22 -19
- package/src/generator/runtime/shared.js +7 -7
- package/src/generator/surfaces/contracts.js +1 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
- package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
- package/src/generator/surfaces/databases/shared.js +3 -3
- package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
- package/src/generator/surfaces/services/persistence-wiring.js +1 -1
- package/src/generator/surfaces/services/server-contract.js +1 -1
- package/src/generator/surfaces/shared.js +1 -1
- package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
- package/src/generator/widget-conformance.js +6 -6
- package/src/generator/widgets.js +1 -1
- package/src/generator-policy.js +10 -10
- package/src/import/core/runner.js +60 -50
- package/src/project-config.js +5 -42
- package/src/proofs/contract-audit.js +1 -1
- package/src/realization/backend/build-backend-runtime-realization.js +3 -3
- package/src/realization/ui/build-ui-shared-realization.js +9 -9
- package/src/realization/ui/build-web-realization.js +3 -3
- package/src/reconcile/journeys.js +1 -1
- package/src/resolver/enrich/widget.js +2 -2
- package/src/resolver/index.js +4 -23
- package/src/validator/index.js +10 -10
- package/src/workflows.js +49 -49
package/src/generator-policy.js
CHANGED
|
@@ -31,7 +31,7 @@ export const GENERATOR_POLICY_FILE = "topogram.generator-policy.json";
|
|
|
31
31
|
* @property {string|null} path
|
|
32
32
|
* @property {string|null} suggestedFix
|
|
33
33
|
* @property {string|null} step
|
|
34
|
-
* @property {string|null} [
|
|
34
|
+
* @property {string|null} [runtimeId]
|
|
35
35
|
* @property {string|null} [generatorId]
|
|
36
36
|
* @property {string|null} [packageName]
|
|
37
37
|
* @property {string|null} [version]
|
|
@@ -39,8 +39,8 @@ export const GENERATOR_POLICY_FILE = "topogram.generator-policy.json";
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* @typedef {Object} PackageGeneratorBinding
|
|
42
|
-
* @property {string}
|
|
43
|
-
* @property {string}
|
|
42
|
+
* @property {string} runtimeId
|
|
43
|
+
* @property {string} runtimeKind
|
|
44
44
|
* @property {string} projection
|
|
45
45
|
* @property {string} generatorId
|
|
46
46
|
* @property {string} version
|
|
@@ -59,7 +59,7 @@ function generatorPolicyDiagnostic(input) {
|
|
|
59
59
|
path: typeof input.path === "string" ? input.path : null,
|
|
60
60
|
suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null,
|
|
61
61
|
step: typeof input.step === "string" ? input.step : null,
|
|
62
|
-
|
|
62
|
+
runtimeId: typeof input.runtimeId === "string" ? input.runtimeId : null,
|
|
63
63
|
generatorId: typeof input.generatorId === "string" ? input.generatorId : null,
|
|
64
64
|
packageName: typeof input.packageName === "string" ? input.packageName : null,
|
|
65
65
|
version: typeof input.version === "string" ? input.version : null
|
|
@@ -179,8 +179,8 @@ export function packageBackedGeneratorBindings(projectConfig) {
|
|
|
179
179
|
return runtimes
|
|
180
180
|
.filter((runtime) => typeof runtime?.generator?.package === "string" && runtime.generator.package.length > 0)
|
|
181
181
|
.map((runtime) => ({
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
runtimeId: String(runtime.id || "unknown"),
|
|
183
|
+
runtimeKind: String(runtime.kind || "unknown"),
|
|
184
184
|
projection: String(runtime.projection || "unknown"),
|
|
185
185
|
generatorId: String(runtime.generator.id || "unknown"),
|
|
186
186
|
version: String(runtime.generator.version || "unknown"),
|
|
@@ -263,11 +263,11 @@ export function generatorPolicyDiagnosticsForBindings(policyInfo, bindings, step
|
|
|
263
263
|
const allowedPackages = policy.allowedPackages.join(", ") || "(none)";
|
|
264
264
|
diagnostics.push(generatorPolicyDiagnostic({
|
|
265
265
|
code: "generator_package_denied",
|
|
266
|
-
message: `
|
|
266
|
+
message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is not allowed by ${GENERATOR_POLICY_FILE}.`,
|
|
267
267
|
path: policyInfo.path,
|
|
268
268
|
suggestedFix: `Review '${binding.packageName}', then run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` or add '${scope || binding.packageName}' to ${GENERATOR_POLICY_FILE}.`,
|
|
269
269
|
step,
|
|
270
|
-
|
|
270
|
+
runtimeId: binding.runtimeId,
|
|
271
271
|
generatorId: binding.generatorId,
|
|
272
272
|
packageName: binding.packageName,
|
|
273
273
|
version: binding.version
|
|
@@ -278,11 +278,11 @@ export function generatorPolicyDiagnosticsForBindings(policyInfo, bindings, step
|
|
|
278
278
|
if (pinnedVersion && pinnedVersion !== binding.version) {
|
|
279
279
|
diagnostics.push(generatorPolicyDiagnostic({
|
|
280
280
|
code: "generator_version_mismatch",
|
|
281
|
-
message: `
|
|
281
|
+
message: `Runtime '${binding.runtimeId}' generator '${binding.generatorId}' uses version '${binding.version}', but ${GENERATOR_POLICY_FILE} pins '${binding.packageName}' to '${pinnedVersion}'.`,
|
|
282
282
|
path: policyInfo.path,
|
|
283
283
|
suggestedFix: `Use generator version '${pinnedVersion}', or run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`,
|
|
284
284
|
step,
|
|
285
|
-
|
|
285
|
+
runtimeId: binding.runtimeId,
|
|
286
286
|
generatorId: binding.generatorId,
|
|
287
287
|
packageName: binding.packageName,
|
|
288
288
|
version: binding.version
|
|
@@ -126,12 +126,19 @@ function capabilityHintsForScreen(screen) {
|
|
|
126
126
|
return rawHints.map(normalizeCapabilityHint).filter(Boolean);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
function uiWidgetCandidates(candidates) {
|
|
130
|
+
return [
|
|
131
|
+
...(Array.isArray(candidates?.widgets) ? candidates.widgets : []),
|
|
132
|
+
...(Array.isArray(candidates?.components) ? candidates.components : [])
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function inferredDataSourceForWidget(widget, allCandidates) {
|
|
137
|
+
if (widget.data_source) {
|
|
138
|
+
return widget.data_source;
|
|
132
139
|
}
|
|
133
140
|
const capabilityIds = importedApiCapabilityIds(allCandidates);
|
|
134
|
-
const screenStem = String(
|
|
141
|
+
const screenStem = String(widget.screen_id || "")
|
|
135
142
|
.replace(/_(list|index|table|grid|results)$/, "")
|
|
136
143
|
.replace(/^list_/, "");
|
|
137
144
|
return capabilityIds.find((id) => /^cap_(list|get)_/.test(id) && id.includes(screenStem)) ||
|
|
@@ -139,22 +146,22 @@ function inferredDataSourceForComponent(component, allCandidates) {
|
|
|
139
146
|
null;
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
function
|
|
149
|
+
function deriveUiWidgetCandidates(candidates) {
|
|
143
150
|
const screens = candidates.screens || [];
|
|
144
151
|
const actions = candidates.actions || [];
|
|
145
152
|
const presentations = [...new Set(actions
|
|
146
153
|
.filter((entry) => entry.kind === "ui_presentation")
|
|
147
154
|
.map((entry) => entry.presentation)
|
|
148
155
|
.filter(Boolean))].sort();
|
|
149
|
-
const
|
|
156
|
+
const widgetScreens = screens.filter((screen) => ["list", "dashboard", "analytics", "report", "feed", "inbox"].includes(screen.screen_kind));
|
|
150
157
|
|
|
151
|
-
return
|
|
158
|
+
return widgetScreens.map((screen) => {
|
|
152
159
|
const pattern = collectionPatternFromPresentations(presentations);
|
|
153
|
-
const
|
|
160
|
+
const widgetStem = idHintify(`${screen.id_hint}_results`);
|
|
154
161
|
const loadCapability = loadCapabilityForScreen(screen);
|
|
155
162
|
return makeCandidateRecord({
|
|
156
163
|
kind: "widget",
|
|
157
|
-
idHint: `widget_${
|
|
164
|
+
idHint: `widget_${widgetStem}`,
|
|
158
165
|
label: `${screen.label || screen.id_hint} results`,
|
|
159
166
|
confidence: presentations.length > 0 ? "medium" : "low",
|
|
160
167
|
sourceKind: "ui_projection_inference",
|
|
@@ -204,13 +211,13 @@ function normalizeCandidatesForTrack(track, candidates) {
|
|
|
204
211
|
};
|
|
205
212
|
}
|
|
206
213
|
if (track === "ui") {
|
|
207
|
-
const
|
|
208
|
-
const
|
|
214
|
+
const explicitWidgets = uiWidgetCandidates(candidates);
|
|
215
|
+
const derivedWidgets = deriveUiWidgetCandidates(candidates);
|
|
209
216
|
return {
|
|
210
217
|
screens: dedupeCandidateRecords(candidates.screens || [], (record) => record.id_hint),
|
|
211
218
|
routes: dedupeCandidateRecords(candidates.routes || [], (record) => record.id_hint),
|
|
212
219
|
actions: dedupeCandidateRecords(candidates.actions || [], (record) => record.id_hint),
|
|
213
|
-
|
|
220
|
+
widgets: dedupeCandidateRecords([...explicitWidgets, ...derivedWidgets], (record) => record.id_hint),
|
|
214
221
|
stacks: [...new Set(candidates.stacks || [])].sort()
|
|
215
222
|
};
|
|
216
223
|
}
|
|
@@ -289,11 +296,12 @@ function reportMarkdown(track, candidates) {
|
|
|
289
296
|
);
|
|
290
297
|
}
|
|
291
298
|
if (track === "ui") {
|
|
292
|
-
const
|
|
293
|
-
|
|
299
|
+
const widgets = uiWidgetCandidates(candidates);
|
|
300
|
+
const widgetLines = widgets.map((widget) =>
|
|
301
|
+
`- \`${widget.id_hint}\` confidence ${widget.confidence || "unknown"} pattern \`${widget.pattern || widget.inferred_pattern || "unknown"}\` region \`${widget.region || widget.inferred_region || "unknown"}\` evidence ${(widget.evidence || widget.provenance || []).length} missing decisions ${(widget.missing_decisions || []).length}`
|
|
294
302
|
);
|
|
295
303
|
return ensureTrailingNewline(
|
|
296
|
-
`# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${
|
|
304
|
+
`# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${widgets.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Widget Candidates\n\n${widgetLines.length ? widgetLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review candidates under \`topogram/candidates/app/ui/drafts/widgets/**\`.\n- Run \`topogram import plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram widget check <path>\`, and \`topogram widget behavior <path>\`.\n`
|
|
297
305
|
);
|
|
298
306
|
}
|
|
299
307
|
if (track === "verification") {
|
|
@@ -318,60 +326,62 @@ function projectionIdStem(workspaceRoot) {
|
|
|
318
326
|
.replace(/^_+|_+$/g, "") || "imported_app";
|
|
319
327
|
}
|
|
320
328
|
|
|
321
|
-
function
|
|
322
|
-
return `${String(
|
|
329
|
+
function widgetCandidateFileName(widget) {
|
|
330
|
+
return `${String(widget.id_hint || "widget")
|
|
323
331
|
.replace(/^component_/, "")
|
|
324
332
|
.replace(/_/g, "-")}.tg`;
|
|
325
333
|
}
|
|
326
334
|
|
|
327
|
-
function
|
|
328
|
-
const evidenceCount = (
|
|
329
|
-
const missingDecisions =
|
|
335
|
+
function renderWidgetCandidate(widget) {
|
|
336
|
+
const evidenceCount = (widget.evidence || widget.provenance || []).length;
|
|
337
|
+
const missingDecisions = widget.missing_decisions || [
|
|
330
338
|
"confirm widget reuse boundary",
|
|
331
339
|
"confirm prop names and data source",
|
|
332
340
|
"confirm events and behavior"
|
|
333
341
|
];
|
|
334
|
-
return `widget ${
|
|
335
|
-
# Import metadata: confidence ${
|
|
342
|
+
return `widget ${widget.id_hint} {
|
|
343
|
+
# Import metadata: confidence ${widget.confidence || "unknown"}; evidence ${evidenceCount}; inferred pattern ${widget.pattern || widget.inferred_pattern || "search_results"}; inferred region ${widget.region || widget.inferred_region || "results"}.
|
|
336
344
|
# Missing decisions: ${missingDecisions.join("; ")}.
|
|
337
|
-
name "${
|
|
345
|
+
name "${widget.label || widget.id_hint}"
|
|
338
346
|
description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
|
|
339
347
|
category collection
|
|
340
348
|
props {
|
|
341
|
-
${
|
|
349
|
+
${widget.data_prop || "rows"} array required
|
|
342
350
|
}
|
|
343
|
-
patterns [${
|
|
344
|
-
regions [${
|
|
351
|
+
patterns [${widget.pattern || "search_results"}]
|
|
352
|
+
regions [${widget.region || "results"}]
|
|
345
353
|
status proposed
|
|
346
354
|
}
|
|
347
355
|
`;
|
|
348
356
|
}
|
|
349
357
|
|
|
350
|
-
function
|
|
351
|
-
return
|
|
352
|
-
.filter((
|
|
353
|
-
.map((
|
|
354
|
-
const dataSource =
|
|
358
|
+
function uiWidgetLinesForCandidates(widgetCandidates, allCandidates) {
|
|
359
|
+
return widgetCandidates
|
|
360
|
+
.filter((widget) => widget.screen_id && widget.region && widget.id_hint)
|
|
361
|
+
.map((widget) => {
|
|
362
|
+
const dataSource = inferredDataSourceForWidget(widget, allCandidates);
|
|
355
363
|
const dataBinding = dataSource
|
|
356
|
-
? ` data ${
|
|
364
|
+
? ` data ${widget.data_prop || "rows"} from ${dataSource}`
|
|
357
365
|
: "";
|
|
358
|
-
return ` screen ${
|
|
366
|
+
return ` screen ${widget.screen_id} region ${widget.region} widget ${widget.id_hint}${dataBinding}`;
|
|
359
367
|
});
|
|
360
368
|
}
|
|
361
369
|
|
|
362
|
-
function
|
|
363
|
-
if (!uiCandidates
|
|
370
|
+
function enrichUiWidgetDataSources(uiCandidates, allCandidates) {
|
|
371
|
+
if (!uiCandidates) {
|
|
364
372
|
return uiCandidates;
|
|
365
373
|
}
|
|
374
|
+
const widgets = uiWidgetCandidates(uiCandidates);
|
|
375
|
+
const { components, ...canonicalCandidates } = uiCandidates;
|
|
366
376
|
return {
|
|
367
|
-
...
|
|
368
|
-
|
|
369
|
-
const dataSource =
|
|
370
|
-
const dataProp =
|
|
377
|
+
...canonicalCandidates,
|
|
378
|
+
widgets: widgets.map((widget) => {
|
|
379
|
+
const dataSource = inferredDataSourceForWidget(widget, allCandidates);
|
|
380
|
+
const dataProp = widget.data_prop || "rows";
|
|
371
381
|
return {
|
|
372
|
-
...
|
|
373
|
-
data_source:
|
|
374
|
-
inferred_props: (
|
|
382
|
+
...widget,
|
|
383
|
+
data_source: widget.data_source || dataSource,
|
|
384
|
+
inferred_props: (widget.inferred_props || []).map((prop) =>
|
|
375
385
|
prop.name === dataProp ? { ...prop, source: prop.source || dataSource } : prop
|
|
376
386
|
)
|
|
377
387
|
};
|
|
@@ -384,7 +394,7 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
|
|
|
384
394
|
const screens = [...(ui.screens || [])].sort((a, b) => String(a.route_path || "").localeCompare(String(b.route_path || "")) || a.id_hint.localeCompare(b.id_hint));
|
|
385
395
|
const routes = new Map((ui.routes || []).map((route) => [route.screen_id, route.path]));
|
|
386
396
|
const actions = ui.actions || [];
|
|
387
|
-
const
|
|
397
|
+
const widgetCandidates = [...uiWidgetCandidates(ui)].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
|
|
388
398
|
const shell = actions.find((entry) => entry.kind === "ui_shell")?.shell_kind || "topbar";
|
|
389
399
|
const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "navigation").map((entry) => entry.navigation_pattern));
|
|
390
400
|
const presentations = uniqueSorted(actions.filter((entry) => entry.kind === "ui_presentation").map((entry) => entry.presentation));
|
|
@@ -486,7 +496,7 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
|
|
|
486
496
|
}
|
|
487
497
|
}
|
|
488
498
|
}
|
|
489
|
-
const
|
|
499
|
+
const uiWidgetLines = uiWidgetLinesForCandidates(widgetCandidates, allCandidates);
|
|
490
500
|
|
|
491
501
|
const uiSharedDraft = `projection proj_ui_contract_imported_${stem} {
|
|
492
502
|
name "Imported UI Contract Draft"
|
|
@@ -525,7 +535,7 @@ ${uiCollectionsLines.length > 0 ? ` collection_views {\n${uiCollectionsLines.jo
|
|
|
525
535
|
${uiNavigationLines.join("\n")}
|
|
526
536
|
}
|
|
527
537
|
|
|
528
|
-
${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${
|
|
538
|
+
${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${uiWidgetLines.length > 0 ? ` widget_bindings {\n${uiWidgetLines.join("\n")}\n }\n\n` : ""} status proposed
|
|
529
539
|
}
|
|
530
540
|
`;
|
|
531
541
|
|
|
@@ -594,7 +604,7 @@ ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : "
|
|
|
594
604
|
|
|
595
605
|
- Draft UI contract projection: \`candidates/app/ui/drafts/proj-ui-contract.tg\`
|
|
596
606
|
- Draft web surface projection: \`candidates/app/ui/drafts/proj-web-surface.tg\`
|
|
597
|
-
- Draft widget candidates: ${
|
|
607
|
+
- Draft widget candidates: ${widgetCandidates.length}
|
|
598
608
|
- Imported screens: ${screens.length}
|
|
599
609
|
- Imported routes: ${(ui.routes || []).length}
|
|
600
610
|
- Imported UI actions/presentations: ${actions.length}
|
|
@@ -616,8 +626,8 @@ ${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : "
|
|
|
616
626
|
"candidates/app/ui/drafts/proj-web-surface.tg": ensureTrailingNewline(uiWebDraft),
|
|
617
627
|
"candidates/app/ui/drafts/README.md": ensureTrailingNewline(coverage)
|
|
618
628
|
};
|
|
619
|
-
for (const
|
|
620
|
-
files[`candidates/app/ui/drafts/widgets/${
|
|
629
|
+
for (const widget of widgetCandidates) {
|
|
630
|
+
files[`candidates/app/ui/drafts/widgets/${widgetCandidateFileName(widget)}`] = ensureTrailingNewline(renderWidgetCandidate(widget));
|
|
621
631
|
}
|
|
622
632
|
return files;
|
|
623
633
|
}
|
|
@@ -656,7 +666,7 @@ export function runImportApp(inputPath, options = {}) {
|
|
|
656
666
|
}
|
|
657
667
|
|
|
658
668
|
if (candidates.ui) {
|
|
659
|
-
candidates.ui =
|
|
669
|
+
candidates.ui = enrichUiWidgetDataSources(candidates.ui, candidates);
|
|
660
670
|
files["candidates/app/ui/candidates.json"] = `${JSON.stringify(candidates.ui, null, 2)}\n`;
|
|
661
671
|
files["candidates/app/ui/report.md"] = reportMarkdown("ui", candidates.ui);
|
|
662
672
|
Object.assign(files, draftUiProjectionFiles(context, candidates.ui, candidates));
|
|
@@ -676,7 +686,7 @@ export function runImportApp(inputPath, options = {}) {
|
|
|
676
686
|
files["candidates/app/findings.json"] = `${JSON.stringify(findings, null, 2)}\n`;
|
|
677
687
|
files["candidates/app/candidates.json"] = `${JSON.stringify(candidates, null, 2)}\n`;
|
|
678
688
|
files["candidates/app/report.md"] = ensureTrailingNewline(
|
|
679
|
-
`# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities?.length || 0}\n- Enums: ${candidates.db?.enums?.length || 0}\n- Relations: ${candidates.db?.relations?.length || 0}\n\n## API\n\n- Capabilities: ${candidates.api?.capabilities?.length || 0}\n- Routes: ${candidates.api?.routes?.length || 0}\n- Stacks: ${candidates.api?.stacks?.length ? candidates.api.stacks.join(", ") : "none"}\n\n## UI\n\n- Screens: ${candidates.ui?.screens?.length || 0}\n- Routes: ${candidates.ui?.routes?.length || 0}\n- Actions: ${candidates.ui?.actions?.length || 0}\n- Widgets: ${candidates.ui
|
|
689
|
+
`# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities?.length || 0}\n- Enums: ${candidates.db?.enums?.length || 0}\n- Relations: ${candidates.db?.relations?.length || 0}\n\n## API\n\n- Capabilities: ${candidates.api?.capabilities?.length || 0}\n- Routes: ${candidates.api?.routes?.length || 0}\n- Stacks: ${candidates.api?.stacks?.length ? candidates.api.stacks.join(", ") : "none"}\n\n## UI\n\n- Screens: ${candidates.ui?.screens?.length || 0}\n- Routes: ${candidates.ui?.routes?.length || 0}\n- Actions: ${candidates.ui?.actions?.length || 0}\n- Widgets: ${uiWidgetCandidates(candidates.ui).length}\n- Stacks: ${candidates.ui?.stacks?.length ? candidates.ui.stacks.join(", ") : "none"}\n\n## Workflows\n\n- Workflows: ${candidates.workflows?.workflows?.length || 0}\n- States: ${candidates.workflows?.workflow_states?.length || 0}\n- Transitions: ${candidates.workflows?.workflow_transitions?.length || 0}\n\n## Verification\n\n- Verifications: ${candidates.verification?.verifications?.length || 0}\n- Scenarios: ${candidates.verification?.scenarios?.length || 0}\n- Frameworks: ${candidates.verification?.frameworks?.length ? candidates.verification.frameworks.join(", ") : "none"}\n- Scripts: ${candidates.verification?.scripts?.length || 0}\n`
|
|
680
690
|
);
|
|
681
691
|
|
|
682
692
|
return {
|
package/src/project-config.js
CHANGED
|
@@ -26,9 +26,6 @@ import { validateProjectGeneratorPolicy } from "./generator-policy.js";
|
|
|
26
26
|
* @property {number|null} [port]
|
|
27
27
|
* @property {string} [uses_api]
|
|
28
28
|
* @property {string} [uses_database]
|
|
29
|
-
* @property {string} [type] Migration diagnostic only.
|
|
30
|
-
* @property {string} [api] Migration diagnostic only.
|
|
31
|
-
* @property {string} [database] Migration diagnostic only.
|
|
32
29
|
* @property {Record<string, string>} [env]
|
|
33
30
|
*/
|
|
34
31
|
|
|
@@ -36,7 +33,7 @@ import { validateProjectGeneratorPolicy } from "./generator-policy.js";
|
|
|
36
33
|
* @typedef {Object} ProjectConfig
|
|
37
34
|
* @property {string} version
|
|
38
35
|
* @property {Record<string, { path: string, ownership: "generated"|"maintained" }>} outputs
|
|
39
|
-
* @property {{ runtimes: RuntimeTopologyRuntime[]
|
|
36
|
+
* @property {{ runtimes: RuntimeTopologyRuntime[] }} topology
|
|
40
37
|
* @property {{ id?: string, module?: string, export?: string, implementation_module?: string, implementation_export?: string }} [implementation]
|
|
41
38
|
*/
|
|
42
39
|
|
|
@@ -225,40 +222,6 @@ export function defaultProjectConfigForGraph(graph, implementation = null) {
|
|
|
225
222
|
};
|
|
226
223
|
}
|
|
227
224
|
|
|
228
|
-
/**
|
|
229
|
-
* @param {string} kind
|
|
230
|
-
* @returns {string}
|
|
231
|
-
*/
|
|
232
|
-
function legacyRuntimeType(kind) {
|
|
233
|
-
if (kind === "api_service") return "api";
|
|
234
|
-
if (kind === "web_surface") return "web";
|
|
235
|
-
if (kind === "ios_surface" || kind === "android_surface") return "native";
|
|
236
|
-
return kind;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* @param {any} config
|
|
241
|
-
* @returns {any}
|
|
242
|
-
*/
|
|
243
|
-
function normalizeProjectConfigRuntimeAliases(config) {
|
|
244
|
-
if (!config?.topology || !Array.isArray(config.topology.runtimes)) {
|
|
245
|
-
return config;
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
...config,
|
|
249
|
-
topology: {
|
|
250
|
-
...config.topology,
|
|
251
|
-
__normalizedRuntimeAliases: true,
|
|
252
|
-
components: config.topology.runtimes.map((/** @type {RuntimeTopologyRuntime} */ runtime) => ({
|
|
253
|
-
...runtime,
|
|
254
|
-
type: legacyRuntimeType(runtime.kind),
|
|
255
|
-
api: runtime.uses_api,
|
|
256
|
-
database: runtime.uses_database
|
|
257
|
-
}))
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
225
|
/**
|
|
263
226
|
* @param {string} root
|
|
264
227
|
* @returns {ProjectConfigInfo|null}
|
|
@@ -270,7 +233,7 @@ export function loadProjectConfig(root) {
|
|
|
270
233
|
}
|
|
271
234
|
return {
|
|
272
235
|
...found,
|
|
273
|
-
config:
|
|
236
|
+
config: found.config,
|
|
274
237
|
compatibility: false
|
|
275
238
|
};
|
|
276
239
|
}
|
|
@@ -290,7 +253,7 @@ export function projectConfigOrDefault(root, graph = null, implementation = null
|
|
|
290
253
|
return null;
|
|
291
254
|
}
|
|
292
255
|
return {
|
|
293
|
-
config:
|
|
256
|
+
config: defaultProjectConfigForGraph(graph, implementation),
|
|
294
257
|
configPath: null,
|
|
295
258
|
configDir: path.dirname(path.resolve(root)),
|
|
296
259
|
compatibility: true
|
|
@@ -485,7 +448,7 @@ export function validateProjectConfig(config, graph = null, options = {}) {
|
|
|
485
448
|
pushError(errors, "topogram.project.json version must be a non-empty string");
|
|
486
449
|
}
|
|
487
450
|
validateOutputConfig(errors, config);
|
|
488
|
-
if (config.topology?.components != null
|
|
451
|
+
if (config.topology?.components != null) {
|
|
489
452
|
pushError(errors, `topogram.project.json ${renameDiagnostic("'topology.components'", "'topology.runtimes'", `"topology": { "runtimes": [] }`)}`);
|
|
490
453
|
}
|
|
491
454
|
if (!config.topology || typeof config.topology !== "object" || !Array.isArray(config.topology.runtimes)) {
|
|
@@ -495,7 +458,7 @@ export function validateProjectConfig(config, graph = null, options = {}) {
|
|
|
495
458
|
for (const component of config.topology.runtimes) {
|
|
496
459
|
validateComponentShape(errors, component, seenIds);
|
|
497
460
|
}
|
|
498
|
-
const generatorPolicy = validateProjectGeneratorPolicy(
|
|
461
|
+
const generatorPolicy = validateProjectGeneratorPolicy(config, options);
|
|
499
462
|
for (const error of generatorPolicy.errors) {
|
|
500
463
|
pushError(errors, error.message, error.loc);
|
|
501
464
|
}
|
|
@@ -117,7 +117,7 @@ function normalizeServerContract(contract) {
|
|
|
117
117
|
return {
|
|
118
118
|
projection: {
|
|
119
119
|
id: contract.projection?.id ?? null,
|
|
120
|
-
|
|
120
|
+
type: contract.projection?.type ?? null
|
|
121
121
|
},
|
|
122
122
|
routes: (contract.routes ?? []).map(normalizeRoute).sort((a, b) => {
|
|
123
123
|
const leftKey = `${a.method}:${a.path}:${a.capabilityId}`;
|
|
@@ -28,8 +28,8 @@ export function getDefaultBackendDbProjection(graph, options = {}) {
|
|
|
28
28
|
return (
|
|
29
29
|
explicit ||
|
|
30
30
|
preferred ||
|
|
31
|
-
candidates.find((projection) => projection.
|
|
32
|
-
candidates.find((projection) => projection.
|
|
31
|
+
candidates.find((projection) => projection.type === "db_contract") ||
|
|
32
|
+
candidates.find((projection) => projection.type === "db_contract") ||
|
|
33
33
|
candidates[0] ||
|
|
34
34
|
null
|
|
35
35
|
);
|
|
@@ -76,7 +76,7 @@ export function buildBackendRuntimeRealization(graph, options = {}) {
|
|
|
76
76
|
repositoryReference,
|
|
77
77
|
dbProjection: {
|
|
78
78
|
id: dbProjection.id,
|
|
79
|
-
|
|
79
|
+
type: dbProjection.type
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
}
|
|
@@ -49,12 +49,12 @@ function ownershipFieldByCapability(graph) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function widgetById(graph, widgetId) {
|
|
52
|
-
return (graph.byKind.widget ||
|
|
52
|
+
return (graph.byKind.widget || []).find((widget) => widget.id === widgetId) || null;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function widgetContractFor(graph, widgetId) {
|
|
56
56
|
const widget = widgetById(graph, widgetId);
|
|
57
|
-
return widget?.widgetContract ||
|
|
57
|
+
return widget?.widgetContract || null;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function summarizeWidgetRef(graph, widgetId) {
|
|
@@ -119,7 +119,7 @@ function buildDesignIntentContract(projection) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export function buildWidgetUsageContract(graph, entry, options = {}) {
|
|
122
|
-
const widgetId = entry.widget?.id ||
|
|
122
|
+
const widgetId = entry.widget?.id || null;
|
|
123
123
|
const contract = widgetId ? widgetContractFor(graph, widgetId) : null;
|
|
124
124
|
const region = options.region || null;
|
|
125
125
|
return {
|
|
@@ -143,7 +143,7 @@ export function buildWidgetUsageContract(graph, entry, options = {}) {
|
|
|
143
143
|
|
|
144
144
|
export function buildWidgetContractMap(graph, widgetUsages) {
|
|
145
145
|
return Object.fromEntries(
|
|
146
|
-
[...new Set(widgetUsages.map((entry) => entry.widget?.id
|
|
146
|
+
[...new Set(widgetUsages.map((entry) => entry.widget?.id).filter(Boolean))]
|
|
147
147
|
.sort()
|
|
148
148
|
.map((widgetId) => [widgetId, widgetContractFor(graph, widgetId)])
|
|
149
149
|
.filter(([, contract]) => contract)
|
|
@@ -202,7 +202,7 @@ function buildUiScreenContract(graph, projection, screen, ownershipFields) {
|
|
|
202
202
|
const actionEntries = (projection.uiActions || []).filter((entry) => entry.screenId === screen.id);
|
|
203
203
|
const lookupEntries = (projection.uiLookups || []).filter((entry) => entry.screenId === screen.id);
|
|
204
204
|
const regionEntries = (projection.uiScreenRegions || []).filter((entry) => entry.screenId === screen.id);
|
|
205
|
-
const widgetEntries = (projection.
|
|
205
|
+
const widgetEntries = (projection.widgetBindings || []).filter((entry) => entry.screenId === screen.id);
|
|
206
206
|
const screenActionIds = new Set(
|
|
207
207
|
[
|
|
208
208
|
screen.primaryAction?.id,
|
|
@@ -305,13 +305,13 @@ export function buildUiSharedRealization(graph, options = {}) {
|
|
|
305
305
|
|
|
306
306
|
if (options.projectionId) {
|
|
307
307
|
const projection = projections[0];
|
|
308
|
-
const widgetUsages = projection.
|
|
308
|
+
const widgetUsages = projection.widgetBindings || [];
|
|
309
309
|
const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
|
|
310
310
|
return {
|
|
311
311
|
projection: {
|
|
312
312
|
id: projection.id,
|
|
313
313
|
name: projection.name || projection.id,
|
|
314
|
-
type: projection.type || projection.
|
|
314
|
+
type: projection.type || projection.type
|
|
315
315
|
},
|
|
316
316
|
realizes: projection.realizes,
|
|
317
317
|
outputs: projection.outputs,
|
|
@@ -325,13 +325,13 @@ export function buildUiSharedRealization(graph, options = {}) {
|
|
|
325
325
|
|
|
326
326
|
const output = {};
|
|
327
327
|
for (const projection of projections) {
|
|
328
|
-
const widgetUsages = projection.
|
|
328
|
+
const widgetUsages = projection.widgetBindings || [];
|
|
329
329
|
const screens = (projection.uiScreens || []).map((screen) => buildUiScreenContract(graph, projection, screen, ownershipFields));
|
|
330
330
|
output[projection.id] = {
|
|
331
331
|
projection: {
|
|
332
332
|
id: projection.id,
|
|
333
333
|
name: projection.name || projection.id,
|
|
334
|
-
type: projection.type || projection.
|
|
334
|
+
type: projection.type || projection.type
|
|
335
335
|
},
|
|
336
336
|
realizes: projection.realizes,
|
|
337
337
|
outputs: projection.outputs,
|
|
@@ -26,7 +26,7 @@ export function buildWebRealization(graph, options = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const projection = getProjection(graph, options.projectionId);
|
|
29
|
-
const projectionType = projection.type || projection.
|
|
29
|
+
const projectionType = projection.type || projection.type;
|
|
30
30
|
const surfaceHints =
|
|
31
31
|
projectionType === "ios_surface" ? projection.uiIos || [] : projection.uiWeb || [];
|
|
32
32
|
const sharedProjection = sharedUiProjectionForWeb(graph, projection);
|
|
@@ -85,13 +85,13 @@ export function buildWebRealization(graph, options = {}) {
|
|
|
85
85
|
projection: {
|
|
86
86
|
id: projection.id,
|
|
87
87
|
name: projection.name || projection.id,
|
|
88
|
-
type: projection.type || projection.
|
|
88
|
+
type: projection.type || projection.type
|
|
89
89
|
},
|
|
90
90
|
uiContract: sharedProjection
|
|
91
91
|
? {
|
|
92
92
|
id: sharedProjection.id,
|
|
93
93
|
name: sharedProjection.name || sharedProjection.id,
|
|
94
|
-
type: sharedProjection.type || sharedProjection.
|
|
94
|
+
type: sharedProjection.type || sharedProjection.type
|
|
95
95
|
}
|
|
96
96
|
: null,
|
|
97
97
|
generatorDefaults: generatorDefaultsMap(projection),
|
|
@@ -40,7 +40,7 @@ function collectJourneyGenerationContext(graph) {
|
|
|
40
40
|
const rules = graph.byKind.rule || [];
|
|
41
41
|
const projections = graph.byKind.projection || [];
|
|
42
42
|
const uiSharedScreens = projections
|
|
43
|
-
.filter((projection) => projection.
|
|
43
|
+
.filter((projection) => projection.type === "ui_contract")
|
|
44
44
|
.flatMap((projection) => (projection.uiScreens || []).map((screen) => ({ ...screen, projectionId: projection.id })));
|
|
45
45
|
const canonicalJourneys = (graph.docs || []).filter((doc) => doc.kind === "journey");
|
|
46
46
|
const coveredEntityIds = new Set(canonicalJourneys.flatMap((doc) => doc.relatedEntities || []));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
// The public enriched shape is `statement.
|
|
1
|
+
// Widget enrichment is orchestrated from ../index.js in this split.
|
|
2
|
+
// The public enriched shape is `statement.widgetContract`.
|
package/src/resolver/index.js
CHANGED
|
@@ -509,7 +509,7 @@ function buildProjectionPlan(statement) {
|
|
|
509
509
|
designTokens: statement.uiDesign,
|
|
510
510
|
navigation: statement.uiNavigation,
|
|
511
511
|
screenRegions: statement.uiScreenRegions,
|
|
512
|
-
widgetBindings: statement.
|
|
512
|
+
widgetBindings: statement.widgetBindings,
|
|
513
513
|
tables: statement.dbTables,
|
|
514
514
|
columns: statement.dbColumns,
|
|
515
515
|
keys: statement.dbKeys,
|
|
@@ -536,7 +536,6 @@ function buildProjectionPlan(statement) {
|
|
|
536
536
|
uiRoutes: statement.uiRoutes,
|
|
537
537
|
uiWeb: statement.uiWeb,
|
|
538
538
|
uiDesign: statement.uiDesign,
|
|
539
|
-
uiComponents: statement.uiComponents,
|
|
540
539
|
dbTables: statement.dbTables,
|
|
541
540
|
dbColumns: statement.dbColumns,
|
|
542
541
|
dbKeys: statement.dbKeys,
|
|
@@ -1361,8 +1360,7 @@ function parseProjectionUiScreenRegionsBlock(statement) {
|
|
|
1361
1360
|
});
|
|
1362
1361
|
}
|
|
1363
1362
|
|
|
1364
|
-
function parseProjectionUiComponentsBlock(statement, registry
|
|
1365
|
-
const includeComponentAlias = options.includeComponentAlias !== false;
|
|
1363
|
+
function parseProjectionUiComponentsBlock(statement, registry) {
|
|
1366
1364
|
return blockEntries(getFieldValue(statement, "widget_bindings")).map((entry) => {
|
|
1367
1365
|
const dataBindings = [];
|
|
1368
1366
|
const eventBindings = [];
|
|
@@ -1421,11 +1419,6 @@ function parseProjectionUiComponentsBlock(statement, registry, options = {}) {
|
|
|
1421
1419
|
raw: normalizeSequence(entry.items),
|
|
1422
1420
|
loc: entry.loc
|
|
1423
1421
|
};
|
|
1424
|
-
if (includeComponentAlias) {
|
|
1425
|
-
// Internal compatibility for existing generator adapters during the
|
|
1426
|
-
// coordinated public DSL rename. Public contracts should expose widget.
|
|
1427
|
-
binding.component = widgetRef;
|
|
1428
|
-
}
|
|
1429
1422
|
return binding;
|
|
1430
1423
|
});
|
|
1431
1424
|
}
|
|
@@ -1897,9 +1890,6 @@ export function normalizeStatement(statement, registry) {
|
|
|
1897
1890
|
return {
|
|
1898
1891
|
...base,
|
|
1899
1892
|
type: symbolValue(getFieldValue(statement, "type")),
|
|
1900
|
-
// Internal compatibility for existing generator adapters during the
|
|
1901
|
-
// coordinated public DSL rename. Public contracts should expose type.
|
|
1902
|
-
platform: symbolValue(getFieldValue(statement, "type")),
|
|
1903
1893
|
realizes: resolveReferenceList(registry, getFieldValue(statement, "realizes")),
|
|
1904
1894
|
outputs: symbolValues(getFieldValue(statement, "outputs")),
|
|
1905
1895
|
endpoints: parseProjectionHttpBlock(statement, registry),
|
|
@@ -1952,8 +1942,7 @@ export function normalizeStatement(statement, registry) {
|
|
|
1952
1942
|
navigation: parseProjectionUiNavigationBlock(statement),
|
|
1953
1943
|
uiScreenRegions: parseProjectionUiScreenRegionsBlock(statement),
|
|
1954
1944
|
screenRegions: parseProjectionUiScreenRegionsBlock(statement),
|
|
1955
|
-
|
|
1956
|
-
widgetBindings: parseProjectionUiComponentsBlock(statement, registry, { includeComponentAlias: false }),
|
|
1945
|
+
widgetBindings: parseProjectionUiComponentsBlock(statement, registry),
|
|
1957
1946
|
dbTables: parseProjectionDbTablesBlock(statement, registry),
|
|
1958
1947
|
tables: parseProjectionDbTablesBlock(statement, registry),
|
|
1959
1948
|
dbColumns: parseProjectionDbColumnsBlock(statement, registry),
|
|
@@ -2348,10 +2337,7 @@ export function resolveWorkspace(workspaceAst) {
|
|
|
2348
2337
|
case "widget":
|
|
2349
2338
|
return {
|
|
2350
2339
|
...statement,
|
|
2351
|
-
widgetContract: buildComponentContract(statement)
|
|
2352
|
-
// Internal compatibility for existing generator adapters during the
|
|
2353
|
-
// coordinated public DSL rename. Public contracts should expose widgetContract.
|
|
2354
|
-
componentContract: buildComponentContract(statement)
|
|
2340
|
+
widgetContract: buildComponentContract(statement)
|
|
2355
2341
|
};
|
|
2356
2342
|
case "rule":
|
|
2357
2343
|
return {
|
|
@@ -2497,11 +2483,6 @@ export function resolveWorkspace(workspaceAst) {
|
|
|
2497
2483
|
};
|
|
2498
2484
|
});
|
|
2499
2485
|
const finalByKind = groupBy(finalStatements, (statement) => statement.kind);
|
|
2500
|
-
if (finalByKind.widget && !finalByKind.component) {
|
|
2501
|
-
// Internal compatibility for existing generator/context modules while the
|
|
2502
|
-
// public DSL moves from component to widget in one coordinated release.
|
|
2503
|
-
finalByKind.component = finalByKind.widget;
|
|
2504
|
-
}
|
|
2505
2486
|
|
|
2506
2487
|
const graph = mergeArchivedIntoGraph({
|
|
2507
2488
|
root: workspaceAst.root,
|