@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,482 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { createRequire } from "node:module";
6
+
7
+ /**
8
+ * @typedef {Object} GeneratorManifest
9
+ * @property {string} id
10
+ * @property {string} version
11
+ * @property {"api"|"web"|"database"|"native"} surface
12
+ * @property {"api"|"web"|"database"|"native"} [targetKind]
13
+ * @property {string[]} projectionPlatforms
14
+ * @property {string[]} inputs
15
+ * @property {string[]} outputs
16
+ * @property {Record<string, string>} stack
17
+ * @property {Record<string, boolean>} capabilities
18
+ * @property {{ patterns?: string[], behaviors?: string[], unsupported?: "error"|"warning"|"contract-only" }} [componentSupport]
19
+ * @property {"bundled"|"package"} source
20
+ * @property {string} [profile]
21
+ * @property {string} [package]
22
+ * @property {string} [export]
23
+ * @property {boolean} [planned]
24
+ */
25
+
26
+ /** @type {GeneratorManifest[]} */
27
+ export const GENERATOR_MANIFESTS = [
28
+ {
29
+ id: "topogram/hono",
30
+ version: "1",
31
+ surface: "api",
32
+ targetKind: "api",
33
+ projectionPlatforms: ["api"],
34
+ inputs: ["server-contract", "api-contracts"],
35
+ outputs: ["api-service"],
36
+ stack: { runtime: "node", framework: "hono", language: "typescript" },
37
+ capabilities: { http: true, stateless: true, persistence: true },
38
+ source: "bundled",
39
+ profile: "hono"
40
+ },
41
+ {
42
+ id: "topogram/express",
43
+ version: "1",
44
+ surface: "api",
45
+ targetKind: "api",
46
+ projectionPlatforms: ["api"],
47
+ inputs: ["server-contract", "api-contracts"],
48
+ outputs: ["api-service"],
49
+ stack: { runtime: "node", framework: "express", language: "typescript" },
50
+ capabilities: { http: true, stateless: true, persistence: true },
51
+ source: "bundled",
52
+ profile: "express"
53
+ },
54
+ {
55
+ id: "topogram/vanilla-web",
56
+ version: "1",
57
+ surface: "web",
58
+ targetKind: "web",
59
+ projectionPlatforms: ["ui_web"],
60
+ inputs: ["ui-web-contract"],
61
+ outputs: ["web-app", "generation-coverage"],
62
+ stack: { runtime: "browser", framework: "vanilla", language: "javascript" },
63
+ capabilities: { routes: true, components: false, coverage: false },
64
+ componentSupport: { patterns: [], behaviors: [], unsupported: "contract-only" },
65
+ source: "bundled",
66
+ profile: "vanilla"
67
+ },
68
+ {
69
+ id: "topogram/sveltekit",
70
+ version: "1",
71
+ surface: "web",
72
+ targetKind: "web",
73
+ projectionPlatforms: ["ui_web"],
74
+ inputs: ["ui-web-contract", "api-contracts"],
75
+ outputs: ["web-app", "generation-coverage"],
76
+ stack: { runtime: "node", framework: "sveltekit", language: "typescript" },
77
+ capabilities: { routes: true, components: true, coverage: true },
78
+ componentSupport: {
79
+ patterns: ["summary_stats", "board_view", "calendar_view", "resource_table", "data_grid_view"],
80
+ behaviors: ["selection", "sorting", "filtering", "search", "pagination", "bulk_action", "optimistic_update"],
81
+ unsupported: "warning"
82
+ },
83
+ source: "bundled",
84
+ profile: "sveltekit"
85
+ },
86
+ {
87
+ id: "topogram/react",
88
+ version: "1",
89
+ surface: "web",
90
+ targetKind: "web",
91
+ projectionPlatforms: ["ui_web"],
92
+ inputs: ["ui-web-contract", "api-contracts"],
93
+ outputs: ["web-app", "generation-coverage"],
94
+ stack: { runtime: "browser", framework: "react", language: "typescript" },
95
+ capabilities: { routes: true, components: true, coverage: true },
96
+ componentSupport: {
97
+ patterns: ["summary_stats", "board_view", "calendar_view", "resource_table", "data_grid_view"],
98
+ behaviors: ["selection", "sorting", "filtering", "search", "pagination", "bulk_action", "optimistic_update"],
99
+ unsupported: "warning"
100
+ },
101
+ source: "bundled",
102
+ profile: "react"
103
+ },
104
+ {
105
+ id: "topogram/swiftui",
106
+ version: "1",
107
+ surface: "native",
108
+ targetKind: "native",
109
+ projectionPlatforms: ["ui_ios"],
110
+ inputs: ["ui-web-contract", "api-contracts"],
111
+ outputs: ["native-app"],
112
+ stack: { platform: "ios", framework: "swiftui", language: "swift" },
113
+ capabilities: { routes: true, components: false, coverage: false },
114
+ componentSupport: { patterns: [], behaviors: [], unsupported: "contract-only" },
115
+ source: "bundled",
116
+ profile: "swiftui"
117
+ },
118
+ {
119
+ id: "topogram/postgres",
120
+ version: "1",
121
+ surface: "database",
122
+ targetKind: "database",
123
+ projectionPlatforms: ["db_postgres"],
124
+ inputs: ["db-contract", "db-lifecycle-plan"],
125
+ outputs: ["db-lifecycle-bundle", "sql-schema", "sql-migration", "prisma-schema", "drizzle-schema"],
126
+ stack: { database: "postgres", language: "sql" },
127
+ capabilities: { lifecycle: true, migrations: true, prisma: true, drizzle: true },
128
+ source: "bundled",
129
+ profile: "postgres"
130
+ },
131
+ {
132
+ id: "topogram/sqlite",
133
+ version: "1",
134
+ surface: "database",
135
+ targetKind: "database",
136
+ projectionPlatforms: ["db_sqlite"],
137
+ inputs: ["db-contract", "db-lifecycle-plan"],
138
+ outputs: ["db-lifecycle-bundle", "sql-schema", "sql-migration", "prisma-schema"],
139
+ stack: { database: "sqlite", language: "sql" },
140
+ capabilities: { lifecycle: true, migrations: true, prisma: true, drizzle: false },
141
+ source: "bundled",
142
+ profile: "sqlite"
143
+ },
144
+ {
145
+ id: "topogram/android-compose",
146
+ version: "1",
147
+ surface: "native",
148
+ targetKind: "native",
149
+ projectionPlatforms: ["ui_android"],
150
+ inputs: ["ui-web-contract", "api-contracts"],
151
+ outputs: ["native-app"],
152
+ stack: { platform: "android", framework: "compose", language: "kotlin" },
153
+ capabilities: { routes: true, components: false, coverage: false },
154
+ componentSupport: { patterns: [], behaviors: [], unsupported: "contract-only" },
155
+ source: "bundled",
156
+ profile: "compose",
157
+ planned: true
158
+ }
159
+ ];
160
+
161
+ const GENERATOR_BY_ID = new Map(GENERATOR_MANIFESTS.map((manifest) => [manifest.id, manifest]));
162
+
163
+ /**
164
+ * @typedef {Object} GeneratorBinding
165
+ * @property {string} id
166
+ * @property {string} version
167
+ * @property {string} [package]
168
+ */
169
+
170
+ /**
171
+ * @typedef {Object} ResolvedGeneratorManifest
172
+ * @property {GeneratorManifest|null} manifest
173
+ * @property {string[]} errors
174
+ * @property {"bundled"|"package"|null} source
175
+ * @property {string|null} manifestPath
176
+ * @property {string|null} packageRoot
177
+ */
178
+
179
+ /**
180
+ * @param {any} value
181
+ * @param {boolean} [nonEmpty]
182
+ * @returns {boolean}
183
+ */
184
+ function isStringArray(value, nonEmpty = false) {
185
+ return Array.isArray(value) &&
186
+ (!nonEmpty || value.length > 0) &&
187
+ value.every((entry) => typeof entry === "string" && entry.length > 0);
188
+ }
189
+
190
+ /**
191
+ * @param {string} generatorId
192
+ * @returns {GeneratorManifest|null}
193
+ */
194
+ export function getGeneratorManifest(generatorId) {
195
+ return GENERATOR_BY_ID.get(generatorId) || null;
196
+ }
197
+
198
+ /**
199
+ * @param {string|null|undefined} rootDir
200
+ * @returns {string}
201
+ */
202
+ function packageResolutionBase(rootDir) {
203
+ return path.join(rootDir || process.cwd(), "package.json");
204
+ }
205
+
206
+ /**
207
+ * @param {string|null|undefined} packageName
208
+ * @returns {string|null}
209
+ */
210
+ export function packageGeneratorInstallCommand(packageName) {
211
+ return packageName ? `npm install -D ${packageName}` : null;
212
+ }
213
+
214
+ /**
215
+ * @param {string|null|undefined} packageName
216
+ * @returns {string|null}
217
+ */
218
+ export function packageGeneratorInstallHint(packageName) {
219
+ const command = packageGeneratorInstallCommand(packageName);
220
+ return command ? `Install it from the project root with: ${command}` : null;
221
+ }
222
+
223
+ /**
224
+ * @param {string} packageName
225
+ * @param {string|null|undefined} rootDir
226
+ * @returns {{ manifestPath: string|null, packageRoot: string|null, error: string|null }}
227
+ */
228
+ export function resolvePackageGeneratorManifestPath(packageName, rootDir = process.cwd()) {
229
+ const requireFromRoot = createRequire(packageResolutionBase(rootDir));
230
+ try {
231
+ const manifestPath = requireFromRoot.resolve(`${packageName}/topogram-generator.json`);
232
+ return {
233
+ manifestPath,
234
+ packageRoot: path.dirname(manifestPath),
235
+ error: null
236
+ };
237
+ } catch (manifestError) {
238
+ try {
239
+ const packageJsonPath = requireFromRoot.resolve(`${packageName}/package.json`);
240
+ const packageRoot = path.dirname(packageJsonPath);
241
+ const manifestPath = path.join(packageRoot, "topogram-generator.json");
242
+ if (!fs.existsSync(manifestPath)) {
243
+ return {
244
+ manifestPath: null,
245
+ packageRoot,
246
+ error: `Generator package '${packageName}' is missing topogram-generator.json`
247
+ };
248
+ }
249
+ return {
250
+ manifestPath,
251
+ packageRoot,
252
+ error: null
253
+ };
254
+ } catch {
255
+ const detail = manifestError instanceof Error ? manifestError.message : String(manifestError);
256
+ const installHint = packageGeneratorInstallHint(packageName);
257
+ return {
258
+ manifestPath: null,
259
+ packageRoot: null,
260
+ error: `Generator package '${packageName}' could not be resolved from '${rootDir || process.cwd()}': ${detail}${installHint ? `. ${installHint}` : ""}`
261
+ };
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * @param {string} packageName
268
+ * @param {string|null|undefined} rootDir
269
+ * @returns {{ manifest: GeneratorManifest|null, errors: string[], manifestPath: string|null, packageRoot: string|null }}
270
+ */
271
+ export function loadPackageGeneratorManifest(packageName, rootDir = process.cwd()) {
272
+ const resolved = resolvePackageGeneratorManifestPath(packageName, rootDir);
273
+ if (!resolved.manifestPath) {
274
+ return {
275
+ manifest: null,
276
+ errors: [resolved.error || `Generator package '${packageName}' could not be resolved`],
277
+ manifestPath: null,
278
+ packageRoot: resolved.packageRoot
279
+ };
280
+ }
281
+ try {
282
+ const manifest = JSON.parse(fs.readFileSync(resolved.manifestPath, "utf8"));
283
+ const validation = validateGeneratorManifest(manifest);
284
+ return {
285
+ manifest: validation.ok ? manifest : null,
286
+ errors: validation.errors,
287
+ manifestPath: resolved.manifestPath,
288
+ packageRoot: resolved.packageRoot
289
+ };
290
+ } catch (error) {
291
+ return {
292
+ manifest: null,
293
+ errors: [`Generator package '${packageName}' manifest could not be read: ${error instanceof Error ? error.message : String(error)}`],
294
+ manifestPath: resolved.manifestPath,
295
+ packageRoot: resolved.packageRoot
296
+ };
297
+ }
298
+ }
299
+
300
+ /**
301
+ * @param {GeneratorBinding|string|null|undefined} bindingOrId
302
+ * @param {{ rootDir?: string|null, configDir?: string|null }} [options]
303
+ * @returns {ResolvedGeneratorManifest}
304
+ */
305
+ export function resolveGeneratorManifestForBinding(bindingOrId, options = {}) {
306
+ const binding = typeof bindingOrId === "string"
307
+ ? { id: bindingOrId, version: "" }
308
+ : bindingOrId;
309
+ const generatorId = binding?.id || "";
310
+ const bundled = getGeneratorManifest(generatorId);
311
+ if (bundled) {
312
+ return {
313
+ manifest: bundled,
314
+ errors: [],
315
+ source: "bundled",
316
+ manifestPath: null,
317
+ packageRoot: null
318
+ };
319
+ }
320
+ if (!binding?.package) {
321
+ return {
322
+ manifest: null,
323
+ errors: [`Generator '${generatorId || "unknown"}' is not bundled and does not declare a package`],
324
+ source: null,
325
+ manifestPath: null,
326
+ packageRoot: null
327
+ };
328
+ }
329
+ const rootDir = options.configDir || options.rootDir || process.cwd();
330
+ const loaded = loadPackageGeneratorManifest(binding.package, rootDir);
331
+ if (!loaded.manifest) {
332
+ return {
333
+ manifest: null,
334
+ errors: loaded.errors,
335
+ source: "package",
336
+ manifestPath: loaded.manifestPath,
337
+ packageRoot: loaded.packageRoot
338
+ };
339
+ }
340
+ /** @type {string[]} */
341
+ const errors = [];
342
+ if (loaded.manifest.source !== "package") {
343
+ errors.push(`Generator package '${binding.package}' manifest source must be package`);
344
+ }
345
+ if (loaded.manifest.package !== binding.package) {
346
+ errors.push(`Generator package '${binding.package}' manifest package must match '${binding.package}'`);
347
+ }
348
+ if (loaded.manifest.id !== binding.id) {
349
+ errors.push(`Generator package '${binding.package}' manifest id '${loaded.manifest.id}' does not match binding '${binding.id}'`);
350
+ }
351
+ if (binding.version && loaded.manifest.version !== binding.version) {
352
+ errors.push(`Generator package '${binding.package}' manifest version '${loaded.manifest.version}' does not match binding '${binding.version}'`);
353
+ }
354
+ return {
355
+ manifest: errors.length === 0 ? loaded.manifest : null,
356
+ errors,
357
+ source: "package",
358
+ manifestPath: loaded.manifestPath,
359
+ packageRoot: loaded.packageRoot
360
+ };
361
+ }
362
+
363
+ /**
364
+ * @param {string|undefined|null} generatorId
365
+ * @param {string|null} [fallback]
366
+ * @returns {string|null}
367
+ */
368
+ export function generatorProfile(generatorId, fallback = null) {
369
+ return generatorId ? getGeneratorManifest(generatorId)?.profile || fallback : fallback;
370
+ }
371
+
372
+ /**
373
+ * @param {any} manifest
374
+ * @returns {{ ok: boolean, errors: string[] }}
375
+ */
376
+ export function validateGeneratorManifest(manifest) {
377
+ /** @type {string[]} */
378
+ const errors = [];
379
+ const label = manifest?.id ? `Generator '${manifest.id}'` : "Generator manifest";
380
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
381
+ return { ok: false, errors: ["Generator manifest must be an object"] };
382
+ }
383
+ if (typeof manifest.id !== "string" || manifest.id.length === 0) {
384
+ errors.push(`${label} id must be a non-empty string`);
385
+ }
386
+ if (typeof manifest.version !== "string" || manifest.version.length === 0) {
387
+ errors.push(`${label} version must be a non-empty string`);
388
+ }
389
+ if (!["api", "web", "database", "native"].includes(manifest.surface)) {
390
+ errors.push(`${label} surface must be api, web, database, or native`);
391
+ }
392
+ if (!isStringArray(manifest.projectionPlatforms, true)) {
393
+ errors.push(`${label} projectionPlatforms must be a non-empty string array`);
394
+ }
395
+ if (!isStringArray(manifest.inputs)) {
396
+ errors.push(`${label} inputs must be a string array`);
397
+ }
398
+ if (!isStringArray(manifest.outputs)) {
399
+ errors.push(`${label} outputs must be a string array`);
400
+ }
401
+ if (!manifest.stack || typeof manifest.stack !== "object" || Array.isArray(manifest.stack)) {
402
+ errors.push(`${label} stack must be an object`);
403
+ }
404
+ if (!manifest.capabilities || typeof manifest.capabilities !== "object" || Array.isArray(manifest.capabilities)) {
405
+ errors.push(`${label} capabilities must be an object`);
406
+ }
407
+ if (manifest.componentSupport != null) {
408
+ if (typeof manifest.componentSupport !== "object" || Array.isArray(manifest.componentSupport)) {
409
+ errors.push(`${label} componentSupport must be an object when present`);
410
+ } else {
411
+ if (manifest.componentSupport.patterns != null && !isStringArray(manifest.componentSupport.patterns)) {
412
+ errors.push(`${label} componentSupport.patterns must be a string array`);
413
+ }
414
+ if (manifest.componentSupport.behaviors != null && !isStringArray(manifest.componentSupport.behaviors)) {
415
+ errors.push(`${label} componentSupport.behaviors must be a string array`);
416
+ }
417
+ if (
418
+ manifest.componentSupport.unsupported != null &&
419
+ !["error", "warning", "contract-only"].includes(manifest.componentSupport.unsupported)
420
+ ) {
421
+ errors.push(`${label} componentSupport.unsupported must be error, warning, or contract-only`);
422
+ }
423
+ }
424
+ }
425
+ if (!["bundled", "package"].includes(manifest.source)) {
426
+ errors.push(`${label} source must be bundled or package`);
427
+ }
428
+ if (manifest.source === "package" && (typeof manifest.package !== "string" || manifest.package.length === 0)) {
429
+ errors.push(`${label} package source must include package`);
430
+ }
431
+ return { ok: errors.length === 0, errors };
432
+ }
433
+
434
+ /**
435
+ * @returns {{ ok: boolean, errors: string[] }}
436
+ */
437
+ export function validateGeneratorRegistry() {
438
+ const errors = [];
439
+ const seen = new Set();
440
+ for (const manifest of GENERATOR_MANIFESTS) {
441
+ const result = validateGeneratorManifest(manifest);
442
+ errors.push(...result.errors);
443
+ const key = `${manifest.id}@${manifest.version}`;
444
+ if (seen.has(key)) {
445
+ errors.push(`Duplicate generator manifest '${key}'`);
446
+ }
447
+ seen.add(key);
448
+ }
449
+ return { ok: errors.length === 0, errors };
450
+ }
451
+
452
+ /**
453
+ * @param {Record<string, any>|null|undefined} projection
454
+ * @returns {boolean}
455
+ */
456
+ export function isApiProjection(projection) {
457
+ return Array.isArray(projection?.http) && projection.http.length > 0;
458
+ }
459
+
460
+ /**
461
+ * @param {Record<string, any>|null|undefined} projection
462
+ * @returns {string}
463
+ */
464
+ export function projectionCompatibilityKey(projection) {
465
+ if (isApiProjection(projection)) {
466
+ return "api";
467
+ }
468
+ return projection?.platform || "";
469
+ }
470
+
471
+ /**
472
+ * @param {GeneratorManifest|null|undefined} manifest
473
+ * @param {string} componentType
474
+ * @param {Record<string, any>|null|undefined} projection
475
+ * @returns {boolean}
476
+ */
477
+ export function isGeneratorCompatible(manifest, componentType, projection) {
478
+ if (!manifest || manifest.planned || manifest.surface !== componentType) {
479
+ return false;
480
+ }
481
+ return manifest.projectionPlatforms.includes(projectionCompatibilityKey(projection));
482
+ }