@topogram/cli 0.3.64 → 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 (245) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/agent-ops/query-builders/auth.js +375 -0
  5. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  6. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  7. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  8. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  9. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  10. package/src/agent-ops/query-builders/change-risk.js +25 -0
  11. package/src/agent-ops/query-builders/common.js +149 -0
  12. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  13. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  14. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  15. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  16. package/src/agent-ops/query-builders/work-packets.js +417 -0
  17. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  18. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  19. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  20. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  21. package/src/agent-ops/query-builders.d.ts +26 -26
  22. package/src/agent-ops/query-builders.js +42 -5021
  23. package/src/catalog/constants.js +10 -0
  24. package/src/catalog/copy.js +60 -0
  25. package/src/catalog/diagnostics.js +15 -0
  26. package/src/catalog/entries.js +42 -0
  27. package/src/catalog/files.js +67 -0
  28. package/src/catalog/provenance.js +122 -0
  29. package/src/catalog/source.js +150 -0
  30. package/src/catalog/validation.js +252 -0
  31. package/src/catalog.d.ts +2 -0
  32. package/src/catalog.js +18 -746
  33. package/src/cli/commands/catalog/check.js +31 -0
  34. package/src/cli/commands/catalog/copy.js +59 -0
  35. package/src/cli/commands/catalog/doctor.js +248 -0
  36. package/src/cli/commands/catalog/help.js +21 -0
  37. package/src/cli/commands/catalog/list.js +52 -0
  38. package/src/cli/commands/catalog/runner.js +92 -0
  39. package/src/cli/commands/catalog/shared.js +17 -0
  40. package/src/cli/commands/catalog/show.js +134 -0
  41. package/src/cli/commands/catalog.js +30 -615
  42. package/src/cli/commands/generator-policy/package-info.js +162 -0
  43. package/src/cli/commands/generator-policy/payloads.js +372 -0
  44. package/src/cli/commands/generator-policy/printers.js +159 -0
  45. package/src/cli/commands/generator-policy/runner.js +81 -0
  46. package/src/cli/commands/generator-policy/shared.js +39 -0
  47. package/src/cli/commands/generator-policy.js +15 -783
  48. package/src/cli/commands/import/adopt.js +170 -0
  49. package/src/cli/commands/import/check.js +91 -0
  50. package/src/cli/commands/import/diff.js +84 -0
  51. package/src/cli/commands/import/help.js +47 -0
  52. package/src/cli/commands/import/paths.js +277 -0
  53. package/src/cli/commands/import/plan.js +284 -0
  54. package/src/cli/commands/import/refresh.js +470 -0
  55. package/src/cli/commands/import/status-history.js +196 -0
  56. package/src/cli/commands/import/workspace.js +230 -0
  57. package/src/cli/commands/import.js +33 -1732
  58. package/src/cli/commands/package/constants.js +17 -0
  59. package/src/cli/commands/package/doctor.js +240 -0
  60. package/src/cli/commands/package/help.js +27 -0
  61. package/src/cli/commands/package/lockfile.js +135 -0
  62. package/src/cli/commands/package/npm.js +97 -0
  63. package/src/cli/commands/package/reporting.js +35 -0
  64. package/src/cli/commands/package/runner.js +33 -0
  65. package/src/cli/commands/package/shared.js +9 -0
  66. package/src/cli/commands/package/update-cli.js +252 -0
  67. package/src/cli/commands/package/versions.js +35 -0
  68. package/src/cli/commands/package.js +29 -813
  69. package/src/cli/commands/query/change-plan.js +68 -0
  70. package/src/cli/commands/query/definitions.js +202 -0
  71. package/src/cli/commands/query/import-adopt.js +121 -0
  72. package/src/cli/commands/query/runner/artifacts.js +102 -0
  73. package/src/cli/commands/query/runner/boundaries.js +211 -0
  74. package/src/cli/commands/query/runner/change.js +182 -0
  75. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  76. package/src/cli/commands/query/runner/index.js +31 -0
  77. package/src/cli/commands/query/runner/output.js +12 -0
  78. package/src/cli/commands/query/runner/workflow.js +241 -0
  79. package/src/cli/commands/query/runner.js +3 -0
  80. package/src/cli/commands/query/workflow-context.js +5 -0
  81. package/src/cli/commands/query/workspace.js +274 -0
  82. package/src/cli/commands/query.js +9 -1300
  83. package/src/cli/commands/template/baseline.js +100 -0
  84. package/src/cli/commands/template/check.js +466 -0
  85. package/src/cli/commands/template/constants.js +8 -0
  86. package/src/cli/commands/template/diagnostics.js +26 -0
  87. package/src/cli/commands/template/help.js +28 -0
  88. package/src/cli/commands/template/lifecycle.js +404 -0
  89. package/src/cli/commands/template/list-show.js +287 -0
  90. package/src/cli/commands/template/policy.js +422 -0
  91. package/src/cli/commands/template/shared.js +127 -0
  92. package/src/cli/commands/template/updates.js +352 -0
  93. package/src/cli/commands/template.js +41 -2143
  94. package/src/generator/api/contracts.js +497 -0
  95. package/src/generator/api/metadata.js +221 -0
  96. package/src/generator/api/openapi.js +559 -0
  97. package/src/generator/api/schema.js +124 -0
  98. package/src/generator/api/types.d.ts +98 -0
  99. package/src/generator/api.js +3 -1195
  100. package/src/generator/context/shared/domain-sdlc.js +282 -0
  101. package/src/generator/context/shared/maintained-boundary.js +665 -0
  102. package/src/generator/context/shared/metrics.js +85 -0
  103. package/src/generator/context/shared/primitives.js +64 -0
  104. package/src/generator/context/shared/relationships.js +453 -0
  105. package/src/generator/context/shared/summaries.js +263 -0
  106. package/src/generator/context/shared/types.d.ts +207 -0
  107. package/src/generator/context/shared.d.ts +42 -0
  108. package/src/generator/context/shared.js +80 -1390
  109. package/src/generator/context/slice/core.js +397 -0
  110. package/src/generator/context/slice/sdlc.js +417 -0
  111. package/src/generator/context/slice/ui-packets.js +183 -0
  112. package/src/generator/context/slice.js +2 -859
  113. package/src/generator/registry/index.js +507 -0
  114. package/src/generator/registry.js +18 -504
  115. package/src/generator/runtime/environment/index.js +666 -0
  116. package/src/generator/runtime/environment.js +4 -666
  117. package/src/generator/runtime/runtime-check/index.js +554 -0
  118. package/src/generator/runtime/runtime-check.js +4 -554
  119. package/src/generator/runtime/shared/index.js +572 -0
  120. package/src/generator/runtime/shared.js +19 -570
  121. package/src/generator/shared.d.ts +2 -0
  122. package/src/generator/surfaces/shared.d.ts +3 -0
  123. package/src/generator/widget-conformance/behavior-report.js +258 -0
  124. package/src/generator/widget-conformance/checks.js +371 -0
  125. package/src/generator/widget-conformance/projection-context.js +200 -0
  126. package/src/generator/widget-conformance/report.js +166 -0
  127. package/src/generator/widget-conformance/types.d.ts +121 -0
  128. package/src/generator/widget-conformance.js +3 -824
  129. package/src/import/core/context.d.ts +3 -0
  130. package/src/import/core/contracts.d.ts +1 -0
  131. package/src/import/core/registry.d.ts +4 -0
  132. package/src/import/core/runner/candidates.js +217 -0
  133. package/src/import/core/runner/options.js +22 -0
  134. package/src/import/core/runner/reports.js +50 -0
  135. package/src/import/core/runner/run.js +79 -0
  136. package/src/import/core/runner/tracks.js +150 -0
  137. package/src/import/core/runner/ui-drafts.js +337 -0
  138. package/src/import/core/runner.js +3 -698
  139. package/src/import/core/shared/api-routes.js +221 -0
  140. package/src/import/core/shared/candidates.js +97 -0
  141. package/src/import/core/shared/files.js +177 -0
  142. package/src/import/core/shared/next-app.js +389 -0
  143. package/src/import/core/shared/types.d.ts +51 -0
  144. package/src/import/core/shared/ui-routes.js +230 -0
  145. package/src/import/core/shared.js +60 -861
  146. package/src/new-project/constants.js +128 -0
  147. package/src/new-project/create.js +83 -0
  148. package/src/new-project/json.js +28 -0
  149. package/src/new-project/metadata.js +96 -0
  150. package/src/new-project/package-spec.js +161 -0
  151. package/src/new-project/project-files.js +348 -0
  152. package/src/new-project/template-policy.js +269 -0
  153. package/src/new-project/template-resolution.js +368 -0
  154. package/src/new-project/template-snapshots.js +430 -0
  155. package/src/new-project/template-updates.js +512 -0
  156. package/src/new-project/types.d.ts +83 -0
  157. package/src/new-project.js +6 -2277
  158. package/src/parser.d.ts +87 -1
  159. package/src/parser.js +118 -0
  160. package/src/policy/review-boundaries.d.ts +15 -0
  161. package/src/project-config/index.js +564 -0
  162. package/src/project-config.js +19 -561
  163. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  164. package/src/resolver/enrich/bug.js +2 -0
  165. package/src/resolver/enrich/pitch.js +2 -0
  166. package/src/resolver/enrich/requirement.js +2 -0
  167. package/src/resolver/enrich/task.js +2 -0
  168. package/src/resolver/index.js +19 -2089
  169. package/src/resolver/normalize.js +384 -1
  170. package/src/resolver/plans.js +168 -0
  171. package/src/resolver/projections-api.js +494 -0
  172. package/src/resolver/projections-db.js +133 -0
  173. package/src/resolver/projections-ui.js +317 -0
  174. package/src/resolver/shapes.js +251 -0
  175. package/src/resolver/shared.js +278 -0
  176. package/src/resolver/widgets.js +132 -0
  177. package/src/template-trust/constants.js +62 -0
  178. package/src/template-trust/content.js +258 -0
  179. package/src/template-trust/diff.js +92 -0
  180. package/src/template-trust/policy.js +61 -0
  181. package/src/template-trust/record.js +90 -0
  182. package/src/template-trust/status.js +182 -0
  183. package/src/template-trust.js +24 -687
  184. package/src/text-helpers.d.ts +1 -0
  185. package/src/topogram-types.d.ts +69 -0
  186. package/src/validator/common.js +488 -0
  187. package/src/validator/data-model.js +237 -0
  188. package/src/validator/docs.js +167 -0
  189. package/src/validator/expressions.js +146 -1
  190. package/src/validator/index.d.ts +23 -0
  191. package/src/validator/index.js +32 -3585
  192. package/src/validator/kinds.d.ts +41 -0
  193. package/src/validator/kinds.js +2 -0
  194. package/src/validator/model-helpers.js +46 -0
  195. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  196. package/src/validator/per-kind/bug.js +6 -0
  197. package/src/validator/per-kind/domain.js +15 -2
  198. package/src/validator/per-kind/pitch.js +7 -0
  199. package/src/validator/per-kind/requirement.js +5 -0
  200. package/src/validator/per-kind/task.js +7 -0
  201. package/src/validator/per-kind/widget.js +14 -0
  202. package/src/validator/projections/api-http-async.js +410 -0
  203. package/src/validator/projections/api-http-authz.js +88 -0
  204. package/src/validator/projections/api-http-core.js +205 -0
  205. package/src/validator/projections/api-http-policies.js +339 -0
  206. package/src/validator/projections/api-http-responses.js +233 -0
  207. package/src/validator/projections/api-http.js +44 -0
  208. package/src/validator/projections/db.js +353 -0
  209. package/src/validator/projections/generator-defaults.js +45 -0
  210. package/src/validator/projections/helpers.js +87 -0
  211. package/src/validator/projections/ui-helpers.js +214 -0
  212. package/src/validator/projections/ui-navigation.js +344 -0
  213. package/src/validator/projections/ui-structure.js +364 -0
  214. package/src/validator/projections/ui-widgets.js +493 -0
  215. package/src/validator/projections/ui.js +46 -0
  216. package/src/validator/registry.js +48 -1
  217. package/src/validator/utils.d.ts +20 -0
  218. package/src/validator/utils.js +115 -12
  219. package/src/widget-behavior.d.ts +1 -0
  220. package/src/workflows/import-app/api/collect.js +221 -0
  221. package/src/workflows/import-app/api/openapi.js +257 -0
  222. package/src/workflows/import-app/api/routes.js +327 -0
  223. package/src/workflows/import-app/api/sources.js +22 -0
  224. package/src/workflows/import-app/api.js +2 -797
  225. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  226. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  227. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  228. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  229. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  230. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  231. package/src/workflows/reconcile/adoption-plan.js +30 -740
  232. package/src/workflows/reconcile/auth/closures.js +115 -0
  233. package/src/workflows/reconcile/auth/formatters.js +142 -0
  234. package/src/workflows/reconcile/auth/inference.js +330 -0
  235. package/src/workflows/reconcile/auth/roles.js +122 -0
  236. package/src/workflows/reconcile/auth.js +35 -690
  237. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  238. package/src/workflows/reconcile/bundle-core.js +12 -598
  239. package/src/workflows/reconcile/canonical-surface.js +1 -1
  240. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  241. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  242. package/src/workflows/reconcile/impacts/patches.js +252 -0
  243. package/src/workflows/reconcile/impacts/reports.js +80 -0
  244. package/src/workflows/reconcile/impacts.js +14 -623
  245. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,128 @@
