@topogram/cli 0.3.55 → 0.3.57
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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/generator/adapters.js +21 -5
- package/src/generator/context/slice.js +24 -3
- package/src/generator/runtime/app-bundle.js +1 -1
- package/src/generator/runtime/compile-check.js +4 -4
- package/src/generator/runtime/deployment.js +3 -3
- package/src/generator/runtime/environment.js +11 -11
- package/src/generator/runtime/shared.js +46 -32
- package/src/generator/surfaces/databases/index.js +5 -3
- package/src/generator/surfaces/index.js +5 -3
- package/src/generator/surfaces/services/index.js +10 -6
- package/src/generator/surfaces/web/index.js +5 -3
- package/src/generator/surfaces/web/react.js +3 -1
- package/src/generator/surfaces/web/sveltekit.js +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
- Install package-backed generator dependencies during `topogram template check`
|
|
6
6
|
before starter validation.
|
|
7
7
|
|
|
8
|
+
## 0.3.55 - 2026-05-07
|
|
9
|
+
|
|
10
|
+
- Complete the public DSL cleanup from `component` to `widget`: normalized
|
|
11
|
+
graph/context output now uses `widget`, `widgetBindings`, `widgetContract`,
|
|
12
|
+
and widget report target names without publishing component compatibility
|
|
13
|
+
aliases.
|
|
14
|
+
- Reconcile/adoption import reports now emit `widgets` in candidate model
|
|
15
|
+
bundles, evidence, summaries, and promoted item plans. Existing imported
|
|
16
|
+
workspaces with `candidates.ui.components` are still accepted as a read-only
|
|
17
|
+
fallback, but new import output writes `candidates.ui.widgets`.
|
|
18
|
+
- Old CLI names fail with explicit rename guidance instead of silently running:
|
|
19
|
+
`topogram component`, `--component`, `ui-component-contract`,
|
|
20
|
+
`component-conformance-report`, and `component-behavior-report`.
|
|
21
|
+
- Generator policy diagnostics now describe topology entries as runtimes
|
|
22
|
+
(`runtimeId`) instead of components.
|
|
23
|
+
- Tighten boundary tests so old public DSL vocabulary is allowed only in
|
|
24
|
+
migration guidance and explicit rename diagnostics.
|
|
25
|
+
|
|
8
26
|
## 0.3.16 - 2026-05-03
|
|
9
27
|
|
|
10
28
|
- Install package-backed generator dependencies during `topogram template check`
|
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ import { generateVanillaWebApp } from "./surfaces/web/vanilla.js";
|
|
|
41
41
|
* @property {Record<string, any>} graph
|
|
42
42
|
* @property {Record<string, any>} projection
|
|
43
43
|
* @property {Record<string, any>} runtime
|
|
44
|
-
* @property {Record<string, any>} component
|
|
44
|
+
* @property {Record<string, any>} [component] Legacy runtime alias for existing generator packages.
|
|
45
45
|
* @property {Record<string, any>|null} [topology]
|
|
46
46
|
* @property {Record<string, any>} [contracts]
|
|
47
47
|
* @property {Record<string, any>|null} [implementation]
|
|
@@ -88,7 +88,23 @@ function requiredManifest(generatorId) {
|
|
|
88
88
|
* @returns {string}
|
|
89
89
|
*/
|
|
90
90
|
function runtimeFor(context) {
|
|
91
|
-
return context.runtime || context.component || {};
|
|
91
|
+
return normalizeRuntimeForGenerator(context.runtime || context.component || {});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Keep package-backed generator context canonical while preserving aliases for already-published adapters.
|
|
96
|
+
*
|
|
97
|
+
* @param {Record<string, any>} runtime
|
|
98
|
+
* @returns {Record<string, any>}
|
|
99
|
+
*/
|
|
100
|
+
function normalizeRuntimeForGenerator(runtime) {
|
|
101
|
+
const apiRuntime = runtime.apiRuntime || runtime.apiComponent || null;
|
|
102
|
+
const databaseRuntime = runtime.databaseRuntime || runtime.databaseComponent || null;
|
|
103
|
+
return {
|
|
104
|
+
...runtime,
|
|
105
|
+
...(apiRuntime ? { apiRuntime, apiComponent: apiRuntime } : {}),
|
|
106
|
+
...(databaseRuntime ? { databaseRuntime, databaseComponent: databaseRuntime } : {})
|
|
107
|
+
};
|
|
92
108
|
}
|
|
93
109
|
|
|
94
110
|
/**
|
|
@@ -108,7 +124,7 @@ function projectionIdFor(context) {
|
|
|
108
124
|
function serverOptions(context, profile) {
|
|
109
125
|
const projectionId = projectionIdFor(context);
|
|
110
126
|
const runtime = runtimeFor(context);
|
|
111
|
-
const dbProjectionId = runtime.databaseComponent?.projection?.id || context.options?.dbProjectionId;
|
|
127
|
+
const dbProjectionId = runtime.databaseRuntime?.projection?.id || runtime.databaseComponent?.projection?.id || context.options?.dbProjectionId;
|
|
112
128
|
return {
|
|
113
129
|
...(context.options || {}),
|
|
114
130
|
projectionId,
|
|
@@ -178,7 +194,7 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
|
|
|
178
194
|
manifest: requiredManifest("topogram/hono"),
|
|
179
195
|
generate(context) {
|
|
180
196
|
const runtime = runtimeFor(context);
|
|
181
|
-
if (runtime && !runtime.
|
|
197
|
+
if (runtime && !runtime.databaseRuntime) {
|
|
182
198
|
return fileResult(generateStatelessServer(context.graph, serverOptions(context, "hono")));
|
|
183
199
|
}
|
|
184
200
|
return fileResult(generateHonoServer(context.graph, serverOptions(context, "hono")));
|
|
@@ -188,7 +204,7 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
|
|
|
188
204
|
manifest: requiredManifest("topogram/express"),
|
|
189
205
|
generate(context) {
|
|
190
206
|
const runtime = runtimeFor(context);
|
|
191
|
-
if (runtime && !runtime.
|
|
207
|
+
if (runtime && !runtime.databaseRuntime) {
|
|
192
208
|
return fileResult(generateStatelessServer(context.graph, serverOptions(context, "express")));
|
|
193
209
|
}
|
|
194
210
|
return fileResult(generateExpressServer(context.graph, serverOptions(context, "express")));
|
|
@@ -368,7 +368,7 @@ function uiAgentPacketForProjection(graph, projection) {
|
|
|
368
368
|
screenId: route.screenId,
|
|
369
369
|
path: route.path
|
|
370
370
|
})),
|
|
371
|
-
widgets: (ownerProjection.widgetBindings || []).map((usage) => widgetUsagePacket(usage)),
|
|
371
|
+
widgets: (ownerProjection.widgetBindings || []).map((usage) => widgetUsagePacket(usage, ownerProjection)),
|
|
372
372
|
designTokens: designIntentPacket(ownerProjection),
|
|
373
373
|
requiredGates: uiRequiredGates(projection.id)
|
|
374
374
|
};
|
|
@@ -387,7 +387,7 @@ function uiAgentPacketForWidget(graph, widget, projectionIds) {
|
|
|
387
387
|
type: projectionType,
|
|
388
388
|
ownership: projectionType === "ui_contract" ? "owner" : "concrete"
|
|
389
389
|
},
|
|
390
|
-
usage: widgetUsagePacket(usage),
|
|
390
|
+
usage: widgetUsagePacket(usage, projection),
|
|
391
391
|
designTokens: designIntentPacket(projection)
|
|
392
392
|
});
|
|
393
393
|
projectionSet.add(projection.id);
|
|
@@ -428,10 +428,31 @@ function sharedUiProjectionFor(graph, projection) {
|
|
|
428
428
|
return null;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
function widgetUsagePacket(usage) {
|
|
431
|
+
function widgetUsagePacket(usage, projection = null) {
|
|
432
|
+
const screen = (projection?.uiScreens || []).find((entry) => entry.id === usage.screenId) || null;
|
|
433
|
+
const region = (projection?.uiScreenRegions || []).find((entry) =>
|
|
434
|
+
entry.screenId === usage.screenId && entry.region === usage.region
|
|
435
|
+
) || null;
|
|
432
436
|
return {
|
|
433
437
|
screenId: usage.screenId || null,
|
|
438
|
+
screen: screen
|
|
439
|
+
? {
|
|
440
|
+
id: screen.id,
|
|
441
|
+
kind: screen.kind || null,
|
|
442
|
+
title: screen.title || screen.id
|
|
443
|
+
}
|
|
444
|
+
: null,
|
|
434
445
|
region: usage.region || null,
|
|
446
|
+
regionContract: region
|
|
447
|
+
? {
|
|
448
|
+
name: region.region || null,
|
|
449
|
+
pattern: region.pattern || null,
|
|
450
|
+
placement: region.placement || null,
|
|
451
|
+
title: region.title || null,
|
|
452
|
+
state: region.state || null,
|
|
453
|
+
variant: region.variant || null
|
|
454
|
+
}
|
|
455
|
+
: null,
|
|
435
456
|
widgetId: usage.widget?.id || null,
|
|
436
457
|
dataBindings: (usage.dataBindings || []).map((binding) => ({
|
|
437
458
|
prop: binding.prop || null,
|
|
@@ -413,7 +413,7 @@ function noopBundle(name, message) {
|
|
|
413
413
|
export function generateAppBundle(graph, options = {}) {
|
|
414
414
|
const plan = buildAppBundlePlan(graph, options);
|
|
415
415
|
const topology = resolveRuntimeTopology(graph, options);
|
|
416
|
-
const fullStack = topology.
|
|
416
|
+
const fullStack = topology.apiRuntimes.length > 0 && topology.webRuntimes.length > 0 && topology.dbRuntimes.length > 0;
|
|
417
417
|
const envBundle = generateEnvironmentBundle(graph, { ...options, profileId: plan.profiles.environment });
|
|
418
418
|
const deployBundle = fullStack
|
|
419
419
|
? generateDeploymentBundle(graph, { ...options, profileId: plan.profiles.deployment })
|
|
@@ -29,13 +29,13 @@ function runtimeReferenceFor(graph, options = {}) {
|
|
|
29
29
|
function buildCompileCheckPlan(graph, options = {}) {
|
|
30
30
|
const topology = resolveRuntimeTopology(graph, options);
|
|
31
31
|
const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
|
|
32
|
-
const apiChecks = topology.
|
|
32
|
+
const apiChecks = topology.apiRuntimes.map((component, index) => ({
|
|
33
33
|
id: index === 0 ? "server_typecheck" : `server_typecheck_${component.id}`,
|
|
34
34
|
cwd: topology.serviceDir(component),
|
|
35
35
|
install: "npm install --no-audit --no-fund",
|
|
36
36
|
command: "npm run check"
|
|
37
37
|
}));
|
|
38
|
-
const webChecks = topology.
|
|
38
|
+
const webChecks = topology.webRuntimes.flatMap((component, index) => [
|
|
39
39
|
{
|
|
40
40
|
id: index === 0 ? "web_typecheck" : `web_typecheck_${component.id}`,
|
|
41
41
|
cwd: topology.webDir(component),
|
|
@@ -150,11 +150,11 @@ export function generateCompileCheckBundle(graph, options = {}) {
|
|
|
150
150
|
"compile-check-plan.json": `${JSON.stringify(plan, null, 2)}\n`,
|
|
151
151
|
"scripts/check.sh": renderCompileCheckScript(plan)
|
|
152
152
|
};
|
|
153
|
-
for (const component of topology.
|
|
153
|
+
for (const component of topology.apiRuntimes) {
|
|
154
154
|
const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
|
|
155
155
|
mergeBundleFiles(files, topology.serviceDir(component), serverBundle);
|
|
156
156
|
}
|
|
157
|
-
for (const component of topology.
|
|
157
|
+
for (const component of topology.webRuntimes) {
|
|
158
158
|
const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
|
|
159
159
|
mergeBundleFiles(files, topology.webDir(component), webBundle);
|
|
160
160
|
}
|
|
@@ -262,19 +262,19 @@ export function generateDeploymentBundle(graph, options = {}) {
|
|
|
262
262
|
files["railway.json"] = renderRailwayJson(plan);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
for (const component of topology.
|
|
265
|
+
for (const component of topology.apiRuntimes) {
|
|
266
266
|
const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
|
|
267
267
|
mergeNamedBundles(files, {
|
|
268
268
|
[topology.serviceDir(component)]: serverBundle
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
|
-
for (const component of topology.
|
|
271
|
+
for (const component of topology.webRuntimes) {
|
|
272
272
|
const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
|
|
273
273
|
mergeNamedBundles(files, {
|
|
274
274
|
[topology.webDir(component)]: webBundle
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
|
-
for (const component of topology.
|
|
277
|
+
for (const component of topology.dbRuntimes) {
|
|
278
278
|
const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
|
|
279
279
|
mergeNamedBundles(files, {
|
|
280
280
|
[topology.dbDir(component)]: dbBundle
|
|
@@ -113,24 +113,24 @@ function buildEnvironmentPlan(graph, options = {}) {
|
|
|
113
113
|
scripts: "scripts"
|
|
114
114
|
},
|
|
115
115
|
runtimes: {
|
|
116
|
-
apis: topology.
|
|
116
|
+
apis: topology.apiRuntimes.map((component) => ({
|
|
117
117
|
id: component.id,
|
|
118
118
|
projection: component.projection.id,
|
|
119
119
|
port: component.port || ports.server,
|
|
120
120
|
dir: topology.serviceDir(component),
|
|
121
121
|
uses_database: component.database,
|
|
122
|
-
databaseEnv: component.
|
|
123
|
-
? dbEnvVarsForComponent(component.
|
|
122
|
+
databaseEnv: component.databaseRuntime
|
|
123
|
+
? dbEnvVarsForComponent(component.databaseRuntime, { primary: component.databaseRuntime?.id === topology.primaryDb?.id })
|
|
124
124
|
: null
|
|
125
125
|
})),
|
|
126
|
-
webs: topology.
|
|
126
|
+
webs: topology.webRuntimes.map((component) => ({
|
|
127
127
|
id: component.id,
|
|
128
128
|
projection: component.projection.id,
|
|
129
129
|
port: component.port || ports.web,
|
|
130
130
|
dir: topology.webDir(component),
|
|
131
131
|
uses_api: component.api
|
|
132
132
|
})),
|
|
133
|
-
databases: topology.
|
|
133
|
+
databases: topology.dbRuntimes.map((component) => ({
|
|
134
134
|
id: component.id,
|
|
135
135
|
projection: component.projection.id,
|
|
136
136
|
type: component.projection.type,
|
|
@@ -330,7 +330,7 @@ function renderEnvironmentLoadEnvScript() {
|
|
|
330
330
|
function renderEnvironmentBootstrapDbScript(plan) {
|
|
331
331
|
const dbBootstrapLines = plan.runtimes.databases.map((component) => {
|
|
332
332
|
const env = component.env;
|
|
333
|
-
const runtimeApi = plan.runtimes.apis.find((
|
|
333
|
+
const runtimeApi = plan.runtimes.apis.find((apiRuntime) => apiRuntime.uses_database === component.id);
|
|
334
334
|
const assignments = [
|
|
335
335
|
`DATABASE_URL="\${${env.databaseUrl}:-}"`,
|
|
336
336
|
`DATABASE_ADMIN_URL="\${${env.databaseAdminUrl}:-}"`,
|
|
@@ -393,12 +393,12 @@ function renderEnvironmentWebDevScript(plan, component = plan.runtimes.webs[0],
|
|
|
393
393
|
if (!component) {
|
|
394
394
|
return renderEnvAwareShellScript(['echo "No web runtimes are configured."']);
|
|
395
395
|
}
|
|
396
|
-
const
|
|
396
|
+
const apiRuntime = plan.runtimes.apis.find((entry) => entry.id === component.uses_api) || plan.runtimes.apis[0];
|
|
397
397
|
const guardPortsScript = options.componentScript ? '"$ROOT_DIR/scripts/guard-ports.mjs"' : '"$SCRIPT_DIR/guard-ports.mjs"';
|
|
398
398
|
return renderEnvAwareShellScript([
|
|
399
399
|
`node ${guardPortsScript} web`,
|
|
400
400
|
"",
|
|
401
|
-
...(
|
|
401
|
+
...(apiRuntime ? [`export PUBLIC_TOPOGRAM_API_BASE_URL="\${PUBLIC_TOPOGRAM_API_BASE_URL:-http://localhost:\${${apiRuntime.id.toUpperCase()}_PORT:-\${SERVER_PORT:-${apiRuntime.port}}}}"`] : []),
|
|
402
402
|
`export TOPOGRAM_CORS_ORIGINS="\${TOPOGRAM_CORS_ORIGINS:-http://localhost:\${${component.id.toUpperCase()}_PORT:-\${WEB_PORT:-${component.port}}},http://127.0.0.1:\${${component.id.toUpperCase()}_PORT:-\${WEB_PORT:-${component.port}}}}"`,
|
|
403
403
|
"",
|
|
404
404
|
`cd "$ROOT_DIR/${component.dir}"`,
|
|
@@ -612,19 +612,19 @@ export function generateEnvironmentBundle(graph, options = {}) {
|
|
|
612
612
|
files["scripts/docker-stack.sh"] = renderEnvironmentDockerStackScript();
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
-
for (const component of topology.
|
|
615
|
+
for (const component of topology.apiRuntimes) {
|
|
616
616
|
const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
|
|
617
617
|
mergeNamedBundles(files, {
|
|
618
618
|
[topology.serviceDir(component)]: serverBundle
|
|
619
619
|
});
|
|
620
620
|
}
|
|
621
|
-
for (const component of topology.
|
|
621
|
+
for (const component of topology.webRuntimes) {
|
|
622
622
|
const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
|
|
623
623
|
mergeNamedBundles(files, {
|
|
624
624
|
[topology.webDir(component)]: webBundle
|
|
625
625
|
});
|
|
626
626
|
}
|
|
627
|
-
for (const component of topology.
|
|
627
|
+
for (const component of topology.dbRuntimes) {
|
|
628
628
|
const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
|
|
629
629
|
mergeNamedBundles(files, {
|
|
630
630
|
[topology.dbDir(component)]: dbBundle
|
|
@@ -27,8 +27,10 @@ import { defaultProjectConfigForGraph, validateProjectConfig } from "../../proje
|
|
|
27
27
|
* @property {string|null} [api]
|
|
28
28
|
* @property {string|null} [database]
|
|
29
29
|
* @property {Record<string, string>} [env]
|
|
30
|
-
* @property {RuntimeComponent|null} [
|
|
31
|
-
* @property {RuntimeComponent|null} [
|
|
30
|
+
* @property {RuntimeComponent|null} [apiRuntime]
|
|
31
|
+
* @property {RuntimeComponent|null} [databaseRuntime]
|
|
32
|
+
* @property {RuntimeComponent|null} [apiComponent] Legacy adapter alias for apiRuntime.
|
|
33
|
+
* @property {RuntimeComponent|null} [databaseComponent] Legacy adapter alias for databaseRuntime.
|
|
32
34
|
*/
|
|
33
35
|
|
|
34
36
|
/**
|
|
@@ -39,10 +41,13 @@ import { defaultProjectConfigForGraph, validateProjectConfig } from "../../proje
|
|
|
39
41
|
* @typedef {Object} RuntimeTopology
|
|
40
42
|
* @property {import("../../project-config.js").ProjectConfig} config
|
|
41
43
|
* @property {RuntimeComponent[]} runtimes
|
|
42
|
-
* @property {RuntimeComponent[]}
|
|
43
|
-
* @property {RuntimeComponent[]}
|
|
44
|
-
* @property {RuntimeComponent[]}
|
|
45
|
-
* @property {RuntimeComponent[]}
|
|
44
|
+
* @property {RuntimeComponent[]} apiRuntimes
|
|
45
|
+
* @property {RuntimeComponent[]} webRuntimes
|
|
46
|
+
* @property {RuntimeComponent[]} dbRuntimes
|
|
47
|
+
* @property {RuntimeComponent[]} components Legacy alias for runtimes.
|
|
48
|
+
* @property {RuntimeComponent[]} apiComponents Legacy alias for apiRuntimes.
|
|
49
|
+
* @property {RuntimeComponent[]} webComponents Legacy alias for webRuntimes.
|
|
50
|
+
* @property {RuntimeComponent[]} dbComponents Legacy alias for dbRuntimes.
|
|
46
51
|
* @property {RuntimeComponent|null} primaryApi
|
|
47
52
|
* @property {RuntimeComponent|null} primaryWeb
|
|
48
53
|
* @property {RuntimeComponent|null} primaryDb
|
|
@@ -65,7 +70,8 @@ import { defaultProjectConfigForGraph, validateProjectConfig } from "../../proje
|
|
|
65
70
|
* @property {string} [dbProjectionId]
|
|
66
71
|
* @property {string} [configDir]
|
|
67
72
|
* @property {string} [projectRoot]
|
|
68
|
-
* @property {RuntimeComponent} [
|
|
73
|
+
* @property {RuntimeComponent} [runtime]
|
|
74
|
+
* @property {RuntimeComponent} [component] Legacy alias for runtime.
|
|
69
75
|
*/
|
|
70
76
|
|
|
71
77
|
/**
|
|
@@ -327,14 +333,15 @@ export function getDefaultEnvironmentProjections(graph, options = {}) {
|
|
|
327
333
|
*/
|
|
328
334
|
export function generateServerBundle(graph, projectionId, options = {}) {
|
|
329
335
|
const topology = resolveRuntimeTopology(graph, options);
|
|
330
|
-
const
|
|
331
|
-
if (!
|
|
332
|
-
throw new Error(`No api
|
|
336
|
+
const runtime = options.runtime || options.component || topology.apiRuntimes.find((entry) => entry.projection.id === projectionId);
|
|
337
|
+
if (!runtime) {
|
|
338
|
+
throw new Error(`No api runtime found for projection '${projectionId}'`);
|
|
333
339
|
}
|
|
334
340
|
return generateWithComponentGenerator({
|
|
335
341
|
graph,
|
|
336
|
-
projection:
|
|
337
|
-
|
|
342
|
+
projection: runtime.projection,
|
|
343
|
+
runtime,
|
|
344
|
+
component: runtime,
|
|
338
345
|
topology,
|
|
339
346
|
implementation: options.implementation || null,
|
|
340
347
|
options: { ...options, projectionId }
|
|
@@ -349,14 +356,15 @@ export function generateServerBundle(graph, projectionId, options = {}) {
|
|
|
349
356
|
*/
|
|
350
357
|
export function generateWebBundle(graph, projectionId, options = {}) {
|
|
351
358
|
const topology = resolveRuntimeTopology(graph, options);
|
|
352
|
-
const
|
|
353
|
-
if (!
|
|
354
|
-
throw new Error(`No web
|
|
359
|
+
const runtime = options.runtime || options.component || topology.webRuntimes.find((entry) => entry.projection.id === projectionId);
|
|
360
|
+
if (!runtime) {
|
|
361
|
+
throw new Error(`No web runtime found for projection '${projectionId}'`);
|
|
355
362
|
}
|
|
356
363
|
return generateWithComponentGenerator({
|
|
357
364
|
graph,
|
|
358
|
-
projection:
|
|
359
|
-
|
|
365
|
+
projection: runtime.projection,
|
|
366
|
+
runtime,
|
|
367
|
+
component: runtime,
|
|
360
368
|
topology,
|
|
361
369
|
implementation: options.implementation || null,
|
|
362
370
|
options: { ...options, projectionId }
|
|
@@ -371,14 +379,15 @@ export function generateWebBundle(graph, projectionId, options = {}) {
|
|
|
371
379
|
*/
|
|
372
380
|
export function generateDbBundle(graph, projectionId, options = {}) {
|
|
373
381
|
const topology = resolveRuntimeTopology(graph, options);
|
|
374
|
-
const
|
|
375
|
-
if (!
|
|
376
|
-
throw new Error(`No database
|
|
382
|
+
const runtime = options.runtime || options.component || topology.dbRuntimes.find((entry) => entry.projection.id === projectionId);
|
|
383
|
+
if (!runtime) {
|
|
384
|
+
throw new Error(`No database runtime found for projection '${projectionId}'`);
|
|
377
385
|
}
|
|
378
386
|
return generateWithComponentGenerator({
|
|
379
387
|
graph,
|
|
380
388
|
projection: getProjection(graph, projectionId),
|
|
381
|
-
|
|
389
|
+
runtime,
|
|
390
|
+
component: runtime,
|
|
382
391
|
topology,
|
|
383
392
|
implementation: options.implementation || null,
|
|
384
393
|
options: { ...options, projectionId }
|
|
@@ -438,10 +447,12 @@ function decorateRuntimes(graph, config) {
|
|
|
438
447
|
const byId = new Map(runtimes.map((runtime) => [runtime.id, runtime]));
|
|
439
448
|
for (const runtime of runtimes) {
|
|
440
449
|
if (runtime.kind === "api_service" && runtime.database) {
|
|
441
|
-
runtime.
|
|
450
|
+
runtime.databaseRuntime = byId.get(runtime.database) || null;
|
|
451
|
+
runtime.databaseComponent = runtime.databaseRuntime;
|
|
442
452
|
}
|
|
443
453
|
if (runtime.kind === "web_surface" && runtime.api) {
|
|
444
|
-
runtime.
|
|
454
|
+
runtime.apiRuntime = byId.get(runtime.api) || null;
|
|
455
|
+
runtime.apiComponent = runtime.apiRuntime;
|
|
445
456
|
}
|
|
446
457
|
}
|
|
447
458
|
return runtimes;
|
|
@@ -462,20 +473,23 @@ export function resolveRuntimeTopology(graph, options = {}) {
|
|
|
462
473
|
throw new Error(validation.errors.map((error) => error.message).join("\n"));
|
|
463
474
|
}
|
|
464
475
|
const runtimes = decorateRuntimes(graph, config);
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
const primaryApi =
|
|
469
|
-
const primaryWeb =
|
|
470
|
-
const primaryDb = primaryApi?.
|
|
476
|
+
const apiRuntimes = runtimes.filter((runtime) => runtime.kind === "api_service");
|
|
477
|
+
const webRuntimes = runtimes.filter((runtime) => runtime.kind === "web_surface");
|
|
478
|
+
const dbRuntimes = runtimes.filter((runtime) => runtime.kind === "database");
|
|
479
|
+
const primaryApi = apiRuntimes[0] || null;
|
|
480
|
+
const primaryWeb = webRuntimes[0] || null;
|
|
481
|
+
const primaryDb = primaryApi?.databaseRuntime || dbRuntimes[0] || null;
|
|
471
482
|
|
|
472
483
|
return {
|
|
473
484
|
config,
|
|
474
485
|
runtimes,
|
|
475
486
|
components: runtimes,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
487
|
+
apiRuntimes,
|
|
488
|
+
webRuntimes,
|
|
489
|
+
dbRuntimes,
|
|
490
|
+
apiComponents: apiRuntimes,
|
|
491
|
+
webComponents: webRuntimes,
|
|
492
|
+
dbComponents: dbRuntimes,
|
|
479
493
|
primaryApi,
|
|
480
494
|
primaryWeb,
|
|
481
495
|
primaryDb,
|
|
@@ -57,11 +57,13 @@ export function generateDbTarget(target, graph, options = {}) {
|
|
|
57
57
|
: generatePostgresDbLifecyclePlan(graph, options);
|
|
58
58
|
}
|
|
59
59
|
if (target === "db-lifecycle-bundle") {
|
|
60
|
-
|
|
60
|
+
const runtime = options.runtime || options.component;
|
|
61
|
+
if (runtime?.generator?.id) {
|
|
61
62
|
return generateWithComponentGenerator({
|
|
62
63
|
graph,
|
|
63
|
-
projection:
|
|
64
|
-
|
|
64
|
+
projection: runtime.projection,
|
|
65
|
+
runtime,
|
|
66
|
+
component: runtime,
|
|
65
67
|
topology: options.topology || null,
|
|
66
68
|
implementation: options.implementation || null,
|
|
67
69
|
options
|
|
@@ -8,11 +8,13 @@ export function generateAppTarget(target, graph, options = {}) {
|
|
|
8
8
|
return generateBackendTarget(target, graph, options);
|
|
9
9
|
}
|
|
10
10
|
if (target === "swiftui-app") {
|
|
11
|
-
|
|
11
|
+
const runtime = options.runtime || options.component;
|
|
12
|
+
if (runtime?.generator?.id) {
|
|
12
13
|
return generateWithComponentGenerator({
|
|
13
14
|
graph,
|
|
14
|
-
projection:
|
|
15
|
-
|
|
15
|
+
projection: runtime.projection,
|
|
16
|
+
runtime,
|
|
17
|
+
component: runtime,
|
|
16
18
|
topology: options.topology || null,
|
|
17
19
|
implementation: options.implementation || null,
|
|
18
20
|
options
|
|
@@ -12,11 +12,13 @@ export function generateBackendTarget(target, graph, options = {}) {
|
|
|
12
12
|
return generatePersistenceScaffold(graph, options);
|
|
13
13
|
}
|
|
14
14
|
if (target === "hono-server") {
|
|
15
|
-
|
|
15
|
+
const runtime = options.runtime || options.component;
|
|
16
|
+
if (runtime?.generator?.id) {
|
|
16
17
|
return generateWithComponentGenerator({
|
|
17
18
|
graph,
|
|
18
|
-
projection:
|
|
19
|
-
|
|
19
|
+
projection: runtime.projection,
|
|
20
|
+
runtime,
|
|
21
|
+
component: runtime,
|
|
20
22
|
topology: options.topology || null,
|
|
21
23
|
implementation: options.implementation || null,
|
|
22
24
|
options
|
|
@@ -25,11 +27,13 @@ export function generateBackendTarget(target, graph, options = {}) {
|
|
|
25
27
|
return generateHonoServer(graph, options);
|
|
26
28
|
}
|
|
27
29
|
if (target === "express-server") {
|
|
28
|
-
|
|
30
|
+
const runtime = options.runtime || options.component;
|
|
31
|
+
if (runtime?.generator?.id) {
|
|
29
32
|
return generateWithComponentGenerator({
|
|
30
33
|
graph,
|
|
31
|
-
projection:
|
|
32
|
-
|
|
34
|
+
projection: runtime.projection,
|
|
35
|
+
runtime,
|
|
36
|
+
component: runtime,
|
|
33
37
|
topology: options.topology || null,
|
|
34
38
|
implementation: options.implementation || null,
|
|
35
39
|
options
|
|
@@ -15,17 +15,19 @@ import {
|
|
|
15
15
|
|
|
16
16
|
export function generateWebApp(graph, options = {}) {
|
|
17
17
|
const projection = getProjection(graph, options.projectionId);
|
|
18
|
-
|
|
18
|
+
const runtime = options.runtime || options.component;
|
|
19
|
+
if (runtime?.generator?.id) {
|
|
19
20
|
return generateWithComponentGenerator({
|
|
20
21
|
graph,
|
|
21
22
|
projection,
|
|
22
|
-
|
|
23
|
+
runtime,
|
|
24
|
+
component: runtime,
|
|
23
25
|
topology: options.topology || null,
|
|
24
26
|
implementation: options.implementation || null,
|
|
25
27
|
options: { ...options, projectionId: projection.id }
|
|
26
28
|
}).files;
|
|
27
29
|
}
|
|
28
|
-
const profile = generatorProfile(
|
|
30
|
+
const profile = generatorProfile(runtime?.generator?.id, null) || generatorDefaultsMap(projection).profile || "sveltekit";
|
|
29
31
|
if (profile === "vanilla") {
|
|
30
32
|
return generateVanillaWebApp(graph, options);
|
|
31
33
|
}
|
|
@@ -222,6 +222,7 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
222
222
|
const marker = widgetId ? `data-topogram-widget="${widgetId}"` : null;
|
|
223
223
|
const support = reactWidgetUsageSupport(usage, contract.widgets);
|
|
224
224
|
const usageRendered = Boolean(marker && contents.includes(marker));
|
|
225
|
+
const status = !support.supported ? "unsupported" : usageRendered ? "rendered" : "failed";
|
|
225
226
|
if (widgetId && rendered && !support.supported) {
|
|
226
227
|
diagnostics.push({
|
|
227
228
|
code: "widget_pattern_not_supported",
|
|
@@ -238,7 +239,7 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
238
239
|
if (widgetId && rendered && !usageRendered) {
|
|
239
240
|
diagnostics.push({
|
|
240
241
|
code: "widget_usage_not_rendered",
|
|
241
|
-
severity: "
|
|
242
|
+
severity: "error",
|
|
242
243
|
screen: screen.id,
|
|
243
244
|
route: screen.route,
|
|
244
245
|
region: usage.region || null,
|
|
@@ -252,6 +253,7 @@ function buildReactGenerationCoverage(contract, files, routeScreens) {
|
|
|
252
253
|
region: usage.region || null,
|
|
253
254
|
pattern: support.pattern || null,
|
|
254
255
|
supported: support.supported,
|
|
256
|
+
status,
|
|
255
257
|
rendered: usageRendered,
|
|
256
258
|
marker
|
|
257
259
|
};
|
|
@@ -194,6 +194,13 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
|
|
|
194
194
|
const marker = widgetId ? `data-topogram-widget="${widgetId}"` : null;
|
|
195
195
|
const support = svelteKitWidgetUsageSupport(usage, contract.widgets);
|
|
196
196
|
const usageRendered = Boolean(marker && contents.includes(marker));
|
|
197
|
+
const status = !support.supported
|
|
198
|
+
? "unsupported"
|
|
199
|
+
: usageRendered
|
|
200
|
+
? "rendered"
|
|
201
|
+
: renderer === "implementation"
|
|
202
|
+
? "implementation_owned"
|
|
203
|
+
: "failed";
|
|
197
204
|
if (widgetId && rendered && renderer !== "implementation" && !support.supported) {
|
|
198
205
|
diagnostics.push({
|
|
199
206
|
code: "widget_pattern_not_supported",
|
|
@@ -210,7 +217,7 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
|
|
|
210
217
|
if (widgetId && rendered && !usageRendered) {
|
|
211
218
|
diagnostics.push({
|
|
212
219
|
code: "widget_usage_not_rendered",
|
|
213
|
-
severity: "warning",
|
|
220
|
+
severity: renderer === "implementation" ? "warning" : "error",
|
|
214
221
|
screen: screen.id,
|
|
215
222
|
route: screen.route,
|
|
216
223
|
region: usage.region || null,
|
|
@@ -224,6 +231,7 @@ function buildSvelteKitGenerationCoverage(contract, files, implementationScreenI
|
|
|
224
231
|
region: usage.region || null,
|
|
225
232
|
pattern: support.pattern || null,
|
|
226
233
|
supported: support.supported,
|
|
234
|
+
status,
|
|
227
235
|
rendered: usageRendered,
|
|
228
236
|
marker
|
|
229
237
|
};
|