@topogram/cli 0.3.59 → 0.3.60

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.59",
3
+ "version": "0.3.60",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -182,7 +182,8 @@ function buildContractsForContext(context) {
182
182
  }
183
183
  if (surface === "native" || surface === "ios_surface" || surface === "android_surface") {
184
184
  return {
185
- uiSurface: generateUiSurfaceContract(context.graph, { ...(context.options || {}), projectionId })
185
+ uiSurface: generateUiSurfaceContract(context.graph, { ...(context.options || {}), projectionId }),
186
+ api: generateApiContractGraph(context.graph, {})
186
187
  };
187
188
  }
188
189
  return {};
@@ -1,4 +1,5 @@
1
1
  import {
2
+ generateNativeBundle,
2
3
  generateServerBundle,
3
4
  generateWebBundle,
4
5
  getDefaultEnvironmentProjections,
@@ -49,6 +50,12 @@ function buildCompileCheckPlan(graph, options = {}) {
49
50
  command: "npm run build"
50
51
  }
51
52
  ]);
53
+ const nativeChecks = topology.nativeRuntimes.map((component, index) => ({
54
+ id: index === 0 ? "native_swift_build" : `native_swift_build_${component.id}`,
55
+ cwd: topology.nativeDir(component),
56
+ install: null,
57
+ command: "swift build"
58
+ }));
52
59
  return {
53
60
  type: "compile_check_plan",
54
61
  name: compileCheckName(graph, options),
@@ -65,7 +72,7 @@ function buildCompileCheckPlan(graph, options = {}) {
65
72
  generator: runtime.generator
66
73
  }))
67
74
  },
68
- checks: [...apiChecks, ...webChecks]
75
+ checks: [...apiChecks, ...webChecks, ...nativeChecks]
69
76
  };
70
77
  }
71
78
 
