@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,153 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableStringify } from "../../format.js";
7
+ import {
8
+ DEFAULT_TOPO_FOLDER_NAME,
9
+ DEFAULT_WORKSPACE_PATH,
10
+ LEGACY_TOPOGRAM_FOLDER_NAME,
11
+ PROJECT_CONFIG_FILE
12
+ } from "../../workspace-paths.js";
13
+
14
+ /**
15
+ * @param {string|null|undefined} inputPath
16
+ * @returns {string}
17
+ */
18
+ function projectRootForMigration(inputPath) {
19
+ const absolute = path.resolve(inputPath || ".");
20
+ const base = path.basename(absolute);
21
+ if (base === DEFAULT_TOPO_FOLDER_NAME || base === LEGACY_TOPOGRAM_FOLDER_NAME) {
22
+ return path.dirname(absolute);
23
+ }
24
+ return absolute;
25
+ }
26
+
27
+ /**
28
+ * @param {string} projectRoot
29
+ * @returns {string[]}
30
+ */
31
+ function caseCollisionEntries(projectRoot) {
32
+ if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
33
+ return [];
34
+ }
35
+ return fs.readdirSync(projectRoot)
36
+ .filter((/** @type {string} */ entry) => entry.toLowerCase() === DEFAULT_TOPO_FOLDER_NAME && entry !== DEFAULT_TOPO_FOLDER_NAME);
37
+ }
38
+
39
+ /**
40
+ * @param {string} projectRoot
41
+ * @returns {{ write: boolean, path: string|null, before: any|null, after: any|null }}
42
+ */
43
+ function plannedProjectConfigUpdate(projectRoot) {
44
+ const configPath = path.join(projectRoot, PROJECT_CONFIG_FILE);
45
+ if (!fs.existsSync(configPath)) {
46
+ return { write: false, path: null, before: null, after: null };
47
+ }
48
+ const before = JSON.parse(fs.readFileSync(configPath, "utf8"));
49
+ const after = { ...before };
50
+ const currentWorkspace = before.workspace;
51
+ if (currentWorkspace == null || currentWorkspace === "./topogram" || currentWorkspace === "topogram") {
52
+ after.workspace = DEFAULT_WORKSPACE_PATH;
53
+ }
54
+ return {
55
+ write: JSON.stringify(before) !== JSON.stringify(after),
56
+ path: configPath,
57
+ before,
58
+ after
59
+ };
60
+ }
61
+
62
+ /**
63
+ * @param {string|null|undefined} inputPath
64
+ * @param {{ write?: boolean, json?: boolean }} [options]
65
+ * @returns {number}
66
+ */
67
+ export function runMigrateCommand(inputPath, options = {}) {
68
+ const projectRoot = projectRootForMigration(inputPath);
69
+ const legacyPath = path.join(projectRoot, LEGACY_TOPOGRAM_FOLDER_NAME);
70
+ const topoPath = path.join(projectRoot, DEFAULT_TOPO_FOLDER_NAME);
71
+ const write = Boolean(options.write);
72
+ /** @type {Array<Record<string, any>>} */
73
+ const diagnostics = [];
74
+ /** @type {Array<Record<string, any>>} */
75
+ const actions = [];
76
+
77
+ if (fs.existsSync(legacyPath) && fs.lstatSync(legacyPath).isSymbolicLink()) {
78
+ diagnostics.push({ severity: "error", message: `Refusing to migrate symlinked ${LEGACY_TOPOGRAM_FOLDER_NAME}/ at ${legacyPath}.` });
79
+ }
80
+ const collisions = caseCollisionEntries(projectRoot);
81
+ if (collisions.length > 0) {
82
+ diagnostics.push({ severity: "error", message: `Refusing to migrate because case-conflicting topo path(s) exist: ${collisions.join(", ")}.` });
83
+ }
84
+ if (fs.existsSync(legacyPath) && fs.existsSync(topoPath)) {
85
+ diagnostics.push({ severity: "error", message: `Refusing to migrate because both ${LEGACY_TOPOGRAM_FOLDER_NAME}/ and ${DEFAULT_TOPO_FOLDER_NAME}/ exist.` });
86
+ }
87
+ if (!fs.existsSync(legacyPath) && !fs.existsSync(topoPath)) {
88
+ diagnostics.push({ severity: "error", message: `No ${LEGACY_TOPOGRAM_FOLDER_NAME}/ or ${DEFAULT_TOPO_FOLDER_NAME}/ workspace folder found at ${projectRoot}.` });
89
+ }
90
+ if (fs.existsSync(topoPath) && fs.statSync(topoPath).isDirectory() && fs.readdirSync(topoPath).length > 0 && fs.existsSync(legacyPath)) {
91
+ diagnostics.push({ severity: "error", message: `Refusing to overwrite non-empty ${DEFAULT_TOPO_FOLDER_NAME}/ at ${topoPath}.` });
92
+ }
93
+
94
+ if (fs.existsSync(legacyPath) && diagnostics.length === 0) {
95
+ actions.push({
96
+ kind: "rename",
97
+ from: legacyPath,
98
+ to: topoPath
99
+ });
100
+ }
101
+ const configUpdate = plannedProjectConfigUpdate(projectRoot);
102
+ if (configUpdate.write) {
103
+ actions.push({
104
+ kind: "update_config",
105
+ path: configUpdate.path,
106
+ workspace: DEFAULT_WORKSPACE_PATH
107
+ });
108
+ }
109
+
110
+ const ok = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length === 0;
111
+ if (ok && write) {
112
+ for (const action of actions) {
113
+ if (action.kind === "rename") {
114
+ fs.renameSync(action.from, action.to);
115
+ }
116
+ if (action.kind === "update_config" && configUpdate.path && configUpdate.after) {
117
+ fs.writeFileSync(configUpdate.path, `${JSON.stringify(configUpdate.after, null, 2)}\n`, "utf8");
118
+ }
119
+ }
120
+ }
121
+
122
+ const payload = {
123
+ ok,
124
+ dryRun: !write,
125
+ projectRoot,
126
+ legacyPath,
127
+ topoPath,
128
+ actions,
129
+ diagnostics,
130
+ errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message)
131
+ };
132
+ if (options.json) {
133
+ console.log(stableStringify(payload));
134
+ } else if (payload.ok) {
135
+ console.log(write ? "Workspace folder migration complete." : "Workspace folder migration dry run.");
136
+ if (actions.length === 0) {
137
+ console.log("No changes needed.");
138
+ }
139
+ for (const action of actions) {
140
+ if (action.kind === "rename") {
141
+ console.log(`Rename: ${action.from} -> ${action.to}`);
142
+ }
143
+ if (action.kind === "update_config") {
144
+ console.log(`Update ${action.path}: workspace ${DEFAULT_WORKSPACE_PATH}`);
145
+ }
146
+ }
147
+ } else {
148
+ for (const error of payload.errors) {
149
+ console.error(error);
150
+ }
151
+ }
152
+ return payload.ok ? 0 : 1;
153
+ }
@@ -0,0 +1,17 @@
1
+ // @ts-check
2
+
3
+ export const CLI_PACKAGE_NAME = "@topogram/cli";
4
+ export const NPMJS_REGISTRY = "https://registry.npmjs.org";
5
+
6
+ export const PACKAGE_UPDATE_CLI_CHECK_SCRIPTS = [
7
+ "cli:surface",
8
+ "doctor",
9
+ "catalog:show",
10
+ "catalog:template-show",
11
+ "check",
12
+ "pack:check",
13
+ "verify"
14
+ ];
15
+ export const PACKAGE_UPDATE_CLI_INFO_SCRIPTS = ["cli:surface", "doctor", "catalog:show", "catalog:template-show"];
16
+ export const PACKAGE_UPDATE_CLI_VERIFICATION_SCRIPTS = ["verify", "pack:check", "check"];
17
+ export const ENGINE_ROOT = decodeURIComponent(new URL("../../../../", import.meta.url).pathname);
@@ -0,0 +1,240 @@
1
+ // @ts-check
2
+
3
+ import childProcess from "node:child_process";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+
7
+ import {
8
+ catalogDoctorPackageDiagnostic,
9
+ runNpmViewPackageSpec
10
+ } from "../catalog.js";
11
+ import { CLI_PACKAGE_NAME, ENGINE_ROOT } from "./constants.js";
12
+ import { compareSemver } from "./versions.js";
13
+
14
+ /**
15
+ * @param {string} cwd
16
+ * @returns {string|null}
17
+ */
18
+ export function readProjectCliDependencySpec(cwd) {
19
+ const packagePath = path.join(cwd, "package.json");
20
+ if (!fs.existsSync(packagePath)) {
21
+ return null;
22
+ }
23
+ try {
24
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
25
+ const dependencies = {
26
+ ...(pkg.dependencies && typeof pkg.dependencies === "object" ? pkg.dependencies : {}),
27
+ ...(pkg.devDependencies && typeof pkg.devDependencies === "object" ? pkg.devDependencies : {})
28
+ };
29
+ const spec = dependencies[CLI_PACKAGE_NAME];
30
+ return typeof spec === "string" && spec ? spec : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {string|null} spec
38
+ * @returns {boolean}
39
+ */
40
+ export function isLocalCliDependencySpec(spec) {
41
+ if (!spec) {
42
+ return false;
43
+ }
44
+ return spec.startsWith("file:") ||
45
+ spec.startsWith(".") ||
46
+ spec.startsWith("/") ||
47
+ spec.endsWith(".tgz");
48
+ }
49
+
50
+ /**
51
+ * @returns {{ version: string, minimum: string, ok: boolean, diagnostics: any[] }}
52
+ */
53
+ export function checkDoctorNode() {
54
+ const version = process.version;
55
+ const minimum = "20.0.0";
56
+ const ok = compareSemver(version.replace(/^v/, ""), minimum) >= 0;
57
+ return {
58
+ version,
59
+ minimum: `>=${minimum}`,
60
+ ok,
61
+ diagnostics: ok ? [] : [{
62
+ code: "node_version_unsupported",
63
+ severity: "error",
64
+ message: `Topogram requires Node.js >=${minimum}; current version is ${version}.`,
65
+ path: null,
66
+ suggestedFix: "Install Node.js 20 or newer."
67
+ }]
68
+ };
69
+ }
70
+
71
+ /**
72
+ * @returns {{ available: boolean, version: string|null, diagnostics: any[] }}
73
+ */
74
+ export function checkDoctorNpm() {
75
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
76
+ const result = childProcess.spawnSync(npmBin, ["--version"], {
77
+ encoding: "utf8",
78
+ env: {
79
+ ...process.env,
80
+ PATH: process.env.PATH || ""
81
+ }
82
+ });
83
+ if (result.status === 0) {
84
+ return {
85
+ available: true,
86
+ version: String(result.stdout || "").trim() || null,
87
+ diagnostics: []
88
+ };
89
+ }
90
+ return {
91
+ available: false,
92
+ version: null,
93
+ diagnostics: [{
94
+ code: "npm_not_found",
95
+ severity: "error",
96
+ message: "npm was not found on PATH.",
97
+ path: null,
98
+ suggestedFix: "Install Node.js/npm, then rerun `topogram doctor`."
99
+ }]
100
+ };
101
+ }
102
+
103
+ /**
104
+ * @returns {string}
105
+ */
106
+ export function readInstalledCliPackageVersion() {
107
+ const packagePath = path.join(ENGINE_ROOT, "package.json");
108
+ if (!fs.existsSync(packagePath)) {
109
+ return "0.0.0";
110
+ }
111
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
112
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
113
+ }
114
+
115
+ /**
116
+ * @param {string} key
117
+ * @returns {string|null}
118
+ */
119
+ export function npmConfigGet(key) {
120
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
121
+ const result = childProcess.spawnSync(npmBin, ["config", "get", key], {
122
+ encoding: "utf8",
123
+ env: {
124
+ ...process.env,
125
+ PATH: process.env.PATH || ""
126
+ }
127
+ });
128
+ if (result.status !== 0) {
129
+ return null;
130
+ }
131
+ const value = String(result.stdout || "").trim();
132
+ return value && value !== "undefined" && value !== "null" ? value : null;
133
+ }
134
+
135
+ /**
136
+ * @param {string} packageSpec
137
+ * @returns {{ ok: boolean, checkedVersion: string|null, diagnostics: any[] }}
138
+ */
139
+ export function checkDoctorPackageAccess(packageSpec) {
140
+ const result = runNpmViewPackageSpec(packageSpec);
141
+ if (result.status === 0) {
142
+ return {
143
+ ok: true,
144
+ checkedVersion: String(result.stdout || "").trim().replace(/^"|"$/g, "") || null,
145
+ diagnostics: []
146
+ };
147
+ }
148
+ return {
149
+ ok: false,
150
+ checkedVersion: null,
151
+ diagnostics: [doctorPackageDiagnostic(packageSpec, result)]
152
+ };
153
+ }
154
+
155
+ /**
156
+ * @param {string} spec
157
+ * @returns {string|null}
158
+ */
159
+ export function registryPackageNameFromSpec(spec) {
160
+ if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("file:") || spec.endsWith(".tgz")) {
161
+ return null;
162
+ }
163
+ if (spec.startsWith("@")) {
164
+ const parts = spec.split("/");
165
+ if (parts.length < 2) {
166
+ return null;
167
+ }
168
+ return `${parts[0]}/${parts[1].replace(/@[^@]+$/, "")}`;
169
+ }
170
+ return spec.replace(/@[^@]+$/, "");
171
+ }
172
+
173
+ /**
174
+ * @param {string} packageSpec
175
+ * @returns {{ ok: boolean, package: string|null, packageSpec: string, currentVersion: string|null, latestVersion: string|null, current: boolean|null, diagnostics: any[] }}
176
+ */
177
+ export function checkTemplatePackageStatus(packageSpec) {
178
+ const packageName = registryPackageNameFromSpec(packageSpec);
179
+ if (!packageName) {
180
+ return {
181
+ ok: true,
182
+ package: null,
183
+ packageSpec,
184
+ currentVersion: null,
185
+ latestVersion: null,
186
+ current: null,
187
+ diagnostics: []
188
+ };
189
+ }
190
+ const access = checkDoctorPackageAccess(packageSpec);
191
+ const latest = checkDoctorPackageAccess(`${packageName}@latest`);
192
+ const currentVersion = access.checkedVersion;
193
+ const latestVersion = latest.checkedVersion;
194
+ return {
195
+ ok: access.ok && latest.ok,
196
+ package: packageName,
197
+ packageSpec,
198
+ currentVersion,
199
+ latestVersion,
200
+ current: currentVersion && latestVersion ? currentVersion === latestVersion : null,
201
+ diagnostics: [...access.diagnostics, ...latest.diagnostics]
202
+ };
203
+ }
204
+
205
+ /**
206
+ * @param {string} packageSpec
207
+ * @returns {{ checked: false, ok: null, package: string|null, packageSpec: string, currentVersion: null, latestVersion: null, current: null, reason: string, diagnostics: any[] }}
208
+ */
209
+ export function localTemplatePackageStatus(packageSpec) {
210
+ return {
211
+ checked: false,
212
+ ok: null,
213
+ package: registryPackageNameFromSpec(packageSpec),
214
+ packageSpec,
215
+ currentVersion: null,
216
+ latestVersion: null,
217
+ current: null,
218
+ reason: "Package registry checks were skipped because --local was used.",
219
+ diagnostics: []
220
+ };
221
+ }
222
+
223
+ /**
224
+ * @param {string} packageSpec
225
+ * @param {{ stdout?: string, stderr?: string, error?: Error }} result
226
+ * @returns {any}
227
+ */
228
+ function doctorPackageDiagnostic(packageSpec, result) {
229
+ const diagnostic = catalogDoctorPackageDiagnostic({
230
+ id: CLI_PACKAGE_NAME,
231
+ kind: "package",
232
+ package: CLI_PACKAGE_NAME,
233
+ defaultVersion: packageSpec.slice(`${CLI_PACKAGE_NAME}@`.length)
234
+ }, packageSpec, result);
235
+ return {
236
+ ...diagnostic,
237
+ code: diagnostic.code.replace(/^catalog_package_/, "package_registry_"),
238
+ path: CLI_PACKAGE_NAME
239
+ };
240
+ }
@@ -0,0 +1,27 @@
1
+ // @ts-check
2
+
3
+ import { PACKAGE_UPDATE_CLI_CHECK_SCRIPTS } from "./constants.js";
4
+
5
+ /**
6
+ * @returns {void}
7
+ */
8
+ export function printPackageHelp() {
9
+ console.log("Usage: topogram package update-cli <version|--latest> [--json]");
10
+ console.log("");
11
+ console.log("Updates a consumer project to a Topogram CLI version and runs verification when dependencies are current.");
12
+ console.log("");
13
+ console.log("Behavior:");
14
+ console.log(" - npmjs package inspection confirms the requested public CLI version.");
15
+ console.log(" - npm install updates package.json and package-lock.json.");
16
+ console.log(" - Available consumer verification scripts run after install.");
17
+ console.log(` - Recognized scripts: ${PACKAGE_UPDATE_CLI_CHECK_SCRIPTS.join(", ")}.`);
18
+ console.log(" - Verification scripts are selected by strength: verify, then pack:check, then check.");
19
+ console.log("");
20
+ console.log("Examples:");
21
+ console.log(" topogram package update-cli 0.3.5");
22
+ console.log(" topogram package update-cli --latest");
23
+ console.log(" topogram package update-cli --latest --json");
24
+ console.log("");
25
+ console.log("Auth help:");
26
+ console.log(" topogram setup package-auth");
27
+ }
@@ -0,0 +1,135 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { CLI_PACKAGE_NAME, NPMJS_REGISTRY } from "./constants.js";
7
+ import { readProjectCliDependencySpec } from "./doctor.js";
8
+ import { messageFromError } from "./shared.js";
9
+ import { normalizeRegistryUrl } from "./versions.js";
10
+
11
+ /**
12
+ * Remove stale tarball metadata for the CLI package before npm installs the
13
+ * requested version. npm package registry can repack publish metadata, so copying a
14
+ * local npm-pack resolved URL or integrity into a consumer lockfile can make
15
+ * npm ci fail with a checksum mismatch.
16
+ *
17
+ * @param {string} cwd
18
+ * @param {string} version
19
+ * @returns {boolean}
20
+ */
21
+ export function sanitizeTopogramLockForPackageUpdate(cwd, version) {
22
+ const lockPath = path.join(cwd, "package-lock.json");
23
+ if (!fs.existsSync(lockPath)) {
24
+ return false;
25
+ }
26
+ const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
27
+ const packages = lock && typeof lock === "object" && lock.packages && typeof lock.packages === "object"
28
+ ? lock.packages
29
+ : null;
30
+ const packageEntry = packages?.[`node_modules/${CLI_PACKAGE_NAME}`];
31
+ if (!packageEntry || typeof packageEntry !== "object" || packageEntry.version !== version) {
32
+ return false;
33
+ }
34
+ let changed = false;
35
+ for (const key of ["resolved", "integrity"]) {
36
+ if (Object.prototype.hasOwnProperty.call(packageEntry, key)) {
37
+ delete packageEntry[key];
38
+ changed = true;
39
+ }
40
+ }
41
+ if (changed) {
42
+ fs.writeFileSync(lockPath, `${JSON.stringify(lock, null, 2)}\n`, "utf8");
43
+ }
44
+ return changed;
45
+ }
46
+
47
+ /**
48
+ * @param {string} cwd
49
+ * @returns {{ checked: boolean, path: string, packageVersion: string|null, dependencySpec: string|null, hasTarballMetadata: boolean, resolvedVersion: string|null, refreshRecommended: boolean, diagnostics: Array<Record<string, any>> }}
50
+ */
51
+ export function inspectTopogramCliLockfile(cwd) {
52
+ const lockPath = path.join(cwd, "package-lock.json");
53
+ /** @type {{ checked: boolean, path: string, packageVersion: string|null, dependencySpec: string|null, hasTarballMetadata: boolean, resolvedVersion: string|null, refreshRecommended: boolean, diagnostics: Array<Record<string, any>> }} */
54
+ const result = {
55
+ checked: false,
56
+ path: lockPath,
57
+ packageVersion: null,
58
+ dependencySpec: readProjectCliDependencySpec(cwd),
59
+ hasTarballMetadata: false,
60
+ resolvedVersion: null,
61
+ refreshRecommended: false,
62
+ diagnostics: []
63
+ };
64
+ if (!fs.existsSync(lockPath)) {
65
+ return result;
66
+ }
67
+ result.checked = true;
68
+ try {
69
+ const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
70
+ const entry = lock?.packages?.[`node_modules/${CLI_PACKAGE_NAME}`];
71
+ if (!entry || typeof entry !== "object") {
72
+ return result;
73
+ }
74
+ result.packageVersion = typeof entry.version === "string" ? entry.version : null;
75
+ const resolved = typeof entry.resolved === "string" ? entry.resolved : null;
76
+ result.resolvedVersion = resolved ? resolvedTopogramCliVersion(resolved) : null;
77
+ result.hasTarballMetadata = Object.prototype.hasOwnProperty.call(entry, "resolved") ||
78
+ Object.prototype.hasOwnProperty.call(entry, "integrity");
79
+ const conventionVersion = readTopogramCliVersionConvention(cwd);
80
+ const resolvedVersionMismatch = Boolean(result.packageVersion && result.resolvedVersion && result.resolvedVersion !== result.packageVersion);
81
+ const normalizedResolved = normalizeRegistryUrl(resolved);
82
+ const normalizedRegistry = normalizeRegistryUrl(NPMJS_REGISTRY) || NPMJS_REGISTRY;
83
+ const npmjsTarball = Boolean(normalizedResolved && normalizedResolved.startsWith(`${normalizedRegistry}/`));
84
+ const localTarballMetadata = Boolean(resolved && (
85
+ /^file:/.test(resolved) ||
86
+ (!npmjsTarball && /\.tgz(?:$|[?#])/.test(resolved))
87
+ ));
88
+ result.refreshRecommended = Boolean(
89
+ result.packageVersion &&
90
+ conventionVersion &&
91
+ conventionVersion === result.packageVersion &&
92
+ (resolvedVersionMismatch || localTarballMetadata)
93
+ );
94
+ if (result.refreshRecommended) {
95
+ result.diagnostics.push({
96
+ code: "topogram_cli_lockfile_refresh_available",
97
+ severity: "warning",
98
+ message: "package-lock.json contains stale Topogram CLI tarball metadata for the pinned version.",
99
+ path: lockPath,
100
+ suggestedFix: `Run \`topogram package update-cli ${result.packageVersion}\` to refresh from npm registry metadata.`
101
+ });
102
+ }
103
+ } catch (error) {
104
+ result.diagnostics.push({
105
+ code: "topogram_cli_lockfile_unreadable",
106
+ severity: "warning",
107
+ message: `Could not inspect package-lock.json: ${messageFromError(error)}`,
108
+ path: lockPath,
109
+ suggestedFix: "Regenerate package-lock.json with npm install."
110
+ });
111
+ }
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * @param {string} resolved
117
+ * @returns {string|null}
118
+ */
119
+ function resolvedTopogramCliVersion(resolved) {
120
+ const match = resolved.match(/\/@topogram\/cli\/-\/cli-([^/.?#]+(?:\.[^/.?#]+){2}(?:[-+][^/?#]+)?)\.tgz/);
121
+ return match ? match[1] : null;
122
+ }
123
+
124
+ /**
125
+ * @param {string} cwd
126
+ * @returns {string|null}
127
+ */
128
+ export function readTopogramCliVersionConvention(cwd) {
129
+ const versionPath = path.join(cwd, "topogram-cli.version");
130
+ if (!fs.existsSync(versionPath)) {
131
+ return null;
132
+ }
133
+ const value = fs.readFileSync(versionPath, "utf8").trim();
134
+ return value || null;
135
+ }
@@ -0,0 +1,97 @@
1
+ // @ts-check
2
+
3
+ import childProcess from "node:child_process";
4
+
5
+ import { localNpmrcEnv } from "../../../npm-safety.js";
6
+ import { CLI_PACKAGE_NAME, NPMJS_REGISTRY } from "./constants.js";
7
+ import { isPackageVersion } from "./versions.js";
8
+
9
+ /**
10
+ * @param {string[]} args
11
+ * @param {string} cwd
12
+ * @returns {any}
13
+ */
14
+ export function runNpmForPackageUpdate(args, cwd) {
15
+ const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
16
+ return childProcess.spawnSync(npmBin, args, {
17
+ cwd,
18
+ encoding: "utf8",
19
+ env: {
20
+ ...process.env,
21
+ ...localNpmrcEnv(cwd),
22
+ PATH: process.env.PATH || ""
23
+ }
24
+ });
25
+ }
26
+
27
+ /**
28
+ * @param {string} cwd
29
+ * @returns {string}
30
+ */
31
+ export function latestTopogramCliVersion(cwd) {
32
+ const result = runNpmForPackageUpdate(["view", "--json", `--registry=${NPMJS_REGISTRY}`, "--", CLI_PACKAGE_NAME, "version"], cwd);
33
+ if (result.status !== 0) {
34
+ throw new Error(formatPackageUpdateNpmError(`${CLI_PACKAGE_NAME}@latest`, "inspect", result));
35
+ }
36
+ const raw = String(result.stdout || "").trim();
37
+ const version = raw.startsWith("\"") ? JSON.parse(raw) : raw;
38
+ if (!isPackageVersion(version)) {
39
+ throw new Error(`npm returned invalid latest version '${version || "(empty)"}' for ${CLI_PACKAGE_NAME}.`);
40
+ }
41
+ return version;
42
+ }
43
+
44
+ /**
45
+ * @param {any} result
46
+ * @returns {boolean}
47
+ */
48
+ function isPackageUpdateNpmAuthFailure(result) {
49
+ const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
50
+ const normalized = output.toLowerCase();
51
+ return /\b(e401|eneedauth)\b/.test(normalized) ||
52
+ normalized.includes("unauthenticated") ||
53
+ normalized.includes("authentication required");
54
+ }
55
+
56
+ /**
57
+ * @param {string} spec
58
+ * @param {"inspect"|"install"|"check"} step
59
+ * @param {any} result
60
+ * @returns {string}
61
+ */
62
+ export function formatPackageUpdateNpmError(spec, step, result) {
63
+ const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
64
+ const normalized = output.toLowerCase();
65
+ if (result.error?.code === "ENOENT") {
66
+ return "npm was not found. Install Node.js/npm and retry.";
67
+ }
68
+ if (isPackageUpdateNpmAuthFailure(result)) {
69
+ return [
70
+ `Authentication is required to ${step} ${spec}.`,
71
+ "Configure registry-specific npm auth when using private packages.",
72
+ output
73
+ ].filter(Boolean).join("\n");
74
+ }
75
+ if (/\be403\b/.test(normalized) || normalized.includes("forbidden") || normalized.includes("permission")) {
76
+ return [
77
+ `Package access was denied while trying to ${step} ${spec}.`,
78
+ "Check npm package registry read access for the consumer environment.",
79
+ output
80
+ ].filter(Boolean).join("\n");
81
+ }
82
+ if (/\b(e404|404)\b/.test(normalized) || normalized.includes("not found")) {
83
+ return [
84
+ `${spec} was not found, or the current token does not have access to it.`,
85
+ "Check the package version and npm package registry access.",
86
+ output
87
+ ].filter(Boolean).join("\n");
88
+ }
89
+ if (/\beintegrity\b/.test(normalized) || normalized.includes("integrity checksum failed")) {
90
+ return [
91
+ `Package integrity failed while trying to ${step} ${spec}.`,
92
+ "Regenerate package-lock.json from the published npm package registry tarball.",
93
+ output
94
+ ].filter(Boolean).join("\n");
95
+ }
96
+ return `Failed to ${step} ${spec}.\n${output || "unknown error"}`.trim();
97
+ }