1
+ // @ts-check
2
+
3
+ export const CLI_PACKAGE_NAME = "@topogram/cli";
4
+ export const DEFAULT_TEMPLATE_NAME = "hello-web";
5
+ export const TEMPLATE_MANIFEST = "topogram-template.json";
6
+ export const TEMPLATE_FILES_MANIFEST = ".topogram-template-files.json";
7
+ export const TEMPLATE_POLICY_FILE = "topogram.template-policy.json";
8
+ export const MAX_TEXT_DIFF_BYTES = 256 * 1024;
9
+
10
+ export const GENERATOR_LABELS = new Map([
11
+ ["topogram/express", "Express"],
12
+ ["topogram/hono", "Hono"],
13
+ ["topogram/postgres", "Postgres"],
14
+ ["topogram/react", "React"],
15
+ ["topogram/sqlite", "SQLite"],
16
+ ["topogram/sveltekit", "SvelteKit"],
17
+ ["topogram/vanilla-web", "Vanilla HTML/CSS/JS"]
18
+ ]);
19
+
20
+ export const SURFACE_ORDER = new Map([
21
+ ["web_surface", 10],
22
+ ["api_service", 20],
23
+ ["database", 30],
24
+ ["ios_surface", 40],
25
+ ["android_surface", 50]
26
+ ]);
27
+
28
+ /**
29
+ * @param {string} templateId
30
+ * @param {string} relativePath
31
+ * @returns {string}
32
+ */
33
+ export function unsupportedTemplateSymlinkMessage(templateId, relativePath) {
34
+ return `Template '${templateId}' contains unsupported symlink '${relativePath}'. Template packs must copy real files because Topogram records hashes for copied topogram/ and implementation/ content; symlinks can point outside the trusted template root. Replace the symlink with a real file or directory before running topogram new or topogram template check.`;
35
+ }
36
+
37
+ /**
38
+ * @typedef {Object} CreateNewProjectOptions
39
+ * @property {string} targetPath
40
+ * @property {string} [templateName]
41
+ * @property {string} engineRoot
42
+ * @property {string} templatesRoot
43
+ * @property {CatalogTemplateProvenance|null} [templateProvenance]
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} TemplateUpdatePlanOptions
48
+ * @property {string} projectRoot
49
+ * @property {Record<string, any>} projectConfig
50
+ * @property {string|null} [templateName]
51
+ * @property {string} templatesRoot
52
+ */
53
+
54
+ /**
55
+ * @typedef {TemplateUpdatePlanOptions & { filePath: string, action: "accept-current"|"accept-candidate"|"delete-current" }} TemplateUpdateFileActionOptions
56
+ */
57
+
58
+ /**
59
+ * @typedef {Object} TemplateOwnedFileRecord
60
+ * @property {string} path
61
+ * @property {string} sha256
62
+ * @property {number} size
63
+ */
64
+
65
+ /**
66
+ * @typedef {Object} TemplateManifest
67
+ * @property {string} id
68
+ * @property {string} version
69
+ * @property {string} kind
70
+ * @property {string} topogramVersion
71
+ * @property {boolean} [includesExecutableImplementation]
72
+ * @property {string} [description]
73
+ * @property {Record<string, string>} [starterScripts]
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} TemplateTopologySummary
78
+ * @property {string[]} surfaces
79
+ * @property {string[]} generators
80
+ * @property {string} stack
81
+ */
82
+
83
+ /**
84
+ * @typedef {Object} TemplatePolicy
85
+ * @property {string} version
86
+ * @property {Array<"local"|"package">} allowedSources
87
+ * @property {string[]} allowedTemplateIds
88
+ * @property {string[]} [allowedPackageScopes]
89
+ * @property {"allow"|"warn"|"deny"} executableImplementation
90
+ * @property {Record<string, string>} [pinnedVersions]
91
+ */
92
+
93
+ /**
94
+ * @typedef {Object} TemplatePolicyInfo
95
+ * @property {string} path
96
+ * @property {TemplatePolicy|null} policy
97
+ * @property {boolean} exists
98
+ * @property {TemplateUpdateDiagnostic[]} diagnostics
99
+ */
100
+
101
+ /**
102
+ * @typedef {Object} TemplateUpdateDiagnostic
103
+ * @property {string} code
104
+ * @property {"error"|"warning"} severity
105
+ * @property {string} message
106
+ * @property {string|null} path
107
+ * @property {string|null} suggestedFix
108
+ * @property {string|null} step
109
+ */
110
+
111
+ /**
112
+ * @typedef {Object} ResolvedTemplate
113
+ * @property {string} requested
114
+ * @property {string} root
115
+ * @property {TemplateManifest} manifest
116
+ * @property {"local"|"package"} source
117
+ * @property {string|null} packageSpec
118
+ */
119
+
120
+ /**
121
+ * @typedef {Object} CatalogTemplateProvenance
122
+ * @property {string} id
123
+ * @property {string} source
124
+ * @property {string} package
125
+ * @property {string} version
126
+ * @property {string} packageSpec
127
+ * @property {boolean} [includesExecutableImplementation]
128
+ */
@@ -0,0 +1,83 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ import { defaultGeneratorPolicy, writeGeneratorPolicy } from "../generator-policy.js";
6
+ import { writeTemplateTrustRecord } from "../template-trust.js";
7
+ import { DEFAULT_TEMPLATE_NAME } from "./constants.js";
8
+ import { writeProjectTemplateMetadata } from "./metadata.js";
9
+ import { assertProjectOutsideEngine, copyTopogramWorkspace, ensureCreatableProjectRoot, writeAgentsGuide, writeExplainScript, writeProjectPackage, writeProjectReadme } from "./project-files.js";
10
+ import { resolveTemplate } from "./template-resolution.js";
11
+ import { defaultTemplatePolicyForTemplate, writeTemplatePolicy } from "./template-policy.js";
12
+ import { writeTemplateFilesManifest } from "./template-snapshots.js";
13
+
14
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
15
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
16
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
17
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
18
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
19
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
20
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
21
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
22
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
23
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
24
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
25
+
26
+ /**
27
+ * @param {CreateNewProjectOptions} options
28
+ * @returns {{ projectRoot: string, templateName: string, template: Record<string, any>, topogramPath: string, appPath: string, warnings: string[] }}
29
+ */
30
+ export function createNewProject({
31
+ targetPath,
32
+ templateName = DEFAULT_TEMPLATE_NAME,
33
+ engineRoot,
34
+ templatesRoot,
35
+ templateProvenance = null
36
+ }) {
37
+ if (!targetPath) {
38
+ throw new Error("topogram new requires <path>.");
39
+ }
40
+ const projectRoot = path.resolve(targetPath);
41
+ assertProjectOutsideEngine(projectRoot, engineRoot);
42
+ const template = resolveTemplate(templateName, templatesRoot);
43
+ if (
44
+ templateProvenance &&
45
+ typeof templateProvenance.includesExecutableImplementation === "boolean" &&
46
+ templateProvenance.includesExecutableImplementation !== Boolean(template.manifest.includesExecutableImplementation)
47
+ ) {
48
+ throw new Error(
49
+ `Catalog entry '${templateProvenance.id}' declares includesExecutableImplementation: ${templateProvenance.includesExecutableImplementation}, ` +
50
+ `but template package '${template.packageSpec || template.requested}' declares includesExecutableImplementation: ${Boolean(template.manifest.includesExecutableImplementation)}.`
51
+ );
52
+ }
53
+
54
+ ensureCreatableProjectRoot(projectRoot);
55
+ copyTopogramWorkspace(template.root, projectRoot);
56
+ const projectConfig = writeProjectTemplateMetadata(projectRoot, template, templateProvenance);
57
+ writeProjectPackage(projectRoot, engineRoot, template);
58
+ writeExplainScript(projectRoot);
59
+ writeProjectReadme(projectRoot, projectConfig);
60
+ writeAgentsGuide(projectRoot, projectConfig);
61
+ writeTemplateFilesManifest(projectRoot, projectConfig);
62
+ writeTemplatePolicy(projectRoot, defaultTemplatePolicyForTemplate(template));
63
+ writeGeneratorPolicy(projectRoot, defaultGeneratorPolicy());
64
+
65
+ const warnings = [];
66
+ if (template.manifest.includesExecutableImplementation) {
67
+ writeTemplateTrustRecord(projectRoot, projectConfig);
68
+ warnings.push(
69
+ `Template '${template.manifest.id}' copied implementation/ code into this project. ` +
70
+ "topogram new did not execute it, but topogram generate may load it later. " +
71
+ "Recorded local trust in .topogram-template-trust.json."
72
+ );
73
+ }
74
+
75
+ return {
76
+ projectRoot,
77
+ templateName: template.manifest.id,
78
+ template: projectConfig.template,
79
+ topogramPath: path.join(projectRoot, "topogram"),
80
+ appPath: path.join(projectRoot, "app"),
81
+ warnings
82
+ };
83
+ }
@@ -0,0 +1,28 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {any} value
5
+ * @returns {any}
6
+ */
7
+ function sortJsonValue(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map(sortJsonValue);
10
+ }
11
+ if (value && typeof value === "object") {
12
+ /** @type {Record<string, any>} */
13
+ const sorted = {};
14
+ for (const key of Object.keys(value).sort((left, right) => left.localeCompare(right))) {
15
+ sorted[key] = sortJsonValue(value[key]);
16
+ }
17
+ return sorted;
18
+ }
19
+ return value;
20
+ }
21
+
22
+ /**
23
+ * @param {any} value
24
+ * @returns {string}
25
+ */
26
+ export function stableJsonStringify(value) {
27
+ return JSON.stringify(sortJsonValue(value), null, 2);
28
+ }
@@ -0,0 +1,96 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableJsonStringify } from "./json.js";
7
+
8
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
9
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
10
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
11
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
12
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
13
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
14
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
15
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
16
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
17
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
18
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
19
+
20
+ /**
21
+ * @param {string} projectRoot
22
+ * @param {ResolvedTemplate} template
23
+ * @param {CatalogTemplateProvenance|null} [templateProvenance]
24
+ * @returns {Record<string, any>}
25
+ */
26
+ export function writeProjectTemplateMetadata(projectRoot, template, templateProvenance = null) {
27
+ const projectConfigPath = path.join(projectRoot, "topogram.project.json");
28
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
29
+ projectConfig.template = projectTemplateMetadata(template, templateProvenance);
30
+ fs.writeFileSync(projectConfigPath, `${stableJsonStringify(projectConfig)}\n`, "utf8");
31
+ return projectConfig;
32
+ }
33
+
34
+ /**
35
+ * @param {ResolvedTemplate} template
36
+ * @param {CatalogTemplateProvenance|null} [templateProvenance]
37
+ * @returns {{ id: string, version: string, source: string, requested: string, sourceSpec: string, sourceRoot: string|null, includesExecutableImplementation: boolean, catalog?: CatalogTemplateProvenance }}
38
+ */
39
+ export function projectTemplateMetadata(template, templateProvenance = null) {
40
+ /** @type {{ id: string, version: string, source: string, requested: string, sourceSpec: string, sourceRoot: string|null, includesExecutableImplementation: boolean, catalog?: CatalogTemplateProvenance }} */
41
+ const metadata = {
42
+ id: template.manifest.id,
43
+ version: template.manifest.version,
44
+ source: template.source,
45
+ requested: templateProvenance?.id || template.requested,
46
+ sourceSpec: template.packageSpec || template.requested,
47
+ sourceRoot: template.source === "local" ? template.root : null,
48
+ includesExecutableImplementation: Boolean(template.manifest.includesExecutableImplementation)
49
+ };
50
+ if (templateProvenance) {
51
+ metadata.catalog = templateProvenance;
52
+ }
53
+ return metadata;
54
+ }
55
+
56
+ /**
57
+ * @param {Record<string, any>} projectConfig
58
+ * @returns {{ id: string|null, version: string|null, source: string|null, sourceSpec: string|null, requested: string|null }}
59
+ */
60
+ export function currentTemplateMetadata(projectConfig) {
61
+ const currentTemplate = projectConfig.template || {};
62
+ return {
63
+ id: typeof currentTemplate.id === "string" ? currentTemplate.id : null,
64
+ version: typeof currentTemplate.version === "string" ? currentTemplate.version : null,
65
+ source: typeof currentTemplate.source === "string" ? currentTemplate.source : null,
66
+ sourceSpec: typeof currentTemplate.sourceSpec === "string" ? currentTemplate.sourceSpec : null,
67
+ requested: typeof currentTemplate.requested === "string" ? currentTemplate.requested : null
68
+ };
69
+ }
70
+
71
+ /**
72
+ * @param {ResolvedTemplate} template
73
+ * @param {Record<string, any>|null} currentProjectConfig
74
+ * @returns {ReturnType<typeof projectTemplateMetadata>}
75
+ */
76
+ export function candidateProjectTemplateMetadata(template, currentProjectConfig) {
77
+ const metadata = projectTemplateMetadata(template);
78
+ const currentTemplate = currentProjectConfig?.template || null;
79
+ if (!currentTemplate || currentTemplate.id !== metadata.id) {
80
+ return metadata;
81
+ }
82
+ if (typeof currentTemplate.requested === "string" && currentTemplate.requested) {
83
+ metadata.requested = currentTemplate.requested;
84
+ }
85
+ if (currentTemplate.catalog && typeof currentTemplate.catalog === "object") {
86
+ metadata.catalog = {
87
+ ...currentTemplate.catalog,
88
+ package: typeof currentTemplate.catalog.package === "string"
89
+ ? currentTemplate.catalog.package
90
+ : metadata.id,
91
+ version: metadata.version,
92
+ packageSpec: metadata.sourceSpec
93
+ };
94
+ }
95
+ return metadata;
96
+ }
@@ -0,0 +1,161 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { CLI_PACKAGE_NAME } from "./constants.js";
7
+
8
+ /**
9
+ * @param {string} projectRoot
10
+ * @returns {string}
11
+ */
12
+ export function packageNameFromPath(projectRoot) {
13
+ const baseName = path.basename(path.resolve(projectRoot)).toLowerCase();
14
+ const normalized = baseName
15
+ .replace(/[^a-z0-9._-]+/g, "-")
16
+ .replace(/^[._-]+/, "")
17
+ .replace(/[._-]+$/, "");
18
+ return normalized || "topogram-app";
19
+ }
20
+
21
+ /**
22
+ * @param {string} projectRoot
23
+ * @param {string} engineRoot
24
+ * @returns {string}
25
+ */
26
+ function fileDependencyForEngine(projectRoot, engineRoot) {
27
+ const relative = path.relative(projectRoot, engineRoot).replace(/\\/g, "/");
28
+ if (!relative || relative.startsWith("..")) {
29
+ return `file:${engineRoot}`;
30
+ }
31
+ return `file:./${relative}`;
32
+ }
33
+
34
+ /**
35
+ * @param {string} engineRoot
36
+ * @returns {{ name: string, version: string }}
37
+ */
38
+ function readCliPackageMetadata(engineRoot) {
39
+ const packagePath = path.join(engineRoot, "package.json");
40
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
41
+ return {
42
+ name: typeof pkg.name === "string" ? pkg.name : CLI_PACKAGE_NAME,
43
+ version: typeof pkg.version === "string" ? pkg.version : "0.0.0"
44
+ };
45
+ }
46
+
47
+ /**
48
+ * @param {string} engineRoot
49
+ * @returns {boolean}
50
+ */
51
+ function isSourceCheckoutEngine(engineRoot) {
52
+ return fs.existsSync(path.join(engineRoot, "tests", "active"));
53
+ }
54
+
55
+ /**
56
+ * @param {string} projectRoot
57
+ * @param {string} engineRoot
58
+ * @returns {{ name: string, spec: string }}
59
+ */
60
+ export function cliDependencyForProject(projectRoot, engineRoot) {
61
+ const metadata = readCliPackageMetadata(engineRoot);
62
+ const overrideSpec = process.env.TOPOGRAM_CLI_PACKAGE_SPEC || "";
63
+ if (overrideSpec) {
64
+ return { name: metadata.name, spec: overrideSpec };
65
+ }
66
+ if (isSourceCheckoutEngine(engineRoot)) {
67
+ return { name: metadata.name, spec: fileDependencyForEngine(projectRoot, engineRoot) };
68
+ }
69
+ return { name: metadata.name, spec: metadata.version };
70
+ }
71
+
72
+ /**
73
+ * @param {string} projectRoot
74
+ * @param {{ name: string, spec: string }} cliDependency
75
+ * @returns {void}
76
+ */
77
+ export function writeProjectNpmConfig(projectRoot, cliDependency) {
78
+ void projectRoot;
79
+ void cliDependency;
80
+ }
81
+
82
+ /**
83
+ * @param {string} templateRoot
84
+ * @returns {Record<string, string>}
85
+ */
86
+ export function generatorDependenciesForTemplate(templateRoot) {
87
+ const packagePath = path.join(templateRoot, "package.json");
88
+ if (!fs.existsSync(packagePath)) {
89
+ return {};
90
+ }
91
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
92
+ const explicit = pkg.topogramGeneratorDependencies &&
93
+ typeof pkg.topogramGeneratorDependencies === "object" &&
94
+ !Array.isArray(pkg.topogramGeneratorDependencies)
95
+ ? pkg.topogramGeneratorDependencies
96
+ : {};
97
+ const dependencies = {
98
+ ...(pkg.dependencies || {}),
99
+ ...(pkg.devDependencies || {}),
100
+ ...explicit
101
+ };
102
+ return Object.fromEntries(Object.entries(dependencies).filter(([name, spec]) =>
103
+ typeof name === "string" &&
104
+ (name.includes("topogram-generator") || name.startsWith("@topogram/generator-")) &&
105
+ typeof spec === "string" &&
106
+ spec.length > 0
107
+ ));
108
+ }
109
+
110
+ /**
111
+ * @param {string} parent
112
+ * @param {string} child
113
+ * @returns {boolean}
114
+ */
115
+ export function isSameOrInside(parent, child) {
116
+ const relative = path.relative(parent, child);
117
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
118
+ }
119
+
120
+ /**
121
+ * @param {string} value
122
+ * @returns {boolean}
123
+ */
124
+ export function isLocalTemplateSpec(value) {
125
+ return value === "." ||
126
+ value.startsWith("./") ||
127
+ value.startsWith("../") ||
128
+ path.isAbsolute(value);
129
+ }
130
+
131
+ /**
132
+ * @param {string} spec
133
+ * @returns {string}
134
+ */
135
+ export function packageNameFromSpec(spec) {
136
+ if (spec.startsWith("@")) {
137
+ const segments = spec.split("/");
138
+ if (segments.length < 2) {
139
+ throw new Error(`Invalid scoped template package spec '${spec}'.`);
140
+ }
141
+ const scope = segments[0];
142
+ const nameAndVersion = segments[1];
143
+ const versionIndex = nameAndVersion.indexOf("@");
144
+ const name = versionIndex >= 0 ? nameAndVersion.slice(0, versionIndex) : nameAndVersion;
145
+ return `${scope}/${name}`;
146
+ }
147
+ const versionIndex = spec.indexOf("@");
148
+ return versionIndex >= 0 ? spec.slice(0, versionIndex) : spec;
149
+ }
150
+
151
+ /**
152
+ * @param {string|null|undefined} spec
153
+ * @returns {string|null}
154
+ */
155
+ export function packageScopeFromSpec(spec) {
156
+ if (!spec) {
157
+ return null;
158
+ }
159
+ const packageName = packageNameFromSpec(spec);
160
+ return packageName.startsWith("@") ? packageName.split("/")[0] : null;
161
+ }