@topogram/cli 0.3.56 → 0.3.58

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.56",
3
+ "version": "0.3.58",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -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,
@@ -370,16 +370,26 @@ function componentScriptOptions() {
370
370
  };
371
371
  }
372
372
 
373
+ function runtimePortExpression(plan, runtimes, component, sharedEnvName) {
374
+ const runtimeEnvName = `${component.id.toUpperCase()}_PORT`;
375
+ const primaryRuntime = runtimes[0];
376
+ const fallback = primaryRuntime?.id === component.id
377
+ ? `\${${sharedEnvName}:-${component.port}}`
378
+ : `${component.port}`;
379
+ return `\${${runtimeEnvName}:-${fallback}}`;
380
+ }
381
+
373
382
  function renderEnvironmentServerDevScript(plan, component = plan.runtimes.apis[0], options = {}) {
374
383
  if (!component) {
375
384
  return renderEnvAwareShellScript(['echo "No API runtimes are configured."']);
376
385
  }
377
386
  const guardPortsScript = options.componentScript ? '"$ROOT_DIR/scripts/guard-ports.mjs"' : '"$SCRIPT_DIR/guard-ports.mjs"';
387
+ const serverPortExpression = runtimePortExpression(plan, plan.runtimes.apis, component, "SERVER_PORT");
378
388
  return renderEnvAwareShellScript([
379
389
  `node ${guardPortsScript} api`,
380
390
  "",
381
391
  ...apiDatabaseExportLines(component),
382
- `export PORT="\${${component.id.toUpperCase()}_PORT:-\${SERVER_PORT:-${component.port}}}"`,
392
+ `export PORT="${serverPortExpression}"`,
383
393
  `export TOPOGRAM_CORS_ORIGINS="\${TOPOGRAM_CORS_ORIGINS:-http://localhost:\${WEB_PORT:-${plan.ports.web}},http://127.0.0.1:\${WEB_PORT:-${plan.ports.web}}}"`,
384
394
  "",
385
395
  `cd "$ROOT_DIR/${component.dir}"`,
@@ -395,15 +405,16 @@ function renderEnvironmentWebDevScript(plan, component = plan.runtimes.webs[0],
395
405
  }
396
406
  const apiRuntime = plan.runtimes.apis.find((entry) => entry.id === component.uses_api) || plan.runtimes.apis[0];
397
407
  const guardPortsScript = options.componentScript ? '"$ROOT_DIR/scripts/guard-ports.mjs"' : '"$SCRIPT_DIR/guard-ports.mjs"';
408
+ const webPortExpression = runtimePortExpression(plan, plan.runtimes.webs, component, "WEB_PORT");
398
409
  return renderEnvAwareShellScript([
399
410
  `node ${guardPortsScript} web`,
400
411
  "",
401
412
  ...(apiRuntime ? [`export PUBLIC_TOPOGRAM_API_BASE_URL="\${PUBLIC_TOPOGRAM_API_BASE_URL:-http://localhost:\${${apiRuntime.id.toUpperCase()}_PORT:-\${SERVER_PORT:-${apiRuntime.port}}}}"`] : []),
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}}}}"`,
413
+ `export TOPOGRAM_CORS_ORIGINS="\${TOPOGRAM_CORS_ORIGINS:-http://localhost:${webPortExpression},http://127.0.0.1:${webPortExpression}}"`,
403
414
  "",
404
415
  `cd "$ROOT_DIR/${component.dir}"`,
405
416
  "npm install",
406
- `npm run dev -- --host "\${WEB_HOST:-127.0.0.1}" --port "\${${component.id.toUpperCase()}_PORT:-\${WEB_PORT:-${component.port}}}"`,
417
+ `npm run dev -- --host "\${WEB_HOST:-127.0.0.1}" --port "${webPortExpression}"`,
407
418
  ], options.componentScript ? componentScriptOptions() : {});
408
419
  }
409
420
 
@@ -450,8 +461,8 @@ ${startLines.length ? "wait" : ""}
450
461
 
451
462
  function renderEnvironmentGuardPortsScript(plan) {
452
463
  const ports = [
453
- ...plan.runtimes.apis.map((component) => ({ id: component.id, type: "api", env: `${component.id.toUpperCase()}_PORT`, fallbackEnv: "SERVER_PORT", port: component.port })),
454
- ...plan.runtimes.webs.map((component) => ({ id: component.id, type: "web", env: `${component.id.toUpperCase()}_PORT`, fallbackEnv: "WEB_PORT", port: component.port }))
464
+ ...plan.runtimes.apis.map((component, index) => ({ id: component.id, type: "api", env: `${component.id.toUpperCase()}_PORT`, fallbackEnv: index === 0 ? "SERVER_PORT" : null, port: component.port })),
465
+ ...plan.runtimes.webs.map((component, index) => ({ id: component.id, type: "web", env: `${component.id.toUpperCase()}_PORT`, fallbackEnv: index === 0 ? "WEB_PORT" : null, port: component.port }))
455
466
  ];
456
467
  return `#!/usr/bin/env node
457
468
  import net from "node:net";
@@ -461,7 +472,7 @@ const ports = ${JSON.stringify(ports, null, 2)};
461
472
  const expectedService = ${JSON.stringify(plan.runtimeReference.serviceName || "")};
462
473
 
463
474
  function effectivePort(entry) {
464
- return Number(process.env[entry.env] || process.env[entry.fallbackEnv] || entry.port);
475
+ return Number(process.env[entry.env] || (entry.fallbackEnv ? process.env[entry.fallbackEnv] : "") || entry.port);
465
476
  }
466
477
 
467
478
  function portInUse(port) {
@@ -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: "warning",
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
  };