@mettlecast/domain-cli 0.2.0

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 (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. package/tsconfig.json +13 -0
@@ -0,0 +1,237 @@
1
+ import { relative, resolve } from 'node:path';
2
+ import type {
3
+ DomainRegistry,
4
+ ApiRegistryEntry,
5
+ ApiVersionSnapshot,
6
+ SchemaSnapshot,
7
+ WebhookRegistryEntry,
8
+ SubscriberRegistryEntry,
9
+ ScheduleRegistryEntry,
10
+ JobRegistryEntry,
11
+ ActionRegistryEntry,
12
+ IntegrationRegistryEntry,
13
+ EventRegistryEntry,
14
+ DomainRegistryEntry,
15
+ SerialDeploymentConfig,
16
+ } from '@mettlecast/domain-cdk-packer';
17
+ import { walkDomainDir } from '../utils/file-helpers.js';
18
+ import { loadModuleExports, type RawPrimitiveExport } from './load-module.js';
19
+
20
+ /** Returns true if a value looks like a JSON Schema object (has a 'type' or '$schema' property). */
21
+ function isJsonSchema(v: unknown): boolean {
22
+ return v !== null && typeof v === 'object' && !Array.isArray(v) &&
23
+ ('type' in (v as object) || '$schema' in (v as object) || 'properties' in (v as object));
24
+ }
25
+
26
+ /**
27
+ * Result of a registry build operation.
28
+ */
29
+ export interface BuildResult {
30
+ /** The assembled DomainRegistry ready for serialisation. */
31
+ registry: DomainRegistry;
32
+ /** Any non-fatal warnings encountered during the build. */
33
+ warnings: string[];
34
+ }
35
+
36
+ /**
37
+ * Build a DomainRegistry from a domain source directory.
38
+ * Walks the conventional directory structure, loads each primitive definition
39
+ * file via the tsx child-process loader, and assembles the registry snapshot.
40
+ * Zod schema fields are omitted in Phase 1e (schema extraction is v1.x scope).
41
+ * @param domainRoot - Absolute path to the domain root directory.
42
+ * @returns BuildResult containing the assembled registry and any warnings.
43
+ * @throws If domain.config.ts is missing or does not export a 'domain' primitive.
44
+ */
45
+ export async function buildRegistry(domainRoot: string): Promise<BuildResult> {
46
+ // Resolve to absolute path so downstream fs operations and tsx eval imports
47
+ // work correctly when the caller passes a relative path.
48
+ domainRoot = resolve(domainRoot);
49
+
50
+ const warnings: string[] = [];
51
+ const paths = await walkDomainDir(domainRoot);
52
+
53
+ if (!paths.domain) {
54
+ throw new Error(`buildRegistry: domain.config.ts not found in ${domainRoot}`);
55
+ }
56
+
57
+ async function load(filePath: string): Promise<RawPrimitiveExport[]> {
58
+ try {
59
+ return await loadModuleExports(filePath);
60
+ } catch (err) {
61
+ warnings.push(`Failed to load ${filePath}: ${String(err)}`);
62
+ return [];
63
+ }
64
+ }
65
+
66
+ function relPath(absPath: string): string {
67
+ return relative(domainRoot, absPath);
68
+ }
69
+
70
+ function deployment(raw: RawPrimitiveExport): SerialDeploymentConfig | undefined {
71
+ return raw['deployment'] as SerialDeploymentConfig | undefined;
72
+ }
73
+
74
+ const domainExports = await load(paths.domain);
75
+ const domainRaw = domainExports.find(e => e['_kind'] === 'domain');
76
+ if (!domainRaw) {
77
+ throw new Error(`buildRegistry: no 'domain' export found in ${paths.domain}`);
78
+ }
79
+
80
+ const domain: DomainRegistryEntry = {
81
+ id: String(domainRaw['id']),
82
+ kind: 'domain',
83
+ name: String(domainRaw['name']),
84
+ tenancy: String(domainRaw['tenancy']),
85
+ defaultDeployment: deployment(domainRaw),
86
+ };
87
+
88
+ const [apiExports, webhookExports, subscriberExports, actionExports,
89
+ scheduleExports, jobExports, integrationExports, eventExports] = await Promise.all([
90
+ Promise.all(paths.apis.map(load)),
91
+ Promise.all(paths.webhooks.map(load)),
92
+ Promise.all(paths.subscribers.map(load)),
93
+ Promise.all(paths.actions.map(load)),
94
+ Promise.all(paths.schedules.map(load)),
95
+ Promise.all(paths.jobs.map(load)),
96
+ Promise.all(paths.integrations.map(load)),
97
+ paths.publishes ? load(paths.publishes) : Promise.resolve([]),
98
+ ]);
99
+
100
+ const apis: ApiRegistryEntry[] = paths.apis.flatMap((filePath, i) =>
101
+ (apiExports[i] ?? [])
102
+ .filter(e => e['_kind'] === 'api')
103
+ .map(e => {
104
+ const rawVersions = e['versions'] as Record<string, { input?: unknown; output?: unknown }> | undefined;
105
+ const versionSnapshots: ApiVersionSnapshot[] = rawVersions
106
+ ? Object.entries(rawVersions).map(([ver, v]) => ({
107
+ version: ver,
108
+ requestSchema: isJsonSchema(v?.input) ? (v.input as SchemaSnapshot) : undefined,
109
+ responseSchema: isJsonSchema(v?.output) ? (v.output as SchemaSnapshot) : undefined,
110
+ }))
111
+ : [];
112
+ const latestVersion = versionSnapshots[versionSnapshots.length - 1];
113
+ return {
114
+ id: String(e['id']),
115
+ kind: 'api' as const,
116
+ handlerFile: relPath(filePath),
117
+ path: String(e['path']),
118
+ method: typeof e['method'] === 'string' ? e['method'].toUpperCase() : 'ANY',
119
+ authType: ((e['auth'] as { type?: string } | undefined)?.type as 'jwt' | 'api-key' | 'none' | undefined) ?? 'jwt',
120
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
121
+ deployment: deployment(e),
122
+ requestSchema: latestVersion?.requestSchema,
123
+ responseSchema: latestVersion?.responseSchema,
124
+ versions: versionSnapshots.length > 0 ? versionSnapshots : undefined,
125
+ };
126
+ })
127
+ );
128
+
129
+ const webhooks: WebhookRegistryEntry[] = paths.webhooks.flatMap((filePath, i) =>
130
+ (webhookExports[i] ?? [])
131
+ .filter(e => e['_kind'] === 'webhook')
132
+ .map(e => ({
133
+ id: String(e['id']),
134
+ kind: 'webhook' as const,
135
+ handlerFile: relPath(filePath),
136
+ path: String(e['path']),
137
+ provider: String(e['provider']),
138
+ hmacAlgorithm: (e['verification'] as { type?: string } | undefined)?.type,
139
+ hmacSecretRef: (e['verification'] as { secretRef?: string } | undefined)?.secretRef,
140
+ deployment: deployment(e),
141
+ }))
142
+ );
143
+
144
+ const subscribers: SubscriberRegistryEntry[] = paths.subscribers.flatMap((filePath, i) =>
145
+ (subscriberExports[i] ?? [])
146
+ .filter(e => e['_kind'] === 'subscriber')
147
+ .map(e => ({
148
+ id: String(e['id']),
149
+ kind: 'subscriber' as const,
150
+ handlerFile: relPath(filePath),
151
+ event: String(e['event']),
152
+ semverRange: String(e['semverRange']),
153
+ concurrency: e['concurrency'] as number | undefined,
154
+ deployment: deployment(e),
155
+ }))
156
+ );
157
+
158
+ const schedules: ScheduleRegistryEntry[] = paths.schedules.flatMap((filePath, i) =>
159
+ (scheduleExports[i] ?? [])
160
+ .filter(e => e['_kind'] === 'schedule')
161
+ .map(e => ({
162
+ id: String(e['id']),
163
+ kind: 'schedule' as const,
164
+ handlerFile: relPath(filePath),
165
+ cron: String(e['cron']),
166
+ enabled: Boolean(e['enabled'] ?? true),
167
+ deployment: deployment(e),
168
+ }))
169
+ );
170
+
171
+ const jobs: JobRegistryEntry[] = paths.jobs.flatMap((filePath, i) =>
172
+ (jobExports[i] ?? [])
173
+ .filter(e => e['_kind'] === 'job')
174
+ .map(e => ({
175
+ id: String(e['id']),
176
+ kind: 'job' as const,
177
+ handlerFile: relPath(filePath),
178
+ maxRetries: Number(e['maxRetries'] ?? 3),
179
+ visibilityTimeoutSeconds: Number(e['visibilityTimeoutSeconds'] ?? 30),
180
+ deployment: deployment(e),
181
+ }))
182
+ );
183
+
184
+ const actions: ActionRegistryEntry[] = paths.actions.flatMap((filePath, i) =>
185
+ (actionExports[i] ?? [])
186
+ .filter(e => e['_kind'] === 'action')
187
+ .map(e => ({
188
+ id: String(e['id']),
189
+ kind: 'action' as const,
190
+ handlerFile: relPath(filePath),
191
+ visibility: String(e['visibility']) as 'private' | 'domain' | 'workspace',
192
+ idempotent: Boolean(e['idempotent'] ?? false),
193
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
194
+ deployment: deployment(e),
195
+ inputSchema: isJsonSchema(e['input']) ? (e['input'] as SchemaSnapshot) : undefined,
196
+ outputSchema: isJsonSchema(e['output']) ? (e['output'] as SchemaSnapshot) : undefined,
197
+ }))
198
+ );
199
+
200
+ const integrations: IntegrationRegistryEntry[] = paths.integrations.flatMap((_filePath, i) =>
201
+ (integrationExports[i] ?? [])
202
+ .filter(e => e['_kind'] === 'integration')
203
+ .map(e => ({
204
+ id: String(e['id']),
205
+ kind: 'integration' as const,
206
+ baseUrl: String(e['baseUrl']),
207
+ }))
208
+ );
209
+
210
+ const events: EventRegistryEntry[] = eventExports
211
+ .filter(e => e['_kind'] === 'event')
212
+ .map(e => ({
213
+ id: String(e['id']),
214
+ kind: 'event' as const,
215
+ busName: 'default',
216
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
217
+ versions: isJsonSchema(e['schema'])
218
+ ? [{ version: Number(e['version'] ?? 1), schema: e['schema'] as SchemaSnapshot }]
219
+ : undefined,
220
+ }));
221
+
222
+ const registry: DomainRegistry = {
223
+ schemaVersion: '1',
224
+ domainRoot,
225
+ domain,
226
+ apis,
227
+ webhooks,
228
+ subscribers,
229
+ schedules,
230
+ jobs,
231
+ actions,
232
+ integrations,
233
+ events,
234
+ };
235
+
236
+ return { registry, warnings };
237
+ }
@@ -0,0 +1,36 @@
1
+ import type { DomainRegistry } from '@mettlecast/domain-cdk-packer';
2
+
3
+ /**
4
+ * Generate a .d.ts file declaring typed `ctx.actions.call(actionId, input)`
5
+ * overrides for the union of actions across all registries. The generated
6
+ * file augments `@mettlecast/domain-runtime`'s ActionsProxy type.
7
+ *
8
+ * @param registries - Array of DomainRegistry objects to extract actions from.
9
+ * @returns A complete .d.ts file as a string, ready to write to disk.
10
+ */
11
+ export function buildActionsTypes(registries: DomainRegistry[]): string {
12
+ const overloads: string[] = [];
13
+
14
+ for (const reg of registries) {
15
+ for (const action of reg.actions) {
16
+ const fqn = `${reg.domain.id}.${action.id}`;
17
+ // Phase 1: use unknown for input/output
18
+ // TODO(#1787-Wave3): extract types from action.inputSchema / action.outputSchema via json-schema-to-typescript
19
+ overloads.push(` call(actionId: '${fqn}', input?: unknown): Promise<unknown>;`);
20
+ }
21
+ }
22
+
23
+ return [
24
+ '// Generated by `mc-domain-module build` — DO NOT EDIT',
25
+ '// Source of truth: .mc/{domain-id}-registry.json files',
26
+ '',
27
+ `declare module '@mettlecast/domain-runtime' {`,
28
+ ' interface ActionsProxy {',
29
+ ...overloads,
30
+ ' }',
31
+ '}',
32
+ '',
33
+ 'export {};',
34
+ '',
35
+ ].join('\n');
36
+ }
@@ -0,0 +1,43 @@
1
+ import { join } from 'node:path';
2
+ import { access } from 'node:fs/promises';
3
+ import { DomainModuleConfigSchema } from '@mettlecast/domain-runtime/types';
4
+ import type { DomainModuleConfig } from '@mettlecast/domain-runtime/types';
5
+ import { loadModuleExports } from './load-module.js';
6
+
7
+ const CONFIG_FILE = '.mc/domain-module.config.ts';
8
+
9
+ /**
10
+ * Load and validate the domain module configuration from `.mc/domain-module.config.ts`.
11
+ *
12
+ * Uses the same tsx child-process loader as `loadModuleExports` to handle TypeScript.
13
+ * Falls back to `{ mode: 'optional' }` when the config file does not exist.
14
+ * Throws a descriptive error when the file exists but fails schema validation.
15
+ *
16
+ * @param projectRoot - Absolute path to the project root (directory containing `.mc/`).
17
+ * @returns Validated DomainModuleConfig.
18
+ */
19
+ export async function loadDomainModuleConfig(projectRoot: string): Promise<DomainModuleConfig> {
20
+ const configPath = join(projectRoot, CONFIG_FILE);
21
+
22
+ try {
23
+ await access(configPath);
24
+ } catch {
25
+ return { mode: 'optional' };
26
+ }
27
+
28
+ const exports = await loadModuleExports(configPath);
29
+ const raw = exports.find((e) => e._exportName === 'default' || e._exportName === 'config') ?? exports[0];
30
+
31
+ if (!raw) {
32
+ return { mode: 'optional' };
33
+ }
34
+
35
+ const parsed = DomainModuleConfigSchema.safeParse(raw);
36
+ if (!parsed.success) {
37
+ throw new Error(
38
+ `Invalid domain-module.config.ts: ${parsed.error.issues.map((i) => i.message).join(', ')}`,
39
+ );
40
+ }
41
+
42
+ return parsed.data;
43
+ }
@@ -0,0 +1,133 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { writeFile, unlink, mkdir } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, dirname } from 'node:path';
5
+ import { randomBytes } from 'node:crypto';
6
+ import { createRequire } from 'node:module';
7
+ import { pathToFileURL } from 'node:url';
8
+
9
+ // Resolve tsx's CLI entry point relative to this package, so we never
10
+ // depend on PATH containing node_modules/.bin.
11
+ const _require = createRequire(import.meta.url);
12
+ const tsxRoot = dirname(_require.resolve('tsx/package.json'));
13
+ const tsxCli = join(tsxRoot, 'dist', 'cli.mjs');
14
+
15
+ /**
16
+ * The set of `_kind` discriminant values that identify a domain primitive export.
17
+ */
18
+ export const PRIMITIVE_KINDS = new Set([
19
+ 'api', 'webhook', 'subscriber', 'schedule', 'job',
20
+ 'action', 'integration', 'event', 'domain',
21
+ ] as const);
22
+
23
+ /**
24
+ * A raw primitive export extracted from a domain source file.
25
+ * Functions (handler) and Zod schemas (input/output/versions) are stripped
26
+ * because they cannot cross the child-process JSON boundary.
27
+ */
28
+ export interface RawPrimitiveExport {
29
+ /** The `_kind` discriminant identifying the primitive type. */
30
+ _kind: string;
31
+ /** The export name within the source module. */
32
+ _exportName: string;
33
+ /** All scalar/plain-object fields from the export (functions stripped). */
34
+ [key: string]: unknown;
35
+ }
36
+
37
+ /**
38
+ * Generates an eval script that imports a TypeScript module and extracts
39
+ * primitive exports with their functions and schemas stripped.
40
+ * @param absoluteFilePath - Absolute path to the `.ts` source file.
41
+ * @returns A string containing the eval script code.
42
+ */
43
+ function makeEvalScript(absoluteFilePath: string): string {
44
+ // Convert to file:// URL so ESM loader works on all platforms (especially Windows).
45
+ const fileUrl = pathToFileURL(absoluteFilePath).href;
46
+ return `
47
+ import { zodToJsonSchema } from 'zod-to-json-schema';
48
+ import * as mod from ${JSON.stringify(fileUrl)};
49
+ const KINDS = new Set(['api','webhook','subscriber','schedule','job','action','integration','event','domain']);
50
+ function isZod(v) {
51
+ return v && typeof v === 'object' && typeof v.parse === 'function' && typeof v.safeParse === 'function' && v._def !== undefined;
52
+ }
53
+ function strip(obj) {
54
+ const out = {};
55
+ for (const [k, v] of Object.entries(obj)) {
56
+ if (typeof v === 'function') continue;
57
+ if (isZod(v)) {
58
+ try { out[k] = zodToJsonSchema(v, { target: 'jsonSchema7' }); } catch { /* skip unserializable */ }
59
+ } else if (Array.isArray(v)) {
60
+ out[k] = v.map(item => (item && typeof item === 'object' && !isZod(item) ? strip(item) : isZod(item) ? (() => { try { return zodToJsonSchema(item, { target: 'jsonSchema7' }); } catch { return undefined; } })() : item));
61
+ } else if (v && typeof v === 'object') {
62
+ out[k] = strip(v);
63
+ } else {
64
+ out[k] = v;
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ const seen = new Map();
70
+ for (const [name, val] of Object.entries(mod)) {
71
+ if (val && typeof val === 'object' && KINDS.has(val._kind)) {
72
+ const key = val._kind + ':' + name;
73
+ if (!seen.has(key)) seen.set(key, { ...strip(val), _exportName: name });
74
+ } else if (val && typeof val === 'object') {
75
+ // Handle CJS-transpiled exports where named exports are
76
+ // nested inside default / module.exports.
77
+ for (const [nestedName, nestedVal] of Object.entries(val)) {
78
+ if (nestedVal && typeof nestedVal === 'object' && KINDS.has(nestedVal._kind)) {
79
+ const key = nestedVal._kind + ':' + nestedName;
80
+ if (!seen.has(key)) seen.set(key, { ...strip(nestedVal), _exportName: nestedName });
81
+ }
82
+ }
83
+ }
84
+ }
85
+ const results = Array.from(seen.values());
86
+ process.stdout.write(JSON.stringify(results));
87
+ `;
88
+ }
89
+
90
+ /**
91
+ * Spawns a tsx child process to import a TypeScript domain source file and
92
+ * extract all exports whose `_kind` matches a known primitive kind.
93
+ * Functions and Zod schema objects are stripped before JSON serialisation.
94
+ * @param absoluteFilePath - Absolute path to the `.ts` source file to load.
95
+ * @returns Array of raw primitive export objects with scalar fields only.
96
+ * @throws If tsx exits with a non-zero code or stdout is not valid JSON.
97
+ */
98
+ export async function loadModuleExports(absoluteFilePath: string): Promise<RawPrimitiveExport[]> {
99
+ // Use a subdir of the project root rather than OS tmpdir so that ESM import
100
+ // resolution can walk up and find node_modules packages like zod-to-json-schema.
101
+ const tibTmpDir = join(process.cwd(), '.tib', 'tmp');
102
+ await mkdir(tibTmpDir, { recursive: true }).catch(() => undefined);
103
+ const tempPath = join(tibTmpDir, `tib-load-${randomBytes(8).toString('hex')}.mts`);
104
+ await writeFile(tempPath, makeEvalScript(absoluteFilePath), 'utf8');
105
+
106
+ try {
107
+ return await new Promise<RawPrimitiveExport[]>((resolve, reject) => {
108
+ const tsxChild = spawn(process.execPath, [tsxCli, tempPath], {
109
+ stdio: ['ignore', 'pipe', 'pipe'],
110
+ env: { ...process.env },
111
+ });
112
+
113
+ let stdout = '';
114
+ let stderr = '';
115
+ tsxChild.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString(); });
116
+ tsxChild.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString(); });
117
+ tsxChild.on('close', (code) => {
118
+ if (code !== 0) {
119
+ reject(new Error(`loadModuleExports: tsx exited ${code} for ${absoluteFilePath}\n${stderr}`));
120
+ return;
121
+ }
122
+ try {
123
+ resolve(JSON.parse(stdout) as RawPrimitiveExport[]);
124
+ } catch {
125
+ reject(new Error(`loadModuleExports: invalid JSON from tsx for ${absoluteFilePath}: ${stdout}`));
126
+ }
127
+ });
128
+ tsxChild.on('error', (err) => reject(err));
129
+ });
130
+ } finally {
131
+ await unlink(tempPath).catch(() => undefined);
132
+ }
133
+ }
@@ -0,0 +1,16 @@
1
+ import { zodToJsonSchema } from 'zod-to-json-schema';
2
+ import type { SchemaSnapshot } from '@mettlecast/domain-cdk-packer';
3
+
4
+ /**
5
+ * Convert a Zod schema to a plain JSON Schema object compatible with SchemaSnapshot.
6
+ * Uses the `zod-to-json-schema` package under the hood with JSON Schema draft-07 target.
7
+ * Returns undefined if schema is nullish so callers can safely skip optional schemas.
8
+ * Input is typed `unknown` to bridge zod v3/v4 generic mismatches with the converter.
9
+ * @param schema - A Zod schema instance (any version), or undefined/null.
10
+ * @returns A SchemaSnapshot (plain JSON-serialisable object) or undefined.
11
+ */
12
+ export function toJsonSchema(schema: unknown): SchemaSnapshot | undefined {
13
+ if (!schema) return undefined;
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ return zodToJsonSchema(schema as any, { target: 'jsonSchema7' }) as SchemaSnapshot;
16
+ }