@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.
- package/CHANGELOG.md +18 -0
- package/README.md +133 -0
- package/dist/builder/build-registry.d.ts +20 -0
- package/dist/builder/build-registry.js +178 -0
- package/dist/builder/build-types.d.ts +10 -0
- package/dist/builder/build-types.js +32 -0
- package/dist/builder/load-domain-module-config.d.ts +12 -0
- package/dist/builder/load-domain-module-config.js +34 -0
- package/dist/builder/load-module.d.ts +26 -0
- package/dist/builder/load-module.js +114 -0
- package/dist/builder/zod-to-json.d.ts +10 -0
- package/dist/builder/zod-to-json.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +338 -0
- package/dist/commands/add-api.d.ts +32 -0
- package/dist/commands/add-api.js +106 -0
- package/dist/commands/add-component.d.ts +15 -0
- package/dist/commands/add-component.js +78 -0
- package/dist/commands/add-domain.d.ts +21 -0
- package/dist/commands/add-domain.js +71 -0
- package/dist/commands/add-flow.d.ts +14 -0
- package/dist/commands/add-flow.js +108 -0
- package/dist/commands/add-hook.d.ts +15 -0
- package/dist/commands/add-hook.js +65 -0
- package/dist/commands/add-migration.d.ts +26 -0
- package/dist/commands/add-migration.js +93 -0
- package/dist/commands/add-module.d.ts +7 -0
- package/dist/commands/add-module.js +261 -0
- package/dist/commands/add-page.d.ts +13 -0
- package/dist/commands/add-page.js +170 -0
- package/dist/commands/add-seed-page.d.ts +5 -0
- package/dist/commands/add-seed-page.js +251 -0
- package/dist/commands/add-subscriber.d.ts +30 -0
- package/dist/commands/add-subscriber.js +87 -0
- package/dist/commands/add-test.d.ts +22 -0
- package/dist/commands/add-test.js +285 -0
- package/dist/commands/audit.d.ts +39 -0
- package/dist/commands/audit.js +171 -0
- package/dist/commands/build-catalog.d.ts +68 -0
- package/dist/commands/build-catalog.js +105 -0
- package/dist/commands/build-flows.d.ts +26 -0
- package/dist/commands/build-flows.js +174 -0
- package/dist/commands/build.d.ts +17 -0
- package/dist/commands/build.js +39 -0
- package/dist/commands/check-hashes.d.ts +38 -0
- package/dist/commands/check-hashes.js +97 -0
- package/dist/commands/create-project.d.ts +5 -0
- package/dist/commands/create-project.js +272 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +27 -0
- package/dist/commands/doctor.d.ts +48 -0
- package/dist/commands/doctor.js +1301 -0
- package/dist/commands/explain.d.ts +34 -0
- package/dist/commands/explain.js +167 -0
- package/dist/commands/power-tune.d.ts +1 -0
- package/dist/commands/power-tune.js +87 -0
- package/dist/commands/reseed-page.d.ts +6 -0
- package/dist/commands/reseed-page.js +297 -0
- package/dist/commands/show-dns.d.ts +15 -0
- package/dist/commands/show-dns.js +101 -0
- package/dist/commands/show.d.ts +46 -0
- package/dist/commands/show.js +135 -0
- package/dist/commands/test.d.ts +18 -0
- package/dist/commands/test.js +56 -0
- package/dist/commands/upgrade-backend.d.ts +9 -0
- package/dist/commands/upgrade-backend.js +46 -0
- package/dist/commands/upgrade-event.d.ts +22 -0
- package/dist/commands/upgrade-event.js +111 -0
- package/dist/commands/upgrade-frontend.d.ts +5 -0
- package/dist/commands/upgrade-frontend.js +99 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +495 -0
- package/dist/commands/validate.d.ts +30 -0
- package/dist/commands/validate.js +148 -0
- package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
- package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
- package/dist/runtime-stubs/load-secrets.d.ts +11 -0
- package/dist/runtime-stubs/load-secrets.js +37 -0
- package/dist/server/api-server.d.ts +21 -0
- package/dist/server/api-server.js +14 -0
- package/dist/server/mount-routes.d.ts +19 -0
- package/dist/server/mount-routes.js +42 -0
- package/dist/templates/api-skeleton.d.ts +18 -0
- package/dist/templates/api-skeleton.js +69 -0
- package/dist/templates/claude-md.d.ts +4 -0
- package/dist/templates/claude-md.js +7 -0
- package/dist/templates/dashboard-pages/index.d.ts +101 -0
- package/dist/templates/dashboard-pages/index.js +22 -0
- package/dist/templates/domain-config.d.ts +10 -0
- package/dist/templates/domain-config.js +19 -0
- package/dist/templates/events-skeleton.d.ts +9 -0
- package/dist/templates/events-skeleton.js +23 -0
- package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
- package/dist/templates/patterns/api/create-with-event.js +69 -0
- package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
- package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
- package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
- package/dist/templates/patterns/api/paginated-list.js +71 -0
- package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
- package/dist/templates/patterns/api/simple-crud.js +95 -0
- package/dist/templates/patterns/api/system-admin.d.ts +5 -0
- package/dist/templates/patterns/api/system-admin.js +98 -0
- package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
- package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
- package/dist/templates/patterns/component/index.d.ts +25 -0
- package/dist/templates/patterns/component/index.js +814 -0
- package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
- package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
- package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
- package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
- package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
- package/dist/templates/patterns/flow/human-in-loop.js +90 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
- package/dist/templates/patterns/hook/index.d.ts +7 -0
- package/dist/templates/patterns/hook/index.js +378 -0
- package/dist/templates/patterns/migration/column-add.d.ts +5 -0
- package/dist/templates/patterns/migration/column-add.js +52 -0
- package/dist/templates/patterns/migration/event-store.d.ts +6 -0
- package/dist/templates/patterns/migration/event-store.js +97 -0
- package/dist/templates/patterns/migration/index.d.ts +5 -0
- package/dist/templates/patterns/migration/index.js +52 -0
- package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
- package/dist/templates/patterns/migration/rls-policy.js +78 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
- package/dist/templates/patterns/page/dashboard.d.ts +4 -0
- package/dist/templates/patterns/page/dashboard.js +164 -0
- package/dist/templates/patterns/page/detail.d.ts +4 -0
- package/dist/templates/patterns/page/detail.js +162 -0
- package/dist/templates/patterns/page/form.d.ts +4 -0
- package/dist/templates/patterns/page/form.js +192 -0
- package/dist/templates/patterns/page/index.d.ts +8 -0
- package/dist/templates/patterns/page/index.js +14 -0
- package/dist/templates/patterns/page/list.d.ts +4 -0
- package/dist/templates/patterns/page/list.js +138 -0
- package/dist/templates/patterns/page/settings.d.ts +4 -0
- package/dist/templates/patterns/page/settings.js +318 -0
- package/dist/templates/patterns/page/wizard.d.ts +4 -0
- package/dist/templates/patterns/page/wizard.js +293 -0
- package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
- package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
- package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
- package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
- package/dist/templates/readme.d.ts +4 -0
- package/dist/templates/readme.js +7 -0
- package/dist/templates/subscriber-skeleton.d.ts +11 -0
- package/dist/templates/subscriber-skeleton.js +23 -0
- package/dist/utils/checksum.d.ts +18 -0
- package/dist/utils/checksum.js +39 -0
- package/dist/utils/file-helpers.d.ts +35 -0
- package/dist/utils/file-helpers.js +59 -0
- package/dist/utils/header-inject.d.ts +15 -0
- package/dist/utils/header-inject.js +93 -0
- package/dist/utils/install-file.d.ts +17 -0
- package/dist/utils/install-file.js +78 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/manifest.d.ts +28 -0
- package/dist/utils/manifest.js +93 -0
- package/dist/utils/s3-fetch.d.ts +25 -0
- package/dist/utils/s3-fetch.js +52 -0
- package/dist/utils/scaffold-config.d.ts +95 -0
- package/dist/utils/scaffold-config.js +53 -0
- package/dist/utils/token-substitution.d.ts +35 -0
- package/dist/utils/token-substitution.js +52 -0
- package/dist/utils/zod-extractor.d.ts +10 -0
- package/dist/utils/zod-extractor.js +28 -0
- package/package.json +43 -0
- package/src/__tests__/build-registry.test.ts +115 -0
- package/src/__tests__/build-types.test.ts +100 -0
- package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
- package/src/__tests__/commands/add-domain.test.ts +174 -0
- package/src/__tests__/commands/add-flow.test.ts +69 -0
- package/src/__tests__/commands/add-module.test.ts +207 -0
- package/src/__tests__/commands/build-flows.test.ts +154 -0
- package/src/__tests__/commands/check-hashes.test.ts +142 -0
- package/src/__tests__/commands/create-project.test.ts +712 -0
- package/src/__tests__/commands/upgrade.test.ts +762 -0
- package/src/__tests__/doctor.test.ts +291 -0
- package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
- package/src/__tests__/load-domain-module-config.test.ts +10 -0
- package/src/__tests__/mount-routes.test.ts +100 -0
- package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
- package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
- package/src/__tests__/utils/checksum.test.ts +90 -0
- package/src/__tests__/utils/header-inject.test.ts +89 -0
- package/src/__tests__/utils/install-file.test.ts +207 -0
- package/src/__tests__/utils/manifest.test.ts +229 -0
- package/src/__tests__/utils/token-substitution.test.ts +134 -0
- package/src/__tests__/validate.test.ts +18 -0
- package/src/builder/build-registry.ts +237 -0
- package/src/builder/build-types.ts +36 -0
- package/src/builder/load-domain-module-config.ts +43 -0
- package/src/builder/load-module.ts +133 -0
- package/src/builder/zod-to-json.ts +16 -0
- package/src/cli.ts +387 -0
- package/src/commands/add-api.ts +145 -0
- package/src/commands/add-component.ts +116 -0
- package/src/commands/add-domain.ts +100 -0
- package/src/commands/add-flow.ts +136 -0
- package/src/commands/add-hook.ts +100 -0
- package/src/commands/add-migration.ts +129 -0
- package/src/commands/add-module.ts +347 -0
- package/src/commands/add-page.ts +242 -0
- package/src/commands/add-seed-page.ts +338 -0
- package/src/commands/add-subscriber.ts +123 -0
- package/src/commands/add-test.ts +328 -0
- package/src/commands/audit.ts +217 -0
- package/src/commands/build-catalog.ts +181 -0
- package/src/commands/build-flows.ts +198 -0
- package/src/commands/build.ts +59 -0
- package/src/commands/check-hashes.ts +150 -0
- package/src/commands/create-project.ts +367 -0
- package/src/commands/dev.ts +47 -0
- package/src/commands/doctor.ts +1362 -0
- package/src/commands/explain.ts +209 -0
- package/src/commands/power-tune.ts +112 -0
- package/src/commands/reseed-page.ts +410 -0
- package/src/commands/show-dns.ts +139 -0
- package/src/commands/show.ts +179 -0
- package/src/commands/test.ts +91 -0
- package/src/commands/upgrade-backend.ts +72 -0
- package/src/commands/upgrade-event.ts +175 -0
- package/src/commands/upgrade-frontend.ts +135 -0
- package/src/commands/upgrade.ts +668 -0
- package/src/commands/validate.ts +208 -0
- package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
- package/src/runtime-stubs/load-secrets.ts +40 -0
- package/src/server/api-server.ts +34 -0
- package/src/server/mount-routes.ts +68 -0
- package/src/templates/api-skeleton.ts +72 -0
- package/src/templates/claude-md.ts +7 -0
- package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
- package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
- package/src/templates/dashboard-pages/account/index.ts +5 -0
- package/src/templates/dashboard-pages/account/members.tsx +107 -0
- package/src/templates/dashboard-pages/account/profile.tsx +53 -0
- package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
- package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
- package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
- package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
- package/src/templates/dashboard-pages/auth/index.ts +10 -0
- package/src/templates/dashboard-pages/auth/login.tsx +75 -0
- package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
- package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
- package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
- package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
- package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
- package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
- package/src/templates/dashboard-pages/index.ts +22 -0
- package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
- package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
- package/src/templates/dashboard-pages/shell/index.ts +5 -0
- package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
- package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
- package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
- package/src/templates/domain-config.ts +20 -0
- package/src/templates/events-skeleton.ts +24 -0
- package/src/templates/patterns/api/create-with-event.ts +72 -0
- package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
- package/src/templates/patterns/api/paginated-list.ts +74 -0
- package/src/templates/patterns/api/simple-crud.ts +100 -0
- package/src/templates/patterns/api/system-admin.ts +101 -0
- package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
- package/src/templates/patterns/component/index.ts +833 -0
- package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
- package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
- package/src/templates/patterns/flow/human-in-loop.ts +93 -0
- package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
- package/src/templates/patterns/hook/index.ts +382 -0
- package/src/templates/patterns/migration/column-add.ts +54 -0
- package/src/templates/patterns/migration/event-store.ts +99 -0
- package/src/templates/patterns/migration/index.ts +54 -0
- package/src/templates/patterns/migration/rls-policy.ts +80 -0
- package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
- package/src/templates/patterns/page/dashboard.ts +164 -0
- package/src/templates/patterns/page/detail.ts +162 -0
- package/src/templates/patterns/page/form.ts +192 -0
- package/src/templates/patterns/page/index.ts +17 -0
- package/src/templates/patterns/page/list.ts +138 -0
- package/src/templates/patterns/page/settings.ts +318 -0
- package/src/templates/patterns/page/wizard.ts +293 -0
- package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
- package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
- package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
- package/src/templates/readme.ts +7 -0
- package/src/templates/subscriber-skeleton.ts +28 -0
- package/src/utils/checksum.ts +43 -0
- package/src/utils/file-helpers.ts +91 -0
- package/src/utils/header-inject.ts +126 -0
- package/src/utils/install-file.ts +99 -0
- package/src/utils/logger.ts +26 -0
- package/src/utils/manifest.ts +137 -0
- package/src/utils/s3-fetch.ts +91 -0
- package/src/utils/scaffold-config.ts +172 -0
- package/src/utils/token-substitution.ts +80 -0
- package/src/utils/zod-extractor.ts +31 -0
- 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