@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/dist/cli.js ADDED
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { runBuild } from './commands/build.js';
4
+ import { runValidate } from './commands/validate.js';
5
+ import { runTest } from './commands/test.js';
6
+ import { runDev } from './commands/dev.js';
7
+ import { runBuildCatalog } from './commands/build-catalog.js';
8
+ import { runAddDomain } from './commands/add-domain.js';
9
+ import { runAddApi, API_PATTERNS } from './commands/add-api.js';
10
+ import { runAddSubscriber } from './commands/add-subscriber.js';
11
+ import { runAddFlow } from './commands/add-flow.js';
12
+ import { runAddModule } from './commands/add-module.js';
13
+ import { runBuildFlows } from './commands/build-flows.js';
14
+ import { runDoctor } from './commands/doctor.js';
15
+ import { runCheckHashes } from './commands/check-hashes.js';
16
+ import { runUpgradeBackend } from './commands/upgrade-backend.js';
17
+ import { runAddPage } from './commands/add-page.js';
18
+ import { runCreateProject } from './commands/create-project.js';
19
+ import { runUpgradeFrontend } from './commands/upgrade-frontend.js';
20
+ import { runUpgrade } from './commands/upgrade.js';
21
+ import { runAddMigration, MIGRATION_PATTERNS } from './commands/add-migration.js';
22
+ import { runAddTest } from './commands/add-test.js';
23
+ import { runUpgradeEvent } from './commands/upgrade-event.js';
24
+ import { runShowDomainCli } from './commands/show.js';
25
+ import { runAuditCli } from './commands/audit.js';
26
+ import { runExplainCli } from './commands/explain.js';
27
+ import { runAddHook, HOOK_PATTERNS } from './commands/add-hook.js';
28
+ import { runAddComponent, COMPONENT_TIERS } from './commands/add-component.js';
29
+ import { runPowerTune } from './commands/power-tune.js';
30
+ import { runReseedPage } from './commands/reseed-page.js';
31
+ import { runAddSeedPage } from './commands/add-seed-page.js';
32
+ import { runShowDns } from './commands/show-dns.js';
33
+ /**
34
+ * Root CLI program for the TIB Domain Module local developer toolchain.
35
+ * Provides build, validate, test, dev, add-domain, add-api, and add-subscriber subcommands.
36
+ */
37
+ const program = new Command();
38
+ program
39
+ .name('mc-domain-module')
40
+ .description('TIB Domain Module local developer toolchain')
41
+ .version('0.1.0');
42
+ program
43
+ .command('build <domain>')
44
+ .description('Build the DomainRegistry from source and write .mc/domain-registry.json')
45
+ .option('--out <path>', 'Override the output file path')
46
+ .action(async (domain, opts) => {
47
+ await runBuild({ domainRoot: domain, outFile: opts.out });
48
+ });
49
+ program
50
+ .command('validate <domain>')
51
+ .description('Build the registry and validate structure (CI gate — exits 1 on failure)')
52
+ .action(async (domain) => {
53
+ await runValidate(domain, true);
54
+ });
55
+ program
56
+ .command('test <domain> <handler-id>')
57
+ .description('Invoke a handler with fixture data and print the result')
58
+ .requiredOption('--fixture <path>', 'Path to fixture JSON file ({ event, ctx? })')
59
+ .action(async (domain, handlerId, opts) => {
60
+ await runTest({ domainRoot: domain, handlerId, fixturePath: opts.fixture });
61
+ });
62
+ program
63
+ .command('dev <domain>')
64
+ .description('Start a local HTTP server simulating API Gateway for all defineApi handlers')
65
+ .option('--port <n>', 'Port to listen on', '3000')
66
+ .action(async (domain, opts) => {
67
+ await runDev({ domainRoot: domain, port: opts.port ? parseInt(opts.port, 10) : 3000 });
68
+ });
69
+ program
70
+ .command('build-catalog')
71
+ .description('Merge all per-domain registry files into .mc/domain-registry.json for TIB sync')
72
+ .option('--tib-dir <path>', 'Path to the .tib directory (defaults to .tib in cwd)')
73
+ .action(async (opts) => {
74
+ await runBuildCatalog(opts.tibDir);
75
+ });
76
+ program
77
+ .command('add-domain <id>')
78
+ .description('Scaffold a new domain under domains/<id> and register it in .mc/scaffold-config.json')
79
+ .option('--tenancy <mode>', 'Tenancy mode (required|none|system)', 'required')
80
+ .action(async (id, opts) => {
81
+ await runAddDomain({ id, tenancy: opts.tenancy });
82
+ });
83
+ program
84
+ .command('add-api <domain> <id>')
85
+ .description('Add a new API primitive to an existing domain')
86
+ .option('--method <method>', 'HTTP method', 'GET')
87
+ .option('--tenancy <mode>', 'Tenancy mode', 'required')
88
+ .option('--pattern <pattern>', `Pattern template: ${API_PATTERNS.join(' | ')}`)
89
+ .action(async (domain, id, opts) => {
90
+ await runAddApi({
91
+ domain,
92
+ id,
93
+ method: opts.method,
94
+ tenancy: opts.tenancy,
95
+ pattern: opts.pattern,
96
+ });
97
+ });
98
+ program
99
+ .command('add-subscriber <domain> <id>')
100
+ .description('Add a new event subscriber to an existing domain')
101
+ .requiredOption('--event <eventId>', 'Event id to subscribe to')
102
+ .action(async (domain, id, opts) => {
103
+ await runAddSubscriber({ domain, id, event: opts.event });
104
+ });
105
+ program
106
+ .command('add-flow <domain> <id>')
107
+ .description('Scaffold a new flow under domains/<domain>/flows/<id>.ts')
108
+ .option('--type <type>', 'Flow type: express (default) or standard', 'express')
109
+ .action(async (domain, id, opts) => {
110
+ await runAddFlow({ domain, id, type: opts.type });
111
+ });
112
+ program
113
+ .command('add-module <module>')
114
+ .description('Install a scaffold module from S3 at the project\'s pinned scaffold version and raise a PR')
115
+ .option('--github-token <token>', 'GitHub token for PR creation (overrides GITHUB_TOKEN env var)')
116
+ .option('--dry-run', 'Show what would be installed without making changes', false)
117
+ .action(async (module, opts) => {
118
+ await runAddModule({ moduleId: module, githubToken: opts.githubToken, dryRun: opts.dryRun });
119
+ });
120
+ program
121
+ .command('build-flows')
122
+ .description('Build all flows into .mc/flows-registry.json')
123
+ .option('--out <path>', 'Override the output file path')
124
+ .action(async (opts) => {
125
+ await runBuildFlows({ outFile: opts.out });
126
+ });
127
+ program
128
+ .command('doctor')
129
+ .description('Run all structural checks for the current TIB project (strict by default — any FAIL causes exit code 1)')
130
+ .option('--strict', 'DEPRECATED: Doctor is always strict now. This flag is a no-op.', false)
131
+ .option('--json', 'Output JSON instead of pretty table', false)
132
+ .action(async (opts) => {
133
+ if (opts.strict) {
134
+ // eslint-disable-next-line no-console
135
+ console.warn('⚠ --strict is deprecated: doctor is always strict now. This flag is a no-op.');
136
+ }
137
+ const report = await runDoctor({ projectRoot: process.cwd() });
138
+ if (opts.json) {
139
+ // eslint-disable-next-line no-console
140
+ console.log(JSON.stringify(report, null, 2));
141
+ }
142
+ else {
143
+ // Pretty-print table
144
+ // eslint-disable-next-line no-console
145
+ console.log('\n=== TIB Project Health Check ===\n');
146
+ for (const check of report.checks) {
147
+ const symbol = check.status === 'PASS' ? '✓' : check.status === 'WARN' ? '⚠' : '✗';
148
+ // eslint-disable-next-line no-console
149
+ console.log(`${symbol} ${check.name}: ${check.message}`);
150
+ if (check.fixHint) {
151
+ // eslint-disable-next-line no-console
152
+ console.log(` Fix: ${check.fixHint}`);
153
+ }
154
+ }
155
+ // eslint-disable-next-line no-console
156
+ console.log(`\nExit code: ${report.exitCode}\n`);
157
+ }
158
+ process.exit(report.exitCode);
159
+ });
160
+ program
161
+ .command('check-hashes')
162
+ .description('Verify infra/modules/ has not been hand-edited since last scaffold')
163
+ .action(async () => {
164
+ const result = await runCheckHashes({});
165
+ process.exit(result.ok ? 0 : 1);
166
+ });
167
+ program
168
+ .command('upgrade-backend <target-major>')
169
+ .description('Run jscodeshift/ts-morph migrations between major versions of domain-runtime')
170
+ .option('--repo-root <path>', 'Root of the repo (defaults to cwd)')
171
+ .option('--dry-run', 'Print changes without applying', false)
172
+ .action(async (targetMajor, opts) => {
173
+ await runUpgradeBackend({ targetMajor, repoRoot: opts.repoRoot, dryRun: opts.dryRun });
174
+ });
175
+ program
176
+ .command('add-page <pageId>')
177
+ .description('Scaffold a seed page from the dashboard-pages template registry')
178
+ .option('--output <dir>', 'Override the output directory (default: src/pages/<category>)')
179
+ .option('--pattern <pattern>', 'Scaffold with a page pattern template (list, detail, form, wizard, dashboard, settings)')
180
+ .option('--layout <layout>', 'Layout to use: app (default) or auth', 'app')
181
+ .option('--auth', 'Scaffold with auth guard wiring (login-redirect on unauthenticated)')
182
+ .option('--protected', 'Add requireAuth() loader to the route')
183
+ .option('--public', 'No auth guard (public page)')
184
+ .action(async (pageId, opts) => {
185
+ // Validate pattern early to give a clear error before the inner command
186
+ if (opts.pattern && !['list', 'detail', 'form', 'wizard', 'dashboard', 'settings'].includes(opts.pattern)) {
187
+ throw new Error(`Unknown page pattern "${opts.pattern}". Available patterns: list, detail, form, wizard, dashboard, settings`);
188
+ }
189
+ if (!['app', 'auth'].includes(opts.layout)) {
190
+ throw new Error(`Unknown layout "${opts.layout}". Available layouts: app, auth`);
191
+ }
192
+ await runAddPage({
193
+ pageId,
194
+ outputDir: opts.output,
195
+ pattern: opts.pattern,
196
+ layout: opts.layout,
197
+ auth: opts.auth,
198
+ protected: opts.protected,
199
+ public: opts.public,
200
+ });
201
+ });
202
+ program
203
+ .command('add-hook <name>')
204
+ .description('Scaffold a typed data-fetching hook (TanStack Query + @mettlecast/sdk)')
205
+ .option('--pattern <pattern>', 'Hook pattern', 'paginated-list')
206
+ .option('--output <dir>', 'Override the output directory (default: src/hooks/)')
207
+ .action(async (name, opts) => {
208
+ if (!HOOK_PATTERNS.includes(opts.pattern)) {
209
+ throw new Error(`Unknown hook pattern "${opts.pattern}". Available patterns: ${HOOK_PATTERNS.join(', ')}`);
210
+ }
211
+ await runAddHook({
212
+ name,
213
+ pattern: opts.pattern,
214
+ outputDir: opts.output,
215
+ });
216
+ });
217
+ program
218
+ .command('add-component <name>')
219
+ .description('Scaffold a design-system component with Storybook story and a11y check')
220
+ .option('--pattern <tier>', 'Component tier: atom, molecule, or organism', 'atom')
221
+ .option('--output <dir>', 'Override the output directory (default: src/components/)')
222
+ .action(async (name, opts) => {
223
+ if (!COMPONENT_TIERS.includes(opts.pattern)) {
224
+ throw new Error(`Unknown component tier "${opts.pattern}". Available tiers: ${COMPONENT_TIERS.join(', ')}`);
225
+ }
226
+ await runAddComponent({
227
+ name,
228
+ tier: opts.pattern,
229
+ outputDir: opts.output,
230
+ });
231
+ });
232
+ program
233
+ .command('create-project <name>')
234
+ .description('Initialise a new TIB frontend project with seed pages and router wiring')
235
+ .option('--out <dir>', 'Parent directory for the new project (default: current directory)')
236
+ .action(async (name, opts) => {
237
+ await runCreateProject({ name, outDir: opts.out });
238
+ });
239
+ program
240
+ .command('upgrade-frontend')
241
+ .description('Check @mettlecast/* frontend package versions and report missing/outdated packages')
242
+ .option('--dry-run', 'Report issues without making changes', false)
243
+ .option('--project-dir <path>', 'Path to the project to check (default: current directory)')
244
+ .action(async (opts) => {
245
+ await runUpgradeFrontend({ dryRun: opts.dryRun, projectDir: opts.projectDir });
246
+ });
247
+ program
248
+ .command('upgrade [version]')
249
+ .description('Upgrade scaffold to a new version — diffs S3 tarballs, raises a GitHub PR with exact file changes')
250
+ .option('--dry-run', 'Preview changes without writing files or creating PR', false)
251
+ .option('--project-dir <path>', 'Path to the project to upgrade')
252
+ .option('--github-token <token>', 'GitHub token for PR creation (overrides GITHUB_TOKEN env var)')
253
+ .option('--frontend-components', 'Scope upgrade to scaffold-tracked frontend files only (opens a PR with Chromatic visual diff)', false)
254
+ .action(async (version, opts) => {
255
+ await runUpgrade(version, { dryRun: opts.dryRun, projectDir: opts.projectDir ?? process.cwd(), githubToken: opts.githubToken, frontendComponents: opts.frontendComponents });
256
+ });
257
+ // ── New W4 commands ───────────────────────────────────────────────
258
+ program
259
+ .command('add-migration <domain> <name>')
260
+ .description('Add a new database migration to an existing domain')
261
+ .requiredOption('--pattern <pattern>', `Migration pattern: ${MIGRATION_PATTERNS.join(' | ')}`)
262
+ .action(async (domain, name, opts) => {
263
+ await runAddMigration({
264
+ domain,
265
+ name,
266
+ pattern: opts.pattern,
267
+ });
268
+ });
269
+ program
270
+ .command('add-test <domain> <primitive-id>')
271
+ .description('Scaffold a vitest fixture for an existing domain primitive')
272
+ .action(async (domain, primitiveId) => {
273
+ await runAddTest({ domain, primitiveId });
274
+ });
275
+ program
276
+ .command('upgrade-event <domain> <event-id>')
277
+ .description('Bump an event definition to a new version with sunset semantics')
278
+ .requiredOption('--to-version <N>', 'Target version number (integer >= 2)', parseInt)
279
+ .action(async (domain, eventId, opts) => {
280
+ await runUpgradeEvent({ domain, eventId, toVersion: opts.toVersion });
281
+ });
282
+ program
283
+ .command('show domain <domain>')
284
+ .description('Show a structured JSON summary of a domain')
285
+ .action(async (domain) => {
286
+ await runShowDomainCli({ domain });
287
+ });
288
+ program
289
+ .command('audit [domain]')
290
+ .description('Run structural audit checks, optionally filtered to one domain')
291
+ .option('--json', 'Output structured JSON', false)
292
+ .action(async (domain, opts) => {
293
+ await runAuditCli({ domain, json: opts.json });
294
+ });
295
+ program
296
+ .command('explain <rule-id>')
297
+ .description('Explain a rule ID with description, examples, and K-node reference')
298
+ .action(async (ruleId) => {
299
+ runExplainCli({ ruleId });
300
+ });
301
+ program
302
+ .command('power-tune <domain> <primitive>')
303
+ .description('Run AWS Lambda Power Tuning on a deployed Lambda. Returns recommended memorySize.')
304
+ .option('--strategy <strategy>', 'Optimization strategy: cost, speed, or balanced', 'cost')
305
+ .action(async (domain, primitive) => {
306
+ await runPowerTune(domain, primitive);
307
+ });
308
+ program
309
+ .command('reseed-page [name]')
310
+ .description('Force-reseed a seed page (or all seed pages) with the latest scaffold version. Opens a [DESTRUCTIVE] PR.')
311
+ .option('--all', 'Reseed all seed pages in bulk (single PR)', false)
312
+ .option('--project-dir <path>', 'Path to the project to reseed')
313
+ .action(async (name, opts) => {
314
+ if (!name && !opts.all) {
315
+ throw new Error('Provide a page name or use --all to reseed all seed pages');
316
+ }
317
+ await runReseedPage({ pageName: name, all: opts.all, projectDir: opts.projectDir });
318
+ });
319
+ program
320
+ .command('add-seed-page <name>')
321
+ .description('Add a seed page from the scaffold library. The page is yours immediately — TIB will never auto-update it.')
322
+ .option('--project-dir <path>', 'Path to the project')
323
+ .action(async (name, opts) => {
324
+ await runAddSeedPage({ pageName: name, projectDir: opts.projectDir });
325
+ });
326
+ program
327
+ .command('show-dns')
328
+ .description('Print DNS records (zone-file format) needed to activate custom domains')
329
+ .option('--env <env>', 'Filter to a single environment (dev|staging|prod)')
330
+ .option('--project-dir <path>', 'Path to the project root (defaults to cwd)')
331
+ .action(async (opts) => {
332
+ await runShowDns(opts);
333
+ });
334
+ program.parseAsync(process.argv).catch((err) => {
335
+ // eslint-disable-next-line no-console
336
+ console.error(err);
337
+ process.exit(1);
338
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Command to add a new API to an existing domain.
3
+ * Supports --pattern to scaffold from predefined templates.
4
+ */
5
+ /**
6
+ * Valid API pattern IDs.
7
+ */
8
+ export declare const API_PATTERNS: readonly ["simple-crud", "paginated-list", "create-with-event", "idempotent-mutation", "webhook-receiver-style", "system-admin"];
9
+ export type ApiPattern = (typeof API_PATTERNS)[number];
10
+ /**
11
+ * Options for the add-api command.
12
+ */
13
+ export interface AddApiOptions {
14
+ /** Domain ID in kebab-case. */
15
+ domain: string;
16
+ /** API ID in kebab-case. */
17
+ id: string;
18
+ /** HTTP method (defaults to GET). */
19
+ method?: string;
20
+ /** Tenancy mode: 'required', 'none', or 'system'. */
21
+ tenancy: 'required' | 'none' | 'system';
22
+ /** Pattern template ID (optional — when provided, uses pattern instead of skeleton). */
23
+ pattern?: ApiPattern;
24
+ /** Override directory for domain root (used in tests). */
25
+ domainsDir?: string;
26
+ }
27
+ /**
28
+ * Execute the add-api command: create a new API handler in an existing domain.
29
+ * @param opts - AddApiOptions specifying domain, API ID, and tenancy.
30
+ * @throws If domain does not exist, API ID is invalid, or file already exists.
31
+ */
32
+ export declare function runAddApi(opts: AddApiOptions): Promise<void>;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Command to add a new API to an existing domain.
3
+ * Supports --pattern to scaffold from predefined templates.
4
+ */
5
+ import { writeFile, access, mkdir } from 'node:fs/promises';
6
+ import { join, resolve } from 'node:path';
7
+ import { apiSkeletonTemplate, apiFixtureSkeleton } from '../templates/api-skeleton.js';
8
+ import { simpleCrudTemplate, simpleCrudListTemplate } from '../templates/patterns/api/simple-crud.js';
9
+ import { paginatedListTemplate } from '../templates/patterns/api/paginated-list.js';
10
+ import { createWithEventTemplate } from '../templates/patterns/api/create-with-event.js';
11
+ import { idempotentMutationTemplate } from '../templates/patterns/api/idempotent-mutation.js';
12
+ import { webhookReceiverStyleTemplate } from '../templates/patterns/api/webhook-receiver-style.js';
13
+ import { systemAdminTemplate } from '../templates/patterns/api/system-admin.js';
14
+ import { cliLogger } from '../utils/logger.js';
15
+ /**
16
+ * Valid API pattern IDs.
17
+ */
18
+ export const API_PATTERNS = [
19
+ 'simple-crud',
20
+ 'paginated-list',
21
+ 'create-with-event',
22
+ 'idempotent-mutation',
23
+ 'webhook-receiver-style',
24
+ 'system-admin',
25
+ ];
26
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
27
+ /**
28
+ * Execute the add-api command: create a new API handler in an existing domain.
29
+ * @param opts - AddApiOptions specifying domain, API ID, and tenancy.
30
+ * @throws If domain does not exist, API ID is invalid, or file already exists.
31
+ */
32
+ export async function runAddApi(opts) {
33
+ if (!KEBAB_REGEX.test(opts.id)) {
34
+ throw new Error(`Invalid API id "${opts.id}" — must be kebab-case`);
35
+ }
36
+ if (!KEBAB_REGEX.test(opts.domain)) {
37
+ throw new Error(`Invalid domain id "${opts.domain}" — must be kebab-case`);
38
+ }
39
+ const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
40
+ const domainDir = join(root, opts.domain);
41
+ const domainConfigPath = join(domainDir, 'domain.config.ts');
42
+ // Verify domain exists
43
+ try {
44
+ await access(domainConfigPath);
45
+ }
46
+ catch {
47
+ throw new Error(`Domain "${opts.domain}" not found at ${domainDir}`);
48
+ }
49
+ const apiFilePath = join(domainDir, 'api', `${opts.id}.ts`);
50
+ // Refuse if API already exists
51
+ try {
52
+ await access(apiFilePath);
53
+ throw new Error(`API "${opts.id}" already exists at ${apiFilePath}`);
54
+ }
55
+ catch (err) {
56
+ if (err.code !== 'ENOENT') {
57
+ throw err;
58
+ }
59
+ }
60
+ // Resolve template content
61
+ let apiContent;
62
+ if (opts.pattern) {
63
+ apiContent = resolveApiPattern(opts.pattern, opts.domain, opts.id, opts.tenancy);
64
+ cliLogger.info({ pattern: opts.pattern }, 'Using pattern template');
65
+ }
66
+ else {
67
+ apiContent = apiSkeletonTemplate(opts.domain, opts.id, opts.tenancy);
68
+ }
69
+ // Write API file
70
+ await writeFile(apiFilePath, apiContent);
71
+ // Create fixture
72
+ const apiTestDir = join(domainDir, 'api', '__tests__');
73
+ await mkdir(apiTestDir, { recursive: true });
74
+ await writeFile(join(apiTestDir, `${opts.id}.fixture.json`), apiFixtureSkeleton(opts.domain, opts.id));
75
+ cliLogger.info({ domain: opts.domain, api: opts.id, method: opts.method ?? 'GET', pattern: opts.pattern ?? 'skeleton' }, 'API added');
76
+ // eslint-disable-next-line no-console
77
+ console.log(`\n✓ API "${opts.id}" added to domain "${opts.domain}" at ${apiFilePath}`);
78
+ }
79
+ /**
80
+ * Resolve a pattern template to its generated TypeScript source.
81
+ * @param pattern - The pattern ID.
82
+ * @param domain - Domain ID.
83
+ * @param id - API ID.
84
+ * @param tenancy - Tenancy mode.
85
+ * @returns TypeScript source code as a string.
86
+ */
87
+ function resolveApiPattern(pattern, domain, id, tenancy) {
88
+ switch (pattern) {
89
+ case 'simple-crud':
90
+ return simpleCrudTemplate(domain, id, tenancy) +
91
+ '\n' +
92
+ simpleCrudListTemplate(domain, id, tenancy);
93
+ case 'paginated-list':
94
+ return paginatedListTemplate(domain, id, tenancy);
95
+ case 'create-with-event':
96
+ return createWithEventTemplate(domain, id, tenancy);
97
+ case 'idempotent-mutation':
98
+ return idempotentMutationTemplate(domain, id, tenancy);
99
+ case 'webhook-receiver-style':
100
+ return webhookReceiverStyleTemplate(domain, id, tenancy);
101
+ case 'system-admin':
102
+ return systemAdminTemplate(domain, id, tenancy);
103
+ default:
104
+ throw new Error(`Unknown API pattern "${pattern}". Available: ${API_PATTERNS.join(', ')}`);
105
+ }
106
+ }
@@ -0,0 +1,15 @@
1
+ /** Supported component tiers. */
2
+ export type ComponentTier = 'atom' | 'molecule' | 'organism';
3
+ export declare const COMPONENT_TIERS: readonly ComponentTier[];
4
+ export interface AddComponentOptions {
5
+ /** Component name in kebab-case (e.g. "action-button"). */
6
+ name: string;
7
+ /** Component tier / pattern. */
8
+ tier: ComponentTier;
9
+ /** Override the output base directory (default: src/components/). */
10
+ outputDir?: string;
11
+ }
12
+ /**
13
+ * Scaffold a design-system component with story + test files.
14
+ */
15
+ export declare function runAddComponent(opts: AddComponentOptions): Promise<void>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Command: tib add-component <name> --pattern <tier>
3
+ *
4
+ * Scaffolds a design-system component with Storybook story and a11y check.
5
+ *
6
+ * Tiers:
7
+ * - atom: Single design token usage (Button, Input, Icon).
8
+ * - molecule: Composition of 2+ atoms (SearchBar, FormField, DataRow).
9
+ * - organism: Complex section (DataTable, WizardFlow, DashboardWidget).
10
+ */
11
+ import { writeFile, mkdir, access } from 'node:fs/promises';
12
+ import { join, resolve } from 'node:path';
13
+ import { cliLogger } from '../utils/logger.js';
14
+ import { atomTemplate, moleculeTemplate, organismTemplate, } from '../templates/patterns/component/index.js';
15
+ export const COMPONENT_TIERS = [
16
+ 'atom',
17
+ 'molecule',
18
+ 'organism',
19
+ ];
20
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
21
+ const TIER_TEMPLATES = {
22
+ atom: atomTemplate,
23
+ molecule: moleculeTemplate,
24
+ organism: organismTemplate,
25
+ };
26
+ /**
27
+ * Scaffold a design-system component with story + test files.
28
+ */
29
+ export async function runAddComponent(opts) {
30
+ const { name, tier, outputDir } = opts;
31
+ if (!KEBAB_REGEX.test(name)) {
32
+ throw new Error(`Invalid component name "${name}" — must be kebab-case`);
33
+ }
34
+ if (!COMPONENT_TIERS.includes(tier)) {
35
+ const available = COMPONENT_TIERS.join(', ');
36
+ throw new Error(`Unknown component tier "${tier}".\n\nAvailable tiers: ${available}`);
37
+ }
38
+ const templateFn = TIER_TEMPLATES[tier];
39
+ const { component, story, test } = templateFn(name);
40
+ const baseDir = outputDir
41
+ ? resolve(outputDir)
42
+ : resolve(process.cwd(), 'src', 'components');
43
+ const compDir = join(baseDir, name);
44
+ const files = [
45
+ { name: `${name}.tsx`, content: component },
46
+ { name: `${name}.stories.tsx`, content: story },
47
+ { name: `${name}.test.tsx`, content: test },
48
+ ];
49
+ // Check for existing component directory first
50
+ try {
51
+ await access(compDir);
52
+ throw new Error(`Component directory already exists at ${compDir}. Remove it first or use a different --output directory.`);
53
+ }
54
+ catch (err) {
55
+ if (err.code !== 'ENOENT')
56
+ throw err;
57
+ }
58
+ await mkdir(compDir, { recursive: true });
59
+ for (const file of files) {
60
+ const outPath = join(compDir, file.name);
61
+ await writeFile(outPath, file.content, 'utf8');
62
+ }
63
+ cliLogger.info({ name, tier, compDir }, 'Component scaffolded');
64
+ // eslint-disable-next-line no-console
65
+ console.log(`\n✓ Component "${name}" (tier: ${tier}) added in ${compDir}/\n` +
66
+ ` ${name}.tsx — component implementation\n` +
67
+ ` ${name}.stories.tsx — Storybook story (2+ variants)\n` +
68
+ ` ${name}.test.tsx — Playwright smoke test + a11y check\n` +
69
+ `\n` +
70
+ ` Import: import { ${capitalize(name)} } from './components/${name}/${name}';\n`);
71
+ }
72
+ /** Capitalizes the first character of a kebab-case name into PascalCase. */
73
+ function capitalize(s) {
74
+ return s
75
+ .split('-')
76
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
77
+ .join('');
78
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Command to scaffold a new domain skeleton.
3
+ */
4
+ /**
5
+ * Options for the add-domain command.
6
+ */
7
+ export interface AddDomainOptions {
8
+ /** Domain ID in kebab-case. */
9
+ id: string;
10
+ /** Tenancy mode: 'required', 'none', or 'system'. */
11
+ tenancy: 'required' | 'none' | 'system';
12
+ /** Override directory for domain root (used in tests). */
13
+ domainsDir?: string;
14
+ }
15
+ /**
16
+ * Execute the add-domain command: scaffold a new domain under domains/<id>
17
+ * and register it in .mc/scaffold-config.json.
18
+ * @param opts - AddDomainOptions specifying the domain ID and tenancy mode.
19
+ * @throws If domain ID is invalid, domain already exists, or file operations fail.
20
+ */
21
+ export declare function runAddDomain(opts: AddDomainOptions): Promise<void>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Command to scaffold a new domain skeleton.
3
+ */
4
+ import { mkdir, writeFile, access, readFile } from 'node:fs/promises';
5
+ import { join, resolve } from 'node:path';
6
+ import { domainConfigTemplate } from '../templates/domain-config.js';
7
+ import { apiSkeletonTemplate, apiFixtureSkeleton } from '../templates/api-skeleton.js';
8
+ import { eventsSkeletonTemplate } from '../templates/events-skeleton.js';
9
+ import { claudeMdTemplate } from '../templates/claude-md.js';
10
+ import { readmeTemplate } from '../templates/readme.js';
11
+ import { addDomainToScaffoldConfig } from '../utils/scaffold-config.js';
12
+ import { cliLogger } from '../utils/logger.js';
13
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
14
+ /**
15
+ * Execute the add-domain command: scaffold a new domain under domains/<id>
16
+ * and register it in .mc/scaffold-config.json.
17
+ * @param opts - AddDomainOptions specifying the domain ID and tenancy mode.
18
+ * @throws If domain ID is invalid, domain already exists, or file operations fail.
19
+ */
20
+ export async function runAddDomain(opts) {
21
+ if (!KEBAB_REGEX.test(opts.id)) {
22
+ throw new Error(`Invalid domain id "${opts.id}" — must be kebab-case (lowercase with hyphens)`);
23
+ }
24
+ const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
25
+ const domainDir = join(root, opts.id);
26
+ // Refuse if domain already exists
27
+ try {
28
+ await access(domainDir);
29
+ throw new Error(`Domain "${opts.id}" already exists at ${domainDir}`);
30
+ }
31
+ catch (err) {
32
+ if (err.code !== 'ENOENT') {
33
+ throw err;
34
+ }
35
+ }
36
+ // Create directory structure
37
+ await mkdir(join(domainDir, 'api', '__tests__'), { recursive: true });
38
+ await mkdir(join(domainDir, 'subscribers'), { recursive: true });
39
+ await mkdir(join(domainDir, 'publishes'), { recursive: true });
40
+ // Write skeleton files
41
+ await writeFile(join(domainDir, 'domain.config.ts'), domainConfigTemplate(opts.id, opts.tenancy));
42
+ await writeFile(join(domainDir, 'api', 'example.ts'), apiSkeletonTemplate(opts.id, 'example', opts.tenancy));
43
+ await writeFile(join(domainDir, 'api', '__tests__', 'example.fixture.json'), apiFixtureSkeleton(opts.id, 'example'));
44
+ await writeFile(join(domainDir, 'publishes', 'events.ts'), eventsSkeletonTemplate(opts.id));
45
+ await writeFile(join(domainDir, 'CLAUDE.md'), claudeMdTemplate(opts.id));
46
+ await writeFile(join(domainDir, 'README.md'), readmeTemplate(opts.id));
47
+ // Generate package.json for L1 graph scanner auto-detection
48
+ let projectScope = 'project';
49
+ try {
50
+ const pkgRaw = await readFile(join(resolve(opts.domainsDir ?? process.cwd(), '..'), 'package.json'), 'utf8');
51
+ const rootPkg = JSON.parse(pkgRaw);
52
+ if (rootPkg.name) {
53
+ const match = /^@([^/]+)/.exec(rootPkg.name);
54
+ projectScope = match ? match[1] : rootPkg.name;
55
+ }
56
+ }
57
+ catch { /* fall back to 'project' */ }
58
+ await writeFile(join(domainDir, 'package.json'), JSON.stringify({
59
+ name: `@${projectScope}/domain-${opts.id}`,
60
+ version: '0.1.0',
61
+ private: true,
62
+ description: `Domain module for ${opts.id}`,
63
+ }, null, 2) + '\n');
64
+ // Register domain in scaffold config
65
+ const configRoot = opts.domainsDir ?? process.cwd();
66
+ await addDomainToScaffoldConfig(opts.id, configRoot);
67
+ cliLogger.info({ id: opts.id, dir: domainDir }, 'Domain scaffolded');
68
+ // eslint-disable-next-line no-console
69
+ console.log(`\n✓ Domain "${opts.id}" added at ${domainDir}\nNext: edit api/example.ts, ` +
70
+ `then run \`mc-domain-module build ${opts.id}\` to generate the registry.`);
71
+ }