@topogram/cli 0.3.63 → 0.3.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
package/src/catalog.js CHANGED
@@ -1,752 +1,20 @@
1
1
  // @ts-check
2
2
 
3
- import childProcess from "node:child_process";
4
- import crypto from "node:crypto";
5
- import fs from "node:fs";
6
- import path from "node:path";
7
-
8
- import { installPackageSpec } from "./new-project.js";
9
-
10
- export const DEFAULT_CATALOG_SOURCE = "https://raw.githubusercontent.com/attebury/topograms/main/topograms.catalog.json";
11
- export const CATALOG_FILE_NAME = "topograms.catalog.json";
12
- export const TOPOGRAM_SOURCE_FILE = ".topogram-source.json";
13
- const KNOWN_CATALOG_SURFACES = new Set(["web", "api", "database", "native"]);
14
-
15
- /**
16
- * @typedef {Object} CatalogTrust
17
- * @property {string} scope
18
- * @property {boolean} includesExecutableImplementation
19
- * @property {string} [notes]
20
- */
21
-
22
- /**
23
- * @typedef {Object} CatalogEntry
24
- * @property {string} id
25
- * @property {"template"|"topogram"} kind
26
- * @property {string} package
27
- * @property {string} defaultVersion
28
- * @property {string} description
29
- * @property {string[]} tags
30
- * @property {string[]} [surfaces]
31
- * @property {string[]} [generators]
32
- * @property {string} [stack]
33
- * @property {CatalogTrust} trust
34
- */
35
-
36
- /**
37
- * @typedef {Object} TopogramCatalog
38
- * @property {string} version
39
- * @property {CatalogEntry[]} entries
40
- */
41
-
42
- /**
43
- * @typedef {Object} CatalogDiagnostic
44
- * @property {string} code
45
- * @property {"error"|"warning"} severity
46
- * @property {string} message
47
- * @property {string|null} path
48
- * @property {string|null} suggestedFix
49
- */
50
-
51
- /**
52
- * @typedef {Object} CatalogValidationResult
53
- * @property {boolean} ok
54
- * @property {TopogramCatalog|null} catalog
55
- * @property {CatalogDiagnostic[]} diagnostics
56
- * @property {string[]} errors
57
- */
58
-
59
- /**
60
- * @typedef {Object} CatalogLoadResult
61
- * @property {string} source
62
- * @property {TopogramCatalog} catalog
63
- * @property {CatalogDiagnostic[]} diagnostics
64
- */
65
-
66
- /**
67
- * @param {Record<string, unknown>} input
68
- * @returns {CatalogDiagnostic}
69
- */
70
- function catalogDiagnostic(input) {
71
- return {
72
- code: String(input.code || "catalog_invalid"),
73
- severity: input.severity === "warning" ? "warning" : "error",
74
- message: String(input.message || "Catalog is invalid."),
75
- path: typeof input.path === "string" ? input.path : null,
76
- suggestedFix: typeof input.suggestedFix === "string" ? input.suggestedFix : null
77
- };
78
- }
79
-
80
- /**
81
- * @param {string|undefined|null} source
82
- * @returns {string}
83
- */
84
- export function catalogSourceOrDefault(source = null) {
85
- return source || process.env.TOPOGRAM_CATALOG_SOURCE || DEFAULT_CATALOG_SOURCE;
86
- }
87
-
88
- /**
89
- * @param {string|undefined|null} source
90
- * @returns {boolean}
91
- */
92
- export function isCatalogSourceDisabled(source) {
93
- const normalized = String(source || "").trim().toLowerCase();
94
- return normalized === "none" || normalized === "off" || normalized === "false";
95
- }
96
-
97
- /**
98
- * @param {string} value
99
- * @returns {boolean}
100
- */
101
- function isPackageName(value) {
102
- return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(value);
103
- }
104
-
105
- /**
106
- * @param {unknown} value
107
- * @param {string} source
108
- * @returns {CatalogValidationResult}
109
- */
110
- export function validateCatalog(value, source = "") {
111
- /** @type {CatalogDiagnostic[]} */
112
- const diagnostics = [];
113
- if (!value || typeof value !== "object" || Array.isArray(value)) {
114
- diagnostics.push(catalogDiagnostic({
115
- code: "catalog_not_object",
116
- message: "Catalog must contain a JSON object.",
117
- path: source || null,
118
- suggestedFix: `Create ${CATALOG_FILE_NAME} with version and entries[].`
119
- }));
120
- return validationResult(null, diagnostics);
121
- }
122
-
123
- const input = /** @type {Record<string, unknown>} */ (value);
124
- const version = typeof input.version === "string" && input.version ? input.version : "";
125
- if (!version) {
126
- diagnostics.push(catalogDiagnostic({
127
- code: "catalog_version_missing",
128
- message: "Catalog is missing required string field 'version'.",
129
- path: source || null,
130
- suggestedFix: "Add a version string such as \"0.1\"."
131
- }));
132
- }
133
- if (!Array.isArray(input.entries)) {
134
- diagnostics.push(catalogDiagnostic({
135
- code: "catalog_entries_missing",
136
- message: "Catalog is missing required array field 'entries'.",
137
- path: source || null,
138
- suggestedFix: "Add entries[] with template and topogram package references."
139
- }));
140
- return validationResult({ version, entries: [] }, diagnostics);
141
- }
142
-
143
- /** @type {CatalogEntry[]} */
144
- const entries = [];
145
- const ids = new Set();
146
- input.entries.forEach((entryValue, index) => {
147
- const entryPath = `entries[${index}]`;
148
- if (!entryValue || typeof entryValue !== "object" || Array.isArray(entryValue)) {
149
- diagnostics.push(catalogDiagnostic({
150
- code: "catalog_entry_not_object",
151
- message: `Catalog ${entryPath} must be an object.`,
152
- path: source || null,
153
- suggestedFix: "Replace the entry with an object containing id, kind, package, defaultVersion, description, tags, and trust."
154
- }));
155
- return;
156
- }
157
- const entry = /** @type {Record<string, unknown>} */ (entryValue);
158
- const id = stringField(entry, "id");
159
- const kind = stringField(entry, "kind");
160
- const packageName = stringField(entry, "package");
161
- const defaultVersion = stringField(entry, "defaultVersion");
162
- const description = stringField(entry, "description");
163
- const tags = Array.isArray(entry.tags) ? entry.tags.map(String).filter(Boolean) : [];
164
- const surfaces = Array.isArray(entry.surfaces) ? entry.surfaces.map(String).filter(Boolean) : [];
165
- const generators = Array.isArray(entry.generators) ? entry.generators.map(String).filter(Boolean) : [];
166
- const stack = stringField(entry, "stack");
167
- const trust = trustField(entry.trust);
168
-
169
- for (const field of ["id", "kind", "package", "defaultVersion", "description"]) {
170
- if (!stringField(entry, field)) {
171
- diagnostics.push(catalogDiagnostic({
172
- code: "catalog_entry_field_missing",
173
- message: `Catalog ${entryPath} is missing required string field '${field}'.`,
174
- path: source || null,
175
- suggestedFix: `Add ${field} to ${entryPath}.`
176
- }));
177
- }
178
- }
179
- if (id && ids.has(id)) {
180
- diagnostics.push(catalogDiagnostic({
181
- code: "catalog_duplicate_id",
182
- message: `Catalog entry id '${id}' is duplicated.`,
183
- path: source || null,
184
- suggestedFix: "Use stable unique ids for catalog entries."
185
- }));
186
- }
187
- if (id) {
188
- ids.add(id);
189
- }
190
- if (kind && kind !== "template" && kind !== "topogram") {
191
- diagnostics.push(catalogDiagnostic({
192
- code: "catalog_invalid_kind",
193
- message: `Catalog entry '${id || entryPath}' has invalid kind '${kind}'.`,
194
- path: source || null,
195
- suggestedFix: "Use kind \"template\" or \"topogram\"."
196
- }));
197
- }
198
- if (packageName && !isPackageName(packageName)) {
199
- diagnostics.push(catalogDiagnostic({
200
- code: "catalog_invalid_package",
201
- message: `Catalog entry '${id || entryPath}' package must be an npm package name, not '${packageName}'.`,
202
- path: source || null,
203
- suggestedFix: "Use package plus defaultVersion separately, for example @scope/topogram-template-name and 0.1.0."
204
- }));
205
- }
206
- if (defaultVersion && /\s/.test(defaultVersion)) {
207
- diagnostics.push(catalogDiagnostic({
208
- code: "catalog_invalid_default_version",
209
- message: `Catalog entry '${id || entryPath}' defaultVersion must not contain whitespace.`,
210
- path: source || null,
211
- suggestedFix: "Use an exact version or npm dist-tag."
212
- }));
213
- }
214
- if (!Array.isArray(entry.tags)) {
215
- diagnostics.push(catalogDiagnostic({
216
- code: "catalog_tags_missing",
217
- message: `Catalog entry '${id || entryPath}' is missing required tags array.`,
218
- path: source || null,
219
- suggestedFix: "Add tags as an array of strings."
220
- }));
221
- }
222
- if (Object.prototype.hasOwnProperty.call(entry, "surfaces") && !Array.isArray(entry.surfaces)) {
223
- diagnostics.push(catalogDiagnostic({
224
- code: "catalog_optional_surfaces_invalid",
225
- severity: "warning",
226
- message: `Catalog entry '${id || entryPath}' surfaces should be an array of surface ids.`,
227
- path: source || null,
228
- suggestedFix: "Use surfaces such as [\"web\"], [\"api\"], [\"database\"], or [\"native\"]."
229
- }));
230
- }
231
- for (const surface of surfaces) {
232
- if (!KNOWN_CATALOG_SURFACES.has(surface)) {
233
- diagnostics.push(catalogDiagnostic({
234
- code: "catalog_optional_surface_unknown",
235
- severity: "warning",
236
- message: `Catalog entry '${id || entryPath}' has unknown surface '${surface}'.`,
237
- path: source || null,
238
- suggestedFix: "Use known surface ids: web, api, database, native."
239
- }));
240
- }
241
- }
242
- if (Object.prototype.hasOwnProperty.call(entry, "generators") && !Array.isArray(entry.generators)) {
243
- diagnostics.push(catalogDiagnostic({
244
- code: "catalog_optional_generators_invalid",
245
- severity: "warning",
246
- message: `Catalog entry '${id || entryPath}' generators should be an array of generator ids.`,
247
- path: source || null,
248
- suggestedFix: "Use package-backed generator ids such as [\"@topogram/generator-sveltekit-web\", \"@topogram/generator-hono-api\"]."
249
- }));
250
- }
251
- if (Object.prototype.hasOwnProperty.call(entry, "stack") && typeof entry.stack !== "string") {
252
- diagnostics.push(catalogDiagnostic({
253
- code: "catalog_optional_stack_invalid",
254
- severity: "warning",
255
- message: `Catalog entry '${id || entryPath}' stack should be a string.`,
256
- path: source || null,
257
- suggestedFix: "Use a short stack label such as \"SvelteKit + Hono + Postgres\"."
258
- }));
259
- }
260
- if (!trust) {
261
- diagnostics.push(catalogDiagnostic({
262
- code: "catalog_trust_missing",
263
- message: `Catalog entry '${id || entryPath}' is missing required trust metadata.`,
264
- path: source || null,
265
- suggestedFix: "Add trust.scope and trust.includesExecutableImplementation."
266
- }));
267
- } else if (kind === "topogram" && trust.includesExecutableImplementation) {
268
- diagnostics.push(catalogDiagnostic({
269
- code: "catalog_topogram_executable_not_supported",
270
- message: `Catalog topogram entry '${id || entryPath}' cannot include executable implementation in v1.`,
271
- path: source || null,
272
- suggestedFix: "Move executable code into a template package, or set includesExecutableImplementation to false."
273
- }));
274
- }
275
-
276
- entries.push({
277
- id,
278
- kind: kind === "topogram" ? "topogram" : "template",
279
- package: packageName,
280
- defaultVersion,
281
- description,
282
- tags,
283
- ...(surfaces.length > 0 ? { surfaces } : {}),
284
- ...(generators.length > 0 ? { generators } : {}),
285
- ...(stack ? { stack } : {}),
286
- trust: trust || { scope: "", includesExecutableImplementation: false }
287
- });
288
- });
289
-
290
- return validationResult({ version, entries }, diagnostics);
291
- }
292
-
293
- /**
294
- * @param {TopogramCatalog|null} catalog
295
- * @param {CatalogDiagnostic[]} diagnostics
296
- * @returns {CatalogValidationResult}
297
- */
298
- function validationResult(catalog, diagnostics) {
299
- const errors = diagnostics
300
- .filter((diagnostic) => diagnostic.severity === "error")
301
- .map((diagnostic) => diagnostic.message);
302
- return {
303
- ok: errors.length === 0,
304
- catalog: errors.length === 0 ? catalog : null,
305
- diagnostics,
306
- errors
307
- };
308
- }
309
-
310
- /**
311
- * @param {Record<string, unknown>} input
312
- * @param {string} field
313
- * @returns {string}
314
- */
315
- function stringField(input, field) {
316
- const value = input[field];
317
- return typeof value === "string" ? value.trim() : "";
318
- }
319
-
320
- /**
321
- * @param {unknown} value
322
- * @returns {CatalogTrust|null}
323
- */
324
- function trustField(value) {
325
- if (!value || typeof value !== "object" || Array.isArray(value)) {
326
- return null;
327
- }
328
- const trust = /** @type {Record<string, unknown>} */ (value);
329
- if (typeof trust.scope !== "string" || !trust.scope) {
330
- return null;
331
- }
332
- if (typeof trust.includesExecutableImplementation !== "boolean") {
333
- return null;
334
- }
335
- const result = {
336
- scope: trust.scope,
337
- includesExecutableImplementation: trust.includesExecutableImplementation
338
- };
339
- if (typeof trust.notes === "string" && trust.notes) {
340
- return { ...result, notes: trust.notes };
341
- }
342
- return result;
343
- }
344
-
345
- /**
346
- * @param {string|undefined|null} sourceInput
347
- * @returns {CatalogLoadResult}
348
- */
349
- export function loadCatalog(sourceInput = null) {
350
- const source = catalogSourceOrDefault(sourceInput);
351
- if (isCatalogSourceDisabled(source)) {
352
- throw new Error("Catalog source is disabled.");
353
- }
354
- const text = readCatalogText(source);
355
- const parsed = JSON.parse(text);
356
- const validation = validateCatalog(parsed, source);
357
- if (!validation.ok || !validation.catalog) {
358
- throw new Error(validation.errors.join("\n") || `Catalog '${source}' is invalid.`);
359
- }
360
- return {
361
- source,
362
- catalog: validation.catalog,
363
- diagnostics: validation.diagnostics
364
- };
365
- }
366
-
367
- /**
368
- * @param {string} source
369
- * @returns {CatalogValidationResult & { source: string }}
370
- */
371
- export function checkCatalogSource(source) {
372
- const text = readCatalogText(source);
373
- const parsed = JSON.parse(text);
374
- return {
375
- source,
376
- ...validateCatalog(parsed, source)
377
- };
378
- }
379
-
380
- /**
381
- * @param {string} source
382
- * @returns {string}
383
- */
384
- function readCatalogText(source) {
385
- if (source.startsWith("github:")) {
386
- return readGithubCatalogText(source);
387
- }
388
- if (source.startsWith("https://") || source.startsWith("http://")) {
389
- return readUrlText(source);
390
- }
391
- const resolvedPath = path.resolve(source);
392
- return fs.readFileSync(resolvedPath, "utf8");
393
- }
394
-
395
- /**
396
- * @param {string} source
397
- * @returns {string}
398
- */
399
- function readGithubCatalogText(source) {
400
- const spec = source.slice("github:".length);
401
- const [pathPart, ref] = spec.split("?ref=");
402
- const segments = pathPart.split("/").filter(Boolean);
403
- if (segments.length < 3) {
404
- throw new Error(`Invalid GitHub catalog source '${source}'. Expected github:owner/repo/path/to/catalog.json.`);
405
- }
406
- const [owner, repo, ...fileSegments] = segments;
407
- const apiPath = `repos/${owner}/${repo}/contents/${fileSegments.join("/")}`;
408
- const args = ["api", apiPath, "--jq", ".content"];
409
- if (ref) {
410
- args.splice(2, 0, "-f", `ref=${ref}`);
411
- }
412
- const result = childProcess.spawnSync("gh", args, {
413
- encoding: "utf8",
414
- env: {
415
- ...process.env,
416
- GH_TOKEN: process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "",
417
- PATH: process.env.PATH || ""
418
- }
419
- });
420
- if (result.status !== 0) {
421
- throw new Error(formatGithubCatalogError(source, result));
422
- }
423
- return Buffer.from(result.stdout.replace(/\s+/g, ""), "base64").toString("utf8");
424
- }
425
-
426
- /**
427
- * @param {string} source
428
- * @param {any} result
429
- * @returns {string}
430
- */
431
- function formatGithubCatalogError(source, result) {
432
- const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
433
- const normalized = output.toLowerCase();
434
- if (result.error?.code === "ENOENT") {
435
- return [
436
- `GitHub CLI (gh) is required to read catalog '${source}'.`,
437
- "Install gh, or set TOPOGRAM_CATALOG_SOURCE to a local topograms.catalog.json file."
438
- ].join("\n");
439
- }
440
- if (/\b(401|403)\b/.test(normalized) || normalized.includes("authentication") || normalized.includes("not logged in") || normalized.includes("forbidden")) {
441
- return [
442
- `Authentication is required to read private catalog '${source}'.`,
443
- "Set GITHUB_TOKEN or GH_TOKEN with repository read access, or run gh auth login.",
444
- output
445
- ].filter(Boolean).join("\n");
446
- }
447
- if (/\b404\b/.test(normalized) || normalized.includes("not found")) {
448
- return [
449
- `Catalog source '${source}' was not found, or the current token does not have repository access.`,
450
- "Check the github:owner/repo/path source and grant repository read access to the token or GitHub Actions workflow.",
451
- output
452
- ].filter(Boolean).join("\n");
453
- }
454
- return [
455
- `Failed to read catalog '${source}' with gh api.`,
456
- "Set GITHUB_TOKEN or GH_TOKEN, or run gh auth login.",
457
- output || "unknown error"
458
- ].join("\n");
459
- }
460
-
461
- /**
462
- * @param {string} source
463
- * @returns {string}
464
- */
465
- function readUrlText(source) {
466
- const args = ["-fsSL", source];
467
- const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
468
- if (token && source.includes("github.com")) {
469
- args.unshift("-H", `Authorization: Bearer ${token}`);
470
- }
471
- const result = childProcess.spawnSync("curl", args, {
472
- encoding: "utf8",
473
- env: {
474
- ...process.env,
475
- PATH: process.env.PATH || ""
476
- }
477
- });
478
- if (result.status !== 0) {
479
- const reason = result.error?.message || result.stderr || result.stdout || "unknown error";
480
- throw new Error(`Failed to read catalog URL '${source}'.\n${reason}`.trim());
481
- }
482
- return result.stdout;
483
- }
484
-
485
- /**
486
- * @param {TopogramCatalog} catalog
487
- * @param {string} id
488
- * @param {"template"|"topogram"|null} [kind]
489
- * @returns {CatalogEntry|null}
490
- */
491
- export function findCatalogEntry(catalog, id, kind = null) {
492
- return catalog.entries.find((entry) => entry.id === id && (!kind || entry.kind === kind)) || null;
493
- }
494
-
495
- /**
496
- * @param {CatalogEntry} entry
497
- * @param {string|null|undefined} version
498
- * @returns {string}
499
- */
500
- export function catalogEntryPackageSpec(entry, version = null) {
501
- return `${entry.package}@${version || entry.defaultVersion}`;
502
- }
503
-
504
- /**
505
- * @param {CatalogEntry} entry
506
- * @returns {{ id: string, version: string, source: "catalog", name: string, package: string, defaultVersion: string, description: string, tags: string[], surfaces?: string[], generators?: string[], stack?: string, includesExecutableImplementation: boolean, trust: CatalogTrust }}
507
- */
508
- export function catalogTemplateListItem(entry) {
509
- return {
510
- id: entry.id,
511
- version: entry.defaultVersion,
512
- source: "catalog",
513
- name: entry.id,
514
- package: entry.package,
515
- defaultVersion: entry.defaultVersion,
516
- description: entry.description,
517
- tags: entry.tags,
518
- ...(entry.surfaces ? { surfaces: entry.surfaces } : {}),
519
- ...(entry.generators ? { generators: entry.generators } : {}),
520
- ...(entry.stack ? { stack: entry.stack } : {}),
521
- includesExecutableImplementation: entry.trust.includesExecutableImplementation,
522
- trust: entry.trust
523
- };
524
- }
525
-
526
- /**
527
- * @param {CatalogEntry} entry
528
- * @param {string} targetPath
529
- * @param {{ version?: string|null, catalogSource?: string|null }} [options]
530
- * @returns {{ ok: boolean, id: string, kind: "topogram", packageSpec: string, targetPath: string, provenancePath: string, files: string[] }}
531
- */
532
- export function copyCatalogTopogramEntry(entry, targetPath, options = {}) {
533
- if (entry.kind !== "topogram") {
534
- throw new Error(`Catalog entry '${entry.id}' is a ${entry.kind}, not a topogram.`);
535
- }
536
- const packageSpec = catalogEntryPackageSpec(entry, options.version || null);
537
- const packageRoot = installPackageSpec(packageSpec);
538
- const implementationRoot = path.join(packageRoot, "implementation");
539
- if (fs.existsSync(implementationRoot)) {
540
- throw new Error(
541
- `Catalog topogram entry '${entry.id}' package '${packageSpec}' contains implementation/, which is not allowed for v1 topogram entries.`
542
- );
543
- }
544
- const topogramRoot = path.join(packageRoot, "topogram");
545
- if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
546
- throw new Error(`Catalog topogram entry '${entry.id}' package '${packageSpec}' is missing topogram/.`);
547
- }
548
-
549
- const resolvedTarget = path.resolve(targetPath);
550
- ensureEmptyDirectory(resolvedTarget);
551
- /** @type {string[]} */
552
- const files = [];
553
- copyPath(topogramRoot, path.join(resolvedTarget, "topogram"), "topogram", files);
554
- for (const fileName of ["topogram.project.json", "README.md"]) {
555
- const sourcePath = path.join(packageRoot, fileName);
556
- if (fs.existsSync(sourcePath) && fs.statSync(sourcePath).isFile()) {
557
- copyPath(sourcePath, path.join(resolvedTarget, fileName), fileName, files);
558
- }
559
- }
560
- const provenance = writeTopogramSourceRecord(resolvedTarget, {
561
- catalogSource: options.catalogSource || null,
562
- entry,
563
- packageSpec,
564
- version: options.version || entry.defaultVersion
565
- });
566
- return {
567
- ok: true,
568
- id: entry.id,
569
- kind: "topogram",
570
- packageSpec,
571
- targetPath: resolvedTarget,
572
- provenancePath: provenance.path,
573
- files: files.sort((a, b) => a.localeCompare(b))
574
- };
575
- }
576
-
577
- /**
578
- * @param {string} projectRoot
579
- * @param {{ catalogSource: string|null, entry: CatalogEntry, packageSpec: string, version: string }} input
580
- * @returns {{ path: string, record: Record<string, any> }}
581
- */
582
- function writeTopogramSourceRecord(projectRoot, input) {
583
- const record = {
584
- version: "0.1",
585
- kind: "topogram",
586
- copiedAt: new Date().toISOString(),
587
- catalog: {
588
- id: input.entry.id,
589
- source: input.catalogSource
590
- },
591
- package: {
592
- name: input.entry.package,
593
- version: input.version,
594
- spec: input.packageSpec
595
- },
596
- trust: {
597
- includesExecutableImplementation: false
598
- },
599
- files: collectSourceFileRecords(projectRoot)
600
- };
601
- const sourcePath = path.join(projectRoot, TOPOGRAM_SOURCE_FILE);
602
- fs.writeFileSync(sourcePath, `${JSON.stringify(record, null, 2)}\n`, "utf8");
603
- return { path: sourcePath, record };
604
- }
605
-
606
- /**
607
- * @param {string} projectRoot
608
- * @returns {{ ok: true, exists: boolean, path: string, status: "missing"|"clean"|"changed", source: Record<string, any>|null, content: { changed: string[], added: string[], removed: string[] }, diagnostics: any[], errors: [] }}
609
- */
610
- export function buildTopogramSourceStatus(projectRoot) {
611
- const resolvedRoot = path.resolve(projectRoot);
612
- const sourcePath = path.join(resolvedRoot, TOPOGRAM_SOURCE_FILE);
613
- if (!fs.existsSync(sourcePath)) {
614
- return {
615
- ok: true,
616
- exists: false,
617
- path: sourcePath,
618
- status: "missing",
619
- source: null,
620
- content: { changed: [], added: [], removed: [] },
621
- diagnostics: [{
622
- code: "topogram_source_missing",
623
- severity: "warning",
624
- message: `${TOPOGRAM_SOURCE_FILE} was not found. This project may not have been copied from a catalog topogram entry.`,
625
- path: sourcePath,
626
- suggestedFix: "Run `topogram catalog copy <id> <target>` to create a project with source provenance."
627
- }],
628
- errors: []
629
- };
630
- }
631
- const source = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
632
- const trustedFiles = Array.isArray(source.files) ? source.files : [];
633
- const trustedByPath = new Map(trustedFiles.map((file) => [String(file.path), file]));
634
- const currentByPath = new Map(collectSourceFileRecords(resolvedRoot).map((file) => [file.path, file]));
635
- /** @type {string[]} */
636
- const changed = [];
637
- /** @type {string[]} */
638
- const added = [];
639
- /** @type {string[]} */
640
- const removed = [];
641
- for (const [filePath, current] of currentByPath) {
642
- const trusted = trustedByPath.get(filePath);
643
- if (!trusted) {
644
- added.push(filePath);
645
- } else if (trusted.sha256 !== current.sha256 || trusted.size !== current.size) {
646
- changed.push(filePath);
647
- }
648
- }
649
- for (const filePath of trustedByPath.keys()) {
650
- if (!currentByPath.has(filePath)) {
651
- removed.push(filePath);
652
- }
653
- }
654
- const content = {
655
- changed: changed.sort((a, b) => a.localeCompare(b)),
656
- added: added.sort((a, b) => a.localeCompare(b)),
657
- removed: removed.sort((a, b) => a.localeCompare(b))
658
- };
659
- return {
660
- ok: true,
661
- exists: true,
662
- path: sourcePath,
663
- status: content.changed.length || content.added.length || content.removed.length ? "changed" : "clean",
664
- source,
665
- content,
666
- diagnostics: [],
667
- errors: []
668
- };
669
- }
670
-
671
- /**
672
- * @param {string} projectRoot
673
- * @returns {Array<{ path: string, sha256: string, size: number }>}
674
- */
675
- function collectSourceFileRecords(projectRoot) {
676
- /** @type {string[]} */
677
- const files = [];
678
- for (const sourceRoot of ["topogram", "topogram.project.json", "README.md"]) {
679
- const sourcePath = path.join(projectRoot, sourceRoot);
680
- if (fs.existsSync(sourcePath)) {
681
- collectFiles(sourcePath, sourceRoot, files);
682
- }
683
- }
684
- return files
685
- .sort((a, b) => a.localeCompare(b))
686
- .map((relativePath) => ({
687
- path: relativePath,
688
- ...fileHash(path.join(projectRoot, relativePath))
689
- }));
690
- }
691
-
692
- /**
693
- * @param {string} filePath
694
- * @returns {{ sha256: string, size: number }}
695
- */
696
- function fileHash(filePath) {
697
- const bytes = fs.readFileSync(filePath);
698
- return {
699
- sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
700
- size: bytes.length
701
- };
702
- }
703
-
704
- /**
705
- * @param {string} targetPath
706
- * @returns {void}
707
- */
708
- function ensureEmptyDirectory(targetPath) {
709
- if (!fs.existsSync(targetPath)) {
710
- fs.mkdirSync(targetPath, { recursive: true });
711
- return;
712
- }
713
- if (!fs.statSync(targetPath).isDirectory()) {
714
- throw new Error(`Cannot copy catalog topogram into non-directory path '${targetPath}'.`);
715
- }
716
- const entries = fs.readdirSync(targetPath).filter((entry) => entry !== ".DS_Store");
717
- if (entries.length > 0) {
718
- throw new Error(`Refusing to copy catalog topogram into non-empty directory '${targetPath}'.`);
719
- }
720
- }
721
-
722
- /**
723
- * @param {string} sourcePath
724
- * @param {string} targetPath
725
- * @param {string} relativePath
726
- * @param {string[]} files
727
- * @returns {void}
728
- */
729
- function copyPath(sourcePath, targetPath, relativePath, files) {
730
- fs.cpSync(sourcePath, targetPath, { recursive: true });
731
- collectFiles(targetPath, relativePath, files);
732
- }
733
-
734
- /**
735
- * @param {string} currentPath
736
- * @param {string} relativePath
737
- * @param {string[]} files
738
- * @returns {void}
739
- */
740
- function collectFiles(currentPath, relativePath, files) {
741
- const stat = fs.statSync(currentPath);
742
- if (stat.isFile()) {
743
- files.push(relativePath.replace(/\\/g, "/"));
744
- return;
745
- }
746
- if (!stat.isDirectory()) {
747
- return;
748
- }
749
- for (const entry of fs.readdirSync(currentPath)) {
750
- collectFiles(path.join(currentPath, entry), path.join(relativePath, entry), files);
751
- }
752
- }
3
+ export {
4
+ CATALOG_FILE_NAME,
5
+ TOPOGRAM_SOURCE_FILE
6
+ } from "./catalog/constants.js";
7
+ export {
8
+ catalogEntryPackageSpec,
9
+ catalogTemplateListItem,
10
+ findCatalogEntry
11
+ } from "./catalog/entries.js";
12
+ export {
13
+ catalogSourceOrDefault,
14
+ checkCatalogSource,
15
+ isCatalogSourceDisabled,
16
+ loadCatalog
17
+ } from "./catalog/source.js";
18
+ export { buildTopogramSourceStatus } from "./catalog/provenance.js";
19
+ export { copyCatalogTopogramEntry } from "./catalog/copy.js";
20
+ export { validateCatalog } from "./catalog/validation.js";