@decohere/scaffold 0.0.1-dev.18 → 0.0.1-dev.20261105.46

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 (264) hide show
  1. package/LICENSE +15 -0
  2. package/commonjs/src/add-api.action.js +37 -0
  3. package/commonjs/src/add-api.command.js +39 -0
  4. package/commonjs/src/add.command.js +22 -0
  5. package/commonjs/src/add.module.js +24 -0
  6. package/commonjs/src/auth0-configure.action.js +28 -0
  7. package/commonjs/src/auth0-configure.command.js +24 -0
  8. package/commonjs/src/auth0-login.action.js +28 -0
  9. package/commonjs/src/auth0-login.command.js +23 -0
  10. package/commonjs/src/auth0.command.js +22 -0
  11. package/commonjs/src/auth0.module.js +28 -0
  12. package/commonjs/src/cli.js +8 -0
  13. package/commonjs/src/create.command.js +22 -0
  14. package/commonjs/src/create.module.js +24 -0
  15. package/commonjs/src/domain/prompt.js +62 -0
  16. package/commonjs/src/index.js +48 -0
  17. package/commonjs/src/models/compose.model.js +258 -0
  18. package/commonjs/src/models/config-yaml.model.js +86 -0
  19. package/commonjs/src/models/const.js +30 -0
  20. package/commonjs/src/models/dev-container.model.js +172 -0
  21. package/commonjs/src/models/docker-image.model.js +277 -0
  22. package/commonjs/src/models/env-file.model.js +48 -0
  23. package/commonjs/src/models/gitignore.model.js +48 -0
  24. package/commonjs/src/models/launch-json.model.js +70 -0
  25. package/commonjs/src/models/package-json.model.js +156 -0
  26. package/commonjs/src/models/project.model.js +132 -0
  27. package/commonjs/src/models/source-file.js +4 -0
  28. package/commonjs/src/models/tsconfig.model.js +32 -0
  29. package/commonjs/src/models/vitest-config.model.js +33 -0
  30. package/commonjs/src/models/vscode-settings.model.js +29 -0
  31. package/commonjs/src/program.js +64 -0
  32. package/commonjs/src/resolve-target-dir.js +17 -0
  33. package/commonjs/src/services/scaffold.service.js +60 -0
  34. package/commonjs/src/version.js +55 -0
  35. package/commonjs/src/webapi/api-name.js +93 -0
  36. package/commonjs/src/webapi/auth0-commands.js +250 -0
  37. package/commonjs/src/webapi/authorize-scopes.js +79 -0
  38. package/commonjs/src/webapi/cli-options.js +99 -0
  39. package/commonjs/src/webapi/const.js +54 -0
  40. package/commonjs/src/webapi/create-api.js +265 -0
  41. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +164 -0
  42. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  43. package/commonjs/src/webapi/devcontainer/host-scripts.js +50 -0
  44. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  45. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
  46. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
  47. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
  48. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  49. package/commonjs/src/webapi/features/auth.feature.js +408 -0
  50. package/commonjs/src/webapi/features/auth0-cli.js +472 -0
  51. package/commonjs/src/webapi/features/biome-format.js +47 -0
  52. package/commonjs/src/webapi/features/database.feature.js +433 -0
  53. package/commonjs/src/webapi/features/finalize.feature.js +93 -0
  54. package/commonjs/src/webapi/features/postman.feature.js +322 -0
  55. package/commonjs/src/webapi/models/api-package-json.model.js +58 -0
  56. package/commonjs/src/webapi/models/auth-config.model.js +24 -0
  57. package/commonjs/src/webapi/models/controller-source-file.model.js +190 -0
  58. package/commonjs/src/webapi/models/github-ci.model.js +193 -0
  59. package/commonjs/src/webapi/models/main-source-file.model.js +165 -0
  60. package/commonjs/src/webapi/models/production-dockerfile.model.js +75 -0
  61. package/commonjs/src/webapi/models/sample-files.model.js +58 -0
  62. package/commonjs/src/webapi/models/webapi-image.model.js +25 -0
  63. package/commonjs/src/webapi/postman/hello-openapi.js +173 -0
  64. package/commonjs/src/webapi/postman/local-view.js +182 -0
  65. package/commonjs/src/webapi/postman/openapi-to-postman.js +238 -0
  66. package/commonjs/src/webapi/workspace-readme.js +109 -0
  67. package/commonjs/src/webapi.action.js +28 -0
  68. package/commonjs/src/webapi.command.js +43 -0
  69. package/es6/src/add-api.action.js +27 -0
  70. package/es6/src/add-api.command.js +29 -0
  71. package/es6/src/add.command.js +12 -0
  72. package/es6/src/add.module.js +14 -0
  73. package/es6/src/auth0-configure.action.js +18 -0
  74. package/es6/src/auth0-configure.command.js +14 -0
  75. package/es6/src/auth0-login.action.js +18 -0
  76. package/es6/src/auth0-login.command.js +13 -0
  77. package/es6/src/auth0.command.js +12 -0
  78. package/es6/src/auth0.module.js +18 -0
  79. package/es6/src/cli.js +4 -0
  80. package/es6/src/create.command.js +12 -0
  81. package/es6/src/create.module.js +14 -0
  82. package/es6/src/domain/prompt.js +41 -0
  83. package/es6/src/index.js +9 -0
  84. package/es6/src/models/compose.model.js +254 -0
  85. package/es6/src/models/config-yaml.model.js +74 -0
  86. package/es6/src/models/const.js +20 -0
  87. package/es6/src/models/dev-container.model.js +162 -0
  88. package/es6/src/models/docker-image.model.js +235 -0
  89. package/es6/src/models/env-file.model.js +44 -0
  90. package/es6/src/models/gitignore.model.js +38 -0
  91. package/es6/src/models/launch-json.model.js +60 -0
  92. package/es6/src/models/package-json.model.js +141 -0
  93. package/es6/src/models/project.model.js +117 -0
  94. package/es6/src/models/source-file.js +1 -0
  95. package/es6/src/models/tsconfig.model.js +22 -0
  96. package/es6/src/models/vitest-config.model.js +23 -0
  97. package/es6/src/models/vscode-settings.model.js +19 -0
  98. package/es6/src/program.js +50 -0
  99. package/es6/src/resolve-target-dir.js +7 -0
  100. package/es6/src/services/scaffold.service.js +50 -0
  101. package/es6/src/version.js +34 -0
  102. package/es6/src/webapi/api-name.js +57 -0
  103. package/es6/src/webapi/auth0-commands.js +185 -0
  104. package/es6/src/webapi/authorize-scopes.js +55 -0
  105. package/es6/src/webapi/cli-options.js +34 -0
  106. package/es6/src/webapi/const.js +21 -0
  107. package/es6/src/webapi/create-api.js +245 -0
  108. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +154 -0
  109. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  110. package/es6/src/webapi/devcontainer/host-scripts.js +29 -0
  111. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  112. package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
  113. package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
  114. package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
  115. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  116. package/es6/src/webapi/features/auth.feature.js +357 -0
  117. package/es6/src/webapi/features/auth0-cli.js +406 -0
  118. package/es6/src/webapi/features/biome-format.js +40 -0
  119. package/es6/src/webapi/features/database.feature.js +423 -0
  120. package/es6/src/webapi/features/finalize.feature.js +83 -0
  121. package/es6/src/webapi/features/postman.feature.js +256 -0
  122. package/es6/src/webapi/models/api-package-json.model.js +53 -0
  123. package/es6/src/webapi/models/auth-config.model.js +20 -0
  124. package/es6/src/webapi/models/controller-source-file.model.js +181 -0
  125. package/{src/webapi/models/github-ci.model.ts → es6/src/webapi/models/github-ci.model.js} +27 -29
  126. package/es6/src/webapi/models/main-source-file.model.js +161 -0
  127. package/es6/src/webapi/models/production-dockerfile.model.js +66 -0
  128. package/es6/src/webapi/models/sample-files.model.js +40 -0
  129. package/es6/src/webapi/models/webapi-image.model.js +18 -0
  130. package/es6/src/webapi/postman/hello-openapi.js +166 -0
  131. package/es6/src/webapi/postman/local-view.js +167 -0
  132. package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
  133. package/es6/src/webapi/workspace-readme.js +88 -0
  134. package/es6/src/webapi.action.js +18 -0
  135. package/es6/src/webapi.command.js +33 -0
  136. package/package.json +19 -9
  137. package/types/src/add-api.action.d.ts +13 -0
  138. package/types/src/add-api.command.d.ts +7 -0
  139. package/types/src/add.command.d.ts +6 -0
  140. package/types/src/add.module.d.ts +14 -0
  141. package/types/src/auth0-configure.action.d.ts +7 -0
  142. package/types/src/auth0-configure.command.d.ts +6 -0
  143. package/types/src/auth0-login.action.d.ts +4 -0
  144. package/types/src/auth0-login.command.d.ts +6 -0
  145. package/types/src/auth0.command.d.ts +6 -0
  146. package/types/src/auth0.module.d.ts +18 -0
  147. package/types/src/cli.d.ts +2 -0
  148. package/types/src/create.command.d.ts +6 -0
  149. package/types/src/create.module.d.ts +14 -0
  150. package/types/src/domain/prompt.d.ts +4 -0
  151. package/{src/index.ts → types/src/index.d.ts} +4 -0
  152. package/types/src/models/compose.model.d.ts +73 -0
  153. package/types/src/models/config-yaml.model.d.ts +27 -0
  154. package/types/src/models/const.d.ts +20 -0
  155. package/types/src/models/dev-container.model.d.ts +49 -0
  156. package/types/src/models/docker-image.model.d.ts +90 -0
  157. package/types/src/models/env-file.model.d.ts +16 -0
  158. package/types/src/models/gitignore.model.d.ts +9 -0
  159. package/types/src/models/launch-json.model.d.ts +7 -0
  160. package/types/src/models/package-json.model.d.ts +36 -0
  161. package/types/src/models/project.model.d.ts +60 -0
  162. package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
  163. package/types/src/models/tsconfig.model.d.ts +5 -0
  164. package/types/src/models/vitest-config.model.d.ts +5 -0
  165. package/types/src/models/vscode-settings.model.d.ts +7 -0
  166. package/types/src/program.d.ts +8 -0
  167. package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
  168. package/types/src/services/scaffold.service.d.ts +22 -0
  169. package/types/src/version.d.ts +6 -0
  170. package/types/src/webapi/api-name.d.ts +14 -0
  171. package/types/src/webapi/auth0-commands.d.ts +11 -0
  172. package/types/src/webapi/authorize-scopes.d.ts +7 -0
  173. package/types/src/webapi/cli-options.d.ts +6 -0
  174. package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +9 -21
  175. package/types/src/webapi/create-api.d.ts +15 -0
  176. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
  177. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  178. package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
  179. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  180. package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
  181. package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
  182. package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
  183. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  184. package/types/src/webapi/features/auth.feature.d.ts +10 -0
  185. package/types/src/webapi/features/auth0-cli.d.ts +84 -0
  186. package/types/src/webapi/features/biome-format.d.ts +5 -0
  187. package/types/src/webapi/features/database.feature.d.ts +7 -0
  188. package/types/src/webapi/features/finalize.feature.d.ts +2 -0
  189. package/types/src/webapi/features/postman.feature.d.ts +16 -0
  190. package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
  191. package/types/src/webapi/models/auth-config.model.d.ts +26 -0
  192. package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
  193. package/types/src/webapi/models/github-ci.model.d.ts +9 -0
  194. package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
  195. package/types/src/webapi/models/production-dockerfile.model.d.ts +18 -0
  196. package/types/src/webapi/models/sample-files.model.d.ts +7 -0
  197. package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
  198. package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
  199. package/types/src/webapi/postman/local-view.d.ts +17 -0
  200. package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
  201. package/types/src/webapi/workspace-readme.d.ts +18 -0
  202. package/types/src/webapi.action.d.ts +14 -0
  203. package/types/src/webapi.command.d.ts +11 -0
  204. package/src/add-api.action.ts +0 -25
  205. package/src/add-api.command.ts +0 -31
  206. package/src/add.command.ts +0 -20
  207. package/src/add.module.ts +0 -11
  208. package/src/cli.ts +0 -4
  209. package/src/create.command.ts +0 -20
  210. package/src/create.module.ts +0 -11
  211. package/src/domain/prompt.ts +0 -50
  212. package/src/models/compose.model.ts +0 -205
  213. package/src/models/config-yaml.model.ts +0 -83
  214. package/src/models/const.ts +0 -20
  215. package/src/models/dev-container.model.ts +0 -136
  216. package/src/models/docker-image.model.ts +0 -343
  217. package/src/models/env-file.model.ts +0 -45
  218. package/src/models/gitignore.model.ts +0 -35
  219. package/src/models/launch-json.model.ts +0 -60
  220. package/src/models/package-json.model.ts +0 -169
  221. package/src/models/project.model.ts +0 -141
  222. package/src/models/tsconfig.model.ts +0 -29
  223. package/src/models/vitest-config.model.ts +0 -26
  224. package/src/models/vscode-settings.model.ts +0 -24
  225. package/src/program.ts +0 -63
  226. package/src/services/scaffold.service.ts +0 -89
  227. package/src/version.ts +0 -56
  228. package/src/webapi/api-name.ts +0 -73
  229. package/src/webapi/cli-options.ts +0 -56
  230. package/src/webapi/create-api.ts +0 -275
  231. package/src/webapi/devcontainer/host-scripts.ts +0 -35
  232. package/src/webapi/features/auth.feature.ts +0 -454
  233. package/src/webapi/features/auth0-cli.ts +0 -421
  234. package/src/webapi/features/biome-format.ts +0 -34
  235. package/src/webapi/features/database.feature.ts +0 -522
  236. package/src/webapi/features/finalize.feature.ts +0 -112
  237. package/src/webapi/features/postman.feature.ts +0 -287
  238. package/src/webapi/models/api-package-json.model.ts +0 -60
  239. package/src/webapi/models/auth-config.model.ts +0 -36
  240. package/src/webapi/models/controller-source-file.model.ts +0 -232
  241. package/src/webapi/models/main-source-file.model.ts +0 -208
  242. package/src/webapi/models/production-dockerfile.model.ts +0 -162
  243. package/src/webapi/models/sample-files.model.ts +0 -45
  244. package/src/webapi/models/webapi-image.model.ts +0 -21
  245. package/src/webapi/postman/hello-openapi.ts +0 -92
  246. package/src/webapi/postman/local-view.ts +0 -195
  247. package/src/webapi/postman/openapi-to-postman.ts +0 -208
  248. package/src/webapi/workspace-readme.ts +0 -192
  249. package/src/webapi.action.ts +0 -22
  250. package/src/webapi.command.ts +0 -41
  251. package/tests/auth-postman.wiring.spec.ts +0 -263
  252. package/tests/auth0-cli.spec.ts +0 -232
  253. package/tests/biome-format.spec.ts +0 -49
  254. package/tests/cli.integration.spec.ts +0 -658
  255. package/tests/compose.model.spec.ts +0 -51
  256. package/tests/container-image.model.spec.ts +0 -162
  257. package/tests/dev-container.model.spec.ts +0 -29
  258. package/tests/devcontainer-base-image.spec.ts +0 -32
  259. package/tests/hello-di-inject.scaffold.spec.ts +0 -18
  260. package/tests/hello-route.contract.spec.ts +0 -94
  261. package/tests/package-json.order.spec.ts +0 -59
  262. package/tests/postman-from-openapi.spec.ts +0 -197
  263. package/tests/seed-devcontainer-templates.ts +0 -40
  264. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
