@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,564 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import {
7
+ isGeneratorCompatible,
8
+ resolveGeneratorManifestForBinding,
9
+ validateGeneratorManifest
10
+ } from "../generator/registry.js";
11
+ import { validateProjectGeneratorPolicy } from "../generator-policy.js";
12
+
13
+ /**
14
+ * @typedef {Object} GeneratorBinding
15
+ * @property {string} id
16
+ * @property {string} version
17
+ * @property {string} [package]
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} RuntimeTopologyRuntime
22
+ * @property {string} id
23
+ * @property {"api_service"|"web_surface"|"ios_surface"|"android_surface"|"database"} kind
24
+ * @property {string} projection
25
+ * @property {GeneratorBinding} generator
26
+ * @property {number|null} [port]
27
+ * @property {string} [uses_api]
28
+ * @property {string} [uses_database]
29
+ * @property {Record<string, string>} [env]
30
+ */
31
+
32
+ /**
33
+ * @typedef {Object} ProjectConfig
34
+ * @property {string} version
35
+ * @property {Record<string, { path: string, ownership: "generated"|"maintained" }>} outputs
36
+ * @property {{ runtimes: RuntimeTopologyRuntime[] }} topology
37
+ * @property {{ id?: string, module?: string, export?: string, implementation_module?: string, implementation_export?: string }} [implementation]
38
+ * @property {Record<string, any>} [template]
39
+ */
40
+
41
+ /**
42
+ * @typedef {Object} ProjectConfigInfo
43
+ * @property {ProjectConfig} config
44
+ * @property {string|null} configPath
45
+ * @property {string} configDir
46
+ * @property {boolean} [compatibility]
47
+ */
48
+
49
+ /**
50
+ * @typedef {Object} ValidationError
51
+ * @property {string} message
52
+ * @property {any} loc
53
+ */
54
+
55
+ const PROJECT_CONFIG_FILE = "topogram.project.json";
56
+ const LEGACY_IMPLEMENTATION_FILE = "topogram.implementation.json";
57
+ const GENERATED_OUTPUT_SENTINEL = ".topogram-generated.json";
58
+ const IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
59
+
60
+ /**
61
+ * @param {string|null|undefined} root
62
+ * @returns {string|null}
63
+ */
64
+ function normalizeSearchRoot(root) {
65
+ if (!root) {
66
+ return null;
67
+ }
68
+ const absolute = path.resolve(root);
69
+ try {
70
+ return fs.realpathSync(absolute);
71
+ } catch {
72
+ return absolute;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @param {string} root
78
+ * @returns {string}
79
+ */
80
+ function normalizeRoot(root) {
81
+ return String(root || "").replace(/\\/g, "/");
82
+ }
83
+
84
+ /**
85
+ * @param {string} filePath
86
+ * @returns {string}
87
+ */
88
+ function resolveComparablePath(filePath) {
89
+ const absolute = path.resolve(filePath);
90
+ try {
91
+ return fs.existsSync(absolute)
92
+ ? fs.realpathSync(absolute)
93
+ : path.join(fs.realpathSync(path.dirname(absolute)), path.basename(absolute));
94
+ } catch {
95
+ return absolute;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @param {string} filePath
101
+ * @returns {any}
102
+ */
103
+ function readJson(filePath) {
104
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
105
+ }
106
+
107
+ /**
108
+ * @param {string} oldName
109
+ * @param {string} newName
110
+ * @param {string} example
111
+ * @returns {string}
112
+ */
113
+ function renameDiagnostic(oldName, newName, example) {
114
+ return `${oldName} was renamed to ${newName}. Example fix: ${example}`;
115
+ }
116
+
117
+ /**
118
+ * @param {string} root
119
+ * @param {string} fileName
120
+ * @returns {{ config: any, configPath: string, configDir: string }|null}
121
+ */
122
+ function findConfigFile(root, fileName) {
123
+ let current = normalizeSearchRoot(root);
124
+ while (current && current !== path.dirname(current)) {
125
+ const candidate = path.join(current, fileName);
126
+ if (fs.existsSync(candidate)) {
127
+ return {
128
+ config: readJson(candidate),
129
+ configPath: candidate,
130
+ configDir: path.dirname(candidate)
131
+ };
132
+ }
133
+ current = path.dirname(current);
134
+ }
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * @param {string} root
140
+ * @returns {{ config: any, configPath: string, configDir: string }|null}
141
+ */
142
+ export function findProjectConfig(root) {
143
+ return findConfigFile(root, PROJECT_CONFIG_FILE);
144
+ }
145
+
146
+ /**
147
+ * @param {string} root
148
+ * @returns {{ config: any, configPath: string, configDir: string }|null}
149
+ */
150
+ export function findLegacyImplementationConfig(root) {
151
+ return findConfigFile(root, LEGACY_IMPLEMENTATION_FILE);
152
+ }
153
+
154
+ /**
155
+ * @param {Record<string, any>} graph
156
+ * @param {Record<string, any>|null} [implementation]
157
+ * @returns {ProjectConfig}
158
+ */
159
+ export function defaultProjectConfigForGraph(graph, implementation = null) {
160
+ const runtimeReference = implementation?.runtime?.reference || {};
161
+ /** @type {Array<Record<string, any>>} */
162
+ const projections = graph.byKind.projection || [];
163
+ const apiProjection = projections.find((projection) => (projection.http || []).length > 0 || projection.type === "api_contract");
164
+ const webProjection =
165
+ projections.find((projection) => projection.id === "proj_web") ||
166
+ projections.find((projection) => projection.type === "web_surface");
167
+ const dbProjection =
168
+ projections.find((projection) => projection.id === runtimeReference.localDbProjectionId) ||
169
+ projections.find((projection) => projection.type === "db_contract");
170
+ const ports = runtimeReference.ports || {};
171
+ const dbProfile = (dbProjection?.generatorDefaults || []).find((/** @type {Record<string, any>} */ entry) => entry.key === "profile")?.value;
172
+ const dbGenerator = dbProfile === "sqlite_sql" ? "topogram/sqlite" : "topogram/postgres";
173
+ const dbRuntimeId = dbProfile === "sqlite_sql" ? "app_sqlite" : "app_postgres";
174
+ /** @type {RuntimeTopologyRuntime[]} */
175
+ const runtimes = [
176
+ ...(apiProjection
177
+ ? [{
178
+ id: "app_api",
179
+ kind: /** @type {"api_service"} */ ("api_service"),
180
+ projection: apiProjection.id,
181
+ generator: { id: "topogram/hono", version: "1" },
182
+ port: ports.server || 3000,
183
+ ...(dbProjection ? { uses_database: dbRuntimeId } : {})
184
+ }]
185
+ : []),
186
+ ...(webProjection
187
+ ? [{
188
+ id: "app_sveltekit",
189
+ kind: /** @type {"web_surface"} */ ("web_surface"),
190
+ projection: webProjection.id,
191
+ generator: { id: "topogram/sveltekit", version: "1" },
192
+ port: ports.web || 5173,
193
+ ...(apiProjection ? { uses_api: "app_api" } : {})
194
+ }]
195
+ : []),
196
+ ...(dbProjection
197
+ ? [{
198
+ id: dbRuntimeId,
199
+ kind: /** @type {"database"} */ ("database"),
200
+ projection: dbProjection.id,
201
+ generator: { id: dbGenerator, version: "1" },
202
+ port: dbProfile === "sqlite_sql" ? null : 5432
203
+ }]
204
+ : [])
205
+ ];
206
+
207
+ return {
208
+ version: "0.1",
209
+ implementation: implementation?.exampleId
210
+ ? {
211
+ id: implementation.exampleId
212
+ }
213
+ : undefined,
214
+ outputs: {
215
+ app: {
216
+ path: "./app",
217
+ ownership: "generated"
218
+ }
219
+ },
220
+ topology: {
221
+ runtimes
222
+ }
223
+ };
224
+ }
225
+
226
+ /**
227
+ * @param {string} root
228
+ * @returns {ProjectConfigInfo|null}
229
+ */
230
+ export function loadProjectConfig(root) {
231
+ const found = findProjectConfig(root);
232
+ if (!found) {
233
+ return null;
234
+ }
235
+ return {
236
+ ...found,
237
+ config: found.config,
238
+ compatibility: false
239
+ };
240
+ }
241
+
242
+ /**
243
+ * @param {string} root
244
+ * @param {Record<string, any>|null} [graph]
245
+ * @param {Record<string, any>|null} [implementation]
246
+ * @returns {ProjectConfigInfo|null}
247
+ */
248
+ export function projectConfigOrDefault(root, graph = null, implementation = null) {
249
+ const found = loadProjectConfig(root);
250
+ if (found) {
251
+ return found;
252
+ }
253
+ if (!graph) {
254
+ return null;
255
+ }
256
+ return {
257
+ config: defaultProjectConfigForGraph(graph, implementation),
258
+ configPath: null,
259
+ configDir: path.dirname(path.resolve(root)),
260
+ compatibility: true
261
+ };
262
+ }
263
+
264
+ /**
265
+ * @param {ValidationError[]} errors
266
+ * @param {string} message
267
+ * @param {any} [loc]
268
+ * @returns {void}
269
+ */
270
+ function pushError(errors, message, loc = null) {
271
+ errors.push({ message, loc });
272
+ }
273
+
274
+ /**
275
+ * @param {Record<string, any>} graph
276
+ * @returns {Map<string, Record<string, any>>}
277
+ */
278
+ function projectionById(graph) {
279
+ /** @type {Array<Record<string, any>>} */
280
+ const projections = graph?.byKind?.projection || [];
281
+ return new Map(projections.map((projection) => [projection.id, projection]));
282
+ }
283
+
284
+ /**
285
+ * @param {ValidationError[]} errors
286
+ * @param {any} config
287
+ * @returns {void}
288
+ */
289
+ function validateOutputConfig(errors, config) {
290
+ if (!config.outputs || typeof config.outputs !== "object" || Array.isArray(config.outputs)) {
291
+ pushError(errors, "topogram.project.json outputs must be an object");
292
+ return;
293
+ }
294
+ for (const [name, output] of Object.entries(config.outputs)) {
295
+ if (!output || typeof output !== "object" || Array.isArray(output)) {
296
+ pushError(errors, `Output '${name}' must be an object`);
297
+ continue;
298
+ }
299
+ if (!["generated", "maintained"].includes(output.ownership)) {
300
+ pushError(errors, `Output '${name}' ownership must be generated or maintained`);
301
+ }
302
+ if (typeof output.path !== "string" || output.path.length === 0) {
303
+ pushError(errors, `Output '${name}' path must be a non-empty string`);
304
+ }
305
+ }
306
+ }
307
+
308
+ /**
309
+ * @param {any} runtime
310
+ * @returns {string}
311
+ */
312
+ function runtimeLabel(runtime) {
313
+ return runtime?.id ? `Runtime '${runtime.id}'` : "Topology runtime";
314
+ }
315
+
316
+ /**
317
+ * @param {ValidationError[]} errors
318
+ * @param {any} runtime
319
+ * @param {Set<string>} seenIds
320
+ * @returns {boolean}
321
+ */
322
+ function validateRuntimeShape(errors, runtime, seenIds) {
323
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
324
+ pushError(errors, "Topology runtime must be an object");
325
+ return false;
326
+ }
327
+ if (typeof runtime.id !== "string" || !IDENTIFIER_PATTERN.test(runtime.id)) {
328
+ pushError(errors, `${runtimeLabel(runtime)} id must match ${IDENTIFIER_PATTERN}`);
329
+ } else if (seenIds.has(runtime.id)) {
330
+ pushError(errors, `Duplicate topology runtime id '${runtime.id}'`);
331
+ } else {
332
+ seenIds.add(runtime.id);
333
+ }
334
+ if (runtime.type != null) {
335
+ pushError(errors, `${runtimeLabel(runtime)} ${renameDiagnostic("'type'", "'kind'", `"kind": "api_service"`)}`);
336
+ }
337
+ if (runtime.database != null) {
338
+ pushError(errors, `${runtimeLabel(runtime)} ${renameDiagnostic("'database'", "'uses_database'", `"uses_database": "app_db"`)}`);
339
+ }
340
+ if (runtime.api != null) {
341
+ pushError(errors, `${runtimeLabel(runtime)} ${renameDiagnostic("'api'", "'uses_api'", `"uses_api": "app_api"`)}`);
342
+ }
343
+ if (!["api_service", "web_surface", "ios_surface", "android_surface", "database"].includes(runtime.kind)) {
344
+ pushError(errors, `${runtimeLabel(runtime)} kind must be api_service, web_surface, ios_surface, android_surface, or database`);
345
+ }
346
+ if (typeof runtime.projection !== "string" || runtime.projection.length === 0) {
347
+ pushError(errors, `${runtimeLabel(runtime)} projection must be a non-empty string`);
348
+ }
349
+ if (!runtime.generator || typeof runtime.generator !== "object") {
350
+ pushError(errors, `${runtimeLabel(runtime)} generator must be an object`);
351
+ } else {
352
+ if (typeof runtime.generator.id !== "string" || runtime.generator.id.length === 0) {
353
+ pushError(errors, `${runtimeLabel(runtime)} generator.id must be a non-empty string`);
354
+ }
355
+ if (typeof runtime.generator.version !== "string" || runtime.generator.version.length === 0) {
356
+ pushError(errors, `${runtimeLabel(runtime)} generator.version must be a non-empty string`);
357
+ }
358
+ if (runtime.generator.package != null && (typeof runtime.generator.package !== "string" || runtime.generator.package.length === 0)) {
359
+ pushError(errors, `${runtimeLabel(runtime)} generator.package must be a non-empty string when provided`);
360
+ }
361
+ }
362
+ if (runtime.port != null && (!Number.isInteger(runtime.port) || runtime.port <= 0 || runtime.port > 65535)) {
363
+ pushError(errors, `${runtimeLabel(runtime)} port must be an integer from 1 to 65535`);
364
+ }
365
+ return true;
366
+ }
367
+
368
+ /**
369
+ * @param {ValidationError[]} errors
370
+ * @param {RuntimeTopologyRuntime} runtime
371
+ * @param {Map<string, Record<string, any>>} projections
372
+ * @param {{ configDir?: string|null, rootDir?: string|null }} [options]
373
+ * @returns {void}
374
+ */
375
+ function validateRuntimeCompatibility(errors, runtime, projections, options = {}) {
376
+ const projection = projections.get(runtime.projection);
377
+ if (!projection) {
378
+ pushError(errors, `${runtimeLabel(runtime)} references missing projection '${runtime.projection}'`);
379
+ return;
380
+ }
381
+
382
+ const resolvedManifest = resolveGeneratorManifestForBinding(runtime.generator, options);
383
+ const manifest = resolvedManifest.manifest;
384
+ if (!manifest) {
385
+ const details = resolvedManifest.errors.length > 0 ? `: ${resolvedManifest.errors.join("; ")}` : "";
386
+ pushError(errors, `${runtimeLabel(runtime)} for projection '${projection.id}' uses unknown generator '${runtime.generator?.id}' version '${runtime.generator?.version || "unknown"}'${details}`);
387
+ return;
388
+ }
389
+ const manifestValidation = validateGeneratorManifest(manifest);
390
+ if (!manifestValidation.ok) {
391
+ for (const message of manifestValidation.errors) {
392
+ pushError(errors, `${runtimeLabel(runtime)} generator manifest invalid: ${message}`);
393
+ }
394
+ }
395
+ if (manifest.planned) {
396
+ pushError(errors, `${runtimeLabel(runtime)} for projection '${projection.id}' uses planned generator '${manifest.id}@${manifest.version}', which is not implemented yet`);
397
+ }
398
+ if (manifest.version !== runtime.generator.version) {
399
+ pushError(errors, `${runtimeLabel(runtime)} for projection '${projection.id}' generator '${manifest.id}' version '${runtime.generator.version}' is unsupported; expected '${manifest.version}'`);
400
+ }
401
+ if (!isGeneratorCompatible(manifest, runtime.kind, projection)) {
402
+ pushError(errors, `${runtimeLabel(runtime)} for projection '${projection.id}' generator '${manifest.id}@${manifest.version}' is incompatible with runtime kind '${runtime.kind}' and projection type '${projection.type || "api_contract"}'`);
403
+ }
404
+ }
405
+
406
+ /**
407
+ * @param {ValidationError[]} errors
408
+ * @param {RuntimeTopologyRuntime[]} runtimes
409
+ * @returns {void}
410
+ */
411
+ function validateTopologyReferences(errors, runtimes) {
412
+ const byId = new Map(runtimes.map((runtime) => [runtime.id, runtime]));
413
+ const usedPorts = new Map();
414
+ for (const runtime of runtimes) {
415
+ if (runtime.port != null) {
416
+ const existing = usedPorts.get(runtime.port);
417
+ if (existing) {
418
+ pushError(errors, `Port ${runtime.port} is used by both '${existing}' and '${runtime.id}'`);
419
+ } else {
420
+ usedPorts.set(runtime.port, runtime.id);
421
+ }
422
+ }
423
+ if (runtime.kind === "api_service") {
424
+ if (runtime.uses_database && byId.get(runtime.uses_database)?.kind !== "database") {
425
+ pushError(errors, `${runtimeLabel(runtime)} references missing database runtime '${runtime.uses_database}'`);
426
+ }
427
+ }
428
+ if (["web_surface", "ios_surface", "android_surface"].includes(runtime.kind)) {
429
+ if (runtime.uses_api && byId.get(runtime.uses_api)?.kind !== "api_service") {
430
+ pushError(errors, `${runtimeLabel(runtime)} references missing api runtime '${runtime.uses_api}'`);
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ /**
437
+ * @param {any} config
438
+ * @param {Record<string, any>|null} [graph]
439
+ * @param {{ configDir?: string|null, rootDir?: string|null }} [options]
440
+ * @returns {{ ok: boolean, errors: ValidationError[] }}
441
+ */
442
+ export function validateProjectConfig(config, graph = null, options = {}) {
443
+ /** @type {ValidationError[]} */
444
+ const errors = [];
445
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
446
+ return { ok: false, errors: [{ message: "topogram.project.json must contain a JSON object", loc: null }] };
447
+ }
448
+ if (typeof config.version !== "string" || config.version.length === 0) {
449
+ pushError(errors, "topogram.project.json version must be a non-empty string");
450
+ }
451
+ validateOutputConfig(errors, config);
452
+ if (config.topology?.components != null) {
453
+ pushError(errors, `topogram.project.json ${renameDiagnostic("'topology.components'", "'topology.runtimes'", `"topology": { "runtimes": [] }`)}`);
454
+ }
455
+ if (!config.topology || typeof config.topology !== "object" || !Array.isArray(config.topology.runtimes)) {
456
+ pushError(errors, "topogram.project.json topology.runtimes must be an array");
457
+ } else {
458
+ const seenIds = new Set();
459
+ for (const runtime of config.topology.runtimes) {
460
+ validateRuntimeShape(errors, runtime, seenIds);
461
+ }
462
+ const generatorPolicy = validateProjectGeneratorPolicy(config, options);
463
+ for (const error of generatorPolicy.errors) {
464
+ pushError(errors, error.message, error.loc);
465
+ }
466
+ if (graph) {
467
+ const projections = projectionById(graph);
468
+ for (const runtime of config.topology.runtimes) {
469
+ validateRuntimeCompatibility(errors, runtime, projections, options);
470
+ }
471
+ validateTopologyReferences(errors, config.topology.runtimes);
472
+ }
473
+ }
474
+ return {
475
+ ok: errors.length === 0,
476
+ errors
477
+ };
478
+ }
479
+
480
+ /**
481
+ * @param {{ errors: ValidationError[] }} result
482
+ * @param {string} [configPath]
483
+ * @returns {string}
484
+ */
485
+ export function formatProjectConfigErrors(result, configPath = PROJECT_CONFIG_FILE) {
486
+ return result.errors
487
+ .map((error) => `${normalizeRoot(configPath)} ${error.message}`)
488
+ .join("\n");
489
+ }
490
+
491
+ /**
492
+ * @param {ProjectConfigInfo|null|undefined} configInfo
493
+ * @param {string} outputName
494
+ * @returns {string|null}
495
+ */
496
+ export function resolveOutputPath(configInfo, outputName) {
497
+ const output = configInfo?.config?.outputs?.[outputName];
498
+ if (!configInfo || !output?.path) {
499
+ return null;
500
+ }
501
+ const baseDir = configInfo.configDir || process.cwd();
502
+ return resolveComparablePath(path.resolve(baseDir, output.path));
503
+ }
504
+
505
+ /**
506
+ * @param {ProjectConfigInfo|null|undefined} configInfo
507
+ * @param {string} outDir
508
+ * @returns {{ name: string, ownership: string, path: string }|null}
509
+ */
510
+ export function outputOwnershipForPath(configInfo, outDir) {
511
+ if (!configInfo?.config?.outputs) {
512
+ return null;
513
+ }
514
+ const resolvedOutDir = resolveComparablePath(outDir);
515
+ for (const [name, output] of Object.entries(configInfo.config.outputs)) {
516
+ if (!output?.path) {
517
+ continue;
518
+ }
519
+ const resolvedOutput = resolveComparablePath(path.resolve(configInfo.configDir || process.cwd(), output.path));
520
+ if (resolvedOutput === resolvedOutDir) {
521
+ return {
522
+ name,
523
+ ownership: output.ownership,
524
+ path: resolvedOutput
525
+ };
526
+ }
527
+ }
528
+ return null;
529
+ }
530
+
531
+ /**
532
+ * @param {ProjectConfigInfo|null|undefined} configInfo
533
+ * @returns {{ ok: boolean, errors: ValidationError[] }}
534
+ */
535
+ export function validateProjectOutputOwnership(configInfo) {
536
+ /** @type {ValidationError[]} */
537
+ const errors = [];
538
+ if (!configInfo?.config?.outputs) {
539
+ return { ok: true, errors };
540
+ }
541
+ for (const [name, output] of Object.entries(configInfo.config.outputs)) {
542
+ if (!output?.path || !["generated", "maintained"].includes(output.ownership)) {
543
+ continue;
544
+ }
545
+ const resolvedOutput = resolveComparablePath(path.resolve(configInfo.configDir || process.cwd(), output.path));
546
+ const sentinelPath = path.join(resolvedOutput, GENERATED_OUTPUT_SENTINEL);
547
+ if (output.ownership === "generated" && fs.existsSync(resolvedOutput) && !fs.existsSync(sentinelPath)) {
548
+ pushError(
549
+ errors,
550
+ `Generated output '${name}' at '${normalizeRoot(resolvedOutput)}' is missing ${GENERATED_OUTPUT_SENTINEL}`
551
+ );
552
+ }
553
+ if (output.ownership === "maintained" && fs.existsSync(sentinelPath)) {
554
+ pushError(
555
+ errors,
556
+ `Maintained output '${name}' at '${normalizeRoot(resolvedOutput)}' contains ${GENERATED_OUTPUT_SENTINEL}`
557
+ );
558
+ }
559
+ }
560
+ return {
561
+ ok: errors.length === 0,
562
+ errors
563
+ };
564
+ }