@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,97 @@
1
+ // @ts-check
2
+
3
+ import { getProjection } from "../shared.js";
4
+
5
+ function renderPackageJson(profile) {
6
+ const dependencies = profile === "express"
7
+ ? { express: "^5.1.0" }
8
+ : { hono: "^4.6.14", "@hono/node-server": "^1.13.7" };
9
+ const devDependencies = profile === "express"
10
+ ? { "@types/express": "^5.0.0", "@types/node": "^22.10.2", tsx: "^4.19.2", typescript: "^5.6.3" }
11
+ : { "@types/node": "^22.10.2", tsx: "^4.19.2", typescript: "^5.6.3" };
12
+ return `${JSON.stringify({
13
+ private: true,
14
+ type: "module",
15
+ scripts: {
16
+ dev: "tsx src/index.ts",
17
+ check: "tsc --noEmit",
18
+ start: "node dist/index.js"
19
+ },
20
+ dependencies,
21
+ devDependencies
22
+ }, null, 2)}\n`;
23
+ }
24
+
25
+ function renderTsconfig() {
26
+ return `${JSON.stringify({
27
+ compilerOptions: {
28
+ target: "ES2022",
29
+ module: "NodeNext",
30
+ moduleResolution: "NodeNext",
31
+ strict: true,
32
+ esModuleInterop: true,
33
+ skipLibCheck: true,
34
+ outDir: "dist"
35
+ },
36
+ include: ["src/**/*.ts"]
37
+ }, null, 2)}\n`;
38
+ }
39
+
40
+ function routePath(path) {
41
+ return String(path || "/").replace(/:([A-Za-z0-9_]+)/g, ":$1");
42
+ }
43
+
44
+ function renderHonoIndex(projection) {
45
+ const routes = (projection.http || []).map((route) => {
46
+ const method = String(route.method || "GET").toLowerCase();
47
+ return `app.${method}("${routePath(route.path)}", (c) => c.json({ ok: true, capability: "${route.capabilityId}", input: { params: c.req.param(), query: c.req.query() } }, ${route.success || 200} as any));`;
48
+ }).join("\n");
49
+ return `import { serve } from "@hono/node-server";
50
+ import { Hono } from "hono";
51
+
52
+ const app = new Hono();
53
+
54
+ app.get("/health", (c) => c.json({ ok: true, service: "${projection.id}" }));
55
+ app.get("/ready", (c) => c.json({ ok: true, ready: true, service: "${projection.id}" }));
56
+ ${routes}
57
+
58
+ const port = Number(process.env.PORT || 3000);
59
+ serve({ fetch: app.fetch, port });
60
+ console.log(\`${projection.id} listening on http://localhost:\${port}\`);
61
+ `;
62
+ }
63
+
64
+ function expressPath(path) {
65
+ return routePath(path);
66
+ }
67
+
68
+ function renderExpressIndex(projection) {
69
+ const routes = (projection.http || []).map((route) => {
70
+ const method = String(route.method || "GET").toLowerCase();
71
+ return `app.${method}("${expressPath(route.path)}", (req, res) => res.status(${route.success || 200}).json({ ok: true, capability: "${route.capabilityId}", input: { params: req.params, query: req.query } }));`;
72
+ }).join("\n");
73
+ return `import express from "express";
74
+
75
+ const app = express();
76
+ app.use(express.json());
77
+
78
+ app.get("/health", (_req, res) => res.json({ ok: true, service: "${projection.id}" }));
79
+ app.get("/ready", (_req, res) => res.json({ ok: true, ready: true, service: "${projection.id}" }));
80
+ ${routes}
81
+
82
+ const port = Number(process.env.PORT || 3000);
83
+ app.listen(port, () => {
84
+ console.log(\`${projection.id} listening on http://localhost:\${port}\`);
85
+ });
86
+ `;
87
+ }
88
+
89
+ export function generateStatelessServer(graph, options = {}) {
90
+ const projection = getProjection(graph, options.projectionId);
91
+ const profile = options.profile === "express" ? "express" : "hono";
92
+ return {
93
+ "package.json": renderPackageJson(profile),
94
+ "tsconfig.json": renderTsconfig(),
95
+ "src/index.ts": profile === "express" ? renderExpressIndex(projection) : renderHonoIndex(projection)
96
+ };
97
+ }
@@ -0,0 +1,64 @@
1
+ export const APP_TARGETS = new Set([
2
+ "ui-contract-graph",
3
+ "ui-contract-debug",
4
+ "ui-web-contract",
5
+ "ui-web-debug",
6
+ "sveltekit-app",
7
+ "server-contract",
8
+ "persistence-scaffold",
9
+ "hono-server",
10
+ "express-server"
11
+ ]);
12
+
13
+ function indexStatements(graph) {
14
+ const byId = new Map();
15
+ for (const statement of graph.statements) {
16
+ byId.set(statement.id, statement);
17
+ }
18
+ return byId;
19
+ }
20
+
21
+ export function getProjection(graph, projectionId) {
22
+ const byId = indexStatements(graph);
23
+ const projection = byId.get(projectionId);
24
+ if (!projection || projection.kind !== "projection") {
25
+ throw new Error(`No projection found with id '${projectionId}'`);
26
+ }
27
+ return projection;
28
+ }
29
+
30
+ export function uiProjectionCandidates(graph) {
31
+ return (graph.byKind.projection || []).filter(
32
+ (projection) =>
33
+ (projection.uiScreens || []).length > 0 ||
34
+ (projection.uiCollections || []).length > 0 ||
35
+ (projection.uiActions || []).length > 0 ||
36
+ (projection.uiVisibility || []).length > 0 ||
37
+ (projection.uiLookups || []).length > 0 ||
38
+ (projection.uiAppShell || []).length > 0 ||
39
+ (projection.uiNavigation || []).length > 0 ||
40
+ (projection.uiScreenRegions || []).length > 0 ||
41
+ (projection.uiComponents || []).length > 0
42
+ );
43
+ }
44
+
45
+ export function generatorDefaultsMap(projection) {
46
+ const defaults = {};
47
+ for (const entry of projection.generatorDefaults || []) {
48
+ if (entry.key && entry.value != null) {
49
+ defaults[entry.key] = entry.value;
50
+ }
51
+ }
52
+ return defaults;
53
+ }
54
+
55
+ export function sharedUiProjectionForWeb(graph, projection) {
56
+ const byId = indexStatements(graph);
57
+ for (const ref of projection.realizes || []) {
58
+ const target = byId.get(ref.id);
59
+ if (target?.kind === "projection" && (target.uiScreens || []).length > 0) {
60
+ return target;
61
+ }
62
+ }
63
+ return null;
64
+ }
@@ -0,0 +1 @@
1
+ export { generateSvelteKitApp as generateWebApiClient } from "./sveltekit.js";
@@ -0,0 +1 @@
1
+ export { generateSvelteKitApp as generateWebForms } from "./sveltekit.js";
@@ -0,0 +1,2 @@
1
+ export function generateWebApp(graph: any, options?: any): any;
2
+ export function generateWebTarget(target: string, graph: any, options?: any): any;
@@ -0,0 +1,53 @@
1
+ import {
2
+ generateUiContractDebug,
3
+ generateUiContractGraph
4
+ } from "../contracts.js";
5
+ import { generatorDefaultsMap, getProjection } from "../shared.js";
6
+ import { generateWithComponentGenerator } from "../../adapters.js";
7
+ import { generatorProfile } from "../../registry.js";
8
+ import { generateReactApp } from "./react.js";
9
+ import { generateSvelteKitApp } from "./sveltekit.js";
10
+ import { generateVanillaWebApp } from "./vanilla.js";
11
+ import {
12
+ generateUiWebContract,
13
+ generateUiWebDebug
14
+ } from "./ui-web-contract.js";
15
+
16
+ export function generateWebApp(graph, options = {}) {
17
+ const projection = getProjection(graph, options.projectionId);
18
+ if (options.component?.generator?.id) {
19
+ return generateWithComponentGenerator({
20
+ graph,
21
+ projection,
22
+ component: options.component,
23
+ topology: options.topology || null,
24
+ implementation: options.implementation || null,
25
+ options: { ...options, projectionId: projection.id }
26
+ }).files;
27
+ }
28
+ const profile = generatorProfile(options.component?.generator?.id, null) || generatorDefaultsMap(projection).profile || "sveltekit";
29
+ if (profile === "vanilla") {
30
+ return generateVanillaWebApp(graph, options);
31
+ }
32
+ return profile === "react" ? generateReactApp(graph, options) : generateSvelteKitApp(graph, options);
33
+ }
34
+
35
+ export function generateWebTarget(target, graph, options = {}) {
36
+ if (target === "ui-contract-graph") {
37
+ return generateUiContractGraph(graph, options);
38
+ }
39
+ if (target === "ui-contract-debug") {
40
+ return generateUiContractDebug(graph, options);
41
+ }
42
+ if (target === "ui-web-contract") {
43
+ return generateUiWebContract(graph, options);
44
+ }
45
+ if (target === "ui-web-debug") {
46
+ return generateUiWebDebug(graph, options);
47
+ }
48
+ if (target === "sveltekit-app") {
49
+ return generateWebApp(graph, options);
50
+ }
51
+
52
+ throw new Error(`Unsupported web generator target '${target}'`);
53
+ }
@@ -0,0 +1,248 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @typedef {{ id?: string, name?: string }} ComponentReference
5
+ * @typedef {{ component?: ComponentReference, region?: string }} ComponentUsage
6
+ * @typedef {{ patterns?: string[] }} ComponentContract
7
+ * @typedef {Record<string, ComponentContract>} ComponentContractMap
8
+ * @typedef {{ itemsExpression?: string, componentContracts?: ComponentContractMap, useTypescript?: boolean }} RenderOptions
9
+ * @typedef {{ components?: ComponentUsage[] }} ScreenContract
10
+ */
11
+
12
+ /**
13
+ * @param {unknown} value
14
+ * @returns {string}
15
+ */
16
+ function escapeAttribute(value) {
17
+ return String(value ?? "")
18
+ .replace(/&/g, "&amp;")
19
+ .replace(/"/g, "&quot;");
20
+ }
21
+
22
+ /**
23
+ * @param {unknown} value
24
+ * @returns {string}
25
+ */
26
+ function escapeText(value) {
27
+ return String(value ?? "")
28
+ .replace(/&/g, "&amp;")
29
+ .replace(/</g, "&lt;")
30
+ .replace(/>/g, "&gt;");
31
+ }
32
+
33
+ /**
34
+ * @param {ComponentUsage} usage
35
+ * @returns {string}
36
+ */
37
+ function componentName(usage) {
38
+ return usage?.component?.name || usage?.component?.id || "Component";
39
+ }
40
+
41
+ /**
42
+ * @param {ComponentUsage} usage
43
+ * @returns {string}
44
+ */
45
+ function componentId(usage) {
46
+ return usage?.component?.id || "component";
47
+ }
48
+
49
+ /**
50
+ * @param {ComponentUsage} usage
51
+ * @param {ComponentContractMap | undefined} componentContracts
52
+ * @returns {string[]}
53
+ */
54
+ function componentPatterns(usage, componentContracts) {
55
+ const id = usage?.component?.id;
56
+ const contract = id ? componentContracts?.[id] : null;
57
+ return Array.isArray(contract?.patterns) ? contract.patterns : [];
58
+ }
59
+
60
+ /**
61
+ * @param {ComponentUsage} usage
62
+ * @param {ComponentContractMap | undefined} componentContracts
63
+ * @param {string} pattern
64
+ * @returns {boolean}
65
+ */
66
+ function hasPattern(usage, componentContracts, pattern) {
67
+ return componentPatterns(usage, componentContracts).includes(pattern);
68
+ }
69
+
70
+ /**
71
+ * @param {ComponentUsage} usage
72
+ * @param {RenderOptions} options
73
+ * @returns {string}
74
+ */
75
+ function renderSummaryStats(usage, options) {
76
+ const items = options.itemsExpression || "items";
77
+ return `<section className="component-card component-summary" data-topogram-component="${escapeAttribute(componentId(usage))}">
78
+ <div>
79
+ <p className="component-eyebrow">Component</p>
80
+ <h2>${escapeText(componentName(usage))}</h2>
81
+ </div>
82
+ <div className="summary-grid">
83
+ <div>
84
+ <strong>{${items}.length}</strong>
85
+ <span>Total</span>
86
+ </div>
87
+ <div>
88
+ <strong>{Object.keys(${items}[0] ?? {}).length}</strong>
89
+ <span>Fields</span>
90
+ </div>
91
+ <div>
92
+ <strong>{${items}.filter((item: any) => item && (item.id ?? item.uuid ?? item.key)).length}</strong>
93
+ <span>Identified</span>
94
+ </div>
95
+ </div>
96
+ </section>`;
97
+ }
98
+
99
+ /**
100
+ * @param {ComponentUsage} usage
101
+ * @param {RenderOptions} options
102
+ * @returns {string}
103
+ */
104
+ function renderCollectionTable(usage, options) {
105
+ const items = options.itemsExpression || "items";
106
+ const itemParam = options.useTypescript ? "(item: any)" : "(item)";
107
+ return `<div className="component-card component-table" data-topogram-component="${escapeAttribute(componentId(usage))}">
108
+ <div className="component-header">
109
+ <div>
110
+ <p className="component-eyebrow">Component</p>
111
+ <h2>${escapeText(componentName(usage))}</h2>
112
+ </div>
113
+ <span className="badge">{${items}.length} items</span>
114
+ </div>
115
+ <div className="table-wrap component-table-wrap">
116
+ <table className="resource-table data-grid">
117
+ <thead>
118
+ <tr>
119
+ {Object.keys(${items}[0] ?? {}).slice(0, 4).map((field) => (
120
+ <th key={field}>{field}</th>
121
+ ))}
122
+ </tr>
123
+ </thead>
124
+ <tbody>
125
+ {${items}.map(${itemParam} => (
126
+ <tr key={String(item.id ?? item.title ?? item.name ?? item.message)}>
127
+ {Object.keys(${items}[0] ?? {}).slice(0, 4).map((field) => (
128
+ <td key={field}>{String(item?.[field] ?? "")}</td>
129
+ ))}
130
+ </tr>
131
+ ))}
132
+ </tbody>
133
+ </table>
134
+ </div>
135
+ </div>`;
136
+ }
137
+
138
+ /**
139
+ * @param {ComponentUsage} usage
140
+ * @param {RenderOptions} options
141
+ * @returns {string}
142
+ */
143
+ function renderBoard(usage, options) {
144
+ const items = options.itemsExpression || "items";
145
+ const itemParam = options.useTypescript ? "(item: any)" : "(item)";
146
+ return `<div className="component-card component-board" data-topogram-component="${escapeAttribute(componentId(usage))}">
147
+ <div className="component-header">
148
+ <div>
149
+ <p className="component-eyebrow">Component</p>
150
+ <h2>${escapeText(componentName(usage))}</h2>
151
+ </div>
152
+ </div>
153
+ <div className="board-grid">
154
+ {Array.from(new Set(${items}.map((item: any) => item?.status ?? item?.state ?? item?.stage ?? item?.category ?? "items"))).map((group) => (
155
+ <section className="board-column" key={String(group)}>
156
+ <h3>{String(group)}</h3>
157
+ {${items}.filter(${itemParam} => (item?.status ?? item?.state ?? item?.stage ?? item?.category ?? "items") === group).map(${itemParam} => (
158
+ <div className="board-card" key={String(item.id ?? item.title ?? item.name)}>
159
+ {item.title ?? item.name ?? item.label ?? item.message ?? item.id ?? JSON.stringify(item)}
160
+ </div>
161
+ ))}
162
+ </section>
163
+ ))}
164
+ </div>
165
+ </div>`;
166
+ }
167
+
168
+ /**
169
+ * @param {ComponentUsage} usage
170
+ * @param {RenderOptions} options
171
+ * @returns {string}
172
+ */
173
+ function renderCalendar(usage, options) {
174
+ const items = options.itemsExpression || "items";
175
+ const itemParam = options.useTypescript ? "(item: any)" : "(item)";
176
+ return `<div className="component-card component-calendar" data-topogram-component="${escapeAttribute(componentId(usage))}">
177
+ <div className="component-header">
178
+ <div>
179
+ <p className="component-eyebrow">Component</p>
180
+ <h2>${escapeText(componentName(usage))}</h2>
181
+ </div>
182
+ </div>
183
+ <div className="calendar-list">
184
+ {${items}.filter(${itemParam} => item.date || item.due_at || item.dueAt || item.created_at || item.createdAt || item.updated_at || item.updatedAt).map(${itemParam} => (
185
+ <div className="calendar-card" key={String(item.id ?? item.title ?? item.name)}>
186
+ <span>{item.date ?? item.due_at ?? item.dueAt ?? item.created_at ?? item.createdAt ?? item.updated_at ?? item.updatedAt}</span>
187
+ <strong>{item.title ?? item.name ?? item.label ?? item.message ?? item.id ?? JSON.stringify(item)}</strong>
188
+ </div>
189
+ ))}
190
+ </div>
191
+ </div>`;
192
+ }
193
+
194
+ /**
195
+ * @param {ComponentUsage} usage
196
+ * @param {RenderOptions} options
197
+ * @returns {string}
198
+ */
199
+ function renderUsage(usage, options) {
200
+ const componentContracts = options.componentContracts || {};
201
+ if (hasPattern(usage, componentContracts, "summary_stats")) {
202
+ return renderSummaryStats(usage, options);
203
+ }
204
+ if (hasPattern(usage, componentContracts, "board_view")) {
205
+ return renderBoard(usage, options);
206
+ }
207
+ if (hasPattern(usage, componentContracts, "calendar_view")) {
208
+ return renderCalendar(usage, options);
209
+ }
210
+ if (hasPattern(usage, componentContracts, "resource_table") || hasPattern(usage, componentContracts, "data_grid_view")) {
211
+ return renderCollectionTable(usage, options);
212
+ }
213
+ return "";
214
+ }
215
+
216
+ /**
217
+ * @param {ScreenContract} screen
218
+ * @param {string} region
219
+ * @returns {ComponentUsage[]}
220
+ */
221
+ export function reactComponentUsagesForRegion(screen, region) {
222
+ return (screen?.components || []).filter((usage) => usage?.region === region);
223
+ }
224
+
225
+ /**
226
+ * @param {ScreenContract} screen
227
+ * @param {string} region
228
+ * @returns {boolean}
229
+ */
230
+ export function hasReactComponentRegion(screen, region) {
231
+ return reactComponentUsagesForRegion(screen, region).length > 0;
232
+ }
233
+
234
+ /**
235
+ * @param {ScreenContract} screen
236
+ * @param {string} region
237
+ * @param {RenderOptions} [options]
238
+ * @returns {string}
239
+ */
240
+ export function renderReactComponentRegion(screen, region, options = {}) {
241
+ const rendered = reactComponentUsagesForRegion(screen, region)
242
+ .map((usage) => renderUsage(usage, options))
243
+ .filter(Boolean);
244
+ if (rendered.length === 0) {
245
+ return "";
246
+ }
247
+ return rendered.join("\n");
248
+ }