@@ -92,13 +99,14 @@ ${runtimeReference.environment.envExample || ""}
92
99
  function renderCompileCheckReadme(graph, options = {}) {
93
100
  return `# ${compileCheckName(graph, options).replace("Plan", "Bundle")}
94
101
 
95
- This bundle verifies that the generated server and web projects typecheck and build.
102
+ This bundle verifies that generated server, web, and native projects compile.
96
103
 
97
104
  ## Checks
98
105
 
99
106
  - server TypeScript check
100
107
  - web TypeScript check
101
108
  - web production build
109
+ - native Swift build
102
110
 
103
111
  ## Usage
104
112
 
@@ -124,15 +132,17 @@ function renderCompileCheckScript(plan) {
124
132
  ""
125
133
  ];
126
134
  if (plan.checks.length === 0) {
127
- lines.push('echo "No API or web runtimes are configured; compile check is a no-op."');
135
+ lines.push('echo "No API, web, or native runtimes are configured; compile check is a no-op."');
128
136
  }
129
137
  for (const check of plan.checks) {
130
138
  const label = check.id.includes("web")
131
139
  ? check.id.includes("build") ? "Building generated web" : "Checking generated web"
132
- : "Checking generated server";
140
+ : check.id.includes("native") ? "Building generated native app" : "Checking generated server";
133
141
  lines.push(`echo "${label} (${check.cwd})..."`);
134
- lines.push(`echo "Installing dependencies (${check.cwd})..."`);
135
- lines.push(`(cd "$ROOT_DIR/${check.cwd}" && ${check.install})`);
142
+ if (check.install) {
143
+ lines.push(`echo "Installing dependencies (${check.cwd})..."`);
144
+ lines.push(`(cd "$ROOT_DIR/${check.cwd}" && ${check.install})`);
145
+ }
136
146
  lines.push(`echo "Running ${check.command} (${check.cwd})..."`);
137
147
  lines.push(`(cd "$ROOT_DIR/${check.cwd}" && ${check.command})`);
138
148
  lines.push("");
@@ -158,6 +168,10 @@ export function generateCompileCheckBundle(graph, options = {}) {
158
168
  const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
159
169
  mergeBundleFiles(files, topology.webDir(component), webBundle);
160
170
  }
171
+ for (const component of topology.nativeRuntimes) {
172
+ const nativeBundle = generateNativeBundle(graph, component.projection.id, { ...options, component });
173
+ mergeBundleFiles(files, topology.nativeDir(component), nativeBundle);
174
+ }
161
175
  return files;
162
176
  }
163
177
 
@@ -2,6 +2,7 @@ import { generateDbLifecyclePlan } from "../surfaces/databases/lifecycle-shared.
2
2
  import { getExampleImplementation } from "../../example-implementation.js";
3
3
  import {
4
4
  generateDbBundle,
5
+ generateNativeBundle,
5
6
  generateServerBundle,
6
7
  generateWebBundle,
7
8
  dbEnvVarsForComponent,
@@ -110,6 +111,7 @@ function buildEnvironmentPlan(graph, options = {}) {
110
111
  server: topology.primaryApi ? topology.serviceDir(topology.primaryApi) : null,
111
112
  web: topology.primaryWeb ? topology.webDir(topology.primaryWeb) : null,
112
113
  db: topology.primaryDb ? topology.dbDir(topology.primaryDb) : null,
114
+ native: topology.nativeRuntimes[0] ? topology.nativeDir(topology.nativeRuntimes[0]) : null,
113
115
  scripts: "scripts"
114
116
  },
115
117
  runtimes: {
@@ -130,6 +132,12 @@ function buildEnvironmentPlan(graph, options = {}) {
130
132
  dir: topology.webDir(component),
131
133
  uses_api: component.api
132
134
  })),
135
+ natives: topology.nativeRuntimes.map((component) => ({
136
+ id: component.id,
137
+ projection: component.projection.id,
138
+ dir: topology.nativeDir(component),
139
+ uses_api: component.api
140
+ })),
133
141
  databases: topology.dbRuntimes.map((component) => ({
134
142
  id: component.id,
135
143
  projection: component.projection.id,
@@ -273,6 +281,7 @@ function renderEnvironmentReadme(plan) {
273
281
  const hasDb = plan.runtimes.databases.length > 0;
274
282
  const hasApi = plan.runtimes.apis.length > 0;
275
283
  const hasWeb = plan.runtimes.webs.length > 0;
284
+ const hasNative = plan.runtimes.natives.length > 0;
276
285
  const localProcessNotes = !hasDb
277
286
  ? "- This bundle has no generated database surface."
278
287
  : plan.projections.db.engine === "sqlite"
@@ -294,7 +303,7 @@ ${localProcessNotes}
294
303
 
295
304
  This bundle packages the generated runtime into one local environment:
296
305
 
297
- ${hasApi ? "- `services/<api-id>/`: generated API service scaffolds\n" : ""}${hasWeb ? `- \`web/<web-id>/\`: generated ${plan.runtimeProfiles.web === "react" ? "Vite + React Router" : plan.runtimeProfiles.web === "vanilla" ? "vanilla HTML/CSS/JS" : "SvelteKit"} web scaffolds\n` : ""}${hasDb ? "- `db/<db-id>/`: generated DB lifecycle bundles\n" : ""}${plan.files.dockerCompose ? `- \`${plan.files.dockerCompose}\`: local Postgres container` : hasDb ? (plan.projections.db.engine === "sqlite" ? "- local SQLite file orchestration (no Docker files generated)" : "- local-process Postgres orchestration (no Docker files generated)") : "- no DB orchestration is generated"}
306
+ ${hasApi ? "- `services/<api-id>/`: generated API service scaffolds\n" : ""}${hasWeb ? `- \`web/<web-id>/\`: generated ${plan.runtimeProfiles.web === "react" ? "Vite + React Router" : plan.runtimeProfiles.web === "vanilla" ? "vanilla HTML/CSS/JS" : "SvelteKit"} web scaffolds\n` : ""}${hasNative ? "- `native/<native-id>/`: generated native app scaffolds\n" : ""}${hasDb ? "- `db/<db-id>/`: generated DB lifecycle bundles\n" : ""}${plan.files.dockerCompose ? `- \`${plan.files.dockerCompose}\`: local Postgres container` : hasDb ? (plan.projections.db.engine === "sqlite" ? "- local SQLite file orchestration (no Docker files generated)" : "- local-process Postgres orchestration (no Docker files generated)") : "- no DB orchestration is generated"}
298
307
 
299
308
  ## Quick Start
300
309
 
@@ -318,6 +327,7 @@ ${dockerSection}
318
327
 
319
328
  - ${hasApi && hasDb ? `The generated server expects ${plan.projections.db.engine === "sqlite" ? "SQLite plus Prisma." : "Postgres plus Prisma."}` : hasApi ? "The generated server is stateless." : "No server surface is generated."}
320
329
  - ${hasWeb && hasApi ? "The generated web app talks to `PUBLIC_TOPOGRAM_API_BASE_URL`." : hasWeb ? "The generated web app is standalone." : "No web surface is generated."}
330
+ - ${hasNative ? "Native app scaffolds use the same UI surface contracts as web surfaces." : "No native surface is generated."}
321
331
  - If \`.env\` is missing, generated scripts fall back to \`.env.example\`.
322
332
  - The DB lifecycle scripts remain the source of truth for greenfield bootstrap and brownfield migration.
323
333
  `;
@@ -635,6 +645,12 @@ export function generateEnvironmentBundle(graph, options = {}) {
635
645
  [topology.webDir(component)]: webBundle
636
646
  });
637
647
  }
