@mettlecast/domain-cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. package/tsconfig.json +13 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Command to scaffold a new domain skeleton.
3
+ */
4
+
5
+ import { mkdir, writeFile, access, readFile } from 'node:fs/promises';
6
+ import { join, resolve } from 'node:path';
7
+ import { domainConfigTemplate } from '../templates/domain-config.js';
8
+ import { apiSkeletonTemplate, apiFixtureSkeleton } from '../templates/api-skeleton.js';
9
+ import { eventsSkeletonTemplate } from '../templates/events-skeleton.js';
10
+ import { claudeMdTemplate } from '../templates/claude-md.js';
11
+ import { readmeTemplate } from '../templates/readme.js';
12
+ import { addDomainToScaffoldConfig } from '../utils/scaffold-config.js';
13
+ import { cliLogger } from '../utils/logger.js';
14
+
15
+ /**
16
+ * Options for the add-domain command.
17
+ */
18
+ export interface AddDomainOptions {
19
+ /** Domain ID in kebab-case. */
20
+ id: string;
21
+ /** Tenancy mode: 'required', 'none', or 'system'. */
22
+ tenancy: 'required' | 'none' | 'system';
23
+ /** Override directory for domain root (used in tests). */
24
+ domainsDir?: string;
25
+ }
26
+
27
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
28
+
29
+ /**
30
+ * Execute the add-domain command: scaffold a new domain under domains/<id>
31
+ * and register it in .mc/scaffold-config.json.
32
+ * @param opts - AddDomainOptions specifying the domain ID and tenancy mode.
33
+ * @throws If domain ID is invalid, domain already exists, or file operations fail.
34
+ */
35
+ export async function runAddDomain(opts: AddDomainOptions): Promise<void> {
36
+ if (!KEBAB_REGEX.test(opts.id)) {
37
+ throw new Error(`Invalid domain id "${opts.id}" — must be kebab-case (lowercase with hyphens)`);
38
+ }
39
+
40
+ const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
41
+ const domainDir = join(root, opts.id);
42
+
43
+ // Refuse if domain already exists
44
+ try {
45
+ await access(domainDir);
46
+ throw new Error(`Domain "${opts.id}" already exists at ${domainDir}`);
47
+ } catch (err) {
48
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
49
+ throw err;
50
+ }
51
+ }
52
+
53
+ // Create directory structure
54
+ await mkdir(join(domainDir, 'api', '__tests__'), { recursive: true });
55
+ await mkdir(join(domainDir, 'subscribers'), { recursive: true });
56
+ await mkdir(join(domainDir, 'publishes'), { recursive: true });
57
+
58
+ // Write skeleton files
59
+ await writeFile(join(domainDir, 'domain.config.ts'), domainConfigTemplate(opts.id, opts.tenancy));
60
+ await writeFile(
61
+ join(domainDir, 'api', 'example.ts'),
62
+ apiSkeletonTemplate(opts.id, 'example', opts.tenancy)
63
+ );
64
+ await writeFile(
65
+ join(domainDir, 'api', '__tests__', 'example.fixture.json'),
66
+ apiFixtureSkeleton(opts.id, 'example')
67
+ );
68
+ await writeFile(join(domainDir, 'publishes', 'events.ts'), eventsSkeletonTemplate(opts.id));
69
+ await writeFile(join(domainDir, 'CLAUDE.md'), claudeMdTemplate(opts.id));
70
+ await writeFile(join(domainDir, 'README.md'), readmeTemplate(opts.id));
71
+
72
+ // Generate package.json for L1 graph scanner auto-detection
73
+ let projectScope = 'project';
74
+ try {
75
+ const pkgRaw = await readFile(join(resolve(opts.domainsDir ?? process.cwd(), '..'), 'package.json'), 'utf8');
76
+ const rootPkg = JSON.parse(pkgRaw) as { name?: string };
77
+ if (rootPkg.name) {
78
+ const match = /^@([^/]+)/.exec(rootPkg.name);
79
+ projectScope = match ? match[1] : rootPkg.name;
80
+ }
81
+ } catch { /* fall back to 'project' */ }
82
+
83
+ await writeFile(join(domainDir, 'package.json'), JSON.stringify({
84
+ name: `@${projectScope}/domain-${opts.id}`,
85
+ version: '0.1.0',
86
+ private: true,
87
+ description: `Domain module for ${opts.id}`,
88
+ }, null, 2) + '\n');
89
+
90
+ // Register domain in scaffold config
91
+ const configRoot = opts.domainsDir ?? process.cwd();
92
+ await addDomainToScaffoldConfig(opts.id, configRoot);
93
+
94
+ cliLogger.info({ id: opts.id, dir: domainDir }, 'Domain scaffolded');
95
+ // eslint-disable-next-line no-console
96
+ console.log(
97
+ `\n✓ Domain "${opts.id}" added at ${domainDir}\nNext: edit api/example.ts, ` +
98
+ `then run \`mc-domain-module build ${opts.id}\` to generate the registry.`
99
+ );
100
+ }
@@ -0,0 +1,136 @@
1
+ import { mkdir, writeFile, access } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { fanOutReactionTemplate } from '../templates/patterns/flow/fan-out-reaction.js';
4
+ import { multiStepOrchestrationTemplate } from '../templates/patterns/flow/multi-step-orchestration.js';
5
+ import { autonomousRemediationTemplate } from '../templates/patterns/flow/autonomous-remediation.js';
6
+ import { humanInLoopTemplate } from '../templates/patterns/flow/human-in-loop.js';
7
+ import { cliLogger } from '../utils/logger.js';
8
+
9
+ /**
10
+ * Valid flow pattern IDs.
11
+ */
12
+ export const FLOW_PATTERNS = [
13
+ 'fan-out-reaction',
14
+ 'multi-step-orchestration',
15
+ 'autonomous-remediation',
16
+ 'human-in-loop',
17
+ ] as const;
18
+
19
+ export type FlowPattern = (typeof FLOW_PATTERNS)[number];
20
+
21
+ export interface AddFlowOptions {
22
+ domain: string;
23
+ id: string;
24
+ type?: 'express' | 'standard';
25
+ /** Pattern template ID (optional). */
26
+ pattern?: FlowPattern;
27
+ projectRoot?: string;
28
+ }
29
+
30
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
31
+
32
+ function toCamelCase(s: string): string {
33
+ return s.replace(/-([a-z])/g, (_, c: string) => (c as string).toUpperCase());
34
+ }
35
+
36
+ function flowSkeletonTemplate(domain: string, id: string, type: 'express' | 'standard'): string {
37
+ const varName = toCamelCase(id);
38
+ return `import { defineFlow } from '@mettlecast/domain-runtime';
39
+
40
+ export const ${varName} = defineFlow({
41
+ id: '${id}',
42
+ owningDomain: '${domain}',
43
+ type: '${type}',
44
+ name: '${id}',
45
+ steps: [
46
+ {
47
+ type: 'domain-action',
48
+ name: 'ExampleAction',
49
+ domainId: '${domain}',
50
+ actionId: 'example-action',
51
+ next: 'Done',
52
+ },
53
+ {
54
+ type: 'flow-control',
55
+ control: 'succeed',
56
+ name: 'Done',
57
+ },
58
+ ],
59
+ });
60
+ `;
61
+ }
62
+
63
+ export async function runAddFlow(opts: AddFlowOptions): Promise<void> {
64
+ if (!KEBAB_REGEX.test(opts.domain)) {
65
+ throw new Error(`Invalid domain id "${opts.domain}" — must be kebab-case`);
66
+ }
67
+ if (!KEBAB_REGEX.test(opts.id)) {
68
+ throw new Error(`Invalid flow id "${opts.id}" — must be kebab-case (lowercase with hyphens)`);
69
+ }
70
+
71
+ const root = opts.projectRoot ?? resolve(process.cwd());
72
+ const flowType = opts.type ?? 'express';
73
+
74
+ // Verify domain exists
75
+ const domainDir = join(root, 'domains', opts.domain);
76
+ try {
77
+ await access(domainDir);
78
+ } catch {
79
+ throw new Error(`Domain "${opts.domain}" does not exist at ${domainDir}`);
80
+ }
81
+
82
+ const flowsDir = join(domainDir, 'flows');
83
+ const flowFile = join(flowsDir, `${opts.id}.ts`);
84
+
85
+ // Refuse if flow already exists
86
+ try {
87
+ await access(flowFile);
88
+ throw new Error(`Flow "${opts.id}" already exists at ${flowFile}`);
89
+ } catch (err) {
90
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
91
+ throw err;
92
+ }
93
+ }
94
+
95
+ await mkdir(flowsDir, { recursive: true });
96
+
97
+ // Resolve template content
98
+ let flowContent: string;
99
+ if (opts.pattern) {
100
+ flowContent = resolveFlowPattern(opts.pattern, opts.domain, opts.id);
101
+ cliLogger.info({ pattern: opts.pattern }, 'Using pattern template');
102
+ } else {
103
+ flowContent = flowSkeletonTemplate(opts.domain, opts.id, flowType);
104
+ }
105
+
106
+ await writeFile(flowFile, flowContent);
107
+
108
+ cliLogger.info({ domain: opts.domain, id: opts.id, file: flowFile, pattern: opts.pattern ?? 'skeleton' }, 'Flow scaffolded');
109
+ // eslint-disable-next-line no-console
110
+ console.log(
111
+ `\n✓ Flow "${opts.id}" added at ${flowFile}\nNext: edit the flow definition, ` +
112
+ `then run \`mc-domain-module build-flows\` to generate the registry.`
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Resolve a flow pattern template to its generated TypeScript source.
118
+ */
119
+ function resolveFlowPattern(
120
+ pattern: FlowPattern,
121
+ domain: string,
122
+ id: string,
123
+ ): string {
124
+ switch (pattern) {
125
+ case 'fan-out-reaction':
126
+ return fanOutReactionTemplate(domain, id);
127
+ case 'multi-step-orchestration':
128
+ return multiStepOrchestrationTemplate(domain, id);
129
+ case 'autonomous-remediation':
130
+ return autonomousRemediationTemplate(domain, id);
131
+ case 'human-in-loop':
132
+ return humanInLoopTemplate(domain, id);
133
+ default:
134
+ throw new Error(`Unknown flow pattern "${pattern}". Available: ${FLOW_PATTERNS.join(', ')}`);
135
+ }
136
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Command: tib add-hook <name> --pattern <pattern>
3
+ *
4
+ * Scaffolds a typed data-fetching hook using TanStack Query and @mettlecast/sdk.
5
+ */
6
+ import { writeFile, mkdir, access } from 'node:fs/promises';
7
+ import { join, resolve } from 'node:path';
8
+ import { cliLogger } from '../utils/logger.js';
9
+ import {
10
+ paginatedListHookTemplate,
11
+ mutationHookTemplate,
12
+ featureFlagHookTemplate,
13
+ subscriptionHookTemplate,
14
+ } from '../templates/patterns/hook/index.js';
15
+
16
+ /** Supported hook patterns. */
17
+ export type HookPattern = 'paginated-list' | 'mutation' | 'feature-flag' | 'subscription';
18
+
19
+ export const HOOK_PATTERNS: readonly HookPattern[] = [
20
+ 'paginated-list',
21
+ 'mutation',
22
+ 'feature-flag',
23
+ 'subscription',
24
+ ] as const;
25
+
26
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
27
+
28
+ const PATTERN_TEMPLATES: Record<HookPattern, (name: string) => string> = {
29
+ 'paginated-list': paginatedListHookTemplate,
30
+ mutation: mutationHookTemplate,
31
+ 'feature-flag': featureFlagHookTemplate,
32
+ subscription: subscriptionHookTemplate,
33
+ };
34
+
35
+ export interface AddHookOptions {
36
+ /** Hook name in kebab-case (e.g. "search-items"). */
37
+ name: string;
38
+ /** Hook pattern to scaffold. */
39
+ pattern: HookPattern;
40
+ /** Override the output directory (default: src/hooks/). */
41
+ outputDir?: string;
42
+ }
43
+
44
+ /**
45
+ * Scaffold a typed data-fetching hook file.
46
+ */
47
+ export async function runAddHook(opts: AddHookOptions): Promise<void> {
48
+ const { name, pattern, outputDir } = opts;
49
+
50
+ if (!KEBAB_REGEX.test(name)) {
51
+ throw new Error(`Invalid hook name "${name}" — must be kebab-case`);
52
+ }
53
+
54
+ if (!HOOK_PATTERNS.includes(pattern)) {
55
+ const available = HOOK_PATTERNS.join(', ');
56
+ throw new Error(
57
+ `Unknown hook pattern "${pattern}".\n\nAvailable patterns: ${available}`
58
+ );
59
+ }
60
+
61
+ const templateFn = PATTERN_TEMPLATES[pattern];
62
+ const content = templateFn(name);
63
+
64
+ const outDir = outputDir
65
+ ? resolve(outputDir)
66
+ : resolve(process.cwd(), 'src', 'hooks');
67
+
68
+ await mkdir(outDir, { recursive: true });
69
+
70
+ const fileName = `use-${name}.ts`;
71
+ const outPath = join(outDir, fileName);
72
+
73
+ // Refuse if target already exists
74
+ try {
75
+ await access(outPath);
76
+ throw new Error(
77
+ `Hook file already exists at ${outPath}. Remove it first or use a different --output directory.`
78
+ );
79
+ } catch (err) {
80
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
81
+ }
82
+
83
+ await writeFile(outPath, content, 'utf8');
84
+
85
+ cliLogger.info({ name, pattern, outPath }, 'Hook scaffolded');
86
+ // eslint-disable-next-line no-console
87
+ console.log(
88
+ `\n✓ Hook "use-${name}" (pattern: ${pattern}) added at ${outPath}\n` +
89
+ ` Import: import { use${capitalize(name)} } from './hooks/use-${name}';\n` +
90
+ ` Pattern: ${pattern}\n`
91
+ );
92
+ }
93
+
94
+ /** Capitalizes the first character of a kebab-case name into PascalCase. */
95
+ function capitalize(s: string): string {
96
+ return s
97
+ .split('-')
98
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
99
+ .join('');
100
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Command to add a new database migration to an existing domain.
3
+ * Supports --pattern for predefined migration templates.
4
+ */
5
+
6
+ import { writeFile, access, mkdir } from 'node:fs/promises';
7
+ import { join, resolve } from 'node:path';
8
+ import { tenantScopedTableTemplate } from '../templates/patterns/migration/tenant-scoped-table.js';
9
+ import { rlsPolicyTemplate } from '../templates/patterns/migration/rls-policy.js';
10
+ import { columnAddTemplate } from '../templates/patterns/migration/column-add.js';
11
+ import { indexTemplate } from '../templates/patterns/migration/index.js';
12
+ import { eventStoreTemplate } from '../templates/patterns/migration/event-store.js';
13
+ import { cliLogger } from '../utils/logger.js';
14
+
15
+ /**
16
+ * Valid migration pattern IDs.
17
+ */
18
+ export const MIGRATION_PATTERNS = [
19
+ 'tenant-scoped-table',
20
+ 'rls-policy',
21
+ 'column-add',
22
+ 'index',
23
+ 'event-store',
24
+ ] as const;
25
+
26
+ export type MigrationPattern = (typeof MIGRATION_PATTERNS)[number];
27
+
28
+ /**
29
+ * Options for the add-migration command.
30
+ */
31
+ export interface AddMigrationOptions {
32
+ /** Domain ID in kebab-case. */
33
+ domain: string;
34
+ /** Migration name in kebab-case. */
35
+ name: string;
36
+ /** Pattern template ID (required). */
37
+ pattern: MigrationPattern;
38
+ /** Override directory for domain root (used in tests). */
39
+ domainsDir?: string;
40
+ }
41
+
42
+ const KEBAB_REGEX = /^[a-z][a-z0-9-]*$/;
43
+
44
+ /**
45
+ * Generates a timestamp prefix matching the project convention.
46
+ */
47
+ function generateTimestamp(): string {
48
+ return new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
49
+ }
50
+
51
+ /**
52
+ * Execute the add-migration command: create a new SQL migration in an existing domain.
53
+ */
54
+ export async function runAddMigration(opts: AddMigrationOptions): Promise<void> {
55
+ if (!KEBAB_REGEX.test(opts.domain)) {
56
+ throw new Error(`Invalid domain id "${opts.domain}" — must be kebab-case`);
57
+ }
58
+ if (!KEBAB_REGEX.test(opts.name)) {
59
+ throw new Error(`Invalid migration name "${opts.name}" — must be kebab-case`);
60
+ }
61
+ if (!MIGRATION_PATTERNS.includes(opts.pattern)) {
62
+ throw new Error(
63
+ `Unknown migration pattern "${opts.pattern}". Available: ${MIGRATION_PATTERNS.join(', ')}`,
64
+ );
65
+ }
66
+
67
+ const root = opts.domainsDir ?? resolve(process.cwd(), 'domains');
68
+ const domainDir = join(root, opts.domain);
69
+ const domainConfigPath = join(domainDir, 'domain.config.ts');
70
+
71
+ // Verify domain exists
72
+ try {
73
+ await access(domainConfigPath);
74
+ } catch {
75
+ throw new Error(`Domain "${opts.domain}" not found at ${domainDir}`);
76
+ }
77
+
78
+ const migrationsDir = join(domainDir, 'db', 'migrations');
79
+ await mkdir(migrationsDir, { recursive: true });
80
+
81
+ const timestamp = generateTimestamp();
82
+ const fileName = `${timestamp}_${opts.name}.sql`;
83
+ const filePath = join(migrationsDir, fileName);
84
+
85
+ // Refuse if migration already exists
86
+ try {
87
+ await access(filePath);
88
+ throw new Error(`Migration "${fileName}" already exists at ${filePath}`);
89
+ } catch (err) {
90
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
91
+ throw err;
92
+ }
93
+ }
94
+
95
+ // Resolve pattern template
96
+ const sqlContent = resolveMigrationPattern(opts.pattern, opts.domain, opts.name);
97
+
98
+ await writeFile(filePath, sqlContent);
99
+
100
+ cliLogger.info({ domain: opts.domain, migration: opts.name, pattern: opts.pattern, file: filePath }, 'Migration added');
101
+ // eslint-disable-next-line no-console
102
+ console.log(
103
+ `\n✓ Migration "${opts.name}" added to domain "${opts.domain}" at ${filePath}`,
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Resolve a migration pattern template to its generated SQL source.
109
+ */
110
+ function resolveMigrationPattern(
111
+ pattern: MigrationPattern,
112
+ domain: string,
113
+ name: string,
114
+ ): string {
115
+ switch (pattern) {
116
+ case 'tenant-scoped-table':
117
+ return tenantScopedTableTemplate(domain, name);
118
+ case 'rls-policy':
119
+ return rlsPolicyTemplate(domain, name);
120
+ case 'column-add':
121
+ return columnAddTemplate(domain, name);
122
+ case 'index':
123
+ return indexTemplate(domain, name);
124
+ case 'event-store':
125
+ return eventStoreTemplate(domain, name);
126
+ default:
127
+ throw new Error(`Unknown migration pattern "${pattern}". Available: ${MIGRATION_PATTERNS.join(', ')}`);
128
+ }
129
+ }