@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function stableSortedWidgets(graph) {
|
|
2
|
+
return [...(graph?.byKind?.widget || [])].sort((a, b) => a.id.localeCompare(b.id));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function widgetContract(widget) {
|
|
6
|
+
return widget.widgetContract || {
|
|
7
|
+
type: "ui_widget_contract",
|
|
8
|
+
id: widget.id,
|
|
9
|
+
name: widget.name || widget.id,
|
|
10
|
+
description: widget.description || null,
|
|
11
|
+
category: widget.category || null,
|
|
12
|
+
version: widget.version || null,
|
|
13
|
+
status: widget.status || null,
|
|
14
|
+
props: widget.props || [],
|
|
15
|
+
events: widget.events || [],
|
|
16
|
+
slots: widget.slots || [],
|
|
17
|
+
behavior: widget.behavior || [],
|
|
18
|
+
behaviors: widget.behaviors || [],
|
|
19
|
+
patterns: widget.patterns || [],
|
|
20
|
+
regions: widget.regions || [],
|
|
21
|
+
approvals: widget.approvals || [],
|
|
22
|
+
lookups: widget.lookups || [],
|
|
23
|
+
dependencies: widget.dependencies || []
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function generateUiWidgetContract(graph, options = {}) {
|
|
28
|
+
const widgetId = options.widgetId || options.componentId;
|
|
29
|
+
if (widgetId) {
|
|
30
|
+
const widget = (graph?.byKind?.widget || graph?.byKind?.component || []).find((entry) => entry.id === widgetId);
|
|
31
|
+
if (!widget) {
|
|
32
|
+
throw new Error(`No widget found with id '${widgetId}'`);
|
|
33
|
+
}
|
|
34
|
+
return widgetContract(widget);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return Object.fromEntries(
|
|
38
|
+
stableSortedWidgets(graph).map((widget) => [widget.id, widgetContract(widget)])
|
|
39
|
+
);
|
|
40
|
+
}
|
package/src/generator-policy.js
CHANGED
|
@@ -175,18 +175,16 @@ export function generatorPackageAllowed(policy, packageName) {
|
|
|
175
175
|
* @returns {PackageGeneratorBinding[]}
|
|
176
176
|
*/
|
|
177
177
|
export function packageBackedGeneratorBindings(projectConfig) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
version: String(component.generator.version || "unknown"),
|
|
189
|
-
packageName: String(component.generator.package)
|
|
178
|
+
const runtimes = Array.isArray(projectConfig?.topology?.runtimes) ? projectConfig.topology.runtimes : [];
|
|
179
|
+
return runtimes
|
|
180
|
+
.filter((runtime) => typeof runtime?.generator?.package === "string" && runtime.generator.package.length > 0)
|
|
181
|
+
.map((runtime) => ({
|
|
182
|
+
componentId: String(runtime.id || "unknown"),
|
|
183
|
+
componentType: String(runtime.kind || runtime.type || "unknown"),
|
|
184
|
+
projection: String(runtime.projection || "unknown"),
|
|
185
|
+
generatorId: String(runtime.generator.id || "unknown"),
|
|
186
|
+
version: String(runtime.generator.version || "unknown"),
|
|
187
|
+
packageName: String(runtime.generator.package)
|
|
190
188
|
}));
|
|
191
189
|
}
|
|
192
190
|
|
|
@@ -153,8 +153,8 @@ function deriveUiComponentCandidates(candidates) {
|
|
|
153
153
|
const componentStem = idHintify(`${screen.id_hint}_results`);
|
|
154
154
|
const loadCapability = loadCapabilityForScreen(screen);
|
|
155
155
|
return makeCandidateRecord({
|
|
156
|
-
kind: "
|
|
157
|
-
idHint: `
|
|
156
|
+
kind: "widget",
|
|
157
|
+
idHint: `widget_${componentStem}`,
|
|
158
158
|
label: `${screen.label || screen.id_hint} results`,
|
|
159
159
|
confidence: presentations.length > 0 ? "medium" : "low",
|
|
160
160
|
sourceKind: "ui_projection_inference",
|
|
@@ -171,13 +171,13 @@ function deriveUiComponentCandidates(candidates) {
|
|
|
171
171
|
inferred_pattern: pattern,
|
|
172
172
|
evidence: screen.provenance || [],
|
|
173
173
|
missing_decisions: [
|
|
174
|
-
"confirm
|
|
174
|
+
"confirm widget reuse boundary",
|
|
175
175
|
"confirm prop names and data source",
|
|
176
176
|
"confirm events and behavior",
|
|
177
177
|
"confirm supported regions and patterns"
|
|
178
178
|
],
|
|
179
179
|
notes: [
|
|
180
|
-
"Imported
|
|
180
|
+
"Imported widget candidates are review-only.",
|
|
181
181
|
"Confirm props, behavior, events, and reuse before adoption."
|
|
182
182
|
]
|
|
183
183
|
});
|
|
@@ -293,7 +293,7 @@ function reportMarkdown(track, candidates) {
|
|
|
293
293
|
`- \`${component.id_hint}\` confidence ${component.confidence || "unknown"} pattern \`${component.pattern || component.inferred_pattern || "unknown"}\` region \`${component.region || component.inferred_region || "unknown"}\` evidence ${(component.evidence || component.provenance || []).length} missing decisions ${(component.missing_decisions || []).length}`
|
|
294
294
|
);
|
|
295
295
|
return ensureTrailingNewline(
|
|
296
|
-
`# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n-
|
|
296
|
+
`# UI Import Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Widgets: ${candidates.components.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Widget Candidates\n\n${componentLines.length ? componentLines.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
297
|
);
|
|
298
298
|
}
|
|
299
299
|
if (track === "verification") {
|
|
@@ -319,7 +319,7 @@ function projectionIdStem(workspaceRoot) {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
function componentCandidateFileName(component) {
|
|
322
|
-
return `${String(component.id_hint || "
|
|
322
|
+
return `${String(component.id_hint || "widget")
|
|
323
323
|
.replace(/^component_/, "")
|
|
324
324
|
.replace(/_/g, "-")}.tg`;
|
|
325
325
|
}
|
|
@@ -327,15 +327,15 @@ function componentCandidateFileName(component) {
|
|
|
327
327
|
function renderComponentCandidate(component) {
|
|
328
328
|
const evidenceCount = (component.evidence || component.provenance || []).length;
|
|
329
329
|
const missingDecisions = component.missing_decisions || [
|
|
330
|
-
"confirm
|
|
330
|
+
"confirm widget reuse boundary",
|
|
331
331
|
"confirm prop names and data source",
|
|
332
332
|
"confirm events and behavior"
|
|
333
333
|
];
|
|
334
|
-
return `
|
|
334
|
+
return `widget ${component.id_hint} {
|
|
335
335
|
# Import metadata: confidence ${component.confidence || "unknown"}; evidence ${evidenceCount}; inferred pattern ${component.pattern || component.inferred_pattern || "search_results"}; inferred region ${component.region || component.inferred_region || "results"}.
|
|
336
336
|
# Missing decisions: ${missingDecisions.join("; ")}.
|
|
337
337
|
name "${component.label || component.id_hint}"
|
|
338
|
-
description "Candidate reusable
|
|
338
|
+
description "Candidate reusable widget inferred from imported UI evidence. Review props, behavior, events, and reuse before adoption."
|
|
339
339
|
category collection
|
|
340
340
|
props {
|
|
341
341
|
${component.data_prop || "rows"} array required
|
|
@@ -355,7 +355,7 @@ function uiComponentLinesForCandidates(componentCandidates, allCandidates) {
|
|
|
355
355
|
const dataBinding = dataSource
|
|
356
356
|
? ` data ${component.data_prop || "rows"} from ${dataSource}`
|
|
357
357
|
: "";
|
|
358
|
-
return ` screen ${component.screen_id} region ${component.region}
|
|
358
|
+
return ` screen ${component.screen_id} region ${component.region} widget ${component.id_hint}${dataBinding}`;
|
|
359
359
|
});
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -386,7 +386,7 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
|
|
|
386
386
|
const actions = ui.actions || [];
|
|
387
387
|
const componentCandidates = [...(ui.components || [])].sort((a, b) => a.id_hint.localeCompare(b.id_hint));
|
|
388
388
|
const shell = actions.find((entry) => entry.kind === "ui_shell")?.shell_kind || "topbar";
|
|
389
|
-
const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "
|
|
389
|
+
const navigationPatterns = uniqueSorted(actions.filter((entry) => entry.kind === "navigation").map((entry) => entry.navigation_pattern));
|
|
390
390
|
const presentations = uniqueSorted(actions.filter((entry) => entry.kind === "ui_presentation").map((entry) => entry.presentation));
|
|
391
391
|
const capabilityHints = uniqueSorted([
|
|
392
392
|
...screens.flatMap((screen) => capabilityHintsForScreen(screen)),
|
|
@@ -488,22 +488,22 @@ function draftUiProjectionFiles(context, candidates, allCandidates = {}) {
|
|
|
488
488
|
}
|
|
489
489
|
const uiComponentLines = uiComponentLinesForCandidates(componentCandidates, allCandidates);
|
|
490
490
|
|
|
491
|
-
const uiSharedDraft = `projection
|
|
492
|
-
name "Imported
|
|
491
|
+
const uiSharedDraft = `projection proj_ui_contract_imported_${stem} {
|
|
492
|
+
name "Imported UI Contract Draft"
|
|
493
493
|
description "Drafted from imported UI candidates. Review and adapt before adoption."
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
type ui_contract
|
|
496
496
|
realizes [
|
|
497
497
|
${capabilityHints.length > 0 ? capabilityHints.map((hint) => ` ${hint}`).join(",\n") : " // add capability ids"}
|
|
498
498
|
]
|
|
499
499
|
outputs [ui_contract]
|
|
500
500
|
|
|
501
|
-
|
|
501
|
+
app_shell {
|
|
502
502
|
brand "Imported ${stem.replace(/_/g, " ")}"
|
|
503
503
|
shell ${shell}
|
|
504
504
|
${presentations.includes("search") ? " global_search true\n" : ""}${presentations.includes("multi_window") ? " windowing multi_window\n" : ""} }
|
|
505
505
|
|
|
506
|
-
|
|
506
|
+
design_tokens {
|
|
507
507
|
density comfortable
|
|
508
508
|
tone operational
|
|
509
509
|
radius_scale medium
|
|
@@ -517,15 +517,15 @@ ${presentations.includes("search") ? " global_search true\n" : ""}${presentat
|
|
|
517
517
|
accessibility focus visible
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
|
|
520
|
+
screens {
|
|
521
521
|
${uiScreensBlock}
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
${uiCollectionsLines.length > 0 ? `
|
|
524
|
+
${uiCollectionsLines.length > 0 ? ` collection_views {\n${uiCollectionsLines.join("\n")}\n }\n\n` : ""}${uiActionsLines.length > 0 ? ` screen_actions {\n${uiActionsLines.join("\n")}\n }\n\n` : ""} navigation {
|
|
525
525
|
${uiNavigationLines.join("\n")}
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
${uiScreenRegionLines.length > 0 ? `
|
|
528
|
+
${uiScreenRegionLines.length > 0 ? ` screen_regions {\n${uiScreenRegionLines.join("\n")}\n }\n\n` : ""}${uiComponentLines.length > 0 ? ` widget_bindings {\n${uiComponentLines.join("\n")}\n }\n\n` : ""} status proposed
|
|
529
529
|
}
|
|
530
530
|
`;
|
|
531
531
|
|
|
@@ -565,22 +565,22 @@ ${uiScreenRegionLines.length > 0 ? ` ui_screen_regions {\n${uiScreenRegionLines
|
|
|
565
565
|
uiWebLines.push(` action ${entry.capability_hint} present ${actionPresent}`);
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
const uiWebDraft = `projection
|
|
569
|
-
name "Imported Web
|
|
568
|
+
const uiWebDraft = `projection proj_web_surface_imported_${stem} {
|
|
569
|
+
name "Imported Web Surface Draft"
|
|
570
570
|
description "Drafted from imported UI candidates. Review and adapt before adoption."
|
|
571
571
|
|
|
572
|
-
|
|
572
|
+
type web_surface
|
|
573
573
|
realizes [
|
|
574
|
-
|
|
574
|
+
proj_ui_contract_imported_${stem},
|
|
575
575
|
${webCapHints}
|
|
576
576
|
]
|
|
577
577
|
outputs [ui_contract, web_app]
|
|
578
578
|
|
|
579
|
-
|
|
579
|
+
screen_routes {
|
|
580
580
|
${uiRouteLines.length > 0 ? uiRouteLines.join("\n") : " // add routes"}
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
${uiWebLines.length > 0 ? `
|
|
583
|
+
${uiWebLines.length > 0 ? ` web_hints {\n${uiWebLines.join("\n")}\n }\n\n` : ""} generator_defaults {
|
|
584
584
|
profile react
|
|
585
585
|
language typescript
|
|
586
586
|
styling css
|
|
@@ -592,9 +592,9 @@ ${uiWebLines.length > 0 ? ` ui_web {\n${uiWebLines.join("\n")}\n }\n\n` : ""}
|
|
|
592
592
|
|
|
593
593
|
const coverage = `# Imported UI Projection Drafts
|
|
594
594
|
|
|
595
|
-
- Draft
|
|
596
|
-
- Draft web projection: \`candidates/app/ui/drafts/proj-
|
|
597
|
-
- Draft
|
|
595
|
+
- Draft UI contract projection: \`candidates/app/ui/drafts/proj-ui-contract.tg\`
|
|
596
|
+
- Draft web surface projection: \`candidates/app/ui/drafts/proj-web-surface.tg\`
|
|
597
|
+
- Draft widget candidates: ${componentCandidates.length}
|
|
598
598
|
- Imported screens: ${screens.length}
|
|
599
599
|
- Imported routes: ${(ui.routes || []).length}
|
|
600
600
|
- Imported UI actions/presentations: ${actions.length}
|
|
@@ -605,19 +605,19 @@ ${uiWebLines.length > 0 ? ` ui_web {\n${uiWebLines.join("\n")}\n }\n\n` : ""}
|
|
|
605
605
|
|
|
606
606
|
- These files are drafts, not adopted canonical projections.
|
|
607
607
|
- Capability ids come from imported hints and may need renaming or pruning.
|
|
608
|
-
-
|
|
609
|
-
- Review
|
|
608
|
+
- Widget candidates are suggested reusable contracts, not canonical ownership.
|
|
609
|
+
- Review widget props, events, behavior, regions, and patterns before adopting.
|
|
610
610
|
- Search and refresh directives are inferred heuristically.
|
|
611
611
|
- Navigation groups currently default to a single \`workspace\` group unless stronger grouping evidence exists.
|
|
612
612
|
`;
|
|
613
613
|
|
|
614
614
|
const files = {
|
|
615
|
-
"candidates/app/ui/drafts/proj-ui-
|
|
616
|
-
"candidates/app/ui/drafts/proj-
|
|
615
|
+
"candidates/app/ui/drafts/proj-ui-contract.tg": ensureTrailingNewline(uiSharedDraft),
|
|
616
|
+
"candidates/app/ui/drafts/proj-web-surface.tg": ensureTrailingNewline(uiWebDraft),
|
|
617
617
|
"candidates/app/ui/drafts/README.md": ensureTrailingNewline(coverage)
|
|
618
618
|
};
|
|
619
619
|
for (const component of componentCandidates) {
|
|
620
|
-
files[`candidates/app/ui/drafts/
|
|
620
|
+
files[`candidates/app/ui/drafts/widgets/${componentCandidateFileName(component)}`] = ensureTrailingNewline(renderComponentCandidate(component));
|
|
621
621
|
}
|
|
622
622
|
return files;
|
|
623
623
|
}
|
|
@@ -676,7 +676,7 @@ export function runImportApp(inputPath, options = {}) {
|
|
|
676
676
|
files["candidates/app/findings.json"] = `${JSON.stringify(findings, null, 2)}\n`;
|
|
677
677
|
files["candidates/app/candidates.json"] = `${JSON.stringify(candidates, null, 2)}\n`;
|
|
678
678
|
files["candidates/app/report.md"] = ensureTrailingNewline(
|
|
679
|
-
`# App Import Report\n\nTracks: ${tracks.join(", ")}\n\n## DB\n\n- Entities: ${candidates.db?.entities
|
|
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?.components?.length || 0}\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
680
|
);
|
|
681
681
|
|
|
682
682
|
return {
|
|
@@ -131,7 +131,7 @@ export const androidComposeUiExtractor = {
|
|
|
131
131
|
}
|
|
132
132
|
for (const pattern of aggregateSummary.patterns) {
|
|
133
133
|
candidates.actions.push(makeCandidateRecord({
|
|
134
|
-
kind: "
|
|
134
|
+
kind: "navigation",
|
|
135
135
|
idHint: `compose_${idHintify(pattern)}`,
|
|
136
136
|
label: pattern,
|
|
137
137
|
confidence: "medium",
|
|
@@ -138,7 +138,7 @@ export const blazorUiExtractor = {
|
|
|
138
138
|
}
|
|
139
139
|
for (const pattern of summary.patterns) {
|
|
140
140
|
candidates.actions.push(makeCandidateRecord({
|
|
141
|
-
kind: "
|
|
141
|
+
kind: "navigation",
|
|
142
142
|
idHint: `blazor_${idHintify(pattern)}`,
|
|
143
143
|
label: pattern,
|
|
144
144
|
confidence: "medium",
|
|
@@ -150,7 +150,7 @@ export const razorPagesUiExtractor = {
|
|
|
150
150
|
|
|
151
151
|
for (const pattern of shellPatterns(text)) {
|
|
152
152
|
candidates.actions.push(makeCandidateRecord({
|
|
153
|
-
kind: "
|
|
153
|
+
kind: "navigation",
|
|
154
154
|
idHint: `razor_${idHintify(pattern)}`,
|
|
155
155
|
label: pattern,
|
|
156
156
|
confidence: "low",
|
|
@@ -42,9 +42,9 @@ export const reactRouterUiExtractor = {
|
|
|
42
42
|
const features = detectUiPresentationFeatures(rootDir);
|
|
43
43
|
const shellKind = shellKindFromNavigation(navigation);
|
|
44
44
|
const navigationPatterns = navigationPatternsFromStructure(navigation);
|
|
45
|
-
findings.push({ kind: "
|
|
46
|
-
findings.push({ kind: "
|
|
47
|
-
findings.push({ kind: "
|
|
45
|
+
findings.push({ kind: "react_screen_routes", file: provenance, routes });
|
|
46
|
+
findings.push({ kind: "react_navigation", file: provenance, navigation });
|
|
47
|
+
findings.push({ kind: "react_surface_features", file: provenance, features });
|
|
48
48
|
candidates.stacks.push("react_web");
|
|
49
49
|
if (shellKind) {
|
|
50
50
|
candidates.actions.push(makeCandidateRecord({
|
|
@@ -62,7 +62,7 @@ export const reactRouterUiExtractor = {
|
|
|
62
62
|
}
|
|
63
63
|
for (const pattern of navigationPatterns) {
|
|
64
64
|
candidates.actions.push(makeCandidateRecord({
|
|
65
|
-
kind: "
|
|
65
|
+
kind: "navigation",
|
|
66
66
|
idHint: `${path.basename(rootDir)}_${idHintify(pattern)}`,
|
|
67
67
|
label: pattern,
|
|
68
68
|
confidence: "medium",
|
|
@@ -41,9 +41,9 @@ export const svelteKitUiExtractor = {
|
|
|
41
41
|
const features = detectUiPresentationFeatures(rootDir);
|
|
42
42
|
const shellKind = shellKindFromNavigation(navigation);
|
|
43
43
|
const navigationPatterns = navigationPatternsFromStructure(navigation);
|
|
44
|
-
findings.push({ kind: "
|
|
45
|
-
findings.push({ kind: "
|
|
46
|
-
findings.push({ kind: "
|
|
44
|
+
findings.push({ kind: "sveltekit_screen_routes", file: provenance, routes });
|
|
45
|
+
findings.push({ kind: "sveltekit_navigation", file: provenance, navigation });
|
|
46
|
+
findings.push({ kind: "sveltekit_surface_features", file: provenance, features });
|
|
47
47
|
candidates.stacks.push("sveltekit_web");
|
|
48
48
|
if (shellKind) {
|
|
49
49
|
candidates.actions.push(makeCandidateRecord({
|
|
@@ -61,7 +61,7 @@ export const svelteKitUiExtractor = {
|
|
|
61
61
|
}
|
|
62
62
|
for (const pattern of navigationPatterns) {
|
|
63
63
|
candidates.actions.push(makeCandidateRecord({
|
|
64
|
-
kind: "
|
|
64
|
+
kind: "navigation",
|
|
65
65
|
idHint: `${path.basename(rootDir)}_${pattern}`,
|
|
66
66
|
label: pattern,
|
|
67
67
|
confidence: "medium",
|
|
@@ -153,7 +153,7 @@ export const swiftUiExtractor = {
|
|
|
153
153
|
}
|
|
154
154
|
for (const pattern of summary.patterns) {
|
|
155
155
|
candidates.actions.push(makeCandidateRecord({
|
|
156
|
-
kind: "
|
|
156
|
+
kind: "navigation",
|
|
157
157
|
idHint: `swiftui_${idHintify(pattern)}`,
|
|
158
158
|
label: pattern,
|
|
159
159
|
confidence: "medium",
|
|
@@ -144,7 +144,7 @@ export const uiKitExtractor = {
|
|
|
144
144
|
|
|
145
145
|
if (candidates.screens.length > 0) {
|
|
146
146
|
candidates.actions.push(makeCandidateRecord({
|
|
147
|
-
kind: "
|
|
147
|
+
kind: "navigation",
|
|
148
148
|
idHint: "uikit_stack_navigation",
|
|
149
149
|
label: "stack_navigation",
|
|
150
150
|
confidence: "medium",
|
package/src/new-project.js
CHANGED
|
@@ -27,10 +27,11 @@ const GENERATOR_LABELS = new Map([
|
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
29
|
const SURFACE_ORDER = new Map([
|
|
30
|
-
["
|
|
31
|
-
["
|
|
30
|
+
["web_surface", 10],
|
|
31
|
+
["api_service", 20],
|
|
32
32
|
["database", 30],
|
|
33
|
-
["
|
|
33
|
+
["ios_surface", 40],
|
|
34
|
+
["android_surface", 50]
|
|
34
35
|
]);
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -427,26 +428,26 @@ function generatorLabel(generatorId) {
|
|
|
427
428
|
function summarizeTemplateTopology(templateRoot) {
|
|
428
429
|
const projectConfigPath = path.join(templateRoot, "topogram.project.json");
|
|
429
430
|
const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
|
|
430
|
-
const
|
|
431
|
-
Array.isArray(projectConfig.topology?.
|
|
431
|
+
const rawRuntimes = /** @type {any[]} */ (
|
|
432
|
+
Array.isArray(projectConfig.topology?.runtimes) ? projectConfig.topology.runtimes : []
|
|
432
433
|
);
|
|
433
434
|
/** @type {Array<Record<string, any>>} */
|
|
434
|
-
const
|
|
435
|
-
for (const
|
|
436
|
-
if (
|
|
437
|
-
|
|
435
|
+
const runtimes = [];
|
|
436
|
+
for (const runtime of rawRuntimes) {
|
|
437
|
+
if (runtime && typeof runtime === "object" && typeof runtime.kind === "string") {
|
|
438
|
+
runtimes.push(/** @type {Record<string, any>} */ (runtime));
|
|
438
439
|
}
|
|
439
440
|
}
|
|
440
|
-
const
|
|
441
|
-
const aOrder = SURFACE_ORDER.get(a.
|
|
442
|
-
const bOrder = SURFACE_ORDER.get(b.
|
|
441
|
+
const sortedRuntimes = [...runtimes].sort((a, b) => {
|
|
442
|
+
const aOrder = SURFACE_ORDER.get(a.kind) ?? 100;
|
|
443
|
+
const bOrder = SURFACE_ORDER.get(b.kind) ?? 100;
|
|
443
444
|
return aOrder - bOrder;
|
|
444
445
|
});
|
|
445
|
-
const surfaces = [...new Set(
|
|
446
|
+
const surfaces = [...new Set(sortedRuntimes.map((runtime) => String(runtime.kind)))];
|
|
446
447
|
const generators = [
|
|
447
448
|
...new Set(
|
|
448
|
-
|
|
449
|
-
.map((
|
|
449
|
+
sortedRuntimes
|
|
450
|
+
.map((runtime) => runtime.generator?.id)
|
|
450
451
|
.filter((generatorId) => typeof generatorId === "string")
|
|
451
452
|
.map((generatorId) => String(generatorId))
|
|
452
453
|
)
|
|
@@ -704,7 +705,7 @@ function writeProjectTemplateMetadata(projectRoot, template, templateProvenance
|
|
|
704
705
|
const projectConfigPath = path.join(projectRoot, "topogram.project.json");
|
|
705
706
|
const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
|
|
706
707
|
projectConfig.template = projectTemplateMetadata(template, templateProvenance);
|
|
707
|
-
fs.writeFileSync(projectConfigPath, `${
|
|
708
|
+
fs.writeFileSync(projectConfigPath, `${stableJsonStringify(projectConfig)}\n`, "utf8");
|
|
708
709
|
return projectConfig;
|
|
709
710
|
}
|
|
710
711
|
|
|
@@ -1323,8 +1324,8 @@ function currentTemplateOwnedFiles(projectRoot, includeImplementation, projectCo
|
|
|
1323
1324
|
if (fs.existsSync(projectConfigPath)) {
|
|
1324
1325
|
files.set("topogram.project.json", {
|
|
1325
1326
|
path: "topogram.project.json",
|
|
1326
|
-
absolutePath:
|
|
1327
|
-
content:
|
|
1327
|
+
absolutePath: projectConfigPath,
|
|
1328
|
+
content: null
|
|
1328
1329
|
});
|
|
1329
1330
|
}
|
|
1330
1331
|
return files;
|