@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,367 @@
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 {
9
+ fetchVersionsJson,
10
+ fetchModulesJson,
11
+ fetchModuleTarball,
12
+ type S3ModuleEntry,
13
+ } from '../utils/s3-fetch.js';
14
+ import { computeChecksumString } from '../utils/checksum.js';
15
+ import { applyTokens, buildTokenContext } from '../utils/token-substitution.js';
16
+ import { injectHeader } from '../utils/header-inject.js';
17
+ import {
18
+ createManifest,
19
+ writeManifest,
20
+ upsertManifestFile,
21
+ type TibManifest,
22
+ } from '../utils/manifest.js';
23
+ import { writeScaffoldConfig, type ScaffoldConfig, type ModuleId, type EnvironmentsConfig } from '../utils/scaffold-config.js';
24
+
25
+ const SCAFFOLD_BUCKET = 'mc-scaffold';
26
+
27
+ export interface CreateProjectOptions {
28
+ name: string;
29
+ outDir?: string;
30
+ }
31
+
32
+ // ── Prompts ────────────────────────────────────────────────────────────────────
33
+
34
+ async function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
35
+ return new Promise((res) => rl.question(question, res));
36
+ }
37
+
38
+ async function promptDefault(
39
+ rl: ReturnType<typeof createInterface>,
40
+ question: string,
41
+ defaultVal: string
42
+ ): Promise<string> {
43
+ const answer = await prompt(rl, `${question} [${defaultVal}]: `);
44
+ return answer.trim() || defaultVal;
45
+ }
46
+
47
+ async function promptYesNo(
48
+ rl: ReturnType<typeof createInterface>,
49
+ question: string,
50
+ defaultYes = true
51
+ ): Promise<boolean> {
52
+ const hint = defaultYes ? 'Y/n' : 'y/N';
53
+ const answer = await prompt(rl, `${question} [${hint}]: `);
54
+ const trimmed = answer.trim().toLowerCase();
55
+ if (!trimmed) return defaultYes;
56
+ return trimmed === 'y' || trimmed === 'yes';
57
+ }
58
+
59
+ // ── Topological sort ───────────────────────────────────────────────────────────
60
+
61
+ function topoSort(modules: S3ModuleEntry[], selected: Set<string>): S3ModuleEntry[] {
62
+ const byId = new Map(modules.map((m) => [m.id, m]));
63
+ const result: S3ModuleEntry[] = [];
64
+ const visited = new Set<string>();
65
+
66
+ function visit(id: string): void {
67
+ if (visited.has(id)) return;
68
+ visited.add(id);
69
+ const mod = byId.get(id);
70
+ if (!mod) return;
71
+ for (const dep of mod.dependencies) {
72
+ if (selected.has(dep)) visit(dep);
73
+ }
74
+ result.push(mod);
75
+ }
76
+
77
+ // Always core first, then backend-lambda, then rest
78
+ const priorityOrder = ['core', 'backend-lambda'];
79
+ for (const id of priorityOrder) {
80
+ if (selected.has(id)) visit(id);
81
+ }
82
+ for (const id of selected) {
83
+ visit(id);
84
+ }
85
+
86
+ return result;
87
+ }
88
+
89
+ // ── Tarball parsing (tar npm package) ─────────────────────────────────────────
90
+
91
+ interface TarEntry {
92
+ path: string;
93
+ content: Buffer;
94
+ }
95
+
96
+ interface TarballManifestEntry {
97
+ path: string;
98
+ sha256: string;
99
+ isTemplate: boolean;
100
+ installedAs?: string;
101
+ policy?: 'owned' | 'tracked' | 'seed';
102
+ }
103
+
104
+ async function parseTarball(buf: Buffer): Promise<TarEntry[]> {
105
+ const entries: TarEntry[] = [];
106
+
107
+ await new Promise<void>((resolve, reject) => {
108
+ const parser = new tar.Parser({
109
+ gzip: true,
110
+ onentry(entry: tar.ReadEntry) {
111
+ if (entry.type !== 'File') {
112
+ entry.resume();
113
+ return;
114
+ }
115
+
116
+ const chunks: Buffer[] = [];
117
+ entry.on('data', (chunk: Buffer) => {
118
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as ArrayBuffer));
119
+ });
120
+ entry.on('end', () => {
121
+ entries.push({ path: entry.path, content: Buffer.concat(chunks) });
122
+ });
123
+ entry.on('error', reject);
124
+ },
125
+ });
126
+
127
+ parser.on('finish', resolve);
128
+ parser.on('error', reject);
129
+
130
+ const readable = Readable.from(buf);
131
+ readable.pipe(parser);
132
+ });
133
+
134
+ return entries;
135
+ }
136
+
137
+ function getTarEntry(entries: TarEntry[], targetPath: string): TarEntry | undefined {
138
+ // Strip leading ./ or component prefix from tarball paths
139
+ return entries.find((e) => {
140
+ const normalized = e.path.replace(/^\.\//, '').replace(/^[^/]+\//, '');
141
+ return normalized === targetPath || e.path === targetPath || e.path.endsWith(`/${targetPath}`);
142
+ });
143
+ }
144
+
145
+ // ── Main ───────────────────────────────────────────────────────────────────────
146
+
147
+ export async function runCreateProject(opts: CreateProjectOptions): Promise<void> {
148
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
149
+
150
+ try {
151
+ // 1. Collect project details
152
+ console.log('\nTIB scaffold — create new project\n');
153
+
154
+ const projectName = await promptDefault(rl, 'Project name', opts.name || 'my-project');
155
+ const awsRegion = await promptDefault(rl, 'AWS region', 'eu-north-1');
156
+ const awsAccountId = await promptDefault(rl, 'AWS account ID', '');
157
+ const includeDatabase = await promptYesNo(rl, 'Include database (Aurora)?', false);
158
+ const includeAuth = await promptYesNo(rl, 'Include authentication (Cognito)?', false);
159
+ const includeFrontend = await promptYesNo(rl, 'Include frontend?', false);
160
+ const includeFlows = await promptYesNo(rl, 'Include flows?', false);
161
+ const adminEmail = includeAuth
162
+ ? await promptDefault(rl, 'Admin email', '')
163
+ : undefined;
164
+
165
+ // Optional custom domain for prod (bare domain, no scheme/path/port)
166
+ const BARE_DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/;
167
+ let prodUrl = '';
168
+ if (includeFrontend) {
169
+ const raw = await promptDefault(rl, 'Production dashboard URL (optional — skip for CloudFront-default)', '');
170
+ if (raw && !BARE_DOMAIN_RE.test(raw.trim())) {
171
+ console.warn('[tib] Invalid domain format — must be a bare domain like "app.acme.com". Skipping.');
172
+ } else {
173
+ prodUrl = raw.trim();
174
+ }
175
+ }
176
+
177
+ const defaultOutDir = opts.outDir ? resolve(opts.outDir) : resolve(process.cwd(), projectName);
178
+ const outputDirRaw = await promptDefault(rl, 'Output directory', defaultOutDir);
179
+ const outputDir = resolve(outputDirRaw);
180
+
181
+ // 2. Fetch versions.json
182
+ console.log('\nFetching scaffold versions...');
183
+ const versionsJson = await fetchVersionsJson(SCAFFOLD_BUCKET);
184
+ const scaffoldVersion = await promptDefault(rl, 'Scaffold version', versionsJson.latest);
185
+
186
+ rl.close();
187
+
188
+ // 3. Fetch modules.json
189
+ console.log(`\nFetching modules for scaffold@${scaffoldVersion}...`);
190
+ const modulesJson = await fetchModulesJson(SCAFFOLD_BUCKET, scaffoldVersion);
191
+
192
+ // 4. Resolve module selection
193
+ const selectedIds = new Set<string>(['core', 'backend-lambda']);
194
+ if (includeDatabase) selectedIds.add('db-aurora');
195
+ if (includeAuth) selectedIds.add('auth-cognito');
196
+ if (includeFrontend) selectedIds.add('frontend');
197
+ if (includeFlows) selectedIds.add('flows');
198
+
199
+ // Expand dependencies
200
+ const moduleMap = new Map(modulesJson.modules.map((m) => [m.id, m]));
201
+ let changed = true;
202
+ while (changed) {
203
+ changed = false;
204
+ for (const id of [...selectedIds]) {
205
+ const mod = moduleMap.get(id);
206
+ if (!mod) continue;
207
+ for (const dep of mod.dependencies) {
208
+ if (!selectedIds.has(dep)) {
209
+ selectedIds.add(dep);
210
+ changed = true;
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ const orderedModules = topoSort(modulesJson.modules, selectedIds);
217
+
218
+ // 5. Prepare output directory
219
+ await mkdir(outputDir, { recursive: true });
220
+ await mkdir(join(outputDir, '.tib'), { recursive: true });
221
+
222
+ // 6. Get CLI version from package.json
223
+ let cliVersion = '0.0.0';
224
+ try {
225
+ const { createRequire } = await import('node:module');
226
+ const require = createRequire(import.meta.url);
227
+ const pkgJson = require('../../package.json') as { version: string };
228
+ cliVersion = pkgJson.version;
229
+ } catch {
230
+ // ignore — fallback to 0.0.0
231
+ }
232
+
233
+ // 7. Build token context
234
+ const tokenCtx = buildTokenContext(
235
+ { projectName, awsRegion, awsAccountId, adminEmail },
236
+ scaffoldVersion,
237
+ cliVersion
238
+ );
239
+
240
+ // 8. Create manifest
241
+ const manifest: TibManifest = createManifest(
242
+ scaffoldVersion,
243
+ projectName,
244
+ awsRegion,
245
+ [...selectedIds]
246
+ );
247
+
248
+ // 9. Process each module in topological order
249
+ for (const mod of orderedModules) {
250
+ console.log(` Installing module: ${mod.id}@${mod.version}...`);
251
+
252
+ const tarBuf = await fetchModuleTarball(
253
+ SCAFFOLD_BUCKET,
254
+ scaffoldVersion,
255
+ mod.tarball,
256
+ mod.sha256
257
+ );
258
+
259
+ const tarEntries = await parseTarball(tarBuf);
260
+
261
+ // Extract MANIFEST.json from tarball
262
+ const manifestEntry = getTarEntry(tarEntries, 'MANIFEST.json');
263
+ if (!manifestEntry) {
264
+ cliLogger.warn({ moduleId: mod.id }, 'No MANIFEST.json in tarball — skipping');
265
+ continue;
266
+ }
267
+
268
+ const tarManifest = JSON.parse(manifestEntry.content.toString('utf-8')) as TarballManifestEntry[];
269
+
270
+ // installPrefix for modules like 'frontend'
271
+ const modMeta = moduleMap.get(mod.id);
272
+ const installPrefix: string = (modMeta as (S3ModuleEntry & { installPrefix?: string }) | undefined)?.installPrefix ?? '';
273
+
274
+ for (const fileEntry of tarManifest) {
275
+ const tarEntry = getTarEntry(tarEntries, fileEntry.path);
276
+ if (!tarEntry) {
277
+ cliLogger.warn({ moduleId: mod.id, path: fileEntry.path }, 'File listed in MANIFEST.json not found in tarball');
278
+ continue;
279
+ }
280
+
281
+ // Determine install path
282
+ let installPath = fileEntry.installedAs ?? fileEntry.path;
283
+ // Strip .tpl extension
284
+ if (installPath.endsWith('.tpl')) {
285
+ installPath = installPath.slice(0, -4);
286
+ }
287
+ // Apply installPrefix
288
+ if (installPrefix) {
289
+ installPath = `${installPrefix}/${installPath}`;
290
+ }
291
+
292
+ // Process content
293
+ let content = tarEntry.content.toString('utf-8');
294
+
295
+ // Token substitution for template files
296
+ if (fileEntry.isTemplate) {
297
+ content = applyTokens(content, tokenCtx);
298
+ }
299
+
300
+ // Inject scaffold header
301
+ content = injectHeader(content, installPath, mod.id, mod.version);
302
+
303
+ // Write file
304
+ const destPath = join(outputDir, installPath);
305
+ const parentParts = installPath.split('/').slice(0, -1);
306
+ if (parentParts.length > 0) {
307
+ await mkdir(join(outputDir, ...parentParts), { recursive: true });
308
+ }
309
+ await writeFile(destPath, content, 'utf-8');
310
+
311
+ // Track in manifest (SHA-256 of written content)
312
+ const sha256 = computeChecksumString(content);
313
+ upsertManifestFile(manifest, {
314
+ path: installPath,
315
+ module: mod.id,
316
+ moduleVersion: mod.version,
317
+ sha256,
318
+ wasTemplate: fileEntry.isTemplate,
319
+ installedAt: new Date().toISOString(),
320
+ policy: fileEntry.policy ?? 'tracked',
321
+ });
322
+ }
323
+ }
324
+
325
+ // 10. Write .mc/scaffold-config.json
326
+ const environments: EnvironmentsConfig | undefined = prodUrl
327
+ ? { prod: { dashboardUrl: prodUrl } }
328
+ : undefined;
329
+
330
+ const scaffoldConfig: ScaffoldConfig = {
331
+ scaffoldVersion,
332
+ enabledModules: [...selectedIds] as ModuleId[],
333
+ projectName,
334
+ awsRegion,
335
+ awsAccountId,
336
+ adminEmail,
337
+ domainIds: [],
338
+ flowIds: [],
339
+ scaffoldBucket: SCAFFOLD_BUCKET,
340
+ ...(environments ? { environments } : {}),
341
+ };
342
+ await writeScaffoldConfig(outputDir, scaffoldConfig);
343
+
344
+ // 11. Write .mc/manifest.json
345
+ await writeManifest(outputDir, manifest);
346
+
347
+ // 12. Git init + initial commit
348
+ console.log('\nInitialising git repository...');
349
+ execSync(`git -C "${outputDir}" init`, { stdio: 'pipe' });
350
+ execSync(`git -C "${outputDir}" add -A`, { stdio: 'pipe' });
351
+ execSync(
352
+ `git -C "${outputDir}" commit -m "chore: initial scaffold from mc-scaffold@${scaffoldVersion}"`,
353
+ { stdio: 'pipe' }
354
+ );
355
+
356
+ cliLogger.info({ projectName, outputDir, scaffoldVersion }, 'Project created');
357
+ console.log(
358
+ `\nProject "${projectName}" created at ${outputDir}\n\n` +
359
+ ` Next steps:\n` +
360
+ ` cd ${outputDir}\n` +
361
+ ` npm install\n` +
362
+ ` npm run dev\n`
363
+ );
364
+ } finally {
365
+ rl.close();
366
+ }
367
+ }
@@ -0,0 +1,47 @@
1
+ import { resolve } from 'node:path';
2
+ import { buildRegistry } from '../builder/build-registry.js';
3
+ import { createApiServer } from '../server/api-server.js';
4
+ import { cliLogger } from '../utils/logger.js';
5
+
6
+ /**
7
+ * Options for the dev command.
8
+ */
9
+ export interface DevOptions {
10
+ /** Absolute or relative path to the domain root directory. */
11
+ domainRoot: string;
12
+ /** Port for the local HTTP server. Defaults to 3000. */
13
+ port?: number;
14
+ }
15
+
16
+ /**
17
+ * Execute the dev command: build the domain registry and start the local Fastify
18
+ * HTTP server. Keeps the process running. Shuts down gracefully on SIGINT/SIGTERM.
19
+ * @param options - DevOptions specifying domain root and optional port.
20
+ */
21
+ export async function runDev(options: DevOptions): Promise<void> {
22
+ const domainRoot = resolve(options.domainRoot);
23
+ const port = options.port ?? 3000;
24
+
25
+ cliLogger.info({ domainRoot }, 'Building registry for dev server');
26
+ const { registry, warnings } = await buildRegistry(domainRoot);
27
+
28
+ for (const w of warnings) {
29
+ cliLogger.warn(w);
30
+ }
31
+
32
+ cliLogger.info(
33
+ { domain: registry.domain.id, apis: registry.apis.length, port },
34
+ 'Starting local dev server'
35
+ );
36
+
37
+ const server = await createApiServer({ port, registry, domainRoot });
38
+
39
+ async function shutdown(): Promise<void> {
40
+ cliLogger.info('Shutting down dev server');
41
+ await server.close();
42
+ process.exit(0);
43
+ }
44
+
45
+ process.on('SIGINT', () => { void shutdown(); });
46
+ process.on('SIGTERM', () => { void shutdown(); });
47
+ }