@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
@@ -0,0 +1,370 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import childProcess from "node:child_process";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ import { assertSafeNpmSpec, localNpmrcEnv } from "../npm-safety.js";
9
+ import { DEFAULT_TOPO_FOLDER_NAME, LEGACY_TOPOGRAM_FOLDER_NAME, resolvePackageWorkspace } from "../workspace-paths.js";
10
+ import { GENERATOR_LABELS, SURFACE_ORDER, TEMPLATE_MANIFEST, unsupportedTemplateSymlinkMessage } from "./constants.js";
11
+ import { isLocalTemplateSpec, packageNameFromSpec } from "./package-spec.js";
12
+
13
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
14
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
15
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
16
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
17
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
18
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
19
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
20
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
21
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
22
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
23
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
24
+
25
+ /**
26
+ * @param {unknown} value
27
+ * @returns {TemplateManifest}
28
+ */
29
+ export function validateTemplateManifest(value) {
30
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
31
+ throw new Error(`${TEMPLATE_MANIFEST} must contain a JSON object.`);
32
+ }
33
+ const manifest = /** @type {Record<string, unknown>} */ (value);
34
+ for (const field of ["id", "version", "kind", "topogramVersion"]) {
35
+ if (typeof manifest[field] !== "string" || !manifest[field]) {
36
+ throw new Error(`${TEMPLATE_MANIFEST} is missing required string field '${field}'.`);
37
+ }
38
+ }
39
+ if (manifest.kind !== "starter") {
40
+ throw new Error(`${TEMPLATE_MANIFEST} kind must be 'starter'.`);
41
+ }
42
+ if (
43
+ Object.prototype.hasOwnProperty.call(manifest, "includesExecutableImplementation") &&
44
+ typeof manifest.includesExecutableImplementation !== "boolean"
45
+ ) {
46
+ throw new Error(`${TEMPLATE_MANIFEST} field 'includesExecutableImplementation' must be a boolean.`);
47
+ }
48
+ if (Object.prototype.hasOwnProperty.call(manifest, "starterScripts")) {
49
+ if (!manifest.starterScripts || typeof manifest.starterScripts !== "object" || Array.isArray(manifest.starterScripts)) {
50
+ throw new Error(`${TEMPLATE_MANIFEST} field 'starterScripts' must be an object of package.json script names to commands.`);
51
+ }
52
+ for (const [scriptName, command] of Object.entries(manifest.starterScripts)) {
53
+ if (typeof scriptName !== "string" || !scriptName.trim() || scriptName.startsWith("-") || scriptName.includes("\n")) {
54
+ throw new Error(`${TEMPLATE_MANIFEST} starterScripts contains an invalid script name.`);
55
+ }
56
+ if (typeof command !== "string" || !command.trim()) {
57
+ throw new Error(`${TEMPLATE_MANIFEST} starterScripts.${scriptName} must be a non-empty string.`);
58
+ }
59
+ }
60
+ }
61
+ return /** @type {TemplateManifest} */ (/** @type {unknown} */ (manifest));
62
+ }
63
+
64
+ /**
65
+ * @param {string} templateRoot
66
+ * @returns {TemplateManifest}
67
+ */
68
+ export function readTemplateManifest(templateRoot) {
69
+ const manifestPath = path.join(templateRoot, TEMPLATE_MANIFEST);
70
+ if (!fs.existsSync(manifestPath)) {
71
+ throw new Error(`Template at '${templateRoot}' is missing ${TEMPLATE_MANIFEST}.`);
72
+ }
73
+ return validateTemplateManifest(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
74
+ }
75
+
76
+ /**
77
+ * @param {string} root
78
+ * @param {string} currentDir
79
+ * @param {string} label
80
+ * @param {string} templateId
81
+ * @returns {void}
82
+ */
83
+ function assertTemplateTreeHasNoSymlinks(root, currentDir, label, templateId) {
84
+ const rootStat = fs.lstatSync(currentDir);
85
+ const relativeRoot = path.relative(root, currentDir).replace(/\\/g, "/") || label;
86
+ if (rootStat.isSymbolicLink()) {
87
+ throw new Error(unsupportedTemplateSymlinkMessage(templateId, relativeRoot));
88
+ }
89
+ if (!rootStat.isDirectory()) {
90
+ return;
91
+ }
92
+ for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
93
+ const entryPath = path.join(currentDir, entry.name);
94
+ const relativePath = path.relative(root, entryPath).replace(/\\/g, "/");
95
+ if (entry.isSymbolicLink()) {
96
+ throw new Error(unsupportedTemplateSymlinkMessage(templateId, relativePath));
97
+ }
98
+ if (entry.isDirectory()) {
99
+ assertTemplateTreeHasNoSymlinks(root, entryPath, label, templateId);
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * @param {string} templateRoot
106
+ * @returns {TemplateManifest}
107
+ */
108
+ export function validateTemplateRoot(templateRoot) {
109
+ const manifest = readTemplateManifest(templateRoot);
110
+ const workspace = resolvePackageWorkspace(templateRoot);
111
+ const topogramRoot = workspace.root;
112
+ const projectConfigPath = path.join(templateRoot, "topogram.project.json");
113
+ if (fs.existsSync(topogramRoot) && fs.lstatSync(topogramRoot).isSymbolicLink()) {
114
+ throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, workspace.legacy ? LEGACY_TOPOGRAM_FOLDER_NAME : DEFAULT_TOPO_FOLDER_NAME));
115
+ }
116
+ if (fs.existsSync(projectConfigPath) && fs.lstatSync(projectConfigPath).isSymbolicLink()) {
117
+ throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, "topogram.project.json"));
118
+ }
119
+ if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
120
+ throw new Error(`Template '${manifest.id}' is missing ${DEFAULT_TOPO_FOLDER_NAME}/.`);
121
+ }
122
+ if (!fs.existsSync(projectConfigPath) || !fs.statSync(projectConfigPath).isFile()) {
123
+ throw new Error(`Template '${manifest.id}' is missing topogram.project.json.`);
124
+ }
125
+ assertTemplateTreeHasNoSymlinks(templateRoot, topogramRoot, workspace.legacy ? LEGACY_TOPOGRAM_FOLDER_NAME : DEFAULT_TOPO_FOLDER_NAME, manifest.id);
126
+ if (manifest.includesExecutableImplementation) {
127
+ const implementationRoot = path.join(templateRoot, "implementation");
128
+ if (fs.existsSync(implementationRoot) && fs.lstatSync(implementationRoot).isSymbolicLink()) {
129
+ throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, "implementation"));
130
+ }
131
+ if (!fs.existsSync(implementationRoot) || !fs.statSync(implementationRoot).isDirectory()) {
132
+ throw new Error(
133
+ `Template '${manifest.id}' declares executable implementation code but is missing implementation/.`
134
+ );
135
+ }
136
+ assertTemplateTreeHasNoSymlinks(templateRoot, implementationRoot, "implementation", manifest.id);
137
+ } else {
138
+ const implementationRoot = path.join(templateRoot, "implementation");
139
+ if (fs.existsSync(implementationRoot) && fs.statSync(implementationRoot).isDirectory()) {
140
+ throw new Error(
141
+ `Template '${manifest.id}' contains implementation/ but ${TEMPLATE_MANIFEST} does not declare includesExecutableImplementation: true.`
142
+ );
143
+ }
144
+ }
145
+ return manifest;
146
+ }
147
+
148
+ /**
149
+ * @param {string} generatorId
150
+ * @returns {string}
151
+ */
152
+ function generatorLabel(generatorId) {
153
+ return GENERATOR_LABELS.get(generatorId) || generatorId.replace(/^topogram\//, "");
154
+ }
155
+
156
+ /**
157
+ * @param {string} templateRoot
158
+ * @returns {TemplateTopologySummary}
159
+ */
160
+ export function summarizeTemplateTopology(templateRoot) {
161
+ const projectConfigPath = path.join(templateRoot, "topogram.project.json");
162
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
163
+ const rawRuntimes = /** @type {any[]} */ (
164
+ Array.isArray(projectConfig.topology?.runtimes) ? projectConfig.topology.runtimes : []
165
+ );
166
+ /** @type {Array<Record<string, any>>} */
167
+ const runtimes = [];
168
+ for (const runtime of rawRuntimes) {
169
+ if (runtime && typeof runtime === "object" && typeof runtime.kind === "string") {
170
+ runtimes.push(/** @type {Record<string, any>} */ (runtime));
171
+ }
172
+ }
173
+ const sortedRuntimes = [...runtimes].sort((a, b) => {
174
+ const aOrder = SURFACE_ORDER.get(a.kind) ?? 100;
175
+ const bOrder = SURFACE_ORDER.get(b.kind) ?? 100;
176
+ return aOrder - bOrder;
177
+ });
178
+ const surfaces = [...new Set(sortedRuntimes.map((runtime) => String(runtime.kind)))];
179
+ const generators = [
180
+ ...new Set(
181
+ sortedRuntimes
182
+ .map((runtime) => runtime.generator?.id)
183
+ .filter((generatorId) => typeof generatorId === "string")
184
+ .map((generatorId) => String(generatorId))
185
+ )
186
+ ];
187
+ return {
188
+ surfaces,
189
+ generators,
190
+ stack: generators.map(generatorLabel).join(" + ")
191
+ };
192
+ }
193
+
194
+ /**
195
+ * @param {string} templateSpec
196
+ * @returns {string}
197
+ */
198
+ export function installPackageSpec(templateSpec) {
199
+ assertSafeNpmSpec(templateSpec);
200
+ const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), "topogram-template-"));
201
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
202
+ const result = childProcess.spawnSync(
203
+ npmBin,
204
+ [
205
+ "install",
206
+ "--prefix",
207
+ installRoot,
208
+ "--ignore-scripts",
209
+ "--no-audit",
210
+ "--no-fund",
211
+ "--package-lock=false",
212
+ "--",
213
+ templateSpec
214
+ ],
215
+ {
216
+ encoding: "utf8",
217
+ env: {
218
+ ...process.env,
219
+ ...localNpmrcEnv(process.cwd()),
220
+ PATH: process.env.PATH || ""
221
+ }
222
+ }
223
+ );
224
+ if (result.status !== 0) {
225
+ throw new Error(formatPackageInstallError(templateSpec, result));
226
+ }
227
+ const packageRoot = path.join(installRoot, "node_modules", packageNameFromSpec(templateSpec));
228
+ if (fs.existsSync(packageRoot)) {
229
+ return packageRoot;
230
+ }
231
+ return findInstalledTemplatePackageRoot(installRoot, templateSpec);
232
+ }
233
+
234
+ /**
235
+ * @param {string} templateSpec
236
+ * @param {any} result
237
+ * @returns {string}
238
+ */
239
+ function formatPackageInstallError(templateSpec, result) {
240
+ const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
241
+ const normalized = output.toLowerCase();
242
+ const npmrcHint = "Ensure npm can access the registry required by this template package. Topogram ignores project .npmrc files unless TOPOGRAM_ALLOW_LOCAL_NPMRC=1 or --allow-local-npmrc is used.";
243
+ const packageAccessHint = "For private package registries, configure a token with package read access.";
244
+ const authHint = "For private template packages, configure npm auth for the package registry before installing.";
245
+ const doctorHint = "Run `topogram doctor` to check Node.js, npm, package, and catalog access.";
246
+ if (result.error?.code === "ENOENT") {
247
+ return [
248
+ `Failed to install template package '${templateSpec}': npm was not found.`,
249
+ "Install Node.js/npm and retry."
250
+ ].join("\n");
251
+ }
252
+ if (/\b(e401|eneedauth)\b/.test(normalized) || normalized.includes("unauthenticated") || normalized.includes("authentication required")) {
253
+ return [
254
+ `Authentication is required to install template package '${templateSpec}'.`,
255
+ authHint,
256
+ npmrcHint,
257
+ packageAccessHint,
258
+ doctorHint,
259
+ output
260
+ ].filter(Boolean).join("\n");
261
+ }
262
+ if (/\be403\b/.test(normalized) || normalized.includes("forbidden") || normalized.includes("permission")) {
263
+ return [
264
+ `Package access was denied while installing template package '${templateSpec}'.`,
265
+ authHint,
266
+ packageAccessHint,
267
+ doctorHint,
268
+ output
269
+ ].filter(Boolean).join("\n");
270
+ }
271
+ if (/\b(e404|404)\b/.test(normalized) || normalized.includes("not found")) {
272
+ return [
273
+ `Template package '${templateSpec}' was not found, or the current token does not have access to it.`,
274
+ "Check the package name/version and registry access.",
275
+ packageAccessHint,
276
+ doctorHint,
277
+ output
278
+ ].filter(Boolean).join("\n");
279
+ }
280
+ if (/\beintegrity\b/.test(normalized) || normalized.includes("integrity checksum failed")) {
281
+ return [
282
+ `Package integrity failed while installing template package '${templateSpec}'.`,
283
+ "Refresh package-lock.json from the published registry tarball instead of a local npm pack tarball.",
284
+ output
285
+ ].filter(Boolean).join("\n");
286
+ }
287
+ return `Failed to install template package '${templateSpec}'.\n${output || "unknown error"}`.trim();
288
+ }
289
+
290
+ /**
291
+ * @param {string} installRoot
292
+ * @param {string} templateSpec
293
+ * @returns {string}
294
+ */
295
+ function findInstalledTemplatePackageRoot(installRoot, templateSpec) {
296
+ const nodeModules = path.join(installRoot, "node_modules");
297
+ if (!fs.existsSync(nodeModules)) {
298
+ throw new Error(`Template package '${templateSpec}' did not create node_modules.`);
299
+ }
300
+ /** @type {string[]} */
301
+ const candidates = [];
302
+ for (const entry of fs.readdirSync(nodeModules)) {
303
+ if (entry === ".bin") {
304
+ continue;
305
+ }
306
+ const entryPath = path.join(nodeModules, entry);
307
+ if (entry.startsWith("@")) {
308
+ for (const scopedEntry of fs.readdirSync(entryPath)) {
309
+ candidates.push(path.join(entryPath, scopedEntry));
310
+ }
311
+ continue;
312
+ }
313
+ candidates.push(entryPath);
314
+ }
315
+ const templateRoots = candidates.filter((candidate) =>
316
+ fs.existsSync(path.join(candidate, TEMPLATE_MANIFEST))
317
+ );
318
+ if (templateRoots.length === 1) {
319
+ return templateRoots[0];
320
+ }
321
+ if (templateRoots.length > 1) {
322
+ throw new Error(`Template package '${templateSpec}' installed multiple template manifests.`);
323
+ }
324
+ throw new Error(`Template package '${templateSpec}' did not install a package with ${TEMPLATE_MANIFEST}.`);
325
+ }
326
+
327
+ /**
328
+ * @param {string} templateName
329
+ * @param {string} templatesRoot
330
+ * @returns {ResolvedTemplate}
331
+ */
332
+ export function resolveTemplate(templateName, templatesRoot) {
333
+ void templatesRoot;
334
+
335
+ if (isLocalTemplateSpec(templateName)) {
336
+ const templateRoot = path.resolve(templateName);
337
+ if (!fs.existsSync(templateRoot)) {
338
+ throw new Error(`Local template path '${templateName}' does not exist.`);
339
+ }
340
+ if (!fs.statSync(templateRoot).isDirectory()) {
341
+ const packageTemplateRoot = installPackageSpec(templateName);
342
+ return {
343
+ requested: templateName,
344
+ root: packageTemplateRoot,
345
+ manifest: validateTemplateRoot(packageTemplateRoot),
346
+ source: "package",
347
+ packageSpec: templateName
348
+ };
349
+ }
350
+ return {
351
+ requested: templateName,
352
+ root: templateRoot,
353
+ manifest: validateTemplateRoot(templateRoot),
354
+ source: "local",
355
+ packageSpec: null
356
+ };
357
+ }
358
+
359
+ const templateRoot = installPackageSpec(templateName);
360
+ if (!fs.existsSync(templateRoot)) {
361
+ throw new Error(`Template package '${templateName}' did not install to '${templateRoot}'.`);
362
+ }
363
+ return {
364
+ requested: templateName,
365
+ root: templateRoot,
366
+ manifest: validateTemplateRoot(templateRoot),
367
+ source: "package",
368
+ packageSpec: templateName
369
+ };
370
+ }