@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,96 @@
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
+ /**
27
+ * @param {{
28
+ * inputPath: string,
29
+ * projectRoot: string,
30
+ * outDir?: string|null,
31
+ * profileId?: string|null
32
+ * }} options
33
+ * @returns {Promise<number>}
34
+ */
35
+ export async function runGenerateAppCommand(options) {
36
+ const ast = parsePath(options.inputPath);
37
+ const explicitProjectConfig = loadProjectConfig(options.projectRoot) || loadProjectConfig(options.inputPath);
38
+ const shouldLoadImplementation = Boolean(explicitProjectConfig?.config?.implementation);
39
+ const implementation = shouldLoadImplementation
40
+ ? await loadImplementationProvider(explicitProjectConfig?.configDir || options.projectRoot)
41
+ : null;
42
+ const resolvedForConfig = resolveWorkspace(ast);
43
+ if (!resolvedForConfig.ok) {
44
+ console.error(formatValidationErrors(resolvedForConfig.validation));
45
+ return 1;
46
+ }
47
+ const defaultProjectConfig = projectConfigOrDefault(options.projectRoot, resolvedForConfig.graph, implementation);
48
+ const projectConfigInfo = explicitProjectConfig || defaultProjectConfig;
49
+ if (!projectConfigInfo) {
50
+ console.error("Unable to resolve topogram.project.json or derive a default project config.");
51
+ return 1;
52
+ }
53
+ const projectConfigValidation = validateProjectConfig(projectConfigInfo.config, resolvedForConfig.graph, {
54
+ configDir: projectConfigInfo.configDir
55
+ });
56
+ if (!projectConfigValidation.ok) {
57
+ console.error(formatProjectConfigErrors(projectConfigValidation, projectConfigInfo?.configPath || "topogram.project.json"));
58
+ return 1;
59
+ }
60
+
61
+ const result = generateWorkspace(ast, {
62
+ target: "app-bundle",
63
+ profileId: options.profileId,
64
+ topogramInputPath: topogramInputPathForGeneration(options.inputPath),
65
+ implementation,
66
+ projectConfig: projectConfigInfo.config,
67
+ configDir: projectConfigInfo.configDir || options.projectRoot,
68
+ projectRoot: projectConfigInfo.configDir || options.projectRoot
69
+ });
70
+ if (!result.ok) {
71
+ console.error(formatValidationErrors(result.validation));
72
+ return 1;
73
+ }
74
+
75
+ const resolvedOutDir = path.resolve(options.outDir || "./app");
76
+ assertProjectOutputAllowsWrite(projectConfigInfo, resolvedOutDir);
77
+ assertSafeGeneratedOutputDir(resolvedOutDir, options.inputPath);
78
+ const outputFiles = buildOutputFiles(result, {});
79
+ outputFiles.unshift({
80
+ path: GENERATED_OUTPUT_SENTINEL,
81
+ contents: generatedOutputSentinel("app-bundle")
82
+ });
83
+ fs.rmSync(resolvedOutDir, { recursive: true, force: true });
84
+ fs.mkdirSync(resolvedOutDir, { recursive: true });
85
+
86
+ for (const file of outputFiles) {
87
+ const destination = path.join(resolvedOutDir, file.path);
88
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
89
+ const contents =
90
+ typeof file.contents === "string" ? file.contents : `${stableStringify(file.contents)}\n`;
91
+ fs.writeFileSync(destination, contents, "utf8");
92
+ }
93
+
94
+ console.log(`Wrote ${outputFiles.length} file(s) to ${resolvedOutDir}`);
95
+ return 0;
96
+ }
@@ -0,0 +1,162 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ /**
7
+ * @param {string} filePath
8
+ * @returns {any|null}
9
+ */
10
+ export function readJsonIfPresent(filePath) {
11
+ if (!fs.existsSync(filePath)) {
12
+ return null;
13
+ }
14
+ try {
15
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * @param {Record<string, any>|null} projectPackage
23
+ * @param {string} packageName
24
+ * @returns {{ field: string|null, spec: string|null }}
25
+ */
26
+ function dependencySpecForPackage(projectPackage, packageName) {
27
+ const dependencyFields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
28
+ for (const field of dependencyFields) {
29
+ const dependencies = projectPackage?.[field];
30
+ if (dependencies && typeof dependencies === "object" && typeof dependencies[packageName] === "string") {
31
+ return {
32
+ field,
33
+ spec: dependencies[packageName]
34
+ };
35
+ }
36
+ }
37
+ return {
38
+ field: null,
39
+ spec: null
40
+ };
41
+ }
42
+
43
+ /**
44
+ * @param {Record<string, any>|null} lockfile
45
+ * @param {string} packageName
46
+ * @returns {{ version: string|null, resolved: string|null, integrity: string|null, entryPath: string|null }}
47
+ */
48
+ function npmLockfileInfoForPackage(lockfile, packageName) {
49
+ const packageEntryPath = `node_modules/${packageName}`;
50
+ const packageEntry = lockfile?.packages?.[packageEntryPath];
51
+ if (packageEntry && typeof packageEntry === "object") {
52
+ return {
53
+ version: typeof packageEntry.version === "string" ? packageEntry.version : null,
54
+ resolved: typeof packageEntry.resolved === "string" ? packageEntry.resolved : null,
55
+ integrity: typeof packageEntry.integrity === "string" ? packageEntry.integrity : null,
56
+ entryPath: packageEntryPath
57
+ };
58
+ }
59
+ const dependencyEntry = lockfile?.dependencies?.[packageName];
60
+ if (dependencyEntry && typeof dependencyEntry === "object") {
61
+ return {
62
+ version: typeof dependencyEntry.version === "string" ? dependencyEntry.version : null,
63
+ resolved: typeof dependencyEntry.resolved === "string" ? dependencyEntry.resolved : null,
64
+ integrity: typeof dependencyEntry.integrity === "string" ? dependencyEntry.integrity : null,
65
+ entryPath: packageName
66
+ };
67
+ }
68
+ return {
69
+ version: null,
70
+ resolved: null,
71
+ integrity: null,
72
+ entryPath: null
73
+ };
74
+ }
75
+
76
+ /**
77
+ * @param {string} projectRoot
78
+ * @returns {{ kind: "npm"|"pnpm"|"yarn"|"bun"|null, path: string|null, data: Record<string, any>|null, note: string|null }}
79
+ */
80
+ function lockfileMetadataForProject(projectRoot) {
81
+ const npmLockfilePath = path.join(projectRoot, "package-lock.json");
82
+ if (fs.existsSync(npmLockfilePath)) {
83
+ return {
84
+ kind: "npm",
85
+ path: npmLockfilePath,
86
+ data: readJsonIfPresent(npmLockfilePath),
87
+ note: null
88
+ };
89
+ }
90
+ const candidates = [
91
+ { kind: "pnpm", file: "pnpm-lock.yaml" },
92
+ { kind: "yarn", file: "yarn.lock" },
93
+ { kind: "bun", file: "bun.lock" },
94
+ { kind: "bun", file: "bun.lockb" }
95
+ ];
96
+ for (const candidate of candidates) {
97
+ const lockfilePath = path.join(projectRoot, candidate.file);
98
+ if (fs.existsSync(lockfilePath)) {
99
+ return {
100
+ kind: /** @type {"pnpm"|"yarn"|"bun"} */ (candidate.kind),
101
+ path: lockfilePath,
102
+ data: null,
103
+ note: `${candidate.file} found; package versions are not inspected by this command.`
104
+ };
105
+ }
106
+ }
107
+ return {
108
+ kind: null,
109
+ path: null,
110
+ data: null,
111
+ note: null
112
+ };
113
+ }
114
+
115
+ /**
116
+ * @param {string} projectRoot
117
+ * @param {string} packageName
118
+ * @returns {{ dependencyField: string|null, dependencySpec: string|null, installedVersion: string|null, installedPackageJsonPath: string|null, lockfileKind: "npm"|"pnpm"|"yarn"|"bun"|null, lockfilePath: string|null, lockfileVersion: string|null, lockfileResolved: string|null, lockfileIntegrity: string|null, lockfileEntryPath: string|null, lockfileNote: string|null }}
119
+ */
120
+ export function packageInfoForGenerator(projectRoot, packageName) {
121
+ const projectPackage = readJsonIfPresent(path.join(projectRoot, "package.json"));
122
+ const dependency = dependencySpecForPackage(projectPackage, packageName);
123
+ const lockfile = lockfileMetadataForProject(projectRoot);
124
+ const lockfileInfo = lockfile.kind === "npm"
125
+ ? npmLockfileInfoForPackage(lockfile.data, packageName)
126
+ : {
127
+ version: null,
128
+ resolved: null,
129
+ integrity: null,
130
+ entryPath: null
131
+ };
132
+ const installedPackageJsonPath = path.join(projectRoot, "node_modules", ...packageName.split("/"), "package.json");
133
+ const installedPackage = readJsonIfPresent(installedPackageJsonPath);
134
+ return {
135
+ dependencyField: dependency.field,
136
+ dependencySpec: dependency.spec,
137
+ installedVersion: typeof installedPackage?.version === "string" ? installedPackage.version : null,
138
+ installedPackageJsonPath: installedPackage ? installedPackageJsonPath : null,
139
+ lockfileKind: lockfile.kind,
140
+ lockfilePath: lockfile.path,
141
+ lockfileVersion: lockfileInfo.version,
142
+ lockfileResolved: lockfileInfo.resolved,
143
+ lockfileIntegrity: lockfileInfo.integrity,
144
+ lockfileEntryPath: lockfileInfo.entryPath,
145
+ lockfileNote: lockfile.note
146
+ };
147
+ }
148
+
149
+ /**
150
+ * @param {ReturnType<typeof packageInfoForGenerator>} packageInfo
151
+ * @returns {string}
152
+ */
153
+ export function formatGeneratorPackageLockfile(packageInfo) {
154
+ if (!packageInfo.lockfileKind || !packageInfo.lockfilePath) {
155
+ return "(not found)";
156
+ }
157
+ const label = path.basename(packageInfo.lockfilePath);
158
+ if (packageInfo.lockfileVersion) {
159
+ return `${packageInfo.lockfileKind} ${packageInfo.lockfileVersion}`;
160
+ }
161
+ return `${label} (version not inspected)`;
162
+ }
@@ -0,0 +1,372 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ import {
6
+ defaultGeneratorPolicy,
7
+ GENERATOR_POLICY_FILE,
8
+ generatorPackageAllowed,
9
+ generatorPolicyDiagnosticsForBindings,
10
+ loadGeneratorPolicy,
11
+ packageBackedGeneratorBindings,
12
+ packageScopeFromName,
13
+ parseGeneratorPolicyPin,
14
+ writeGeneratorPolicy
15
+ } from "../../../generator-policy.js";
16
+ import { loadProjectConfig } from "../../../project-config.js";
17
+ import { packageInfoForGenerator } from "./package-info.js";
18
+ import { effectiveGeneratorPolicy, generatorPolicyRule } from "./shared.js";
19
+
20
+ /**
21
+ * @param {string} projectRoot
22
+ * @param {any} policy
23
+ * @param {ReturnType<typeof packageBackedGeneratorBindings>[number]} binding
24
+ * @returns {ReturnType<typeof packageBackedGeneratorBindings>[number] & { allowed: boolean, packageInfo: ReturnType<typeof packageInfoForGenerator>, pin: { key: string|null, version: string|null, matches: boolean|null } }}
25
+ */
26
+ function generatorPolicyBindingStatus(projectRoot, policy, binding) {
27
+ const packagePin = policy.pinnedVersions[binding.packageName] || null;
28
+ const generatorPin = policy.pinnedVersions[binding.generatorId] || null;
29
+ const pinnedVersion = packagePin || generatorPin;
30
+ return {
31
+ ...binding,
32
+ allowed: generatorPackageAllowed(policy, binding.packageName),
33
+ packageInfo: packageInfoForGenerator(projectRoot, binding.packageName),
34
+ pin: {
35
+ key: packagePin ? binding.packageName : generatorPin ? binding.generatorId : null,
36
+ version: pinnedVersion,
37
+ matches: pinnedVersion ? pinnedVersion === binding.version : null
38
+ }
39
+ };
40
+ }
41
+
42
+ /**
43
+ * @param {any[]} diagnostics
44
+ * @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
45
+ * @returns {any[]}
46
+ */
47
+ function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
48
+ return diagnostics.map((diagnostic) => {
49
+ const binding = bindings.find((item) => (
50
+ item.packageName === diagnostic.packageName &&
51
+ (!diagnostic.runtimeId || item.runtimeId === diagnostic.runtimeId)
52
+ ));
53
+ if (!binding) {
54
+ return diagnostic;
55
+ }
56
+ return {
57
+ ...diagnostic,
58
+ packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
59
+ packageDependencyField: binding.packageInfo.dependencyField,
60
+ packageDependencySpec: binding.packageInfo.dependencySpec,
61
+ packageLockfileKind: binding.packageInfo.lockfileKind,
62
+ packageLockfilePath: binding.packageInfo.lockfilePath,
63
+ packageLockVersion: binding.packageInfo.lockfileVersion
64
+ };
65
+ });
66
+ }
67
+
68
+ /**
69
+ * @param {Array<ReturnType<typeof generatorPolicyBindingStatus>>} bindings
70
+ * @returns {any[]}
71
+ */
72
+ function generatorPolicyPackageMetadataDiagnostics(bindings) {
73
+ const diagnostics = [];
74
+ for (const binding of bindings) {
75
+ if (!binding.packageInfo.dependencySpec) {
76
+ diagnostics.push({
77
+ code: "generator_package_dependency_missing",
78
+ severity: "warning",
79
+ message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is not declared in package.json dependencies.`,
80
+ path: binding.packageInfo.installedPackageJsonPath,
81
+ suggestedFix: `Declare '${binding.packageName}' in package.json devDependencies so generator adoption is visible in package review.`,
82
+ step: "generator-policy",
83
+ runtimeId: binding.runtimeId,
84
+ generatorId: binding.generatorId,
85
+ packageName: binding.packageName,
86
+ version: binding.version,
87
+ packageVersion: binding.packageInfo.installedVersion || binding.packageInfo.lockfileVersion || null,
88
+ packageDependencyField: binding.packageInfo.dependencyField,
89
+ packageDependencySpec: binding.packageInfo.dependencySpec,
90
+ packageLockfileKind: binding.packageInfo.lockfileKind,
91
+ packageLockfilePath: binding.packageInfo.lockfilePath,
92
+ packageLockVersion: binding.packageInfo.lockfileVersion
93
+ });
94
+ }
95
+ if (
96
+ binding.packageInfo.installedVersion &&
97
+ binding.packageInfo.lockfileVersion &&
98
+ binding.packageInfo.installedVersion !== binding.packageInfo.lockfileVersion
99
+ ) {
100
+ diagnostics.push({
101
+ code: "generator_package_version_drift",
102
+ severity: "warning",
103
+ message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is installed at '${binding.packageInfo.installedVersion}', but package-lock records '${binding.packageInfo.lockfileVersion}'.`,
104
+ path: binding.packageInfo.lockfilePath,
105
+ suggestedFix: "Run the package manager install command and review the resulting lockfile before pinning generator policy.",
106
+ step: "generator-policy",
107
+ runtimeId: binding.runtimeId,
108
+ generatorId: binding.generatorId,
109
+ packageName: binding.packageName,
110
+ version: binding.version,
111
+ packageVersion: binding.packageInfo.installedVersion,
112
+ packageDependencyField: binding.packageInfo.dependencyField,
113
+ packageDependencySpec: binding.packageInfo.dependencySpec,
114
+ packageLockfileKind: binding.packageInfo.lockfileKind,
115
+ packageLockfilePath: binding.packageInfo.lockfilePath,
116
+ packageLockVersion: binding.packageInfo.lockfileVersion
117
+ });
118
+ }
119
+ }
120
+ return diagnostics;
121
+ }
122
+
123
+ /**
124
+ * @param {string} projectPath
125
+ * @returns {{ ok: boolean, path: string, exists: boolean, policy: any, defaulted: boolean, bindings: Array<ReturnType<typeof generatorPolicyBindingStatus>>, diagnostics: any[], errors: string[] }}
126
+ */
127
+ export function buildGeneratorPolicyCheckPayload(projectPath) {
128
+ const projectConfigInfo = loadProjectConfig(projectPath);
129
+ if (!projectConfigInfo) {
130
+ const diagnostic = {
131
+ code: "generator_policy_project_missing",
132
+ severity: "error",
133
+ message: "Cannot check generator policy without topogram.project.json.",
134
+ path: path.resolve(projectPath),
135
+ suggestedFix: "Run this command in a Topogram project.",
136
+ step: "generator-policy"
137
+ };
138
+ return {
139
+ ok: false,
140
+ path: path.join(path.resolve(projectPath), GENERATOR_POLICY_FILE),
141
+ exists: false,
142
+ policy: null,
143
+ defaulted: false,
144
+ bindings: [],
145
+ diagnostics: [diagnostic],
146
+ errors: [diagnostic.message]
147
+ };
148
+ }
149
+ const policyInfo = loadGeneratorPolicy(projectConfigInfo.configDir);
150
+ const rawBindings = packageBackedGeneratorBindings(projectConfigInfo.config);
151
+ const policy = policyInfo.policy || effectiveGeneratorPolicy(policyInfo);
152
+ const bindings = rawBindings.map((binding) => generatorPolicyBindingStatus(projectConfigInfo.configDir, policy, binding));
153
+ const diagnostics = [];
154
+ if (!policyInfo.exists) {
155
+ diagnostics.push({
156
+ code: "generator_policy_missing",
157
+ severity: "warning",
158
+ message: `No ${GENERATOR_POLICY_FILE} found. Default generator policy allows @topogram/* package-backed generators and blocks other package scopes.`,
159
+ path: policyInfo.path,
160
+ suggestedFix: "Run `topogram generator policy init` to write an explicit project generator policy after review.",
161
+ step: "generator-policy"
162
+ });
163
+ }
164
+ diagnostics.push(...generatorPolicyDiagnosticsForBindings(policyInfo, rawBindings, "generator-policy"));
165
+ diagnostics.push(...generatorPolicyPackageMetadataDiagnostics(bindings));
166
+ const annotatedDiagnostics = annotateGeneratorPolicyDiagnostics(diagnostics, bindings);
167
+ const errors = annotatedDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
168
+ return {
169
+ ok: errors.length === 0,
170
+ path: policyInfo.path,
171
+ exists: policyInfo.exists,
172
+ policy,
173
+ defaulted: !policyInfo.exists,
174
+ bindings,
175
+ diagnostics: annotatedDiagnostics,
176
+ errors
177
+ };
178
+ }
179
+
180
+ /**
181
+ * @param {string} projectPath
182
+ * @returns {ReturnType<typeof buildGeneratorPolicyCheckPayload> & { rules: Array<{ name: string, ok: boolean, actual: string, expected: string, message: string, fix: string|null }> }}
183
+ */
184
+ export function buildGeneratorPolicyExplainPayload(projectPath) {
185
+ const check = buildGeneratorPolicyCheckPayload(projectPath);
186
+ const policy = check.policy || effectiveGeneratorPolicy({ path: check.path, exists: false, policy: null, diagnostics: [] });
187
+ const rules = [];
188
+ rules.push(generatorPolicyRule(
189
+ "policy-file",
190
+ check.exists,
191
+ check.exists ? "present" : "missing",
192
+ "present",
193
+ check.exists
194
+ ? "Project has a generator policy file."
195
+ : "Project is using the default generator policy.",
196
+ check.exists ? null : "Run `topogram generator policy init` after review."
197
+ ));
198
+ for (const binding of check.bindings) {
199
+ const scope = packageScopeFromName(binding.packageName);
200
+ rules.push(generatorPolicyRule(
201
+ "allowed-package",
202
+ generatorPackageAllowed(policy, binding.packageName),
203
+ `${binding.packageName}${scope ? ` (${scope})` : ""}`,
204
+ [
205
+ `scopes=${policy.allowedPackageScopes.join(", ") || "(none)"}`,
206
+ `packages=${policy.allowedPackages.join(", ") || "(none)"}`
207
+ ].join("; "),
208
+ `Runtime '${binding.runtimeId}' package-backed generator must be from an allowed package or scope.`,
209
+ `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after reviewing the generator package.`
210
+ ));
211
+ const pinnedVersion = policy.pinnedVersions[binding.packageName] || policy.pinnedVersions[binding.generatorId] || null;
212
+ rules.push(generatorPolicyRule(
213
+ "pinned-version",
214
+ !pinnedVersion || pinnedVersion === binding.version,
215
+ binding.version,
216
+ pinnedVersion || "(unpinned)",
217
+ `Runtime '${binding.runtimeId}' generator version must match its policy pin when one exists.`,
218
+ `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`
219
+ ));
220
+ }
221
+ return {
222
+ ...check,
223
+ rules
224
+ };
225
+ }
226
+
227
+ /**
228
+ * @param {string} projectPath
229
+ * @returns {ReturnType<typeof buildGeneratorPolicyExplainPayload> & { summary: { packageBackedGenerators: number, allowed: number, denied: number, pinned: number, unpinned: number, pinMismatches: number } }}
230
+ */
231
+ export function buildGeneratorPolicyStatusPayload(projectPath) {
232
+ const explain = buildGeneratorPolicyExplainPayload(projectPath);
233
+ return {
234
+ ...explain,
235
+ summary: {
236
+ packageBackedGenerators: explain.bindings.length,
237
+ allowed: explain.bindings.filter((binding) => binding.allowed).length,
238
+ denied: explain.bindings.filter((binding) => !binding.allowed).length,
239
+ pinned: explain.bindings.filter((binding) => Boolean(binding.pin.version)).length,
240
+ unpinned: explain.bindings.filter((binding) => !binding.pin.version).length,
241
+ pinMismatches: explain.bindings.filter((binding) => binding.pin.matches === false).length
242
+ }
243
+ };
244
+ }
245
+
246
+ /**
247
+ * @param {string} projectPath
248
+ * @returns {{ ok: boolean, path: string, policy: any, diagnostics: any[], errors: string[] }}
249
+ */
250
+ export function buildGeneratorPolicyInitPayload(projectPath) {
251
+ const projectConfigInfo = loadProjectConfig(projectPath);
252
+ if (!projectConfigInfo) {
253
+ throw new Error("Cannot initialize generator policy without topogram.project.json.");
254
+ }
255
+ const policy = writeGeneratorPolicy(projectConfigInfo.configDir, defaultGeneratorPolicy());
256
+ return {
257
+ ok: true,
258
+ path: path.join(projectConfigInfo.configDir, GENERATOR_POLICY_FILE),
259
+ policy,
260
+ diagnostics: [],
261
+ errors: []
262
+ };
263
+ }
264
+
265
+ /**
266
+ * @param {string} projectPath
267
+ * @param {string|null|undefined} spec
268
+ * @returns {{ ok: boolean, path: string, policy: any, pinned: Array<{ packageName: string, version: string }>, diagnostics: any[], errors: string[] }}
269
+ */
270
+ export function buildGeneratorPolicyPinPayload(projectPath, spec) {
271
+ const projectConfigInfo = loadProjectConfig(projectPath);
272
+ if (!projectConfigInfo) {
273
+ const diagnostic = {
274
+ code: "generator_policy_project_missing",
275
+ severity: "error",
276
+ message: "Cannot pin generator policy without topogram.project.json.",
277
+ path: path.resolve(projectPath),
278
+ suggestedFix: "Run this command in a Topogram project.",
279
+ step: "generator-policy"
280
+ };
281
+ return {
282
+ ok: false,
283
+ path: path.join(path.resolve(projectPath), GENERATOR_POLICY_FILE),
284
+ policy: null,
285
+ pinned: [],
286
+ diagnostics: [diagnostic],
287
+ errors: [diagnostic.message]
288
+ };
289
+ }
290
+ const policyInfo = loadGeneratorPolicy(projectConfigInfo.configDir);
291
+ const policyDiagnostics = /** @type {any[]} */ (policyInfo.diagnostics || []);
292
+ if (policyDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
293
+ const errors = policyDiagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message);
294
+ return {
295
+ ok: false,
296
+ path: policyInfo.path,
297
+ policy: policyInfo.policy,
298
+ pinned: [],
299
+ diagnostics: policyDiagnostics,
300
+ errors
301
+ };
302
+ }
303
+ let pins = [];
304
+ try {
305
+ pins = spec
306
+ ? [parseGeneratorPolicyPin(spec)]
307
+ : packageBackedGeneratorBindings(projectConfigInfo.config).map((binding) => ({
308
+ packageName: binding.packageName,
309
+ version: binding.version
310
+ }));
311
+ } catch (error) {
312
+ const diagnostic = {
313
+ code: "generator_policy_pin_invalid",
314
+ severity: "error",
315
+ message: error instanceof Error ? error.message : String(error),
316
+ path: policyInfo.path,
317
+ suggestedFix: "Pass a pin such as @topogram/generator-react-web@1.",
318
+ step: "generator-policy"
319
+ };
320
+ return {
321
+ ok: false,
322
+ path: policyInfo.path,
323
+ policy: policyInfo.policy,
324
+ pinned: [],
325
+ diagnostics: [diagnostic],
326
+ errors: [diagnostic.message]
327
+ };
328
+ }
329
+ if (pins.length === 0) {
330
+ const diagnostic = {
331
+ code: "generator_policy_pin_no_generators",
332
+ severity: "error",
333
+ message: "No package-backed topology generator bindings are available to pin.",
334
+ path: projectConfigInfo.configPath,
335
+ suggestedFix: "Pass an explicit pin such as @topogram/generator-react-web@1, or use bundled generators.",
336
+ step: "generator-policy"
337
+ };
338
+ return {
339
+ ok: false,
340
+ path: policyInfo.path,
341
+ policy: policyInfo.policy,
342
+ pinned: [],
343
+ diagnostics: [diagnostic],
344
+ errors: [diagnostic.message]
345
+ };
346
+ }
347
+ const policy = policyInfo.policy || defaultGeneratorPolicy();
348
+ const allowedPackages = [...policy.allowedPackages];
349
+ const allowedPackageScopes = [...policy.allowedPackageScopes];
350
+ const pinnedVersions = { ...policy.pinnedVersions };
351
+ for (const pin of pins) {
352
+ if (!allowedPackages.includes(pin.packageName)) {
353
+ allowedPackages.push(pin.packageName);
354
+ }
355
+ pinnedVersions[pin.packageName] = pin.version;
356
+ }
357
+ const nextPolicy = {
358
+ ...policy,
359
+ allowedPackageScopes,
360
+ allowedPackages,
361
+ pinnedVersions
362
+ };
363
+ writeGeneratorPolicy(projectConfigInfo.configDir, nextPolicy);
364
+ return {
365
+ ok: true,
366
+ path: path.join(projectConfigInfo.configDir, GENERATOR_POLICY_FILE),
367
+ policy: nextPolicy,
368
+ pinned: pins,
369
+ diagnostics: [],
370
+ errors: []
371
+ };
372
+ }