648
+ for (const component of topology.nativeRuntimes) {
649
+ const nativeBundle = generateNativeBundle(graph, component.projection.id, { ...options, component });
650
+ mergeNamedBundles(files, {
651
+ [topology.nativeDir(component)]: nativeBundle
652
+ });
653
+ }
638
654
  for (const component of topology.dbRuntimes) {
639
655
  const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
640
656
  mergeNamedBundles(files, {
@@ -43,16 +43,19 @@ import { defaultProjectConfigForGraph, validateProjectConfig } from "../../proje
43
43
  * @property {RuntimeComponent[]} runtimes
44
44
  * @property {RuntimeComponent[]} apiRuntimes
45
45
  * @property {RuntimeComponent[]} webRuntimes
46
+ * @property {RuntimeComponent[]} nativeRuntimes
46
47
  * @property {RuntimeComponent[]} dbRuntimes
47
48
  * @property {RuntimeComponent[]} components Legacy alias for runtimes.
48
49
  * @property {RuntimeComponent[]} apiComponents Legacy alias for apiRuntimes.
49
50
  * @property {RuntimeComponent[]} webComponents Legacy alias for webRuntimes.
51
+ * @property {RuntimeComponent[]} nativeComponents Legacy alias for nativeRuntimes.
50
52
  * @property {RuntimeComponent[]} dbComponents Legacy alias for dbRuntimes.
51
53
  * @property {RuntimeComponent|null} primaryApi
52
54
  * @property {RuntimeComponent|null} primaryWeb
53
55
  * @property {RuntimeComponent|null} primaryDb
54
56
  * @property {(component: RuntimeComponent) => string} serviceDir
55
57
  * @property {(component: RuntimeComponent) => string} webDir
58
+ * @property {(component: RuntimeComponent) => string} nativeDir
56
59
  * @property {(component: RuntimeComponent) => string} dbDir
57
60
  */
58
61
 
@@ -371,6 +374,29 @@ export function generateWebBundle(graph, projectionId, options = {}) {
371
374
  }).files;
372
375
  }
373
376
 
377
+ /**
378
+ * @param {ResolvedGraph} graph
379
+ * @param {string} projectionId
380
+ * @param {RuntimeGenerationOptions} [options]
381
+ * @returns {any}
382
+ */
383
+ export function generateNativeBundle(graph, projectionId, options = {}) {
384
+ const topology = resolveRuntimeTopology(graph, options);
385
+ const runtime = options.runtime || options.component || topology.nativeRuntimes.find((entry) => entry.projection.id === projectionId);
386
+ if (!runtime) {
387
+ throw new Error(`No native runtime found for projection '${projectionId}'`);
388
+ }
389
+ return generateWithComponentGenerator({
390
+ graph,
391
+ projection: runtime.projection,
392
+ runtime,
393
+ component: runtime,
394
+ topology,
395
+ implementation: options.implementation || null,
396
+ options: { ...options, projectionId }
397
+ }).files;
398
+ }
399
+
374
400
  /**
375
401
  * @param {ResolvedGraph} graph
376
402
  * @param {string} projectionId
@@ -450,7 +476,7 @@ function decorateRuntimes(graph, config) {
450
476
  runtime.databaseRuntime = byId.get(runtime.database) || null;
451
477
  runtime.databaseComponent = runtime.databaseRuntime;
452
478
  }
453
- if (runtime.kind === "web_surface" && runtime.api) {
479
+ if (["web_surface", "ios_surface", "android_surface"].includes(runtime.kind) && runtime.api) {
454
480
  runtime.apiRuntime = byId.get(runtime.api) || null;
455
481
  runtime.apiComponent = runtime.apiRuntime;
456
482
  }
@@ -475,6 +501,7 @@ export function resolveRuntimeTopology(graph, options = {}) {
475
501
  const runtimes = decorateRuntimes(graph, config);
476
502
  const apiRuntimes = runtimes.filter((runtime) => runtime.kind === "api_service");
477
503
  const webRuntimes = runtimes.filter((runtime) => runtime.kind === "web_surface");
504
+ const nativeRuntimes = runtimes.filter((runtime) => runtime.kind === "ios_surface" || runtime.kind === "android_surface");
478
505
  const dbRuntimes = runtimes.filter((runtime) => runtime.kind === "database");
479
506
  const primaryApi = apiRuntimes[0] || null;
480
507
  const primaryWeb = webRuntimes[0] || null;
@@ -486,9 +513,11 @@ export function resolveRuntimeTopology(graph, options = {}) {
486
513
  components: runtimes,
487
514
  apiRuntimes,
488
515
  webRuntimes,
516
+ nativeRuntimes,
489
517
  dbRuntimes,
490
518
  apiComponents: apiRuntimes,
491
519
  webComponents: webRuntimes,
520
+ nativeComponents: nativeRuntimes,
492
521
  dbComponents: dbRuntimes,
493
522
  primaryApi,
494
523
  primaryWeb,
@@ -499,6 +528,9 @@ export function resolveRuntimeTopology(graph, options = {}) {
499
528
  webDir(component) {
500
529
  return `web/${component.id}`;
501
530
  },
531
+ nativeDir(component) {
532
+ return `native/${component.id}`;
533
+ },
502
534
  dbDir(component) {
503
535
  return `db/${component.id}`;
504
536
  }