@topogram/cli 0.3.63 → 0.3.65

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.
Files changed (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,268 @@
1
+ // @ts-check
2
+
3
+ import { loadImplementationProvider } from "../../example-implementation.js";
4
+ import { stableStringify } from "../../format.js";
5
+ import { parsePath } from "../../parser.js";
6
+ import {
7
+ formatProjectConfigErrors,
8
+ loadProjectConfig,
9
+ projectConfigOrDefault,
10
+ validateProjectConfig,
11
+ validateProjectOutputOwnership
12
+ } from "../../project-config.js";
13
+ import { resolveWorkspace } from "../../resolver.js";
14
+ import { validateProjectImplementationTrust } from "../../template-trust.js";
15
+ import { formatValidationErrors } from "../../validator.js";
16
+
17
+ /**
18
+ * @typedef {Record<string, any>} AnyRecord
19
+ */
20
+
21
+ /**
22
+ * @typedef {{ message: string, loc: any }} ValidationError
23
+ */
24
+
25
+ /**
26
+ * @returns {void}
27
+ */
28
+ export function printCheckHelp() {
29
+ console.log("Usage: topogram check [path] [--json]");
30
+ console.log("");
31
+ console.log("Validates Topogram files, project configuration, topology, generator compatibility, generator policy, output ownership, and template policy.");
32
+ console.log("");
33
+ console.log("Defaults: path is ./topogram.");
34
+ console.log("");
35
+ console.log("Examples:");
36
+ console.log(" topogram check");
37
+ console.log(" topogram check --json");
38
+ console.log(" topogram check ./topogram");
39
+ }
40
+
41
+ /**
42
+ * @param {AnyRecord} component
43
+ * @returns {{ uses_api: string|null, uses_database: string|null }}
44
+ */
45
+ function topologyComponentReferences(component) {
46
+ return {
47
+ uses_api: component.uses_api || null,
48
+ uses_database: component.uses_database || null
49
+ };
50
+ }
51
+
52
+ /**
53
+ * @param {AnyRecord} component
54
+ * @returns {unknown}
55
+ */
56
+ function topologyComponentPort(component) {
57
+ return Object.prototype.hasOwnProperty.call(component, "port") ? component.port : null;
58
+ }
59
+
60
+ /**
61
+ * @param {AnyRecord|null|undefined} config
62
+ * @returns {{ outputs: Array<AnyRecord>, runtimes: Array<AnyRecord>, edges: Array<AnyRecord> }}
63
+ */
64
+ function summarizeProjectTopology(config) {
65
+ const outputs = Object.entries(config?.outputs || {})
66
+ .map(([name, output]) => ({
67
+ name,
68
+ path: /** @type {AnyRecord} */ (output)?.path || null,
69
+ ownership: /** @type {AnyRecord} */ (output)?.ownership || null
70
+ }))
71
+ .sort((left, right) => left.name.localeCompare(right.name));
72
+ const runtimeInputs = /** @type {Array<AnyRecord>} */ (config?.topology?.runtimes || []);
73
+ const runtimes = runtimeInputs
74
+ .map((component) => ({
75
+ id: component.id,
76
+ kind: component.kind,
77
+ projection: component.projection,
78
+ generator: {
79
+ id: component.generator?.id || null,
80
+ version: component.generator?.version || null
81
+ },
82
+ port: topologyComponentPort(component),
83
+ references: topologyComponentReferences(component)
84
+ }))
85
+ .sort((left, right) => left.id.localeCompare(right.id));
86
+ /** @type {Array<AnyRecord>} */
87
+ const edges = runtimes.flatMap((component) => {
88
+ const references = [];
89
+ if (component.references.uses_api) {
90
+ references.push({
91
+ from: component.id,
92
+ to: component.references.uses_api,
93
+ type: "calls_api"
94
+ });
95
+ }
96
+ if (component.references.uses_database) {
97
+ references.push({
98
+ from: component.id,
99
+ to: component.references.uses_database,
100
+ type: "uses_database"
101
+ });
102
+ }
103
+ return references;
104
+ }).sort((left, right) => `${left.from}:${left.type}:${left.to}`.localeCompare(`${right.from}:${right.type}:${right.to}`));
105
+ return {
106
+ outputs,
107
+ runtimes,
108
+ edges
109
+ };
110
+ }
111
+
112
+ /**
113
+ * @param {AnyRecord|null|undefined} topology
114
+ * @returns {AnyRecord|null}
115
+ */
116
+ function publicProjectTopology(topology) {
117
+ if (!topology || typeof topology !== "object") {
118
+ return topology || null;
119
+ }
120
+ return {
121
+ ...Object.fromEntries(Object.entries(topology).filter(([key]) => key !== "components")),
122
+ runtimes: topology.runtimes || []
123
+ };
124
+ }
125
+
126
+ /**
127
+ * @param {AnyRecord} component
128
+ * @returns {string}
129
+ */
130
+ function formatTopologyComponent(component) {
131
+ const generator = component.generator.id
132
+ ? `${component.generator.id}${component.generator.version ? `@${component.generator.version}` : ""}`
133
+ : "unbound generator";
134
+ const port = component.port == null ? "no port" : `port ${component.port}`;
135
+ const refs = Object.entries(component.references)
136
+ .filter(([, value]) => Boolean(value))
137
+ .map(([key, value]) => `${key} ${value}`);
138
+ const suffix = refs.length ? ` -> ${refs.join(", ")}` : "";
139
+ return ` - ${component.id}: ${component.kind} ${component.projection} via ${generator} (${port})${suffix}`;
140
+ }
141
+
142
+ /**
143
+ * @param {{ outputs: Array<AnyRecord>, runtimes: Array<AnyRecord>, edges: Array<AnyRecord> }} topology
144
+ * @returns {void}
145
+ */
146
+ export function printTopologySummary(topology) {
147
+ console.log("Project topology:");
148
+ if (topology.outputs.length > 0) {
149
+ console.log(" Outputs:");
150
+ for (const output of topology.outputs) {
151
+ console.log(` - ${output.name}: ${output.path || "unset"} (${output.ownership || "unknown"})`);
152
+ }
153
+ }
154
+ if (topology.runtimes.length > 0) {
155
+ console.log(" Runtimes:");
156
+ for (const component of topology.runtimes) {
157
+ console.log(formatTopologyComponent(component));
158
+ }
159
+ }
160
+ if (topology.edges.length > 0) {
161
+ console.log(" Edges:");
162
+ for (const edge of topology.edges) {
163
+ console.log(` - ${edge.from} ${edge.type} ${edge.to}`);
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * @param {{ inputPath: string, ast: AnyRecord, resolved: AnyRecord, projectConfigInfo: AnyRecord|null|undefined, projectValidation: AnyRecord }} input
170
+ * @returns {AnyRecord}
171
+ */
172
+ export function checkSummaryPayload({ inputPath, ast, resolved, projectConfigInfo, projectValidation }) {
173
+ const files = /** @type {Array<AnyRecord>} */ (ast.files || []);
174
+ const statementCount = files.flatMap((file) => file.statements).length;
175
+ const projectInfo = projectConfigInfo || {
176
+ configPath: null,
177
+ compatibility: false,
178
+ config: { topology: null }
179
+ };
180
+ const topogramErrors = resolved.ok
181
+ ? []
182
+ : /** @type {Array<AnyRecord>} */ (resolved.validation.errors || []);
183
+ const projectErrors = /** @type {Array<AnyRecord>} */ (projectValidation.errors || []);
184
+ const resolvedTopology = summarizeProjectTopology(projectInfo.config);
185
+ return {
186
+ ok: resolved.ok && projectValidation.ok,
187
+ inputPath,
188
+ topogram: {
189
+ files: files.length,
190
+ statements: statementCount,
191
+ valid: resolved.ok
192
+ },
193
+ project: {
194
+ configPath: projectInfo.configPath,
195
+ compatibility: Boolean(projectInfo.compatibility),
196
+ valid: projectValidation.ok,
197
+ topology: publicProjectTopology(projectInfo.config.topology),
198
+ resolvedTopology
199
+ },
200
+ errors: [
201
+ ...topogramErrors.map((error) => ({
202
+ source: "topogram",
203
+ message: error.message,
204
+ loc: error.loc
205
+ })),
206
+ ...projectErrors.map((error) => ({
207
+ source: "project",
208
+ message: error.message,
209
+ loc: error.loc
210
+ }))
211
+ ]
212
+ };
213
+ }
214
+
215
+ /**
216
+ * @param {...{ ok?: boolean, errors?: ValidationError[] }|null|undefined} results
217
+ * @returns {{ ok: boolean, errors: ValidationError[] }}
218
+ */
219
+ export function combineProjectValidationResults(...results) {
220
+ /** @type {ValidationError[]} */
221
+ const errors = [];
222
+ for (const result of results) {
223
+ errors.push(...(result?.errors || []));
224
+ }
225
+ return {
226
+ ok: errors.length === 0,
227
+ errors
228
+ };
229
+ }
230
+
231
+ /**
232
+ * @param {string|null|undefined} inputPath
233
+ * @param {{ json?: boolean }} [options]
234
+ * @returns {Promise<number>}
235
+ */
236
+ export async function runCheckCommand(inputPath, options = {}) {
237
+ const topogramPath = inputPath || "./topogram";
238
+ const ast = parsePath(topogramPath);
239
+ const resolved = resolveWorkspace(ast);
240
+ const implementation = await loadImplementationProvider(topogramPath).catch(() => null);
241
+ const explicitProjectConfig = loadProjectConfig(topogramPath);
242
+ const projectConfigInfo = explicitProjectConfig ||
243
+ (implementation ? projectConfigOrDefault(topogramPath, resolved.ok ? resolved.graph : null, implementation) : null);
244
+ const projectValidation = projectConfigInfo
245
+ ? combineProjectValidationResults(
246
+ validateProjectConfig(projectConfigInfo.config, resolved.ok ? resolved.graph : null, { configDir: projectConfigInfo.configDir }),
247
+ validateProjectOutputOwnership(projectConfigInfo),
248
+ validateProjectImplementationTrust(projectConfigInfo)
249
+ )
250
+ : { ok: false, errors: [{ message: "Missing topogram.project.json or compatible topogram.implementation.json", loc: null }] };
251
+ const payload = checkSummaryPayload({ inputPath: topogramPath, ast, resolved, projectConfigInfo, projectValidation });
252
+ if (options.json) {
253
+ console.log(stableStringify(payload));
254
+ } else if (payload.ok) {
255
+ console.log(`Topogram check passed for ${topogramPath}.`);
256
+ console.log(`Validated ${payload.topogram.files} file(s) and ${payload.topogram.statements} statement(s).`);
257
+ console.log(`Project config: ${payload.project.configPath || "compatibility defaults"}`);
258
+ printTopologySummary(payload.project.resolvedTopology);
259
+ } else {
260
+ if (!resolved.ok) {
261
+ console.error(formatValidationErrors(resolved.validation));
262
+ }
263
+ if (!projectValidation.ok) {
264
+ console.error(formatProjectConfigErrors(projectValidation, projectConfigInfo?.configPath || "topogram.project.json"));
265
+ }
266
+ }
267
+ return payload.ok ? 0 : 1;
268
+ }
@@ -0,0 +1,268 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import {
7
+ catalogSourceOrDefault,
8
+ isCatalogSourceDisabled
9
+ } from "../../catalog.js";
10
+ import { LOCAL_NPMRC_ENV, localNpmrcStatus } from "../../npm-safety.js";
11
+ import { loadProjectConfig } from "../../project-config.js";
12
+ import {
13
+ buildCatalogDoctorAuth,
14
+ buildCatalogDoctorPayload
15
+ } from "./catalog.js";
16
+ import {
17
+ checkDoctorNode,
18
+ checkDoctorNpm,
19
+ checkDoctorPackageAccess,
20
+ CLI_PACKAGE_NAME,
21
+ inspectTopogramCliLockfile,
22
+ isLocalCliDependencySpec,
23
+ normalizeRegistryUrl,
24
+ NPMJS_REGISTRY,
25
+ npmConfigGet,
26
+ readInstalledCliPackageVersion,
27
+ readProjectCliDependencySpec
28
+ } from "./package.js";
29
+
30
+ /**
31
+ * @returns {void}
32
+ */
33
+ export function printDoctorHelp() {
34
+ console.log("Usage: topogram doctor [--json] [--catalog <path-or-source>]");
35
+ console.log("");
36
+ console.log("Checks local runtime, npm, public package access, CLI lockfile metadata, and catalog access.");
37
+ console.log("");
38
+ console.log("Fresh install check:");
39
+ console.log(" npm install --save-dev @topogram/cli");
40
+ console.log(" npx topogram doctor");
41
+ console.log(" npx topogram template list");
42
+ console.log(" npx topogram new ./my-app --template hello-web");
43
+ console.log("");
44
+ console.log("Related setup commands:");
45
+ console.log(" topogram setup package-auth");
46
+ console.log(" topogram setup catalog-auth");
47
+ console.log("");
48
+ console.log("Examples:");
49
+ console.log(" topogram doctor");
50
+ console.log(" topogram doctor --json");
51
+ console.log(" topogram doctor --catalog ./topograms.catalog.json");
52
+ console.log(" topogram catalog doctor");
53
+ console.log("");
54
+ console.log("Use `catalog doctor` when you only want catalog/package-access diagnostics. Use `doctor --catalog` for the full environment check plus catalog diagnostics.");
55
+ }
56
+
57
+ /**
58
+ * @param {unknown} error
59
+ * @returns {string}
60
+ */
61
+ function messageFromError(error) {
62
+ return error instanceof Error ? error.message : String(error);
63
+ }
64
+
65
+ /**
66
+ * @param {string} inputPath
67
+ * @returns {string}
68
+ */
69
+ function normalizeTopogramPath(inputPath) {
70
+ const absolute = path.resolve(inputPath);
71
+ if (path.basename(absolute) === "topogram") {
72
+ return absolute;
73
+ }
74
+ const candidate = path.join(absolute, "topogram");
75
+ return fs.existsSync(candidate) ? candidate : absolute;
76
+ }
77
+
78
+ /**
79
+ * @param {string|null} source
80
+ * @returns {{ ok: boolean, node: ReturnType<typeof checkDoctorNode>, npm: ReturnType<typeof checkDoctorNpm>, localNpmrc: ReturnType<typeof localNpmrcStatus>, packageRegistry: { required: boolean, reason: string|null, registry: string, configuredRegistry: string|null, registryConfigured: boolean, nodeAuthTokenEnv: boolean, packageName: string, packageSpec: string|null, packageAccess: { ok: boolean, checkedVersion: string|null, diagnostics: any[] } }, lockfile: ReturnType<typeof inspectTopogramCliLockfile>, catalog: ReturnType<typeof buildCatalogDoctorPayload>, diagnostics: any[], errors: string[] }}
81
+ */
82
+ export function buildDoctorPayload(source) {
83
+ const projectCliDependency = readProjectCliDependencySpec(process.cwd());
84
+ const packageRegistryRequired = !isLocalCliDependencySpec(projectCliDependency);
85
+ const node = checkDoctorNode();
86
+ const npm = checkDoctorNpm();
87
+ const configuredRegistry = npm.available ? npmConfigGet("@topogram:registry") : null;
88
+ const registryConfigured = !configuredRegistry ||
89
+ normalizeRegistryUrl(configuredRegistry) === normalizeRegistryUrl(NPMJS_REGISTRY);
90
+ const registryDiagnostics = [];
91
+ if (packageRegistryRequired && npm.available && !registryConfigured) {
92
+ registryDiagnostics.push({
93
+ code: "package_registry_registry_not_configured",
94
+ severity: "error",
95
+ message: `npm is configured to resolve @topogram packages from '${configuredRegistry}', not ${NPMJS_REGISTRY}.`,
96
+ path: ".npmrc",
97
+ suggestedFix: "Remove the custom @topogram registry config or set it to https://registry.npmjs.org, then rerun `topogram doctor`."
98
+ });
99
+ }
100
+ const packageSpec = packageRegistryRequired ? `${CLI_PACKAGE_NAME}@${readInstalledCliPackageVersion()}` : null;
101
+ const packageAccess = packageRegistryRequired && npm.available
102
+ ? checkDoctorPackageAccess(packageSpec || CLI_PACKAGE_NAME)
103
+ : packageRegistryRequired ? {
104
+ ok: false,
105
+ checkedVersion: null,
106
+ diagnostics: [{
107
+ code: "npm_not_found",
108
+ severity: "error",
109
+ message: "npm is required to inspect the Topogram CLI package.",
110
+ path: null,
111
+ suggestedFix: "Install Node.js/npm, then rerun `topogram doctor`."
112
+ }]
113
+ } : {
114
+ ok: true,
115
+ checkedVersion: null,
116
+ diagnostics: []
117
+ };
118
+ const catalog = buildDoctorCatalogPayload(source);
119
+ const lockfile = inspectTopogramCliLockfile(process.cwd());
120
+ const diagnostics = [
121
+ ...node.diagnostics,
122
+ ...npm.diagnostics,
123
+ ...registryDiagnostics,
124
+ ...packageAccess.diagnostics,
125
+ ...lockfile.diagnostics,
126
+ ...catalog.diagnostics
127
+ ];
128
+ const errors = diagnostics
129
+ .filter((diagnostic) => diagnostic.severity === "error")
130
+ .map((diagnostic) => diagnostic.message);
131
+ return {
132
+ ok: errors.length === 0,
133
+ node,
134
+ npm,
135
+ localNpmrc: localNpmrcStatus(process.cwd()),
136
+ packageRegistry: {
137
+ required: packageRegistryRequired,
138
+ reason: packageRegistryRequired ? null : `Project uses local CLI dependency '${projectCliDependency}'.`,
139
+ registry: NPMJS_REGISTRY,
140
+ configuredRegistry,
141
+ registryConfigured,
142
+ nodeAuthTokenEnv: Boolean(process.env.NODE_AUTH_TOKEN),
143
+ packageName: CLI_PACKAGE_NAME,
144
+ packageSpec,
145
+ packageAccess
146
+ },
147
+ lockfile,
148
+ catalog,
149
+ diagnostics,
150
+ errors
151
+ };
152
+ }
153
+
154
+ /**
155
+ * @param {string|null} source
156
+ * @returns {ReturnType<typeof buildCatalogDoctorPayload>}
157
+ */
158
+ function buildDoctorCatalogPayload(source) {
159
+ const resolvedSource = resolveDoctorCatalogSource(source);
160
+ if (isCatalogSourceDisabled(resolvedSource)) {
161
+ return {
162
+ ok: true,
163
+ source: resolvedSource,
164
+ auth: buildCatalogDoctorAuth(resolvedSource),
165
+ catalog: {
166
+ reachable: false,
167
+ version: null,
168
+ entries: 0
169
+ },
170
+ packages: [],
171
+ diagnostics: [{
172
+ code: "catalog_check_skipped",
173
+ severity: "warning",
174
+ message: "Catalog access check was skipped for this project.",
175
+ path: null,
176
+ suggestedFix: "Pass --catalog <source> to check a catalog explicitly."
177
+ }],
178
+ errors: []
179
+ };
180
+ }
181
+ return buildCatalogDoctorPayload(resolvedSource);
182
+ }
183
+
184
+ /**
185
+ * @param {string|null} source
186
+ * @returns {string}
187
+ */
188
+ function resolveDoctorCatalogSource(source) {
189
+ if (source) {
190
+ return source;
191
+ }
192
+ const projectConfigInfo = loadProjectConfig(normalizeTopogramPath(process.cwd()));
193
+ if (projectConfigInfo) {
194
+ const catalog = projectConfigInfo.config?.template?.catalog;
195
+ if (catalog && typeof catalog.source === "string" && catalog.source) {
196
+ return catalog.source;
197
+ }
198
+ return "none";
199
+ }
200
+ return catalogSourceOrDefault(null);
201
+ }
202
+
203
+ /**
204
+ * @param {ReturnType<typeof buildDoctorPayload>} payload
205
+ * @returns {void}
206
+ */
207
+ function printDoctorSetupGuidance(payload) {
208
+ console.log("Setup guidance:");
209
+ if (payload.packageRegistry.required) {
210
+ console.log(`- CLI package access: public @topogram packages should install from ${payload.packageRegistry.registry} without auth.`);
211
+ } else {
212
+ console.log("- CLI package auth: skipped because this project uses a local Topogram CLI dependency.");
213
+ }
214
+ if (isCatalogSourceDisabled(payload.catalog.source)) {
215
+ console.log("- Catalog auth: skipped because catalog discovery is disabled for this project.");
216
+ } else {
217
+ console.log("- Catalog auth: the default catalog is public; private catalogs should use GITHUB_TOKEN or GH_TOKEN. Local `gh auth login` is only a no-token fallback.");
218
+ }
219
+ console.log("- Template package auth: private template packages may need registry-specific npm auth during npm install.");
220
+ console.log(`- Local .npmrc: ignored by default. Use --allow-local-npmrc or ${LOCAL_NPMRC_ENV}=1 only after reviewing the file.`);
221
+ console.log("- Catalog disabled mode: TOPOGRAM_CATALOG_SOURCE=none skips catalog aliases, including the default hello-web starter.");
222
+ }
223
+
224
+ /**
225
+ * @param {ReturnType<typeof buildDoctorPayload>} payload
226
+ * @returns {void}
227
+ */
228
+ export function printDoctor(payload) {
229
+ console.log(payload.ok ? "Topogram doctor passed." : "Topogram doctor found issues.");
230
+ console.log(`Node: ${payload.node.version} (${payload.node.ok ? "ok" : `requires ${payload.node.minimum}`})`);
231
+ console.log(`npm: ${payload.npm.available ? `${payload.npm.version || "available"} (ok)` : "not found"}`);
232
+ console.log(`local .npmrc: ${payload.localNpmrc.exists ? (payload.localNpmrc.enabled ? "enabled" : "ignored") : "not found"}`);
233
+ if (payload.localNpmrc.exists) {
234
+ console.log(`local .npmrc reason: ${payload.localNpmrc.reason}`);
235
+ }
236
+ console.log(`npm registry: ${payload.packageRegistry.required ? (payload.packageRegistry.registryConfigured ? "ok" : "misconfigured") : "not required"}`);
237
+ if (payload.packageRegistry.reason) {
238
+ console.log(`npm registry reason: ${payload.packageRegistry.reason}`);
239
+ }
240
+ if (payload.packageRegistry.configuredRegistry) {
241
+ console.log(`Configured @topogram registry: ${payload.packageRegistry.configuredRegistry}`);
242
+ }
243
+ console.log(`CLI package access: ${payload.packageRegistry.required ? (payload.packageRegistry.packageAccess.ok ? `${payload.packageRegistry.packageSpec} ok` : `${payload.packageRegistry.packageSpec} failed`) : "not checked"}`);
244
+ if (payload.lockfile.checked && payload.lockfile.packageVersion) {
245
+ console.log(`CLI lockfile: ${payload.lockfile.packageVersion}${payload.lockfile.refreshRecommended ? " (refresh recommended)" : " (ok)"}`);
246
+ }
247
+ console.log(`Catalog source: ${payload.catalog.source}`);
248
+ console.log(`Catalog reachable: ${payload.catalog.catalog.reachable ? "yes" : "no"}`);
249
+ if (payload.catalog.catalog.reachable) {
250
+ console.log(`Catalog entries: ${payload.catalog.catalog.entries}`);
251
+ const failedPackages = payload.catalog.packages.filter((item) => !item.ok).length;
252
+ console.log(`Catalog package access: ${failedPackages === 0 ? "ok" : `${failedPackages} failed`}`);
253
+ }
254
+ if (payload.catalog.source !== "none" || payload.catalog.catalog.reachable || payload.packageRegistry.required) {
255
+ console.log("Project provenance: run `topogram source status --local` for catalog, template, trust, and baseline details.");
256
+ }
257
+ printDoctorSetupGuidance(payload);
258
+ if (payload.diagnostics.length > 0) {
259
+ console.log("Diagnostics:");
260
+ for (const diagnostic of payload.diagnostics) {
261
+ const label = diagnostic.severity === "warning" ? "Warning" : "Error";
262
+ console.log(`- ${label}: ${diagnostic.message}`);
263
+ if (diagnostic.suggestedFix) {
264
+ console.log(` Fix: ${diagnostic.suggestedFix}`);
265
+ }
266
+ }
267
+ }
268
+ }
@@ -0,0 +1,149 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { loadImplementationProvider } from "../../example-implementation.js";
7
+ import { stableStringify } from "../../format.js";
8
+ import { buildOutputFiles, generateWorkspace } from "../../generator.js";
9
+ import { parsePath } from "../../parser.js";
10
+ import {
11
+ formatProjectConfigErrors,
12
+ loadProjectConfig,
13
+ projectConfigOrDefault,
14
+ validateProjectConfig
15
+ } from "../../project-config.js";
16
+ import { resolveWorkspace } from "../../resolver.js";
17
+ import { formatValidationErrors } from "../../validator.js";
18
+ import {
19
+ assertProjectOutputAllowsWrite,
20
+ assertSafeGeneratedOutputDir,
21
+ GENERATED_OUTPUT_SENTINEL,
22
+ generatedOutputSentinel,
23
+ topogramInputPathForGeneration
24
+ } from "../output-safety.js";
25
+
26
+ const IMPLEMENTATION_PROVIDER_TARGETS = new Set([
27
+ "persistence-scaffold",
28
+ "hono-server",
29
+ "express-server",
30
+ "sveltekit-app",
31
+ "environment-plan",
32
+ "environment-bundle",
33
+ "deployment-plan",
34
+ "deployment-bundle",
35
+ "runtime-smoke-plan",
36
+ "runtime-smoke-bundle",
37
+ "runtime-check-plan",
38
+ "runtime-check-bundle",
39
+ "compile-check-plan",
40
+ "compile-check-bundle",
41
+ "app-bundle-plan",
42
+ "app-bundle",
43
+ "native-parity-plan",
44
+ "native-parity-bundle"
45
+ ]);
46
+
47
+ const IMPLEMENTATION_OPTIONAL_TARGETS = new Set([
48
+ "app-bundle-plan",
49
+ "app-bundle",
50
+ "environment-plan",
51
+ "environment-bundle",
52
+ "compile-check-plan",
53
+ "compile-check-bundle"
54
+ ]);
55
+
56
+ /**
57
+ * @param {string} target
58
+ * @returns {boolean}
59
+ */
60
+ function targetRequiresImplementationProvider(target) {
61
+ return IMPLEMENTATION_PROVIDER_TARGETS.has(target);
62
+ }
63
+
64
+ /**
65
+ * @param {{
66
+ * inputPath: string,
67
+ * projectRoot: string,
68
+ * target: string,
69
+ * write?: boolean,
70
+ * outDir?: string|null,
71
+ * selectors?: Record<string, any>,
72
+ * outputSelectors?: Record<string, any>,
73
+ * profileId?: string|null,
74
+ * fromSnapshotPath?: string|null,
75
+ * fromTopogramPath?: string|null
76
+ * }} options
77
+ * @returns {Promise<number>}
78
+ */
79
+ export async function runEmitCommand(options) {
80
+ const ast = parsePath(options.inputPath);
81
+ const explicitProjectConfig = loadProjectConfig(options.projectRoot) || loadProjectConfig(options.inputPath);
82
+ const shouldLoadImplementation = targetRequiresImplementationProvider(options.target) &&
83
+ (!IMPLEMENTATION_OPTIONAL_TARGETS.has(options.target) || Boolean(explicitProjectConfig?.config?.implementation));
84
+ const implementation = shouldLoadImplementation
85
+ ? await loadImplementationProvider(explicitProjectConfig?.configDir || options.projectRoot)
86
+ : null;
87
+ const resolvedForConfig = targetRequiresImplementationProvider(options.target) || explicitProjectConfig
88
+ ? resolveWorkspace(ast)
89
+ : null;
90
+ if (resolvedForConfig && !resolvedForConfig.ok) {
91
+ console.error(formatValidationErrors(resolvedForConfig.validation));
92
+ return 1;
93
+ }
94
+ const projectConfigInfo = resolvedForConfig
95
+ ? (explicitProjectConfig || projectConfigOrDefault(options.projectRoot, resolvedForConfig.graph, implementation))
96
+ : null;
97
+ const projectConfigValidation = projectConfigInfo
98
+ ? validateProjectConfig(projectConfigInfo.config, resolvedForConfig?.graph || null, { configDir: projectConfigInfo.configDir })
99
+ : { ok: true, errors: [] };
100
+ if (!projectConfigValidation.ok) {
101
+ console.error(formatProjectConfigErrors(projectConfigValidation, projectConfigInfo?.configPath || "topogram.project.json"));
102
+ return 1;
103
+ }
104
+
105
+ const result = generateWorkspace(ast, {
106
+ target: options.target,
107
+ ...(options.selectors || {}),
108
+ profileId: options.profileId,
109
+ fromSnapshot: options.fromSnapshotPath ? JSON.parse(fs.readFileSync(options.fromSnapshotPath, "utf8")) : null,
110
+ fromSnapshotPath: options.fromSnapshotPath,
111
+ fromTopogramPath: options.fromTopogramPath,
112
+ topogramInputPath: topogramInputPathForGeneration(options.inputPath),
113
+ implementation,
114
+ projectConfig: projectConfigInfo?.config || null,
115
+ configDir: projectConfigInfo?.configDir || options.projectRoot,
116
+ projectRoot: projectConfigInfo?.configDir || options.projectRoot
117
+ });
118
+ if (!result.ok) {
119
+ console.error(formatValidationErrors(result.validation));
120
+ return 1;
121
+ }
122
+
123
+ if (options.write) {
124
+ const resolvedOutDir = path.resolve(options.outDir || "artifacts");
125
+ assertProjectOutputAllowsWrite(projectConfigInfo, resolvedOutDir);
126
+ assertSafeGeneratedOutputDir(resolvedOutDir, options.inputPath);
127
+ const outputFiles = buildOutputFiles(result, options.outputSelectors || {});
128
+ outputFiles.unshift({
129
+ path: GENERATED_OUTPUT_SENTINEL,
130
+ contents: generatedOutputSentinel(options.target)
131
+ });
132
+ fs.rmSync(resolvedOutDir, { recursive: true, force: true });
133
+ fs.mkdirSync(resolvedOutDir, { recursive: true });
134
+
135
+ for (const file of outputFiles) {
136
+ const destination = path.join(resolvedOutDir, file.path);
137
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
138
+ const contents =
139
+ typeof file.contents === "string" ? file.contents : `${stableStringify(file.contents)}\n`;
140
+ fs.writeFileSync(destination, contents, "utf8");
141
+ }
142
+
143
+ console.log(`Wrote ${outputFiles.length} file(s) to ${resolvedOutDir}`);
144
+ return 0;
145
+ }
146
+
147
+ console.log(typeof result.artifact === "string" ? result.artifact : stableStringify(result.artifact));
148
+ return 0;
149
+ }