@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,712 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { randomUUID } from 'node:crypto';
6
+
7
+ // Mock all external dependencies
8
+ vi.mock('../../utils/s3-fetch.js', () => ({
9
+ fetchVersionsJson: vi.fn(),
10
+ fetchModulesJson: vi.fn(),
11
+ fetchModuleTarball: vi.fn(),
12
+ }));
13
+
14
+ vi.mock('node:readline', () => ({
15
+ createInterface: vi.fn(),
16
+ }));
17
+
18
+ vi.mock('node:child_process', () => ({
19
+ execSync: vi.fn(),
20
+ }));
21
+
22
+ vi.mock('../../utils/logger.js', () => ({
23
+ cliLogger: {
24
+ info: vi.fn(),
25
+ warn: vi.fn(),
26
+ },
27
+ }));
28
+
29
+ // Import after mocking
30
+ import { runCreateProject } from '../../commands/create-project.js';
31
+ import { fetchVersionsJson, fetchModulesJson, fetchModuleTarball } from '../../utils/s3-fetch.js';
32
+ import { createInterface } from 'node:readline';
33
+ import { execSync } from 'node:child_process';
34
+ import * as tar from 'tar';
35
+ import { Readable } from 'node:stream';
36
+
37
+ const mockFetchVersionsJson = vi.mocked(fetchVersionsJson);
38
+ const mockFetchModulesJson = vi.mocked(fetchModulesJson);
39
+ const mockFetchModuleTarball = vi.mocked(fetchModuleTarball);
40
+ const mockCreateInterface = vi.mocked(createInterface);
41
+ const mockExecSync = vi.mocked(execSync);
42
+
43
+ /**
44
+ * Create a minimal in-memory tarball with the given files.
45
+ * Each file is a { path, content, isTemplate } object.
46
+ */
47
+ async function createTestTarball(files: Array<{
48
+ path: string;
49
+ content: string;
50
+ isTemplate: boolean;
51
+ }>): Promise<Buffer> {
52
+ return new Promise((resolve, reject) => {
53
+ const chunks: Buffer[] = [];
54
+ const pack = tar.create({ gzip: true }, files.map((f) => ({ path: f.path })));
55
+
56
+ pack.on('data', (chunk) => {
57
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
58
+ });
59
+
60
+ pack.on('end', () => {
61
+ resolve(Buffer.concat(chunks));
62
+ });
63
+
64
+ pack.on('error', reject);
65
+
66
+ // Write mock data
67
+ for (const file of files) {
68
+ pack.write({ path: file.path, content: file.content });
69
+ }
70
+ pack.end();
71
+ });
72
+ }
73
+
74
+ /**
75
+ * Helper to create a MANIFEST.json for a tarball
76
+ */
77
+ function createTarballManifest(files: Array<{ path: string; isTemplate: boolean }>): string {
78
+ return JSON.stringify(
79
+ files.map((f) => ({
80
+ path: f.path,
81
+ sha256: 'fakehash',
82
+ isTemplate: f.isTemplate,
83
+ })),
84
+ null,
85
+ 2
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Mock the readline interface with pre-set answers
91
+ */
92
+ function mockReadlineWithAnswers(answers: Record<string, string>): void {
93
+ const answerQueue = Object.values(answers);
94
+ let answerIdx = 0;
95
+
96
+ const mockRl = {
97
+ question: vi.fn((question: string, callback: (answer: string) => void) => {
98
+ const answer = answerQueue[answerIdx] || '';
99
+ answerIdx++;
100
+ setImmediate(() => callback(answer));
101
+ }),
102
+ close: vi.fn(),
103
+ };
104
+
105
+ mockCreateInterface.mockReturnValue(mockRl as any);
106
+ }
107
+
108
+ describe('runCreateProject', () => {
109
+ let tempDir: string;
110
+
111
+ beforeEach(() => {
112
+ tempDir = join(tmpdir(), `test-tib-${randomUUID()}`);
113
+ vi.clearAllMocks();
114
+ mockExecSync.mockImplementation(() => '');
115
+ });
116
+
117
+ afterEach(() => {
118
+ vi.clearAllMocks();
119
+ });
120
+
121
+ it('AC1: No @mettlecast/* or @mettlecast/* in generated package.json', async () => {
122
+ const outputDir = join(tempDir, 'project1');
123
+
124
+ // Mock versions and modules
125
+ mockFetchVersionsJson.mockResolvedValue({
126
+ latest: '1.0.0',
127
+ versions: ['1.0.0'],
128
+ });
129
+
130
+ mockFetchModulesJson.mockResolvedValue({
131
+ scaffoldVersion: '1.0.0',
132
+ releasedAt: '2026-05-08',
133
+ modules: [
134
+ {
135
+ id: 'core',
136
+ version: '1.0.0',
137
+ description: 'Core module',
138
+ dependencies: [],
139
+ tarball: 'core.tar.gz',
140
+ sha256: 'abc123',
141
+ },
142
+ {
143
+ id: 'backend-lambda',
144
+ version: '1.0.0',
145
+ description: 'Backend Lambda',
146
+ dependencies: ['core'],
147
+ tarball: 'backend-lambda.tar.gz',
148
+ sha256: 'def456',
149
+ },
150
+ ],
151
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
152
+ });
153
+
154
+ // Create a fake package.json with no @mettlecast/* deps
155
+ const packageJsonContent = JSON.stringify({
156
+ name: 'test-project',
157
+ version: '1.0.0',
158
+ dependencies: {
159
+ express: '^4.18.0',
160
+ 'uuid': '^9.0.0',
161
+ },
162
+ devDependencies: {
163
+ typescript: '^5.0.0',
164
+ },
165
+ });
166
+
167
+ // Create tarballs with package.json
168
+ const coreTarball = await createTestTarball([
169
+ {
170
+ path: 'package.json.tpl',
171
+ content: packageJsonContent,
172
+ isTemplate: false,
173
+ },
174
+ {
175
+ path: 'MANIFEST.json',
176
+ content: createTarballManifest([
177
+ { path: 'package.json.tpl', isTemplate: false },
178
+ ]),
179
+ isTemplate: false,
180
+ },
181
+ ]);
182
+
183
+ const backendTarball = await createTestTarball([
184
+ {
185
+ path: 'src/index.ts',
186
+ content: 'export const handler = async () => ({});',
187
+ isTemplate: false,
188
+ },
189
+ {
190
+ path: 'MANIFEST.json',
191
+ content: createTarballManifest([{ path: 'src/index.ts', isTemplate: false }]),
192
+ isTemplate: false,
193
+ },
194
+ ]);
195
+
196
+ mockFetchModuleTarball
197
+ .mockResolvedValueOnce(coreTarball)
198
+ .mockResolvedValueOnce(backendTarball);
199
+
200
+ mockReadlineWithAnswers({
201
+ projectName: 'test-project',
202
+ awsRegion: 'eu-north-1',
203
+ awsAccountId: '123456789012',
204
+ database: '',
205
+ auth: '',
206
+ frontend: '',
207
+ flows: '',
208
+ outputDir: outputDir,
209
+ scaffoldVersion: '1.0.0',
210
+ });
211
+
212
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
213
+
214
+ // Read the generated package.json
215
+ const pkgPath = join(outputDir, 'package.json');
216
+ const pkgContent = await readFile(pkgPath, 'utf-8');
217
+
218
+ expect(pkgContent).not.toContain('@mettlecast/');
219
+ expect(pkgContent).not.toContain('@mettlecast/');
220
+ expect(pkgContent).toContain('express');
221
+ });
222
+
223
+ it('AC5: Generated domain imports use local path instead of @mettlecast/domain-runtime', async () => {
224
+ const outputDir = join(tempDir, 'project2');
225
+
226
+ mockFetchVersionsJson.mockResolvedValue({
227
+ latest: '1.0.0',
228
+ versions: ['1.0.0'],
229
+ });
230
+
231
+ mockFetchModulesJson.mockResolvedValue({
232
+ scaffoldVersion: '1.0.0',
233
+ releasedAt: '2026-05-08',
234
+ modules: [
235
+ {
236
+ id: 'core',
237
+ version: '1.0.0',
238
+ description: 'Core module',
239
+ dependencies: [],
240
+ tarball: 'core.tar.gz',
241
+ sha256: 'abc123',
242
+ },
243
+ {
244
+ id: 'backend-lambda',
245
+ version: '1.0.0',
246
+ description: 'Backend Lambda',
247
+ dependencies: ['core'],
248
+ tarball: 'backend-lambda.tar.gz',
249
+ sha256: 'def456',
250
+ },
251
+ {
252
+ id: 'auth-cognito',
253
+ version: '1.0.0',
254
+ description: 'Auth Cognito',
255
+ dependencies: ['core', 'backend-lambda'],
256
+ tarball: 'auth-cognito.tar.gz',
257
+ sha256: 'ghi789',
258
+ },
259
+ ],
260
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
261
+ });
262
+
263
+ const coreTarball = await createTestTarball([
264
+ {
265
+ path: 'lib/domain-runtime.ts',
266
+ content: 'export const defineApi = () => {};',
267
+ isTemplate: false,
268
+ },
269
+ {
270
+ path: 'MANIFEST.json',
271
+ content: createTarballManifest([
272
+ { path: 'lib/domain-runtime.ts', isTemplate: false },
273
+ ]),
274
+ isTemplate: false,
275
+ },
276
+ ]);
277
+
278
+ const backendTarball = await createTestTarball([
279
+ {
280
+ path: 'MANIFEST.json',
281
+ content: createTarballManifest([]),
282
+ isTemplate: false,
283
+ },
284
+ ]);
285
+
286
+ const authTarball = await createTestTarball([
287
+ {
288
+ path: 'domains/auth/api/login.ts',
289
+ content: 'import { defineApi } from "../../lib/domain-runtime";',
290
+ isTemplate: false,
291
+ },
292
+ {
293
+ path: 'MANIFEST.json',
294
+ content: createTarballManifest([
295
+ { path: 'domains/auth/api/login.ts', isTemplate: false },
296
+ ]),
297
+ isTemplate: false,
298
+ },
299
+ ]);
300
+
301
+ mockFetchModuleTarball
302
+ .mockResolvedValueOnce(coreTarball)
303
+ .mockResolvedValueOnce(backendTarball)
304
+ .mockResolvedValueOnce(authTarball);
305
+
306
+ mockReadlineWithAnswers({
307
+ projectName: 'test-project',
308
+ awsRegion: 'eu-north-1',
309
+ awsAccountId: '123456789012',
310
+ database: '',
311
+ auth: 'y',
312
+ frontend: '',
313
+ flows: '',
314
+ adminEmail: 'admin@example.com',
315
+ outputDir: outputDir,
316
+ scaffoldVersion: '1.0.0',
317
+ });
318
+
319
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
320
+
321
+ // Read the generated auth login file
322
+ const authPath = join(outputDir, 'domains/auth/api/login.ts');
323
+ const authContent = await readFile(authPath, 'utf-8');
324
+
325
+ expect(authContent).toContain('../../lib/domain-runtime');
326
+ expect(authContent).not.toContain('@mettlecast/domain-runtime');
327
+ });
328
+
329
+ it('Core module files are written before backend-lambda files (topological order)', async () => {
330
+ const outputDir = join(tempDir, 'project3');
331
+ const writeCallOrder: string[] = [];
332
+
333
+ mockFetchVersionsJson.mockResolvedValue({
334
+ latest: '1.0.0',
335
+ versions: ['1.0.0'],
336
+ });
337
+
338
+ mockFetchModulesJson.mockResolvedValue({
339
+ scaffoldVersion: '1.0.0',
340
+ releasedAt: '2026-05-08',
341
+ modules: [
342
+ {
343
+ id: 'core',
344
+ version: '1.0.0',
345
+ description: 'Core module',
346
+ dependencies: [],
347
+ tarball: 'core.tar.gz',
348
+ sha256: 'abc123',
349
+ },
350
+ {
351
+ id: 'backend-lambda',
352
+ version: '1.0.0',
353
+ description: 'Backend Lambda',
354
+ dependencies: ['core'],
355
+ tarball: 'backend-lambda.tar.gz',
356
+ sha256: 'def456',
357
+ },
358
+ ],
359
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
360
+ });
361
+
362
+ const coreTarball = await createTestTarball([
363
+ {
364
+ path: 'core-file.txt',
365
+ content: 'core',
366
+ isTemplate: false,
367
+ },
368
+ {
369
+ path: 'MANIFEST.json',
370
+ content: createTarballManifest([
371
+ { path: 'core-file.txt', isTemplate: false },
372
+ ]),
373
+ isTemplate: false,
374
+ },
375
+ ]);
376
+
377
+ const backendTarball = await createTestTarball([
378
+ {
379
+ path: 'backend-file.txt',
380
+ content: 'backend',
381
+ isTemplate: false,
382
+ },
383
+ {
384
+ path: 'MANIFEST.json',
385
+ content: createTarballManifest([
386
+ { path: 'backend-file.txt', isTemplate: false },
387
+ ]),
388
+ isTemplate: false,
389
+ },
390
+ ]);
391
+
392
+ // Spy on writeFile to track order
393
+ let originalWriteFile: any;
394
+ let writeFileOrder: string[] = [];
395
+
396
+ // Import and spy
397
+ const fsModule = await import('node:fs/promises');
398
+ originalWriteFile = fsModule.writeFile;
399
+ vi.spyOn(fsModule, 'writeFile').mockImplementation(async (path, content) => {
400
+ writeFileOrder.push(String(path));
401
+ return originalWriteFile(path, content);
402
+ });
403
+
404
+ mockFetchModuleTarball
405
+ .mockResolvedValueOnce(coreTarball)
406
+ .mockResolvedValueOnce(backendTarball);
407
+
408
+ mockReadlineWithAnswers({
409
+ projectName: 'test-project',
410
+ awsRegion: 'eu-north-1',
411
+ awsAccountId: '123456789012',
412
+ database: '',
413
+ auth: '',
414
+ frontend: '',
415
+ flows: '',
416
+ outputDir: outputDir,
417
+ scaffoldVersion: '1.0.0',
418
+ });
419
+
420
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
421
+
422
+ // Find indices of core and backend files in write order
423
+ const coreFileIdx = writeFileOrder.findIndex((p) => p.includes('core-file.txt'));
424
+ const backendFileIdx = writeFileOrder.findIndex((p) => p.includes('backend-file.txt'));
425
+
426
+ expect(coreFileIdx).toBeLessThan(backendFileIdx);
427
+ });
428
+
429
+ it('Header comment present in generated .ts files', async () => {
430
+ const outputDir = join(tempDir, 'project4');
431
+
432
+ mockFetchVersionsJson.mockResolvedValue({
433
+ latest: '1.0.0',
434
+ versions: ['1.0.0'],
435
+ });
436
+
437
+ mockFetchModulesJson.mockResolvedValue({
438
+ scaffoldVersion: '1.0.0',
439
+ releasedAt: '2026-05-08',
440
+ modules: [
441
+ {
442
+ id: 'core',
443
+ version: '1.0.0',
444
+ description: 'Core module',
445
+ dependencies: [],
446
+ tarball: 'core.tar.gz',
447
+ sha256: 'abc123',
448
+ },
449
+ {
450
+ id: 'backend-lambda',
451
+ version: '1.0.0',
452
+ description: 'Backend Lambda',
453
+ dependencies: ['core'],
454
+ tarball: 'backend-lambda.tar.gz',
455
+ sha256: 'def456',
456
+ },
457
+ ],
458
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
459
+ });
460
+
461
+ const coreTarball = await createTestTarball([
462
+ {
463
+ path: 'lib/helpers.ts',
464
+ content: 'export const helper = () => {};',
465
+ isTemplate: false,
466
+ },
467
+ {
468
+ path: 'MANIFEST.json',
469
+ content: createTarballManifest([
470
+ { path: 'lib/helpers.ts', isTemplate: false },
471
+ ]),
472
+ isTemplate: false,
473
+ },
474
+ ]);
475
+
476
+ const backendTarball = await createTestTarball([
477
+ {
478
+ path: 'src/handler.ts',
479
+ content: 'export const handler = async () => ({});',
480
+ isTemplate: false,
481
+ },
482
+ {
483
+ path: 'MANIFEST.json',
484
+ content: createTarballManifest([
485
+ { path: 'src/handler.ts', isTemplate: false },
486
+ ]),
487
+ isTemplate: false,
488
+ },
489
+ ]);
490
+
491
+ mockFetchModuleTarball
492
+ .mockResolvedValueOnce(coreTarball)
493
+ .mockResolvedValueOnce(backendTarball);
494
+
495
+ mockReadlineWithAnswers({
496
+ projectName: 'test-project',
497
+ awsRegion: 'eu-north-1',
498
+ awsAccountId: '123456789012',
499
+ database: '',
500
+ auth: '',
501
+ frontend: '',
502
+ flows: '',
503
+ outputDir: outputDir,
504
+ scaffoldVersion: '1.0.0',
505
+ });
506
+
507
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
508
+
509
+ // Read both generated .ts files
510
+ const helperPath = join(outputDir, 'lib/helpers.ts');
511
+ const helperContent = await readFile(helperPath, 'utf-8');
512
+ expect(helperContent).toMatch(/^\/\/ @mc-scaffold:/);
513
+
514
+ const handlerPath = join(outputDir, 'src/handler.ts');
515
+ const handlerContent = await readFile(handlerPath, 'utf-8');
516
+ expect(handlerContent).toMatch(/^\/\/ @mc-scaffold:/);
517
+ });
518
+
519
+ it('Manifest written with correct schema and all file entries', async () => {
520
+ const outputDir = join(tempDir, 'project5');
521
+
522
+ mockFetchVersionsJson.mockResolvedValue({
523
+ latest: '1.0.0',
524
+ versions: ['1.0.0'],
525
+ });
526
+
527
+ mockFetchModulesJson.mockResolvedValue({
528
+ scaffoldVersion: '1.0.0',
529
+ releasedAt: '2026-05-08',
530
+ modules: [
531
+ {
532
+ id: 'core',
533
+ version: '1.0.0',
534
+ description: 'Core module',
535
+ dependencies: [],
536
+ tarball: 'core.tar.gz',
537
+ sha256: 'abc123',
538
+ },
539
+ {
540
+ id: 'backend-lambda',
541
+ version: '1.0.0',
542
+ description: 'Backend Lambda',
543
+ dependencies: ['core'],
544
+ tarball: 'backend-lambda.tar.gz',
545
+ sha256: 'def456',
546
+ },
547
+ ],
548
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
549
+ });
550
+
551
+ const coreTarball = await createTestTarball([
552
+ {
553
+ path: 'core-config.json',
554
+ content: JSON.stringify({ coreValue: true }),
555
+ isTemplate: false,
556
+ },
557
+ {
558
+ path: 'MANIFEST.json',
559
+ content: createTarballManifest([
560
+ { path: 'core-config.json', isTemplate: false },
561
+ ]),
562
+ isTemplate: false,
563
+ },
564
+ ]);
565
+
566
+ const backendTarball = await createTestTarball([
567
+ {
568
+ path: 'backend-config.json',
569
+ content: JSON.stringify({ backendValue: true }),
570
+ isTemplate: false,
571
+ },
572
+ {
573
+ path: 'MANIFEST.json',
574
+ content: createTarballManifest([
575
+ { path: 'backend-config.json', isTemplate: false },
576
+ ]),
577
+ isTemplate: false,
578
+ },
579
+ ]);
580
+
581
+ mockFetchModuleTarball
582
+ .mockResolvedValueOnce(coreTarball)
583
+ .mockResolvedValueOnce(backendTarball);
584
+
585
+ mockReadlineWithAnswers({
586
+ projectName: 'test-project',
587
+ awsRegion: 'eu-north-1',
588
+ awsAccountId: '123456789012',
589
+ database: '',
590
+ auth: '',
591
+ frontend: '',
592
+ flows: '',
593
+ outputDir: outputDir,
594
+ scaffoldVersion: '1.0.0',
595
+ });
596
+
597
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
598
+
599
+ // Read the manifest
600
+ const manifestPath = join(outputDir, '.mc/manifest.json');
601
+ const manifestContent = await readFile(manifestPath, 'utf-8');
602
+ const manifest = JSON.parse(manifestContent);
603
+
604
+ // Verify schema fields
605
+ expect(manifest.$schema).toBeDefined();
606
+ expect(manifest.scaffoldVersion).toBe('1.0.0');
607
+ expect(manifest.projectName).toBe('test-project');
608
+ expect(manifest.awsRegion).toBe('eu-north-1');
609
+ expect(manifest.createdAt).toBeDefined();
610
+ expect(manifest.updatedAt).toBeDefined();
611
+ expect(manifest.enabledModules).toContain('core');
612
+ expect(manifest.enabledModules).toContain('backend-lambda');
613
+
614
+ // Verify file entries
615
+ expect(manifest.files).toBeInstanceOf(Array);
616
+ expect(manifest.files.length).toBeGreaterThanOrEqual(2);
617
+
618
+ const coreFile = manifest.files.find((f: any) => f.path === 'core-config.json');
619
+ expect(coreFile).toBeDefined();
620
+ expect(coreFile.module).toBe('core');
621
+ expect(coreFile.moduleVersion).toBe('1.0.0');
622
+ expect(coreFile.sha256).toBeDefined();
623
+ expect(coreFile.wasTemplate).toBe(false);
624
+ expect(coreFile.installedAt).toBeDefined();
625
+
626
+ const backendFile = manifest.files.find((f: any) => f.path === 'backend-config.json');
627
+ expect(backendFile).toBeDefined();
628
+ expect(backendFile.module).toBe('backend-lambda');
629
+ expect(backendFile.moduleVersion).toBe('1.0.0');
630
+ });
631
+
632
+ it('Scaffold config written with correct module list', async () => {
633
+ const outputDir = join(tempDir, 'project6');
634
+
635
+ mockFetchVersionsJson.mockResolvedValue({
636
+ latest: '1.0.0',
637
+ versions: ['1.0.0'],
638
+ });
639
+
640
+ mockFetchModulesJson.mockResolvedValue({
641
+ scaffoldVersion: '1.0.0',
642
+ releasedAt: '2026-05-08',
643
+ modules: [
644
+ {
645
+ id: 'core',
646
+ version: '1.0.0',
647
+ description: 'Core module',
648
+ dependencies: [],
649
+ tarball: 'core.tar.gz',
650
+ sha256: 'abc123',
651
+ },
652
+ {
653
+ id: 'backend-lambda',
654
+ version: '1.0.0',
655
+ description: 'Backend Lambda',
656
+ dependencies: ['core'],
657
+ tarball: 'backend-lambda.tar.gz',
658
+ sha256: 'def456',
659
+ },
660
+ {
661
+ id: 'db-aurora',
662
+ version: '1.0.0',
663
+ description: 'DB Aurora',
664
+ dependencies: ['core'],
665
+ tarball: 'db-aurora.tar.gz',
666
+ sha256: 'jkl012',
667
+ },
668
+ ],
669
+ compatibility: { cliMinVersion: '0.1.0', nodeMinVersion: '18.0.0' },
670
+ });
671
+
672
+ const minimalTarball = await createTestTarball([
673
+ {
674
+ path: 'MANIFEST.json',
675
+ content: createTarballManifest([]),
676
+ isTemplate: false,
677
+ },
678
+ ]);
679
+
680
+ mockFetchModuleTarball
681
+ .mockResolvedValue(minimalTarball);
682
+
683
+ mockReadlineWithAnswers({
684
+ projectName: 'test-project',
685
+ awsRegion: 'eu-north-1',
686
+ awsAccountId: '123456789012',
687
+ database: 'y',
688
+ auth: '',
689
+ frontend: '',
690
+ flows: '',
691
+ outputDir: outputDir,
692
+ scaffoldVersion: '1.0.0',
693
+ });
694
+
695
+ await runCreateProject({ name: 'test-project', outDir: tempDir });
696
+
697
+ // Read scaffold config
698
+ const configPath = join(outputDir, '.mc/scaffold-config.json');
699
+ const configContent = await readFile(configPath, 'utf-8');
700
+ const config = JSON.parse(configContent);
701
+
702
+ expect(config.scaffoldVersion).toBe('1.0.0');
703
+ expect(config.projectName).toBe('test-project');
704
+ expect(config.awsRegion).toBe('eu-north-1');
705
+ expect(config.awsAccountId).toBe('123456789012');
706
+ expect(config.enabledModules).toContain('core');
707
+ expect(config.enabledModules).toContain('backend-lambda');
708
+ expect(config.enabledModules).toContain('db-aurora');
709
+ expect(config.domainIds).toEqual([]);
710
+ expect(config.flowIds).toEqual([]);
711
+ });
712
+ });