@topogram/cli 0.3.64 → 0.3.66

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 (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -1,564 +1,22 @@
1
1
  // @ts-check
2
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
3
  /**
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
- }
4
+ * @typedef {import("./project-config/index.js").GeneratorBinding} GeneratorBinding
5
+ * @typedef {import("./project-config/index.js").RuntimeTopologyRuntime} RuntimeTopologyRuntime
6
+ * @typedef {import("./project-config/index.js").ProjectConfig} ProjectConfig
7
+ * @typedef {import("./project-config/index.js").ProjectConfigInfo} ProjectConfigInfo
8
+ * @typedef {import("./project-config/index.js").ValidationError} ValidationError
9
+ */
10
+
11
+ export {
12
+ defaultProjectConfigForGraph,
13
+ findLegacyImplementationConfig,
14
+ findProjectConfig,
15
+ formatProjectConfigErrors,
16
+ loadProjectConfig,
17
+ outputOwnershipForPath,
18
+ projectConfigOrDefault,
19
+ resolveOutputPath,
20
+ validateProjectConfig,
21
+ validateProjectOutputOwnership
22
+ } from "./project-config/index.js";
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // Resolver enrichment for acceptance criteria.
2
3
  //
3
4
  // Back-link arrays:
@@ -5,6 +6,7 @@
5
6
  // verifications — verifications whose `acceptance_refs` includes this AC
6
7
  // supersededBy — ACs that supersede *this* one
7
8
 
9
+ /** @param {TopogramStatement} ac @param {ResolverBacklinkIndex} index */
8
10
  export function enrichAcceptanceCriterion(ac, index) {
9
11
  return {
10
12
  tasks: (index.tasksByAcceptanceRef.get(ac.id) || []).slice().sort(),
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // Resolver enrichment for bugs.
2
3
  //
3
4
  // Back-link arrays:
@@ -5,6 +6,7 @@
5
6
  // (typically same set as bug.fixedInVerification, but the
6
7
  // author may set only one side; we surface both)
7
8
 
9
+ /** @param {TopogramStatement} bug @param {ResolverBacklinkIndex} index */
8
10
  export function enrichBug(bug, index) {
9
11
  return {
10
12
  verifiedBy: (index.verificationsFixingBug.get(bug.id) || []).slice().sort()
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // Resolver enrichment for pitches.
2
3
  //
3
4
  // Builds back-link arrays so consumers can ask a pitch "who follows you?"
@@ -10,6 +11,7 @@
10
11
  //
11
12
  // Output is merged into the statement by `resolveWorkspace`.
12
13
 
14
+ /** @param {TopogramStatement} pitch @param {ResolverBacklinkIndex} index */
13
15
  export function enrichPitch(pitch, index) {
14
16
  return {
15
17
  requirements: (index.requirementsByPitch.get(pitch.id) || []).slice().sort(),
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // Resolver enrichment for requirements.
2
3
  //
3
4
  // Back-link arrays:
@@ -8,6 +9,7 @@
8
9
  // documents — docs whose `satisfies` frontmatter points here
9
10
  // rules — rules whose `from_requirement` is this requirement
10
11
 
12
+ /** @param {TopogramStatement} requirement @param {ResolverBacklinkIndex} index */
11
13
  export function enrichRequirement(requirement, index) {
12
14
  return {
13
15
  acceptanceCriteria: (index.acsByRequirement.get(requirement.id) || []).slice().sort(),
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  // Resolver enrichment for tasks.
2
3
  //
3
4
  // Back-link arrays:
@@ -8,6 +9,7 @@
8
9
  // `blocked_by` are reciprocal, because authors only write one side. The
9
10
  // resolver bridges them.
10
11
 
12
+ /** @param {TopogramStatement} task @param {ResolverBacklinkIndex} index */
11
13
  export function enrichTask(task, index) {
12
14
  return {
13
15
  blockingMe: (index.tasksThatBlockTarget.get(task.id) || []).slice().sort(),