@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,174 @@
1
+ /**
2
+ * Command to build flows into a registry JSON file.
3
+ */
4
+ import { writeFile, mkdir, readdir, readFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { z } from 'zod';
7
+ import { loadModuleExports } from '../builder/load-module.js';
8
+ import { cliLogger } from '../utils/logger.js';
9
+ /**
10
+ * Zod schema for validating FlowConfig.
11
+ * Mirrors the schema from domain-runtime's defineFlow.
12
+ */
13
+ const DomainActionStepSchema = z.object({
14
+ type: z.literal('domain-action'),
15
+ name: z.string().min(1),
16
+ domainId: z.string().regex(/^[a-z][a-z0-9-]*$/),
17
+ actionId: z.string().min(1),
18
+ parameters: z.record(z.string(), z.unknown()).optional(),
19
+ waitForTaskToken: z.boolean().optional(),
20
+ next: z.string().optional(),
21
+ });
22
+ const DomainApiStepSchema = z.object({
23
+ type: z.literal('domain-api'),
24
+ name: z.string().min(1),
25
+ domainId: z.string().regex(/^[a-z][a-z0-9-]*$/),
26
+ apiId: z.string().min(1),
27
+ parameters: z.record(z.string(), z.unknown()).optional(),
28
+ next: z.string().optional(),
29
+ });
30
+ const DomainEventStepSchema = z.object({
31
+ type: z.literal('domain-event'),
32
+ name: z.string().min(1),
33
+ eventId: z.string().refine(val => val.includes('.'), { message: 'eventId must be namespaced (e.g. domain.event)' }),
34
+ payload: z.record(z.string(), z.unknown()),
35
+ version: z.number().int().positive(),
36
+ next: z.string().optional(),
37
+ });
38
+ const FlowControlStepSchema = z.discriminatedUnion('control', [
39
+ z.object({ type: z.literal('flow-control'), control: z.literal('choice'), name: z.string(), choices: z.array(z.object({ when: z.string(), next: z.string() })), default: z.string().optional() }),
40
+ z.object({ type: z.literal('flow-control'), control: z.literal('parallel'), name: z.string(), branches: z.array(z.array(z.unknown())), next: z.string().optional() }),
41
+ z.object({ type: z.literal('flow-control'), control: z.literal('wait'), name: z.string(), seconds: z.number().int().positive(), next: z.string().optional() }),
42
+ z.object({ type: z.literal('flow-control'), control: z.literal('succeed'), name: z.string() }),
43
+ z.object({ type: z.literal('flow-control'), control: z.literal('fail'), name: z.string(), cause: z.string().optional(), error: z.string().optional() }),
44
+ z.object({ type: z.literal('flow-control'), control: z.literal('pass'), name: z.string(), result: z.unknown().optional(), next: z.string().optional() }),
45
+ ]);
46
+ const FlowStepSchema = z.union([
47
+ DomainActionStepSchema,
48
+ DomainApiStepSchema,
49
+ DomainEventStepSchema,
50
+ FlowControlStepSchema,
51
+ ]);
52
+ const FlowConfigSchema = z.object({
53
+ id: z.string().regex(/^[a-z][a-z0-9-]*$/),
54
+ owningDomain: z.string().regex(/^[a-z][a-z0-9-]*$/).optional(),
55
+ type: z.enum(['express', 'standard']).optional(),
56
+ name: z.string().min(1),
57
+ steps: z.array(FlowStepSchema).min(1),
58
+ trigger: z.object({
59
+ type: z.literal('event'),
60
+ eventId: z.string().includes('.'),
61
+ semverRange: z.string().min(1),
62
+ }).optional(),
63
+ });
64
+ /**
65
+ * Custom error for flow ID collisions.
66
+ */
67
+ export class FlowIdCollisionError extends Error {
68
+ constructor(id) {
69
+ super(`Flow id "${id}" appears in multiple sources`);
70
+ this.name = 'FlowIdCollisionError';
71
+ }
72
+ }
73
+ async function loadFlowsFromDir(dir, owningDomainOverride) {
74
+ const entries = [];
75
+ let files;
76
+ try {
77
+ files = await readdir(dir);
78
+ }
79
+ catch (err) {
80
+ if (err.code === 'ENOENT')
81
+ return entries;
82
+ throw err;
83
+ }
84
+ for (const file of files) {
85
+ if (file.endsWith('.ts')) {
86
+ const filePath = join(dir, file);
87
+ const exports = await loadModuleExports(filePath);
88
+ for (const exp of exports) {
89
+ if (exp._kind !== 'flow')
90
+ continue;
91
+ const config = FlowConfigSchema.parse(exp);
92
+ entries.push({
93
+ id: config.id,
94
+ owningDomain: owningDomainOverride ?? config.owningDomain,
95
+ type: config.type,
96
+ name: config.name,
97
+ steps: config.steps,
98
+ trigger: config.trigger,
99
+ });
100
+ }
101
+ }
102
+ else if (file.endsWith('.json')) {
103
+ const filePath = join(dir, file);
104
+ const raw = await readFile(filePath, 'utf8');
105
+ const config = FlowConfigSchema.parse(JSON.parse(raw));
106
+ entries.push({
107
+ id: config.id,
108
+ owningDomain: owningDomainOverride ?? config.owningDomain,
109
+ type: config.type,
110
+ name: config.name,
111
+ steps: config.steps,
112
+ trigger: config.trigger,
113
+ });
114
+ }
115
+ }
116
+ return entries;
117
+ }
118
+ /**
119
+ * Execute the build-flows command: scan flows/ and domains/x/flows/ directories for .ts and .json files,
120
+ * validate and serialize them, and write .mc/flows-registry.json.
121
+ * @param options - BuildFlowsOptions specifying project root and optional output path.
122
+ * @returns The absolute path to the written registry file.
123
+ * @throws If flow validation fails or IDs collide.
124
+ */
125
+ export async function runBuildFlows(options) {
126
+ const projectRoot = options.projectRoot ?? process.cwd();
127
+ const outFile = options.outFile ?? join(projectRoot, '.tib', 'flows-registry.json');
128
+ cliLogger.info({ projectRoot }, 'Building flows registry');
129
+ const allEntries = [];
130
+ const seenIds = new Set();
131
+ // Scan root-level flows/ (deprecated)
132
+ const rootFlowsDir = join(projectRoot, 'flows');
133
+ const rootEntries = await loadFlowsFromDir(rootFlowsDir);
134
+ if (rootEntries.length > 0) {
135
+ cliLogger.warn({ dir: rootFlowsDir }, 'Root-level flows/ is deprecated. Move flows to domains/{x}/flows/ and re-run build-flows.');
136
+ for (const e of rootEntries) {
137
+ if (seenIds.has(e.id))
138
+ throw new FlowIdCollisionError(e.id);
139
+ seenIds.add(e.id);
140
+ allEntries.push(e);
141
+ }
142
+ }
143
+ // Scan domains/*/flows/ (preferred)
144
+ const domainsDir = join(projectRoot, 'domains');
145
+ let domainEntries;
146
+ try {
147
+ domainEntries = (await readdir(domainsDir, { withFileTypes: true }))
148
+ .filter(e => e.isDirectory())
149
+ .map(e => e.name);
150
+ }
151
+ catch (err) {
152
+ if (err.code === 'ENOENT') {
153
+ domainEntries = [];
154
+ }
155
+ else {
156
+ throw err;
157
+ }
158
+ }
159
+ for (const domainId of domainEntries) {
160
+ const domainFlowsDir = join(domainsDir, domainId, 'flows');
161
+ const domainFlowEntries = await loadFlowsFromDir(domainFlowsDir, domainId);
162
+ for (const e of domainFlowEntries) {
163
+ if (seenIds.has(e.id))
164
+ throw new FlowIdCollisionError(e.id);
165
+ seenIds.add(e.id);
166
+ allEntries.push(e);
167
+ }
168
+ }
169
+ const registry = { schemaVersion: '1', flows: allEntries };
170
+ await mkdir(join(outFile, '..'), { recursive: true });
171
+ await writeFile(outFile, JSON.stringify(registry, null, 2), 'utf8');
172
+ cliLogger.info({ outFile, flowCount: registry.flows.length }, 'Flows registry written');
173
+ return outFile;
174
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Options for the build command.
3
+ */
4
+ export interface BuildOptions {
5
+ /** Absolute or relative path to the domain root directory. */
6
+ domainRoot: string;
7
+ /** Output path for the registry JSON file. Defaults to .mc/domain-registry.json relative to cwd. */
8
+ outFile?: string;
9
+ }
10
+ /**
11
+ * Execute the build command: load the domain source directory, build the DomainRegistry,
12
+ * and write the serialised JSON snapshot to disk.
13
+ * @param options - BuildOptions specifying domain root and optional output path.
14
+ * @returns The absolute path to the written registry file.
15
+ * @throws If domain.config.ts is missing or the registry build fails.
16
+ */
17
+ export declare function runBuild(options: BuildOptions): Promise<string>;
@@ -0,0 +1,39 @@
1
+ import { writeFile, mkdir, readdir, readFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { buildRegistry } from '../builder/build-registry.js';
4
+ import { buildActionsTypes } from '../builder/build-types.js';
5
+ import { cliLogger } from '../utils/logger.js';
6
+ /**
7
+ * Execute the build command: load the domain source directory, build the DomainRegistry,
8
+ * and write the serialised JSON snapshot to disk.
9
+ * @param options - BuildOptions specifying domain root and optional output path.
10
+ * @returns The absolute path to the written registry file.
11
+ * @throws If domain.config.ts is missing or the registry build fails.
12
+ */
13
+ export async function runBuild(options) {
14
+ const domainRoot = resolve(options.domainRoot);
15
+ const outFile = options.outFile
16
+ ? resolve(options.outFile)
17
+ : join(process.cwd(), '.tib', 'domain-registry.json');
18
+ cliLogger.info({ domainRoot }, 'Building domain registry');
19
+ const { registry, warnings } = await buildRegistry(domainRoot);
20
+ for (const w of warnings) {
21
+ cliLogger.warn(w);
22
+ }
23
+ await mkdir(join(outFile, '..'), { recursive: true });
24
+ await writeFile(outFile, JSON.stringify(registry, null, 2), 'utf8');
25
+ cliLogger.info({ outFile, apis: registry.apis.length, events: registry.events.length }, 'Registry written');
26
+ // Discover all sibling registries and emit aggregated types
27
+ const tibDir = join(process.cwd(), '.tib');
28
+ const registryFiles = (await readdir(tibDir)).filter(f => f.endsWith('-registry.json'));
29
+ const allRegistries = [];
30
+ for (const f of registryFiles) {
31
+ const raw = await readFile(join(tibDir, f), 'utf8');
32
+ allRegistries.push(JSON.parse(raw));
33
+ }
34
+ const typesContent = buildActionsTypes(allRegistries);
35
+ const typesFile = join(tibDir, 'actions-types.d.ts');
36
+ await writeFile(typesFile, typesContent);
37
+ cliLogger.info({ outFile: typesFile }, 'Types file written');
38
+ return outFile;
39
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Options for the check-hashes command.
3
+ */
4
+ export interface CheckHashesOptions {
5
+ /** Root directory of the project (defaults to cwd). */
6
+ projectRoot?: string;
7
+ }
8
+ /**
9
+ * A single hash check result.
10
+ */
11
+ export interface DriftedFile {
12
+ /** File path relative to project root. */
13
+ path: string;
14
+ /** Expected SHA256 hash from manifest. */
15
+ expectedHash: string;
16
+ /** Actual SHA256 hash on disk. */
17
+ actualHash: string;
18
+ }
19
+ /**
20
+ * Result of the check-hashes command.
21
+ */
22
+ export interface CheckHashesResult {
23
+ /** Whether all hashes match. */
24
+ ok: boolean;
25
+ /** Files that have been modified since scaffold. */
26
+ drifted: DriftedFile[];
27
+ /** Files that existed at scaffold time but are now missing. */
28
+ missing: string[];
29
+ /** Files on disk that were not in the scaffold manifest. */
30
+ unexpected: string[];
31
+ }
32
+ /**
33
+ * Verify that infra/modules/ has not been hand-edited since last scaffold.
34
+ * Reads .mc/modules-hashes.json manifest and compares each file's SHA256.
35
+ * @param opts Options including project root directory.
36
+ * @returns CheckHashesResult with ok, drifted, missing, and unexpected arrays.
37
+ */
38
+ export declare function runCheckHashes(opts?: CheckHashesOptions): Promise<CheckHashesResult>;
@@ -0,0 +1,97 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { cliLogger } from '../utils/logger.js';
5
+ /**
6
+ * Verify that infra/modules/ has not been hand-edited since last scaffold.
7
+ * Reads .mc/modules-hashes.json manifest and compares each file's SHA256.
8
+ * @param opts Options including project root directory.
9
+ * @returns CheckHashesResult with ok, drifted, missing, and unexpected arrays.
10
+ */
11
+ export async function runCheckHashes(opts = {}) {
12
+ const root = opts.projectRoot ?? process.cwd();
13
+ const manifestPath = join(root, '.tib', 'modules-hashes.json');
14
+ const modulesDir = join(root, 'infra', 'modules');
15
+ let manifest;
16
+ try {
17
+ manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
18
+ }
19
+ catch (err) {
20
+ throw new Error(`tib check-hashes: cannot read ${manifestPath} (${err.message}). Re-scaffold to regenerate the manifest.`);
21
+ }
22
+ const drifted = [];
23
+ const missing = [];
24
+ const unexpected = [];
25
+ // Files/directories to skip when walking infra/modules/
26
+ const SKIP_NAMES = new Set(['node_modules', 'dist', 'cdk.out', 'package-lock.json', '.npmrc']);
27
+ /** Normalize CRLF → LF so hashes match across platforms (Windows vs Linux). */
28
+ function normalizeLineEndings(buf) {
29
+ const str = buf.toString('utf8');
30
+ if (!str.includes('\r\n'))
31
+ return buf;
32
+ return Buffer.from(str.replace(/\r\n/g, '\n'), 'utf8');
33
+ }
34
+ // Walk infra/modules/ to compute current hashes
35
+ async function walk(dir) {
36
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
37
+ const files = [];
38
+ for (const entry of entries) {
39
+ if (SKIP_NAMES.has(entry.name))
40
+ continue;
41
+ const full = join(dir, entry.name);
42
+ if (entry.isDirectory())
43
+ files.push(...await walk(full));
44
+ else if (entry.isFile())
45
+ files.push(full);
46
+ }
47
+ return files;
48
+ }
49
+ const onDiskFiles = await walk(modulesDir);
50
+ const onDiskRel = onDiskFiles.map(f => relative(root, f).replace(/\\/g, '/'));
51
+ const onDiskSet = new Set(onDiskRel);
52
+ // Check each manifest entry
53
+ for (const [relPath, expectedHash] of Object.entries(manifest.files)) {
54
+ if (!onDiskSet.has(relPath)) {
55
+ missing.push(relPath);
56
+ continue;
57
+ }
58
+ const content = await readFile(join(root, relPath));
59
+ const normalized = normalizeLineEndings(content);
60
+ const actualHash = createHash('sha256').update(normalized).digest('hex');
61
+ if (actualHash !== expectedHash) {
62
+ drifted.push({ path: relPath, expectedHash, actualHash });
63
+ }
64
+ }
65
+ // Files on disk but not in manifest
66
+ for (const relPath of onDiskRel) {
67
+ if (!Object.prototype.hasOwnProperty.call(manifest.files, relPath)) {
68
+ unexpected.push(relPath);
69
+ }
70
+ }
71
+ const result = {
72
+ ok: drifted.length === 0 && missing.length === 0 && unexpected.length === 0,
73
+ drifted,
74
+ missing,
75
+ unexpected,
76
+ };
77
+ if (!result.ok) {
78
+ cliLogger.error({ drifted: drifted.length, missing: missing.length, unexpected: unexpected.length }, 'infra/modules/ has drifted from scaffold');
79
+ if (drifted.length > 0) {
80
+ cliLogger.error('Files modified since last scaffold:');
81
+ for (const d of drifted)
82
+ cliLogger.error(` - ${d.path}`);
83
+ }
84
+ if (missing.length > 0) {
85
+ cliLogger.error('Files deleted since last scaffold:');
86
+ for (const m of missing)
87
+ cliLogger.error(` - ${m}`);
88
+ }
89
+ if (unexpected.length > 0) {
90
+ cliLogger.error('Files added that were not scaffolded (manually-added):');
91
+ for (const u of unexpected)
92
+ cliLogger.error(` - ${u}`);
93
+ }
94
+ cliLogger.error('infra/modules/ is TIB-owned. Re-scaffold to update legitimately.');
95
+ }
96
+ return result;
97
+ }
@@ -0,0 +1,5 @@
1
+ export interface CreateProjectOptions {
2
+ name: string;
3
+ outDir?: string;
4
+ }
5
+ export declare function runCreateProject(opts: CreateProjectOptions): Promise<void>;
@@ -0,0 +1,272 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { createInterface } from 'node:readline';
4
+ import { Readable } from 'node:stream';
5
+ import { execSync } from 'node:child_process';
6
+ import * as tar from 'tar';
7
+ import { cliLogger } from '../utils/logger.js';
8
+ import { fetchVersionsJson, fetchModulesJson, fetchModuleTarball, } from '../utils/s3-fetch.js';
9
+ import { computeChecksumString } from '../utils/checksum.js';
10
+ import { applyTokens, buildTokenContext } from '../utils/token-substitution.js';
11
+ import { injectHeader } from '../utils/header-inject.js';
12
+ import { createManifest, writeManifest, upsertManifestFile, } from '../utils/manifest.js';
13
+ import { writeScaffoldConfig } from '../utils/scaffold-config.js';
14
+ const SCAFFOLD_BUCKET = 'mc-scaffold';
15
+ // ── Prompts ────────────────────────────────────────────────────────────────────
16
+ async function prompt(rl, question) {
17
+ return new Promise((res) => rl.question(question, res));
18
+ }
19
+ async function promptDefault(rl, question, defaultVal) {
20
+ const answer = await prompt(rl, `${question} [${defaultVal}]: `);
21
+ return answer.trim() || defaultVal;
22
+ }
23
+ async function promptYesNo(rl, question, defaultYes = true) {
24
+ const hint = defaultYes ? 'Y/n' : 'y/N';
25
+ const answer = await prompt(rl, `${question} [${hint}]: `);
26
+ const trimmed = answer.trim().toLowerCase();
27
+ if (!trimmed)
28
+ return defaultYes;
29
+ return trimmed === 'y' || trimmed === 'yes';
30
+ }
31
+ // ── Topological sort ───────────────────────────────────────────────────────────
32
+ function topoSort(modules, selected) {
33
+ const byId = new Map(modules.map((m) => [m.id, m]));
34
+ const result = [];
35
+ const visited = new Set();
36
+ function visit(id) {
37
+ if (visited.has(id))
38
+ return;
39
+ visited.add(id);
40
+ const mod = byId.get(id);
41
+ if (!mod)
42
+ return;
43
+ for (const dep of mod.dependencies) {
44
+ if (selected.has(dep))
45
+ visit(dep);
46
+ }
47
+ result.push(mod);
48
+ }
49
+ // Always core first, then backend-lambda, then rest
50
+ const priorityOrder = ['core', 'backend-lambda'];
51
+ for (const id of priorityOrder) {
52
+ if (selected.has(id))
53
+ visit(id);
54
+ }
55
+ for (const id of selected) {
56
+ visit(id);
57
+ }
58
+ return result;
59
+ }
60
+ async function parseTarball(buf) {
61
+ const entries = [];
62
+ await new Promise((resolve, reject) => {
63
+ const parser = new tar.Parser({
64
+ gzip: true,
65
+ onentry(entry) {
66
+ if (entry.type !== 'File') {
67
+ entry.resume();
68
+ return;
69
+ }
70
+ const chunks = [];
71
+ entry.on('data', (chunk) => {
72
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
73
+ });
74
+ entry.on('end', () => {
75
+ entries.push({ path: entry.path, content: Buffer.concat(chunks) });
76
+ });
77
+ entry.on('error', reject);
78
+ },
79
+ });
80
+ parser.on('finish', resolve);
81
+ parser.on('error', reject);
82
+ const readable = Readable.from(buf);
83
+ readable.pipe(parser);
84
+ });
85
+ return entries;
86
+ }
87
+ function getTarEntry(entries, targetPath) {
88
+ // Strip leading ./ or component prefix from tarball paths
89
+ return entries.find((e) => {
90
+ const normalized = e.path.replace(/^\.\//, '').replace(/^[^/]+\//, '');
91
+ return normalized === targetPath || e.path === targetPath || e.path.endsWith(`/${targetPath}`);
92
+ });
93
+ }
94
+ // ── Main ───────────────────────────────────────────────────────────────────────
95
+ export async function runCreateProject(opts) {
96
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
97
+ try {
98
+ // 1. Collect project details
99
+ console.log('\nTIB scaffold — create new project\n');
100
+ const projectName = await promptDefault(rl, 'Project name', opts.name || 'my-project');
101
+ const awsRegion = await promptDefault(rl, 'AWS region', 'eu-north-1');
102
+ const awsAccountId = await promptDefault(rl, 'AWS account ID', '');
103
+ const includeDatabase = await promptYesNo(rl, 'Include database (Aurora)?', false);
104
+ const includeAuth = await promptYesNo(rl, 'Include authentication (Cognito)?', false);
105
+ const includeFrontend = await promptYesNo(rl, 'Include frontend?', false);
106
+ const includeFlows = await promptYesNo(rl, 'Include flows?', false);
107
+ const adminEmail = includeAuth
108
+ ? await promptDefault(rl, 'Admin email', '')
109
+ : undefined;
110
+ // Optional custom domain for prod (bare domain, no scheme/path/port)
111
+ const BARE_DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/;
112
+ let prodUrl = '';
113
+ if (includeFrontend) {
114
+ const raw = await promptDefault(rl, 'Production dashboard URL (optional — skip for CloudFront-default)', '');
115
+ if (raw && !BARE_DOMAIN_RE.test(raw.trim())) {
116
+ console.warn('[tib] Invalid domain format — must be a bare domain like "app.acme.com". Skipping.');
117
+ }
118
+ else {
119
+ prodUrl = raw.trim();
120
+ }
121
+ }
122
+ const defaultOutDir = opts.outDir ? resolve(opts.outDir) : resolve(process.cwd(), projectName);
123
+ const outputDirRaw = await promptDefault(rl, 'Output directory', defaultOutDir);
124
+ const outputDir = resolve(outputDirRaw);
125
+ // 2. Fetch versions.json
126
+ console.log('\nFetching scaffold versions...');
127
+ const versionsJson = await fetchVersionsJson(SCAFFOLD_BUCKET);
128
+ const scaffoldVersion = await promptDefault(rl, 'Scaffold version', versionsJson.latest);
129
+ rl.close();
130
+ // 3. Fetch modules.json
131
+ console.log(`\nFetching modules for scaffold@${scaffoldVersion}...`);
132
+ const modulesJson = await fetchModulesJson(SCAFFOLD_BUCKET, scaffoldVersion);
133
+ // 4. Resolve module selection
134
+ const selectedIds = new Set(['core', 'backend-lambda']);
135
+ if (includeDatabase)
136
+ selectedIds.add('db-aurora');
137
+ if (includeAuth)
138
+ selectedIds.add('auth-cognito');
139
+ if (includeFrontend)
140
+ selectedIds.add('frontend');
141
+ if (includeFlows)
142
+ selectedIds.add('flows');
143
+ // Expand dependencies
144
+ const moduleMap = new Map(modulesJson.modules.map((m) => [m.id, m]));
145
+ let changed = true;
146
+ while (changed) {
147
+ changed = false;
148
+ for (const id of [...selectedIds]) {
149
+ const mod = moduleMap.get(id);
150
+ if (!mod)
151
+ continue;
152
+ for (const dep of mod.dependencies) {
153
+ if (!selectedIds.has(dep)) {
154
+ selectedIds.add(dep);
155
+ changed = true;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ const orderedModules = topoSort(modulesJson.modules, selectedIds);
161
+ // 5. Prepare output directory
162
+ await mkdir(outputDir, { recursive: true });
163
+ await mkdir(join(outputDir, '.tib'), { recursive: true });
164
+ // 6. Get CLI version from package.json
165
+ let cliVersion = '0.0.0';
166
+ try {
167
+ const { createRequire } = await import('node:module');
168
+ const require = createRequire(import.meta.url);
169
+ const pkgJson = require('../../package.json');
170
+ cliVersion = pkgJson.version;
171
+ }
172
+ catch {
173
+ // ignore — fallback to 0.0.0
174
+ }
175
+ // 7. Build token context
176
+ const tokenCtx = buildTokenContext({ projectName, awsRegion, awsAccountId, adminEmail }, scaffoldVersion, cliVersion);
177
+ // 8. Create manifest
178
+ const manifest = createManifest(scaffoldVersion, projectName, awsRegion, [...selectedIds]);
179
+ // 9. Process each module in topological order
180
+ for (const mod of orderedModules) {
181
+ console.log(` Installing module: ${mod.id}@${mod.version}...`);
182
+ const tarBuf = await fetchModuleTarball(SCAFFOLD_BUCKET, scaffoldVersion, mod.tarball, mod.sha256);
183
+ const tarEntries = await parseTarball(tarBuf);
184
+ // Extract MANIFEST.json from tarball
185
+ const manifestEntry = getTarEntry(tarEntries, 'MANIFEST.json');
186
+ if (!manifestEntry) {
187
+ cliLogger.warn({ moduleId: mod.id }, 'No MANIFEST.json in tarball — skipping');
188
+ continue;
189
+ }
190
+ const tarManifest = JSON.parse(manifestEntry.content.toString('utf-8'));
191
+ // installPrefix for modules like 'frontend'
192
+ const modMeta = moduleMap.get(mod.id);
193
+ const installPrefix = modMeta?.installPrefix ?? '';
194
+ for (const fileEntry of tarManifest) {
195
+ const tarEntry = getTarEntry(tarEntries, fileEntry.path);
196
+ if (!tarEntry) {
197
+ cliLogger.warn({ moduleId: mod.id, path: fileEntry.path }, 'File listed in MANIFEST.json not found in tarball');
198
+ continue;
199
+ }
200
+ // Determine install path
201
+ let installPath = fileEntry.installedAs ?? fileEntry.path;
202
+ // Strip .tpl extension
203
+ if (installPath.endsWith('.tpl')) {
204
+ installPath = installPath.slice(0, -4);
205
+ }
206
+ // Apply installPrefix
207
+ if (installPrefix) {
208
+ installPath = `${installPrefix}/${installPath}`;
209
+ }
210
+ // Process content
211
+ let content = tarEntry.content.toString('utf-8');
212
+ // Token substitution for template files
213
+ if (fileEntry.isTemplate) {
214
+ content = applyTokens(content, tokenCtx);
215
+ }
216
+ // Inject scaffold header
217
+ content = injectHeader(content, installPath, mod.id, mod.version);
218
+ // Write file
219
+ const destPath = join(outputDir, installPath);
220
+ const parentParts = installPath.split('/').slice(0, -1);
221
+ if (parentParts.length > 0) {
222
+ await mkdir(join(outputDir, ...parentParts), { recursive: true });
223
+ }
224
+ await writeFile(destPath, content, 'utf-8');
225
+ // Track in manifest (SHA-256 of written content)
226
+ const sha256 = computeChecksumString(content);
227
+ upsertManifestFile(manifest, {
228
+ path: installPath,
229
+ module: mod.id,
230
+ moduleVersion: mod.version,
231
+ sha256,
232
+ wasTemplate: fileEntry.isTemplate,
233
+ installedAt: new Date().toISOString(),
234
+ policy: fileEntry.policy ?? 'tracked',
235
+ });
236
+ }
237
+ }
238
+ // 10. Write .mc/scaffold-config.json
239
+ const environments = prodUrl
240
+ ? { prod: { dashboardUrl: prodUrl } }
241
+ : undefined;
242
+ const scaffoldConfig = {
243
+ scaffoldVersion,
244
+ enabledModules: [...selectedIds],
245
+ projectName,
246
+ awsRegion,
247
+ awsAccountId,
248
+ adminEmail,
249
+ domainIds: [],
250
+ flowIds: [],
251
+ scaffoldBucket: SCAFFOLD_BUCKET,
252
+ ...(environments ? { environments } : {}),
253
+ };
254
+ await writeScaffoldConfig(outputDir, scaffoldConfig);
255
+ // 11. Write .mc/manifest.json
256
+ await writeManifest(outputDir, manifest);
257
+ // 12. Git init + initial commit
258
+ console.log('\nInitialising git repository...');
259
+ execSync(`git -C "${outputDir}" init`, { stdio: 'pipe' });
260
+ execSync(`git -C "${outputDir}" add -A`, { stdio: 'pipe' });
261
+ execSync(`git -C "${outputDir}" commit -m "chore: initial scaffold from mc-scaffold@${scaffoldVersion}"`, { stdio: 'pipe' });
262
+ cliLogger.info({ projectName, outputDir, scaffoldVersion }, 'Project created');
263
+ console.log(`\nProject "${projectName}" created at ${outputDir}\n\n` +
264
+ ` Next steps:\n` +
265
+ ` cd ${outputDir}\n` +
266
+ ` npm install\n` +
267
+ ` npm run dev\n`);
268
+ }
269
+ finally {
270
+ rl.close();
271
+ }
272
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Options for the dev command.
3
+ */
4
+ export interface DevOptions {
5
+ /** Absolute or relative path to the domain root directory. */
6
+ domainRoot: string;
7
+ /** Port for the local HTTP server. Defaults to 3000. */
8
+ port?: number;
9
+ }
10
+ /**
11
+ * Execute the dev command: build the domain registry and start the local Fastify
12
+ * HTTP server. Keeps the process running. Shuts down gracefully on SIGINT/SIGTERM.
13
+ * @param options - DevOptions specifying domain root and optional port.
14
+ */
15
+ export declare function runDev(options: DevOptions): Promise<void>;