@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### feat(#2132): Policy-as-data refactor (manifest v2)
6
+
7
+ **Manifest schema bumped to v2.** Each `ManifestFileEntry` now carries a `policy` field:
8
+
9
+ - `owned` — scaffold-managed, always overwritten on upgrade, blocked by pre-commit hook
10
+ - `tracked` — scaffold-managed with drift detection; user edits raise a conflict file on upgrade
11
+ - `seed` — installed once at scaffold init; user owns the file after that, never overwritten
12
+
13
+ **Automatic migration:** On first run of any `tib` command that reads the manifest, v1 entries (missing `policy`) are automatically upgraded using path-based inference:
14
+ - Paths under `infra/`, `.mc/infra/`, matching `mc-deploy.yml`, or `.github/workflows/` → `owned`
15
+ - Frontend seed page paths (`frontend/src/pages/auth/`, `account/`, `shell/`, `_errors/`, `layouts/`, `styles/brand.css`) → `seed`
16
+ - Everything else → `tracked`
17
+
18
+ No behavioural change to upgrade or add-module for existing projects.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @mettlecast/domain-cli
2
+
3
+ Local developer toolchain for TIB Domain Module projects. Provides build, validate, test, and dev subcommands.
4
+
5
+ ## Install
6
+
7
+ Install as a dev dependency:
8
+
9
+ ```bash
10
+ npm install --save-dev @mettlecast/domain-cli
11
+ ```
12
+
13
+ Then add to `package.json`:
14
+
15
+ ```json
16
+ {
17
+ "scripts": {
18
+ "domain:build": "mc-domain-module build ./src/domain",
19
+ "domain:validate": "mc-domain-module validate ./src/domain",
20
+ "domain:test": "mc-domain-module test ./src/domain charge-handler --fixture ./fixtures/charge.json",
21
+ "domain:dev": "mc-domain-module dev ./src/domain"
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## Commands
27
+
28
+ ### Query & Development
29
+
30
+ | Command | Description | Output |
31
+ |---|---|---|
32
+ | `mc-domain-module build <domain>` | Load domain handlers via tsx, walk registry, emit `.mc/domain-registry.json` | Registry JSON at `--out <path>` or default location |
33
+ | `mc-domain-module validate <domain>` | Build registry and lint (semver ranges, 90-day lifecycle rule, enforcement config) | Exits 1 if validation fails (CI gate) |
34
+ | `mc-domain-module test <domain> <handler-id> --fixture <path>` | Invoke handler with fixture data, hydrate local `DomainContext` stub | Handler result printed to stdout |
35
+ | `mc-domain-module dev <domain>` | Start Fastify HTTP server on port 3000 (or `--port <n>`) simulating API Gateway | All `defineApi()` handlers mounted and callable via HTTP |
36
+
37
+ ### Scaffolding
38
+
39
+ New domains and handlers:
40
+
41
+ **Add a new domain:**
42
+ ```bash
43
+ npx mc-domain-module add-domain payments [--tenancy required]
44
+ ```
45
+ Creates `domains/payments/` with `domain.config.ts`, skeleton API, and event definitions.
46
+
47
+ **Add an API endpoint to a domain:**
48
+ ```bash
49
+ npx mc-domain-module add-api payments charge-api [--method POST] [--tenancy required]
50
+ ```
51
+ Creates `domains/payments/api/charge-api.ts` with a `defineApi` v1 stub.
52
+
53
+ **Add an event subscriber to a domain:**
54
+ ```bash
55
+ npx mc-domain-module add-subscriber payments charge-completed-subscriber --event payment.completed
56
+ ```
57
+ Creates `domains/payments/subscribers/charge-completed-subscriber.ts` listening to `payment.completed` event.
58
+
59
+ **Add a flow:**
60
+ ```bash
61
+ npx mc-domain-module add-flow onboard-user
62
+ ```
63
+ Scaffolds `flows/onboard-user.ts` with a constrained-step skeleton. Flows can ONLY orchestrate domain primitives (`domain-action` / `domain-api` / `domain-event` / flow-control built-ins) — no raw Lambda ARNs or arbitrary Step Functions states allowed.
64
+
65
+ ### Validation & Diagnostics
66
+
67
+ **Run structural checks:**
68
+ ```bash
69
+ npx mc-domain-module doctor # human-readable table of all checks
70
+ npx mc-domain-module doctor --strict # exit 1 on any FAIL (CI mode)
71
+ npx mc-domain-module doctor --json # machine-readable JSON output
72
+ ```
73
+
74
+ **Verify infra/modules/ integrity:**
75
+ ```bash
76
+ npx mc-domain-module check-hashes
77
+ ```
78
+ Verifies that `infra/modules/` files have not been hand-edited since the last scaffold. Compares current SHA256 against `.mc/modules-hashes.json` manifest. Reports drifted, missing, and unexpected files. Re-scaffold to legitimately update `infra/modules/`.
79
+
80
+ ### Registry Building
81
+
82
+ **Build domain registries:**
83
+ ```bash
84
+ npx mc-domain-module build domains/<id>
85
+ ```
86
+ Loads domain handlers, walks registry, and emits `.mc/<id>-registry.json` and `.mc/actions-types.d.ts`. CI runs this before `cdk deploy domain-<id>`.
87
+
88
+ **Build flow registries:**
89
+ ```bash
90
+ npx mc-domain-module build-flows --out .mc/flows-registry.json
91
+ ```
92
+ Walks `flows/*.{ts,json}`, validates each flow definition, and emits `.mc/flows-registry.json`. CI runs this before `cdk deploy` of the flows stack.
93
+
94
+ ## Typical Workflow
95
+
96
+ 1. **Develop locally**
97
+ ```bash
98
+ npm run domain:dev
99
+ # Open http://localhost:3000/charge with curl
100
+ ```
101
+
102
+ 2. **Test a handler**
103
+ ```bash
104
+ npm run domain:test charge-handler --fixture ./fixtures/charge.json
105
+ ```
106
+
107
+ 3. **Validate before commit**
108
+ ```bash
109
+ npm run domain:validate
110
+ ```
111
+
112
+ 4. **Build for deployment**
113
+ ```bash
114
+ npm run domain:build
115
+ # .mc/domain-registry.json is ready for CDK or contracts generation
116
+ ```
117
+
118
+ ## Options
119
+
120
+ **`build`:**
121
+ - `--out <path>` — Override output file path (default: `.mc/domain-registry.json`)
122
+
123
+ **`dev`:**
124
+ - `--port <n>` — Listen port (default: `3000`)
125
+
126
+ **`test`:**
127
+ - `--fixture <path>` — Required. JSON file with shape `{ event?: unknown, ctx?: Partial<DomainContext> }`
128
+
129
+ ## See Also
130
+
131
+ - `@mettlecast/domain-runtime` — handler definitions
132
+ - `@mettlecast/domain-cdk-packer` — CDK infrastructure
133
+ - `@mettlecast/eslint-plugin-domain-module` — linting rules
@@ -0,0 +1,20 @@
1
+ import type { DomainRegistry } from '@mettlecast/domain-cdk-packer';
2
+ /**
3
+ * Result of a registry build operation.
4
+ */
5
+ export interface BuildResult {
6
+ /** The assembled DomainRegistry ready for serialisation. */
7
+ registry: DomainRegistry;
8
+ /** Any non-fatal warnings encountered during the build. */
9
+ warnings: string[];
10
+ }
11
+ /**
12
+ * Build a DomainRegistry from a domain source directory.
13
+ * Walks the conventional directory structure, loads each primitive definition
14
+ * file via the tsx child-process loader, and assembles the registry snapshot.
15
+ * Zod schema fields are omitted in Phase 1e (schema extraction is v1.x scope).
16
+ * @param domainRoot - Absolute path to the domain root directory.
17
+ * @returns BuildResult containing the assembled registry and any warnings.
18
+ * @throws If domain.config.ts is missing or does not export a 'domain' primitive.
19
+ */
20
+ export declare function buildRegistry(domainRoot: string): Promise<BuildResult>;
@@ -0,0 +1,178 @@
1
+ import { relative, resolve } from 'node:path';
2
+ import { walkDomainDir } from '../utils/file-helpers.js';
3
+ import { loadModuleExports } from './load-module.js';
4
+ /** Returns true if a value looks like a JSON Schema object (has a 'type' or '$schema' property). */
5
+ function isJsonSchema(v) {
6
+ return v !== null && typeof v === 'object' && !Array.isArray(v) &&
7
+ ('type' in v || '$schema' in v || 'properties' in v);
8
+ }
9
+ /**
10
+ * Build a DomainRegistry from a domain source directory.
11
+ * Walks the conventional directory structure, loads each primitive definition
12
+ * file via the tsx child-process loader, and assembles the registry snapshot.
13
+ * Zod schema fields are omitted in Phase 1e (schema extraction is v1.x scope).
14
+ * @param domainRoot - Absolute path to the domain root directory.
15
+ * @returns BuildResult containing the assembled registry and any warnings.
16
+ * @throws If domain.config.ts is missing or does not export a 'domain' primitive.
17
+ */
18
+ export async function buildRegistry(domainRoot) {
19
+ // Resolve to absolute path so downstream fs operations and tsx eval imports
20
+ // work correctly when the caller passes a relative path.
21
+ domainRoot = resolve(domainRoot);
22
+ const warnings = [];
23
+ const paths = await walkDomainDir(domainRoot);
24
+ if (!paths.domain) {
25
+ throw new Error(`buildRegistry: domain.config.ts not found in ${domainRoot}`);
26
+ }
27
+ async function load(filePath) {
28
+ try {
29
+ return await loadModuleExports(filePath);
30
+ }
31
+ catch (err) {
32
+ warnings.push(`Failed to load ${filePath}: ${String(err)}`);
33
+ return [];
34
+ }
35
+ }
36
+ function relPath(absPath) {
37
+ return relative(domainRoot, absPath);
38
+ }
39
+ function deployment(raw) {
40
+ return raw['deployment'];
41
+ }
42
+ const domainExports = await load(paths.domain);
43
+ const domainRaw = domainExports.find(e => e['_kind'] === 'domain');
44
+ if (!domainRaw) {
45
+ throw new Error(`buildRegistry: no 'domain' export found in ${paths.domain}`);
46
+ }
47
+ const domain = {
48
+ id: String(domainRaw['id']),
49
+ kind: 'domain',
50
+ name: String(domainRaw['name']),
51
+ tenancy: String(domainRaw['tenancy']),
52
+ defaultDeployment: deployment(domainRaw),
53
+ };
54
+ const [apiExports, webhookExports, subscriberExports, actionExports, scheduleExports, jobExports, integrationExports, eventExports] = await Promise.all([
55
+ Promise.all(paths.apis.map(load)),
56
+ Promise.all(paths.webhooks.map(load)),
57
+ Promise.all(paths.subscribers.map(load)),
58
+ Promise.all(paths.actions.map(load)),
59
+ Promise.all(paths.schedules.map(load)),
60
+ Promise.all(paths.jobs.map(load)),
61
+ Promise.all(paths.integrations.map(load)),
62
+ paths.publishes ? load(paths.publishes) : Promise.resolve([]),
63
+ ]);
64
+ const apis = paths.apis.flatMap((filePath, i) => (apiExports[i] ?? [])
65
+ .filter(e => e['_kind'] === 'api')
66
+ .map(e => {
67
+ const rawVersions = e['versions'];
68
+ const versionSnapshots = rawVersions
69
+ ? Object.entries(rawVersions).map(([ver, v]) => ({
70
+ version: ver,
71
+ requestSchema: isJsonSchema(v?.input) ? v.input : undefined,
72
+ responseSchema: isJsonSchema(v?.output) ? v.output : undefined,
73
+ }))
74
+ : [];
75
+ const latestVersion = versionSnapshots[versionSnapshots.length - 1];
76
+ return {
77
+ id: String(e['id']),
78
+ kind: 'api',
79
+ handlerFile: relPath(filePath),
80
+ path: String(e['path']),
81
+ method: typeof e['method'] === 'string' ? e['method'].toUpperCase() : 'ANY',
82
+ authType: e['auth']?.type ?? 'jwt',
83
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
84
+ deployment: deployment(e),
85
+ requestSchema: latestVersion?.requestSchema,
86
+ responseSchema: latestVersion?.responseSchema,
87
+ versions: versionSnapshots.length > 0 ? versionSnapshots : undefined,
88
+ };
89
+ }));
90
+ const webhooks = paths.webhooks.flatMap((filePath, i) => (webhookExports[i] ?? [])
91
+ .filter(e => e['_kind'] === 'webhook')
92
+ .map(e => ({
93
+ id: String(e['id']),
94
+ kind: 'webhook',
95
+ handlerFile: relPath(filePath),
96
+ path: String(e['path']),
97
+ provider: String(e['provider']),
98
+ hmacAlgorithm: e['verification']?.type,
99
+ hmacSecretRef: e['verification']?.secretRef,
100
+ deployment: deployment(e),
101
+ })));
102
+ const subscribers = paths.subscribers.flatMap((filePath, i) => (subscriberExports[i] ?? [])
103
+ .filter(e => e['_kind'] === 'subscriber')
104
+ .map(e => ({
105
+ id: String(e['id']),
106
+ kind: 'subscriber',
107
+ handlerFile: relPath(filePath),
108
+ event: String(e['event']),
109
+ semverRange: String(e['semverRange']),
110
+ concurrency: e['concurrency'],
111
+ deployment: deployment(e),
112
+ })));
113
+ const schedules = paths.schedules.flatMap((filePath, i) => (scheduleExports[i] ?? [])
114
+ .filter(e => e['_kind'] === 'schedule')
115
+ .map(e => ({
116
+ id: String(e['id']),
117
+ kind: 'schedule',
118
+ handlerFile: relPath(filePath),
119
+ cron: String(e['cron']),
120
+ enabled: Boolean(e['enabled'] ?? true),
121
+ deployment: deployment(e),
122
+ })));
123
+ const jobs = paths.jobs.flatMap((filePath, i) => (jobExports[i] ?? [])
124
+ .filter(e => e['_kind'] === 'job')
125
+ .map(e => ({
126
+ id: String(e['id']),
127
+ kind: 'job',
128
+ handlerFile: relPath(filePath),
129
+ maxRetries: Number(e['maxRetries'] ?? 3),
130
+ visibilityTimeoutSeconds: Number(e['visibilityTimeoutSeconds'] ?? 30),
131
+ deployment: deployment(e),
132
+ })));
133
+ const actions = paths.actions.flatMap((filePath, i) => (actionExports[i] ?? [])
134
+ .filter(e => e['_kind'] === 'action')
135
+ .map(e => ({
136
+ id: String(e['id']),
137
+ kind: 'action',
138
+ handlerFile: relPath(filePath),
139
+ visibility: String(e['visibility']),
140
+ idempotent: Boolean(e['idempotent'] ?? false),
141
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
142
+ deployment: deployment(e),
143
+ inputSchema: isJsonSchema(e['input']) ? e['input'] : undefined,
144
+ outputSchema: isJsonSchema(e['output']) ? e['output'] : undefined,
145
+ })));
146
+ const integrations = paths.integrations.flatMap((_filePath, i) => (integrationExports[i] ?? [])
147
+ .filter(e => e['_kind'] === 'integration')
148
+ .map(e => ({
149
+ id: String(e['id']),
150
+ kind: 'integration',
151
+ baseUrl: String(e['baseUrl']),
152
+ })));
153
+ const events = eventExports
154
+ .filter(e => e['_kind'] === 'event')
155
+ .map(e => ({
156
+ id: String(e['id']),
157
+ kind: 'event',
158
+ busName: 'default',
159
+ description: typeof e['description'] === 'string' ? e['description'] : undefined,
160
+ versions: isJsonSchema(e['schema'])
161
+ ? [{ version: Number(e['version'] ?? 1), schema: e['schema'] }]
162
+ : undefined,
163
+ }));
164
+ const registry = {
165
+ schemaVersion: '1',
166
+ domainRoot,
167
+ domain,
168
+ apis,
169
+ webhooks,
170
+ subscribers,
171
+ schedules,
172
+ jobs,
173
+ actions,
174
+ integrations,
175
+ events,
176
+ };
177
+ return { registry, warnings };
178
+ }
@@ -0,0 +1,10 @@
1
+ import type { DomainRegistry } from '@mettlecast/domain-cdk-packer';
2
+ /**
3
+ * Generate a .d.ts file declaring typed `ctx.actions.call(actionId, input)`
4
+ * overrides for the union of actions across all registries. The generated
5
+ * file augments `@mettlecast/domain-runtime`'s ActionsProxy type.
6
+ *
7
+ * @param registries - Array of DomainRegistry objects to extract actions from.
8
+ * @returns A complete .d.ts file as a string, ready to write to disk.
9
+ */
10
+ export declare function buildActionsTypes(registries: DomainRegistry[]): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Generate a .d.ts file declaring typed `ctx.actions.call(actionId, input)`
3
+ * overrides for the union of actions across all registries. The generated
4
+ * file augments `@mettlecast/domain-runtime`'s ActionsProxy type.
5
+ *
6
+ * @param registries - Array of DomainRegistry objects to extract actions from.
7
+ * @returns A complete .d.ts file as a string, ready to write to disk.
8
+ */
9
+ export function buildActionsTypes(registries) {
10
+ const overloads = [];
11
+ for (const reg of registries) {
12
+ for (const action of reg.actions) {
13
+ const fqn = `${reg.domain.id}.${action.id}`;
14
+ // Phase 1: use unknown for input/output
15
+ // TODO(#1787-Wave3): extract types from action.inputSchema / action.outputSchema via json-schema-to-typescript
16
+ overloads.push(` call(actionId: '${fqn}', input?: unknown): Promise<unknown>;`);
17
+ }
18
+ }
19
+ return [
20
+ '// Generated by `mc-domain-module build` — DO NOT EDIT',
21
+ '// Source of truth: .mc/{domain-id}-registry.json files',
22
+ '',
23
+ `declare module '@mettlecast/domain-runtime' {`,
24
+ ' interface ActionsProxy {',
25
+ ...overloads,
26
+ ' }',
27
+ '}',
28
+ '',
29
+ 'export {};',
30
+ '',
31
+ ].join('\n');
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { DomainModuleConfig } from '@mettlecast/domain-runtime/types';
2
+ /**
3
+ * Load and validate the domain module configuration from `.mc/domain-module.config.ts`.
4
+ *
5
+ * Uses the same tsx child-process loader as `loadModuleExports` to handle TypeScript.
6
+ * Falls back to `{ mode: 'optional' }` when the config file does not exist.
7
+ * Throws a descriptive error when the file exists but fails schema validation.
8
+ *
9
+ * @param projectRoot - Absolute path to the project root (directory containing `.mc/`).
10
+ * @returns Validated DomainModuleConfig.
11
+ */
12
+ export declare function loadDomainModuleConfig(projectRoot: string): Promise<DomainModuleConfig>;
@@ -0,0 +1,34 @@
1
+ import { join } from 'node:path';
2
+ import { access } from 'node:fs/promises';
3
+ import { DomainModuleConfigSchema } from '@mettlecast/domain-runtime/types';
4
+ import { loadModuleExports } from './load-module.js';
5
+ const CONFIG_FILE = '.mc/domain-module.config.ts';
6
+ /**
7
+ * Load and validate the domain module configuration from `.mc/domain-module.config.ts`.
8
+ *
9
+ * Uses the same tsx child-process loader as `loadModuleExports` to handle TypeScript.
10
+ * Falls back to `{ mode: 'optional' }` when the config file does not exist.
11
+ * Throws a descriptive error when the file exists but fails schema validation.
12
+ *
13
+ * @param projectRoot - Absolute path to the project root (directory containing `.mc/`).
14
+ * @returns Validated DomainModuleConfig.
15
+ */
16
+ export async function loadDomainModuleConfig(projectRoot) {
17
+ const configPath = join(projectRoot, CONFIG_FILE);
18
+ try {
19
+ await access(configPath);
20
+ }
21
+ catch {
22
+ return { mode: 'optional' };
23
+ }
24
+ const exports = await loadModuleExports(configPath);
25
+ const raw = exports.find((e) => e._exportName === 'default' || e._exportName === 'config') ?? exports[0];
26
+ if (!raw) {
27
+ return { mode: 'optional' };
28
+ }
29
+ const parsed = DomainModuleConfigSchema.safeParse(raw);
30
+ if (!parsed.success) {
31
+ throw new Error(`Invalid domain-module.config.ts: ${parsed.error.issues.map((i) => i.message).join(', ')}`);
32
+ }
33
+ return parsed.data;
34
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The set of `_kind` discriminant values that identify a domain primitive export.
3
+ */
4
+ export declare const PRIMITIVE_KINDS: Set<"api" | "domain" | "webhook" | "subscriber" | "schedule" | "job" | "action" | "integration" | "event">;
5
+ /**
6
+ * A raw primitive export extracted from a domain source file.
7
+ * Functions (handler) and Zod schemas (input/output/versions) are stripped
8
+ * because they cannot cross the child-process JSON boundary.
9
+ */
10
+ export interface RawPrimitiveExport {
11
+ /** The `_kind` discriminant identifying the primitive type. */
12
+ _kind: string;
13
+ /** The export name within the source module. */
14
+ _exportName: string;
15
+ /** All scalar/plain-object fields from the export (functions stripped). */
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * Spawns a tsx child process to import a TypeScript domain source file and
20
+ * extract all exports whose `_kind` matches a known primitive kind.
21
+ * Functions and Zod schema objects are stripped before JSON serialisation.
22
+ * @param absoluteFilePath - Absolute path to the `.ts` source file to load.
23
+ * @returns Array of raw primitive export objects with scalar fields only.
24
+ * @throws If tsx exits with a non-zero code or stdout is not valid JSON.
25
+ */
26
+ export declare function loadModuleExports(absoluteFilePath: string): Promise<RawPrimitiveExport[]>;
@@ -0,0 +1,114 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { writeFile, unlink, mkdir } from 'node:fs/promises';
3
+ import { join, dirname } from 'node:path';
4
+ import { randomBytes } from 'node:crypto';
5
+ import { createRequire } from 'node:module';
6
+ import { pathToFileURL } from 'node:url';
7
+ // Resolve tsx's CLI entry point relative to this package, so we never
8
+ // depend on PATH containing node_modules/.bin.
9
+ const _require = createRequire(import.meta.url);
10
+ const tsxRoot = dirname(_require.resolve('tsx/package.json'));
11
+ const tsxCli = join(tsxRoot, 'dist', 'cli.mjs');
12
+ /**
13
+ * The set of `_kind` discriminant values that identify a domain primitive export.
14
+ */
15
+ export const PRIMITIVE_KINDS = new Set([
16
+ 'api', 'webhook', 'subscriber', 'schedule', 'job',
17
+ 'action', 'integration', 'event', 'domain',
18
+ ]);
19
+ /**
20
+ * Generates an eval script that imports a TypeScript module and extracts
21
+ * primitive exports with their functions and schemas stripped.
22
+ * @param absoluteFilePath - Absolute path to the `.ts` source file.
23
+ * @returns A string containing the eval script code.
24
+ */
25
+ function makeEvalScript(absoluteFilePath) {
26
+ // Convert to file:// URL so ESM loader works on all platforms (especially Windows).
27
+ const fileUrl = pathToFileURL(absoluteFilePath).href;
28
+ return `
29
+ import { zodToJsonSchema } from 'zod-to-json-schema';
30
+ import * as mod from ${JSON.stringify(fileUrl)};
31
+ const KINDS = new Set(['api','webhook','subscriber','schedule','job','action','integration','event','domain']);
32
+ function isZod(v) {
33
+ return v && typeof v === 'object' && typeof v.parse === 'function' && typeof v.safeParse === 'function' && v._def !== undefined;
34
+ }
35
+ function strip(obj) {
36
+ const out = {};
37
+ for (const [k, v] of Object.entries(obj)) {
38
+ if (typeof v === 'function') continue;
39
+ if (isZod(v)) {
40
+ try { out[k] = zodToJsonSchema(v, { target: 'jsonSchema7' }); } catch { /* skip unserializable */ }
41
+ } else if (Array.isArray(v)) {
42
+ 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));
43
+ } else if (v && typeof v === 'object') {
44
+ out[k] = strip(v);
45
+ } else {
46
+ out[k] = v;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ const seen = new Map();
52
+ for (const [name, val] of Object.entries(mod)) {
53
+ if (val && typeof val === 'object' && KINDS.has(val._kind)) {
54
+ const key = val._kind + ':' + name;
55
+ if (!seen.has(key)) seen.set(key, { ...strip(val), _exportName: name });
56
+ } else if (val && typeof val === 'object') {
57
+ // Handle CJS-transpiled exports where named exports are
58
+ // nested inside default / module.exports.
59
+ for (const [nestedName, nestedVal] of Object.entries(val)) {
60
+ if (nestedVal && typeof nestedVal === 'object' && KINDS.has(nestedVal._kind)) {
61
+ const key = nestedVal._kind + ':' + nestedName;
62
+ if (!seen.has(key)) seen.set(key, { ...strip(nestedVal), _exportName: nestedName });
63
+ }
64
+ }
65
+ }
66
+ }
67
+ const results = Array.from(seen.values());
68
+ process.stdout.write(JSON.stringify(results));
69
+ `;
70
+ }
71
+ /**
72
+ * Spawns a tsx child process to import a TypeScript domain source file and
73
+ * extract all exports whose `_kind` matches a known primitive kind.
74
+ * Functions and Zod schema objects are stripped before JSON serialisation.
75
+ * @param absoluteFilePath - Absolute path to the `.ts` source file to load.
76
+ * @returns Array of raw primitive export objects with scalar fields only.
77
+ * @throws If tsx exits with a non-zero code or stdout is not valid JSON.
78
+ */
79
+ export async function loadModuleExports(absoluteFilePath) {
80
+ // Use a subdir of the project root rather than OS tmpdir so that ESM import
81
+ // resolution can walk up and find node_modules packages like zod-to-json-schema.
82
+ const tibTmpDir = join(process.cwd(), '.tib', 'tmp');
83
+ await mkdir(tibTmpDir, { recursive: true }).catch(() => undefined);
84
+ const tempPath = join(tibTmpDir, `tib-load-${randomBytes(8).toString('hex')}.mts`);
85
+ await writeFile(tempPath, makeEvalScript(absoluteFilePath), 'utf8');
86
+ try {
87
+ return await new Promise((resolve, reject) => {
88
+ const tsxChild = spawn(process.execPath, [tsxCli, tempPath], {
89
+ stdio: ['ignore', 'pipe', 'pipe'],
90
+ env: { ...process.env },
91
+ });
92
+ let stdout = '';
93
+ let stderr = '';
94
+ tsxChild.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
95
+ tsxChild.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
96
+ tsxChild.on('close', (code) => {
97
+ if (code !== 0) {
98
+ reject(new Error(`loadModuleExports: tsx exited ${code} for ${absoluteFilePath}\n${stderr}`));
99
+ return;
100
+ }
101
+ try {
102
+ resolve(JSON.parse(stdout));
103
+ }
104
+ catch {
105
+ reject(new Error(`loadModuleExports: invalid JSON from tsx for ${absoluteFilePath}: ${stdout}`));
106
+ }
107
+ });
108
+ tsxChild.on('error', (err) => reject(err));
109
+ });
110
+ }
111
+ finally {
112
+ await unlink(tempPath).catch(() => undefined);
113
+ }
114
+ }
@@ -0,0 +1,10 @@
1
+ import type { SchemaSnapshot } from '@mettlecast/domain-cdk-packer';
2
+ /**
3
+ * Convert a Zod schema to a plain JSON Schema object compatible with SchemaSnapshot.
4
+ * Uses the `zod-to-json-schema` package under the hood with JSON Schema draft-07 target.
5
+ * Returns undefined if schema is nullish so callers can safely skip optional schemas.
6
+ * Input is typed `unknown` to bridge zod v3/v4 generic mismatches with the converter.
7
+ * @param schema - A Zod schema instance (any version), or undefined/null.
8
+ * @returns A SchemaSnapshot (plain JSON-serialisable object) or undefined.
9
+ */
10
+ export declare function toJsonSchema(schema: unknown): SchemaSnapshot | undefined;
@@ -0,0 +1,15 @@
1
+ import { zodToJsonSchema } from 'zod-to-json-schema';
2
+ /**
3
+ * Convert a Zod schema to a plain JSON Schema object compatible with SchemaSnapshot.
4
+ * Uses the `zod-to-json-schema` package under the hood with JSON Schema draft-07 target.
5
+ * Returns undefined if schema is nullish so callers can safely skip optional schemas.
6
+ * Input is typed `unknown` to bridge zod v3/v4 generic mismatches with the converter.
7
+ * @param schema - A Zod schema instance (any version), or undefined/null.
8
+ * @returns A SchemaSnapshot (plain JSON-serialisable object) or undefined.
9
+ */
10
+ export function toJsonSchema(schema) {
11
+ if (!schema)
12
+ return undefined;
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ return zodToJsonSchema(schema, { target: 'jsonSchema7' });
15
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};