@@ -1,658 +0,0 @@
1
- /**
2
- * Behavior: running the create CLI the way Node runs the package bin writes a webapi tree.
3
- *
4
- * One suite — every example uses the same launch:
5
- * argv = [node, /node_modules/.bin/decohere, …userArgs]
6
- * then load the bin entry (writes go to memfs).
7
- *
8
- * Examples are argv permutations only.
9
- */
10
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
11
- import { join } from "node:path";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
13
- import { resetMemfs } from "@decohere/nexus/testing";
14
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
15
- import { seedDevcontainerTemplates } from "./seed-devcontainer-templates.js";
16
-
17
- const realFs = await vi.importActual<typeof import("node:fs")>("node:fs");
18
-
19
- const { mockAsk, mockPick, mockInstall } = vi.hoisted(() => ({
20
- mockAsk: vi.fn((_q: string, fallback = "") => Promise.resolve(fallback)),
21
- mockPick: vi.fn(
22
- <T>(_label: string, items: T[], _display: (item: T) => string): Promise<T | null> =>
23
- Promise.resolve(items[0] ?? null),
24
- ),
25
- mockInstall: vi.fn().mockResolvedValue({ message: "", data: [] }),
26
- }));
27
-
28
- const mockPm = {
29
- name: "pnpm" as const,
30
- install: mockInstall,
31
- syncLockFile: vi.fn().mockResolvedValue({ message: "", data: [] }),
32
- };
33
-
34
- vi.mock("../src/domain/prompt.js", async () => {
35
- const actual = await import("../src/domain/prompt.js");
36
- return { ...actual, ask: mockAsk, pick: mockPick };
37
- });
38
-
39
- vi.mock(import("../../nexus/src/interop/index.js"), async (importOriginal) => {
40
- const actual = await importOriginal();
41
- return {
42
- ...actual,
43
- detectPackageManager: () => mockPm,
44
- promptForPackageManager: () => Promise.resolve(mockPm),
45
- };
46
- });
47
-
48
- vi.mock(import("../src/webapi/features/auth0-cli.js"), () => ({
49
- isInstalled: () => false,
50
- login: () => true,
51
- listTenants: () => [],
52
- }));
53
-
54
- vi.mock("../src/webapi/features/biome-format.js", () => ({
55
- formatScaffoldedFiles: vi.fn(),
56
- }));
57
-
58
- const NODE = process.execPath;
59
- const CLI_FILE = realFs.realpathSync(fileURLToPath(new URL("../src/cli.ts", import.meta.url)));
60
- /** Same argv[1] shape npm/npx use for the package bin. */
61
- const NPM_BIN = "/node_modules/.bin/decohere";
62
-
63
- /** Feature flags only — does not skip install (use with builder env or `--install no`). */
64
- const NON_INTERACTIVE_FEATURES = [
65
- "--database",
66
- "none",
67
- "--auth",
68
- "none",
69
- "--postman",
70
- "none",
71
- ] as const;
72
-
73
- const NON_INTERACTIVE = [...NON_INTERACTIVE_FEATURES, "--install", "no"] as const;
74
-
75
- function expectWebapiTree(root: string): void {
76
- expect(existsSync(join(root, "package.json")), `${root}/package.json`).toBe(true);
77
- expect(existsSync(join(root, "Dockerfile")), `${root}/Dockerfile`).toBe(true);
78
- expect(existsSync(join(root, "tsconfig.json")), `${root}/tsconfig.json`).toBe(true);
79
- expect(existsSync(join(root, "vitest.config.ts")), `${root}/vitest.config.ts`).toBe(true);
80
- const vitestConfig = readFileSync(join(root, "vitest.config.ts"), "utf8");
81
- expect(vitestConfig).toContain('from "@decohere/forge/vitest"');
82
- expect(vitestConfig).toContain("forgeBuild");
83
- expect(
84
- existsSync(join(root, "projects/api/package.json")),
85
- `${root}/projects/api/package.json`,
86
- ).toBe(true);
87
- expect(
88
- existsSync(join(root, "projects/api/src/main.ts")),
89
- `${root}/projects/api/src/main.ts`,
90
- ).toBe(true);
91
- expect(readFileSync(join(root, "projects/api/src/main.ts"), "utf8")).toContain(
92
- "withHealth()",
93
- );
94
- expect(readFileSync(join(root, "projects/api/src/main.ts"), "utf8")).toContain(
95
- "Number(process.env.PORT ?? 3000)",
96
- );
97
- expect(readFileSync(join(root, "projects/api/src/main.ts"), "utf8")).toContain(
98
- "scribe.withPinoPretty().and.withPinoRoll",
99
- );
100
- expect(readFileSync(join(root, ".gitignore"), "utf8")).toContain("logs");
101
- expect(readFileSync(join(root, ".gitignore"), "utf8")).toContain(".cursor/codebase-rag.env");
102
- expect(readFileSync(join(root, ".gitignore"), "utf8")).toContain(".cursor/mcp.json");
103
- expect(
104
- existsSync(join(root, "projects/api/src/controllers/hello.controller.ts")),
105
- `${root}/projects/api/src/controllers/hello.controller.ts`,
106
- ).toBe(true);
107
-
108
- const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
109
- scripts?: Record<string, string>;
110
- dependencies?: Record<string, string>;
111
- devDependencies?: Record<string, string>;
112
- };
113
- expect(pkg.scripts?.build).toBeTruthy();
114
- expect(pkg.scripts?.start).toBeTruthy();
115
- expect(pkg.scripts?.dev).toBeTruthy();
116
- expect(pkg.dependencies?.["pino-pretty"]).toBeTruthy();
117
- expect(pkg.dependencies?.["pino-roll"]).toBeTruthy();
118
- expect(pkg.devDependencies?.["@decohere/forge"]).toBeTruthy();
119
-
120
- const apiPkg = JSON.parse(readFileSync(join(root, "projects/api/package.json"), "utf8")) as {
121
- private?: boolean;
122
- };
123
- expect(apiPkg.private).toBe(true);
124
-
125
- const dockerfile = readFileSync(join(root, "Dockerfile"), "utf8");
126
- expect(dockerfile).toContain("pnpm run build");
127
- expect(dockerfile).toContain("pnpm install --frozen-lockfile --prod");
128
- expect(dockerfile).toContain("COPY --from=prod-deps /app/node_modules ./node_modules");
129
- expect(dockerfile).not.toContain("COPY --from=build /app/node_modules");
130
-
131
- const devcontainerJson = readFileSync(join(root, ".devcontainer/devcontainer.json"), "utf8");
132
- expect(devcontainerJson).toContain(
133
- '"postCreateCommand": "bash .devcontainer/post-create.sh"',
134
- );
135
- expect(devcontainerJson).toContain(
136
- '"updateContentCommand": "bash .devcontainer/post-create.sh"',
137
- );
138
- expect(devcontainerJson).toContain('"postStartCommand": "bash .devcontainer/post-start.sh"');
139
-
140
- const postCreate = readFileSync(join(root, ".devcontainer/post-create.sh"), "utf8");
141
- expect(postCreate).toContain("pnpm install --frozen-lockfile");
142
- expect(postCreate).toContain("pnpm install");
143
-
144
- const postStart = readFileSync(join(root, ".devcontainer/post-start.sh"), "utf8");
145
- expect(postStart).not.toContain("pnpm install");
146
- expect(postStart).toContain("codebase-rag-mcp.ts");
147
- expect(postStart).toContain('prompt_yn "Log in to npm?"');
148
- expect(postStart).toContain('prompt_yn "Log in to GitHub CLI (gh)?"');
149
- expect(postStart).toContain("gh auth login");
150
- expect(postStart).toContain(".devcontainer/gh-auth/");
151
- expect(postStart).not.toContain("exit 1");
152
-
153
- const initialize = readFileSync(join(root, ".devcontainer/initialize.sh"), "utf8");
154
- expect(initialize).toContain('prompt_yn "Configure npm auth for this workspace?"');
155
- expect(initialize).toContain('prompt_yn "Configure GitHub CLI auth for this workspace?"');
156
- expect(initialize).toContain('prompt_yn "Configure codebase RAG MCP (semantic search)?"');
157
- expect(initialize).toContain('gh_dir="$root/.devcontainer/gh-auth"');
158
- expect(initialize).toContain('gh_env="$root/.devcontainer/gh.env"');
159
- expect(initialize).not.toContain("python3");
160
- expect(existsSync(join(root, ".devcontainer/codebase-rag-mcp.ts"))).toBe(true);
161
- expect(existsSync(join(root, ".devcontainer/tty-prompt.sh"))).toBe(true);
162
-
163
- expect(initialize).toContain('cursor_dir="$root/.cursor"');
164
- expect(initialize).toContain("codebase-rag.env");
165
-
166
- const compose = readFileSync(join(root, ".devcontainer/docker-compose.yml"), "utf8");
167
- expect(compose).toContain("codebase-rag-mcp.ts merge");
168
- expect(compose).toContain("postStartCommand runs after Cursor's first MCP snapshot");
169
- expect(compose).toContain("./gh-auth:/home/node/.config/gh");
170
- expect(compose).toContain("./gh.env");
171
- expect(compose).not.toContain("cursor-mcp");
172
- expect(compose).not.toContain("${HOME}/.config/cursor/codebase-rag.env");
173
- expect(compose).toContain("bashhistory:/commandhistory");
174
- expect(compose).toMatch(/^ {2}bashhistory:\s*$/m);
175
-
176
- const appDevDockerfile = readFileSync(join(root, ".devcontainer/Dockerfile"), "utf8");
177
- expect(appDevDockerfile).toMatch(/^FROM ghcr\.io\/typesmiths\/decohere\/devcontainer:/m);
178
- expect(appDevDockerfile).not.toContain("apt-get install -y --no-install-recommends gh");
179
-
180
- const gitignore = readFileSync(join(root, ".gitignore"), "utf8");
181
- expect(gitignore).toContain(".devcontainer/gh.env");
182
- }
183
-
184
- /** argv as `node node_modules/.bin/decohere …args`, then load the bin entry. */
185
- async function runCliAsNodeBin(
186
- userArgs: string[],
187
- options?: { resetFs?: boolean; builder?: boolean },
188
- ): Promise<void> {
189
- vi.resetModules();
190
- if (options?.resetFs !== false) {
191
- resetMemfs();
192
- seedDevcontainerTemplates();
193
- }
194
- vi.spyOn(process, "cwd").mockReturnValue("/");
195
- if (options?.builder === false) {
196
- delete process.env.DECOHERE_BUILDER;
197
- } else {
198
- process.env.DECOHERE_BUILDER = "1";
199
- }
200
- process.argv = [NODE, NPM_BIN, ...userArgs];
201
-
202
- const exited = new Promise<void>((resolve) => {
203
- vi.spyOn(process, "exit").mockImplementation(() => {
204
- resolve();
205
- return undefined as never;
206
- });
207
- });
208
-
209
- await import(`${pathToFileURL(CLI_FILE).href}?t=${Date.now()}`);
210
- await Promise.race([
211
- exited,
212
- new Promise<void>((_, reject) => {
213
- setTimeout(() => reject(new Error("CLI did not exit")), 5000);
214
- }),
215
- ]);
216
- }
217
-
218
- beforeEach(() => {
219
- mockInstall.mockClear();
220
- mockAsk.mockClear();
221
- mockPick.mockClear();
222
- });
223
-
224
- afterEach(() => {
225
- resetMemfs();
226
- vi.restoreAllMocks();
227
- delete process.env.DECOHERE_BUILDER;
228
- delete process.env.DECOHERE_PROJECT_NAME;
229
- });
230
-
231
- describe("scaffolder writes a webapi project", () => {
232
- it("writes files for: webapi my-app (npm create after stealing -d)", async () => {
233
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE]);
234
- expectWebapiTree("/my-app");
235
- });
236
-
237
- it("writes files for: webapi -d my-app (npx / direct bin)", async () => {
238
- await runCliAsNodeBin(["create", "webapi", "-d", "my-app", ...NON_INTERACTIVE]);
239
- expectWebapiTree("/my-app");
240
- });
241
-
242
- it("writes files for: webapi ./apps/api (relative directory)", async () => {
243
- await runCliAsNodeBin(["create", "webapi", "./apps/api", ...NON_INTERACTIVE]);
244
- expectWebapiTree("/apps/api");
245
- });
246
-
247
- it("writes files into cwd for: webapi (no directory)", async () => {
248
- process.env.DECOHERE_PROJECT_NAME = "cwd-app";
249
- await runCliAsNodeBin(["create", "webapi", ...NON_INTERACTIVE]);
250
- expectWebapiTree("/");
251
- });
252
-
253
- it("uses DECOHERE_PROJECT_NAME when scaffolding into /workspace", async () => {
254
- process.env.DECOHERE_PROJECT_NAME = "test1";
255
- await runCliAsNodeBin(["create", "webapi", "-d", "/workspace", ...NON_INTERACTIVE]);
256
-
257
- const settings = JSON.parse(
258
- readFileSync(join("/workspace", ".vscode/settings.json"), "utf8"),
259
- ) as { "window.title"?: string };
260
- expect(settings["window.title"]).toContain("test1");
261
-
262
- const pkg = JSON.parse(readFileSync(join("/workspace", "package.json"), "utf8")) as {
263
- name?: string;
264
- };
265
- expect(pkg.name).toBe("test1.api");
266
- });
267
- });
268
-
269
- describe("scaffolder install behavior", () => {
270
- it("installs dependencies when not in builder env and without --install no", async () => {
271
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE_FEATURES], {
272
- builder: false,
273
- });
274
- expectWebapiTree("/my-app");
275
- expect(mockInstall).toHaveBeenCalledOnce();
276
- });
277
-
278
- it("does not install when DECOHERE_BUILDER=1 even without --install no", async () => {
279
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE_FEATURES]);
280
- expectWebapiTree("/my-app");
281
- expect(mockInstall).not.toHaveBeenCalled();
282
- });
283
-
284
- it("does not install when --install no is set", async () => {
285
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE], {
286
- builder: false,
287
- });
288
- expectWebapiTree("/my-app");
289
- expect(mockInstall).not.toHaveBeenCalled();
290
- });
291
- });
292
-
293
- function readJson(path: string): {
294
- scripts?: Record<string, string>;
295
- name?: string;
296
- private?: boolean;
297
- } {
298
- return JSON.parse(readFileSync(path, "utf8")) as {
299
- scripts?: Record<string, string>;
300
- name?: string;
301
- private?: boolean;
302
- };
303
- }
304
-
305
- function expectApiProject(root: string, apiName: string): void {
306
- expect(existsSync(join(root, `projects/${apiName}/package.json`))).toBe(true);
307
- expect(existsSync(join(root, `projects/${apiName}/src/main.ts`))).toBe(true);
308
- const apiPkg = readJson(join(root, `projects/${apiName}/package.json`));
309
- expect(apiPkg.private).toBe(true);
310
- expect(apiPkg.name).toBe(`@${root === "/" ? "cwd-app" : root.slice(1)}/${apiName}`);
311
- }
312
-
313
- describe("scaffolder names APIs and can add more later", () => {
314
- it("writes projects/billing when --name billing", async () => {
315
- await runCliAsNodeBin([
316
- "create",
317
- "webapi",
318
- "my-app",
319
- "--name",
320
- "billing",
321
- ...NON_INTERACTIVE,
322
- ]);
323
-
324
- expect(existsSync("/my-app/projects/api")).toBe(false);
325
- expectApiProject("/my-app", "billing");
326
-
327
- const pkg = readJson("/my-app/package.json");
328
- expect(pkg.scripts?.start).toContain("projects/billing");
329
- expect(pkg.scripts?.["start:billing"]).toContain("projects/billing");
330
-
331
- const docker = readFileSync("/my-app/Dockerfile", "utf8");
332
- expect(docker).toContain("COPY projects/billing");
333
- expect(docker).not.toContain("COPY projects/api");
334
- expect(existsSync("/my-app/Dockerfile.billing")).toBe(true);
335
- });
336
-
337
- it("writes multiple APIs for repeated --name", async () => {
338
- await runCliAsNodeBin([
339
- "create",
340
- "webapi",
341
- "my-app",
342
- "--name",
343
- "api",
344
- "--name",
345
- "billing",
346
- ...NON_INTERACTIVE,
347
- ]);
348
-
349
- expectApiProject("/my-app", "api");
350
- expectApiProject("/my-app", "billing");
351
-
352
- const pkg = readJson("/my-app/package.json");
353
- expect(pkg.scripts?.start).toContain("projects/api");
354
- expect(pkg.scripts?.["start:billing"]).toContain("projects/billing");
355
-
356
- expect(readFileSync("/my-app/Dockerfile", "utf8")).toContain("COPY projects/api");
357
- expect(existsSync("/my-app/Dockerfile.billing")).toBe(true);
358
- });
359
-
360
- it("adds a named API to an existing workspace without clobbering the first", async () => {
361
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE]);
362
- writeFileSync("/my-app/biome.json", '{ "custom": true }\n');
363
- writeFileSync("/my-app/Dockerfile", "# keep-root-dockerfile\n");
364
- writeFileSync("/my-app/vitest.config.ts", "// keep-vitest-config\n");
365
-
366
- await runCliAsNodeBin(["add", "api", "billing", "-d", "my-app", ...NON_INTERACTIVE], {
367
- resetFs: false,
368
- });
369
-
370
- expect(readFileSync("/my-app/biome.json", "utf8")).toContain("custom");
371
- expect(readFileSync("/my-app/Dockerfile", "utf8")).toBe("# keep-root-dockerfile\n");
372
- expect(readFileSync("/my-app/vitest.config.ts", "utf8")).toBe("// keep-vitest-config\n");
373
- expectApiProject("/my-app", "api");
374
- expectApiProject("/my-app", "billing");
375
-
376
- const pkg = readJson("/my-app/package.json");
377
- expect(pkg.scripts?.start).toContain("projects/api");
378
- expect(pkg.scripts?.["start:billing"]).toContain("projects/billing");
379
- expect(existsSync("/my-app/Dockerfile.billing")).toBe(true);
380
- });
381
-
382
- it("refuses add when the API already exists", async () => {
383
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE]);
384
- const errors: string[] = [];
385
- vi.spyOn(console, "error").mockImplementation((msg?: unknown) => {
386
- errors.push(String(msg ?? ""));
387
- });
388
-
389
- await runCliAsNodeBin(["add", "api", "api", "-d", "my-app", ...NON_INTERACTIVE], {
390
- resetFs: false,
391
- });
392
-
393
- expect(errors.join("\n")).toMatch(/already exists/);
394
- });
395
-
396
- it("refuses add outside a Decohere workspace", async () => {
397
- const errors: string[] = [];
398
- vi.spyOn(console, "error").mockImplementation((msg?: unknown) => {
399
- errors.push(String(msg ?? ""));
400
- });
401
-
402
- await runCliAsNodeBin(["add", "api", "billing", ...NON_INTERACTIVE]);
403
-
404
- expect(errors.join("\n")).toMatch(/Not a Decohere workspace/);
405
- expect(existsSync("/projects/billing")).toBe(false);
406
- });
407
-
408
- it("refuses an invalid --name", async () => {
409
- const errors: string[] = [];
410
- vi.spyOn(console, "error").mockImplementation((msg?: unknown) => {
411
- errors.push(String(msg ?? ""));
412
- });
413
-
414
- await runCliAsNodeBin([
415
- "create",
416
- "webapi",
417
- "my-app",
418
- "--name",
419
- "Billing",
420
- ...NON_INTERACTIVE,
421
- ]);
422
-
423
- expect(errors.join("\n")).toMatch(/Invalid API name/);
424
- expect(existsSync("/my-app/projects/Billing")).toBe(false);
425
- });
426
- });
427
-
428
- const POSTGRES = [
429
- "--database",
430
- "postgres/typeorm",
431
- "--auth",
432
- "none",
433
- "--postman",
434
- "none",
435
- "--install",
436
- "no",
437
- ] as const;
438
-
439
- describe("scaffolder writes a root README with CLI examples", () => {
440
- it("includes webapi add and pnpm dev, and omits db:* when database is none", async () => {
441
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE]);
442
-
443
- const readme = readFileSync("/my-app/README.md", "utf8");
444
- expect(readme).toContain("decohere add api <name>");
445
- expect(readme).toContain("pnpm dev");
446
- expect(readme).toContain("pnpm install");
447
- expect(readme).toContain("Reopen in Container");
448
- expect(readme).toContain("y/N");
449
- expect(readme).toContain("codebase RAG MCP");
450
- expect(readme).toContain(".cursor/");
451
- expect(readme).toContain("pnpm install --prod");
452
- expect(readme).toContain("## Logging");
453
- expect(readme).toContain("NODE_ENV=production");
454
- expect(readme).not.toContain("pnpm db:up");
455
- expect(readme).not.toContain("## Auth0");
456
- expect(readme).not.toContain("## Postman");
457
- });
458
-
459
- it("includes db:up when postgres is enabled", async () => {
460
- await runCliAsNodeBin(["create", "webapi", "my-app", ...POSTGRES]);
461
-
462
- const readme = readFileSync("/my-app/README.md", "utf8");
463
- expect(readme).toContain("pnpm db:up");
464
- expect(readme).toContain("pnpm db:down");
465
- expect(readme).toContain("pnpm db:reset");
466
- expect(readme).toContain("decohere add api <name>");
467
- expect(readme).toContain("pnpm dev");
468
- });
469
-
470
- it("refreshes API scripts when webapi add runs", async () => {
471
- await runCliAsNodeBin(["create", "webapi", "my-app", ...NON_INTERACTIVE]);
472
- await runCliAsNodeBin(["add", "api", "billing", "-d", "my-app", ...NON_INTERACTIVE], {
473
- resetFs: false,
474
- });
475
-
476
- const readme = readFileSync("/my-app/README.md", "utf8");
477
- expect(readme).toContain("pnpm run start:api");
478
- expect(readme).toContain("pnpm run start:billing");
479
- expect(readme).toContain("pnpm run dev:billing");
480
- expect(readme).toContain("decohere add api <name>");
481
- });
482
-
483
- it("keeps db:* after webapi add when the workspace already has postgres", async () => {
484
- await runCliAsNodeBin(["create", "webapi", "my-app", ...POSTGRES]);
485
- await runCliAsNodeBin(["add", "api", "billing", "-d", "my-app", ...NON_INTERACTIVE], {
486
- resetFs: false,
487
- });
488
-
489
- const readme = readFileSync("/my-app/README.md", "utf8");
490
- expect(readme).toContain("pnpm db:up");
491
- expect(readme).toContain("pnpm run start:billing");
492
- });
493
- });
494
-
495
- describe("each API owns a database on the shared Postgres instance", () => {
496
- it("names the default API datasource and prefixes env", async () => {
497
- await runCliAsNodeBin(["create", "webapi", "my-app", ...POSTGRES]);
498
-
499
- const config = readFileSync("/my-app/config/config.yaml", "utf8");
500
- expect(config).toContain("name: api");
501
- expect(config).toContain("${API_DB_HOST}");
502
- expect(config).not.toContain("name: default");
503
- expect(config).not.toContain("${DB_HOST}");
504
-
505
- const env = readFileSync("/my-app/.env", "utf8");
506
- expect(env).toContain("API_DB_HOST=postgres");
507
- expect(env).toContain("API_DB_PORT=5432");
508
- expect(env).toMatch(/API_DB_NAME=/);
509
- expect(env).not.toMatch(/^DB_NAME=/m);
510
- expect(env).toContain("DB_ADMIN_USER=decohere_bootstrap");
511
-
512
- const main = readFileSync("/my-app/projects/api/src/main.ts", "utf8");
513
- expect(main).toContain("withHealth()");
514
- expect(main).toContain('withTypeOrm({ name: "api"');
515
-
516
- const compose = readFileSync("/my-app/.devcontainer/docker-compose.yml", "utf8");
517
- expect(compose).toContain("samples-my-app-postgres");
518
- expect(compose).toContain("build: ./postgres");
519
- expect(compose).toContain("./postgres-data:/var/lib/postgresql/data");
520
- expect(compose).toMatch(/bind mount|dies with the working tree/i);
521
- expect(compose).not.toContain("postgres-init");
522
- expect(compose).not.toMatch(/^\s+my-app-postgres-data:\s*$/m);
523
- expect(compose).not.toContain("adminer");
524
-
525
- const gitignore = readFileSync("/my-app/.gitignore", "utf8");
526
- expect(gitignore).toContain(".devcontainer/postgres-data");
527
-
528
- const rootPkg = JSON.parse(readFileSync("/my-app/package.json", "utf8")) as {
529
- scripts: Record<string, string>;
530
- dependencies: Record<string, string>;
531
- };
532
- expect(rootPkg.scripts["db:reset"]).toContain("rm -rf .devcontainer/postgres-data");
533
- expect(rootPkg.scripts["db:reset"]).not.toContain("down -v");
534
-
535
- expect(
536
- readFileSync("/my-app/.devcontainer/postgres/ensure-bootstrap-role.sh", "utf8"),
537
- ).toContain("ALTER ROLE");
538
- expect(readFileSync("/my-app/.devcontainer/postgres/Dockerfile", "utf8")).toContain(
539
- "FROM postgres:16",
540
- );
541
-
542
- const appDevDockerfile = readFileSync("/my-app/.devcontainer/Dockerfile", "utf8");
543
- expect(appDevDockerfile).toContain("postgresql-client");
544
- expect(appDevDockerfile).toContain("USER root");
545
- expect(appDevDockerfile).toMatch(/^FROM ghcr\.io\/typesmiths\/decohere\/devcontainer:/m);
546
- const devcontainerJson = readFileSync("/my-app/.devcontainer/devcontainer.json", "utf8");
547
- expect(devcontainerJson).toContain("mtxr.sqltools-driver-pg");
548
-
549
- const depKeys = Object.keys(rootPkg.dependencies);
550
- expect(depKeys).toEqual([...depKeys].sort((a, b) => a.localeCompare(b, "en")));
551
- expect(rootPkg.dependencies.tsx).toBeTruthy();
552
- expect(rootPkg.scripts["db:migrate"]).toContain("migration:run");
553
- expect(rootPkg.scripts["db:migrate"]).toContain("typeorm.data-source.ts");
554
- expect(rootPkg.scripts["db:migrate:generate"]).toContain("migration:generate");
555
- expect(rootPkg.scripts["db:migrate:api"]).toContain("projects/api");
556
-
557
- expect(existsSync("/my-app/docs/database.md")).toBe(true);
558
- const dbDocs = readFileSync("/my-app/docs/database.md", "utf8");
559
- expect(dbDocs).toMatch(/synchronize: true/);
560
- expect(dbDocs).toMatch(/migrations only/i);
561
- expect(dbDocs).toContain("db:migrate");
562
- expect(dbDocs).toMatch(/Adminer is \*\*not\*\* scaffolded/i);
563
- expect(dbDocs).toContain("image: adminer");
564
-
565
- expect(existsSync("/my-app/projects/api/src/typeorm.data-source.ts")).toBe(true);
566
- const dataSource = readFileSync("/my-app/projects/api/src/typeorm.data-source.ts", "utf8");
567
- expect(dataSource).toContain("API_DB_MIGRATE_USER");
568
- expect(dataSource).toContain("synchronize: false");
569
- expect(existsSync("/my-app/projects/api/src/migrations/.gitkeep")).toBe(true);
570
- });
571
-
572
- it("emits per-API migrate scripts and data sources for named APIs", async () => {
573
- await runCliAsNodeBin([
574
- "create",
575
- "webapi",
576
- "my-app",
577
- "--name",
578
- "api",
579
- "--name",
580
- "billing",
581
- ...POSTGRES,
582
- ]);
583
-
584
- const pkg = JSON.parse(readFileSync("/my-app/package.json", "utf8")) as {
585
- scripts: Record<string, string>;
586
- };
587
- expect(pkg.scripts["db:migrate:billing"]).toContain("projects/billing");
588
- expect(pkg.scripts["db:migrate:generate:billing"]).toContain("migration:generate");
589
- expect(existsSync("/my-app/projects/billing/src/typeorm.data-source.ts")).toBe(true);
590
- expect(
591
- readFileSync("/my-app/projects/billing/src/typeorm.data-source.ts", "utf8"),
592
- ).toContain("BILLING_DB_MIGRATE_USER");
593
- });
594
-
595
- it("gives each named API its own database on the same host", async () => {
596
- await runCliAsNodeBin([
597
- "create",
598
- "webapi",
599
- "my-app",
600
- "--name",
601
- "api",
602
- "--name",
603
- "billing",
604
- ...POSTGRES,
605
- ]);
606
-
607
- const config = readFileSync("/my-app/config/config.yaml", "utf8");
608
- expect(config).toContain("name: api");
609
- expect(config).toContain("name: billing");
610
- expect(config).toContain("${BILLING_DB_HOST}");
611
-
612
- const env = readFileSync("/my-app/.env", "utf8");
613
- expect(env).toContain("API_DB_HOST=postgres");
614
- expect(env).toContain("BILLING_DB_HOST=postgres");
615
- const apiDb = env.split("\n").find((l) => l.startsWith("API_DB_NAME="));
616
- const billingDb = env.split("\n").find((l) => l.startsWith("BILLING_DB_NAME="));
617
- expect(apiDb).toBeTruthy();
618
- expect(billingDb).toBeTruthy();
619
- expect(apiDb).not.toBe(billingDb);
620
-
621
- expect(readFileSync("/my-app/projects/api/src/main.ts", "utf8")).toContain(
622
- 'withTypeOrm({ name: "api"',
623
- );
624
- expect(readFileSync("/my-app/projects/billing/src/main.ts", "utf8")).toContain(
625
- 'withTypeOrm({ name: "billing"',
626
- );
627
-
628
- const compose = readFileSync("/my-app/.devcontainer/docker-compose.yml", "utf8");
629
- expect(compose).toContain("container_name: samples-my-app-postgres");
630
- expect(compose).not.toContain("samples-my-app-billing-postgres");
631
- });
632
-
633
- it("adds a datasource for webapi add without replacing the first", async () => {
634
- await runCliAsNodeBin(["create", "webapi", "my-app", ...POSTGRES]);
635
- await runCliAsNodeBin(["add", "api", "billing", "-d", "my-app", ...POSTGRES], {
636
- resetFs: false,
637
- });
638
-
639
- const config = readFileSync("/my-app/config/config.yaml", "utf8");
640
- expect(config).toContain("name: api");
641
- expect(config).toContain("name: billing");
642
- expect(config.match(/^database:/gm)?.length).toBe(1);
643
-
644
- const env = readFileSync("/my-app/.env", "utf8");
645
- expect(env).toContain("API_DB_HOST=postgres");
646
- expect(env).toContain("BILLING_DB_HOST=postgres");
647
- expect(readFileSync("/my-app/projects/billing/src/main.ts", "utf8")).toContain(
648
- 'withTypeOrm({ name: "billing"',
649
- );
650
-
651
- const pkg = JSON.parse(readFileSync("/my-app/package.json", "utf8")) as {
652
- scripts: Record<string, string>;
653
- };
654
- expect(pkg.scripts["db:migrate"]).toContain("projects/api");
655
- expect(pkg.scripts["db:migrate:billing"]).toContain("projects/billing");
656
- expect(existsSync("/my-app/projects/billing/src/typeorm.data-source.ts")).toBe(true);
657
- });
658
- });