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

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 (240) hide show
  1. package/commonjs/src/add-api.action.js +39 -0
  2. package/commonjs/src/add-api.command.js +41 -0
  3. package/commonjs/src/add.command.js +24 -0
  4. package/commonjs/src/add.module.js +26 -0
  5. package/commonjs/src/cli.js +9 -0
  6. package/commonjs/src/create.command.js +24 -0
  7. package/commonjs/src/create.module.js +26 -0
  8. package/commonjs/src/domain/prompt.js +64 -0
  9. package/commonjs/src/index.js +38 -0
  10. package/commonjs/src/models/compose.model.js +150 -0
  11. package/commonjs/src/models/config-yaml.model.js +88 -0
  12. package/commonjs/src/models/const.js +32 -0
  13. package/commonjs/src/models/dev-container.model.js +116 -0
  14. package/commonjs/src/models/docker-image.model.js +259 -0
  15. package/commonjs/src/models/env-file.model.js +50 -0
  16. package/commonjs/src/models/gitignore.model.js +43 -0
  17. package/commonjs/src/models/launch-json.model.js +74 -0
  18. package/commonjs/src/models/package-json.model.js +149 -0
  19. package/commonjs/src/models/project.model.js +134 -0
  20. package/commonjs/src/models/source-file.js +6 -0
  21. package/commonjs/src/models/tsconfig.model.js +34 -0
  22. package/commonjs/src/models/vitest-config.model.js +35 -0
  23. package/commonjs/src/models/vscode-settings.model.js +31 -0
  24. package/commonjs/src/program.js +57 -0
  25. package/commonjs/src/resolve-target-dir.js +19 -0
  26. package/commonjs/src/services/scaffold.service.js +62 -0
  27. package/commonjs/src/version.js +57 -0
  28. package/commonjs/src/webapi/api-name.js +95 -0
  29. package/commonjs/src/webapi/cli-options.js +101 -0
  30. package/commonjs/src/webapi/const.js +50 -0
  31. package/commonjs/src/webapi/create-api.js +237 -0
  32. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +166 -0
  33. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  34. package/commonjs/src/webapi/devcontainer/host-scripts.js +52 -0
  35. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  36. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
  37. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
  38. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
  39. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  40. package/commonjs/src/webapi/features/auth.feature.js +441 -0
  41. package/commonjs/src/webapi/features/auth0-cli.js +435 -0
  42. package/commonjs/src/webapi/features/biome-format.js +49 -0
  43. package/commonjs/src/webapi/features/database.feature.js +433 -0
  44. package/commonjs/src/webapi/features/finalize.feature.js +95 -0
  45. package/commonjs/src/webapi/features/postman.feature.js +324 -0
  46. package/commonjs/src/webapi/models/api-package-json.model.js +60 -0
  47. package/commonjs/src/webapi/models/auth-config.model.js +26 -0
  48. package/commonjs/src/webapi/models/controller-source-file.model.js +193 -0
  49. package/commonjs/src/webapi/models/github-ci.model.js +193 -0
  50. package/commonjs/src/webapi/models/main-source-file.model.js +167 -0
  51. package/commonjs/src/webapi/models/production-dockerfile.model.js +157 -0
  52. package/commonjs/src/webapi/models/sample-files.model.js +60 -0
  53. package/commonjs/src/webapi/models/webapi-image.model.js +27 -0
  54. package/commonjs/src/webapi/postman/hello-openapi.js +175 -0
  55. package/commonjs/src/webapi/postman/local-view.js +184 -0
  56. package/commonjs/src/webapi/postman/openapi-to-postman.js +202 -0
  57. package/commonjs/src/webapi/workspace-readme.js +110 -0
  58. package/commonjs/src/webapi.action.js +30 -0
  59. package/commonjs/src/webapi.command.js +45 -0
  60. package/es6/src/add-api.action.js +29 -0
  61. package/es6/src/add-api.command.js +31 -0
  62. package/es6/src/add.command.js +14 -0
  63. package/es6/src/add.module.js +16 -0
  64. package/es6/src/cli.js +5 -0
  65. package/es6/src/create.command.js +14 -0
  66. package/es6/src/create.module.js +16 -0
  67. package/es6/src/domain/prompt.js +43 -0
  68. package/es6/src/index.js +8 -0
  69. package/es6/src/models/compose.model.js +144 -0
  70. package/es6/src/models/config-yaml.model.js +76 -0
  71. package/es6/src/models/const.js +22 -0
  72. package/es6/src/models/dev-container.model.js +106 -0
  73. package/es6/src/models/docker-image.model.js +217 -0
  74. package/es6/src/models/env-file.model.js +46 -0
  75. package/es6/src/models/gitignore.model.js +33 -0
  76. package/es6/src/models/launch-json.model.js +64 -0
  77. package/es6/src/models/package-json.model.js +137 -0
  78. package/es6/src/models/project.model.js +119 -0
  79. package/es6/src/models/source-file.js +3 -0
  80. package/es6/src/models/tsconfig.model.js +24 -0
  81. package/es6/src/models/vitest-config.model.js +25 -0
  82. package/es6/src/models/vscode-settings.model.js +21 -0
  83. package/es6/src/program.js +39 -0
  84. package/es6/src/resolve-target-dir.js +9 -0
  85. package/es6/src/services/scaffold.service.js +52 -0
  86. package/es6/src/version.js +36 -0
  87. package/es6/src/webapi/api-name.js +59 -0
  88. package/es6/src/webapi/cli-options.js +36 -0
  89. package/es6/src/webapi/const.js +20 -0
  90. package/es6/src/webapi/create-api.js +217 -0
  91. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +156 -0
  92. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  93. package/es6/src/webapi/devcontainer/host-scripts.js +31 -0
  94. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  95. package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
  96. package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
  97. package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
  98. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  99. package/es6/src/webapi/features/auth.feature.js +390 -0
  100. package/es6/src/webapi/features/auth0-cli.js +375 -0
  101. package/es6/src/webapi/features/biome-format.js +42 -0
  102. package/es6/src/webapi/features/database.feature.js +423 -0
  103. package/es6/src/webapi/features/finalize.feature.js +85 -0
  104. package/es6/src/webapi/features/postman.feature.js +258 -0
  105. package/es6/src/webapi/models/api-package-json.model.js +55 -0
  106. package/es6/src/webapi/models/auth-config.model.js +22 -0
  107. package/es6/src/webapi/models/controller-source-file.model.js +184 -0
  108. package/es6/src/webapi/models/github-ci.model.js +186 -0
  109. package/es6/src/webapi/models/main-source-file.model.js +163 -0
  110. package/es6/src/webapi/models/production-dockerfile.model.js +148 -0
  111. package/es6/src/webapi/models/sample-files.model.js +42 -0
  112. package/es6/src/webapi/models/webapi-image.model.js +20 -0
  113. package/es6/src/webapi/postman/hello-openapi.js +168 -0
  114. package/es6/src/webapi/postman/local-view.js +169 -0
  115. package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
  116. package/es6/src/webapi/workspace-readme.js +89 -0
  117. package/es6/src/webapi.action.js +20 -0
  118. package/es6/src/webapi.command.js +35 -0
  119. package/package.json +8 -8
  120. package/types/src/add-api.action.d.ts +13 -0
  121. package/types/src/add-api.command.d.ts +7 -0
  122. package/types/src/add.command.d.ts +6 -0
  123. package/types/src/add.module.d.ts +14 -0
  124. package/types/src/cli.d.ts +2 -0
  125. package/types/src/create.command.d.ts +6 -0
  126. package/types/src/create.module.d.ts +14 -0
  127. package/types/src/domain/prompt.d.ts +4 -0
  128. package/types/src/models/compose.model.d.ts +64 -0
  129. package/types/src/models/config-yaml.model.d.ts +27 -0
  130. package/types/src/models/const.d.ts +20 -0
  131. package/types/src/models/dev-container.model.d.ts +37 -0
  132. package/types/src/models/docker-image.model.d.ts +84 -0
  133. package/types/src/models/env-file.model.d.ts +16 -0
  134. package/types/src/models/gitignore.model.d.ts +7 -0
  135. package/types/src/models/launch-json.model.d.ts +7 -0
  136. package/types/src/models/package-json.model.d.ts +35 -0
  137. package/types/src/models/project.model.d.ts +60 -0
  138. package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
  139. package/types/src/models/tsconfig.model.d.ts +5 -0
  140. package/types/src/models/vitest-config.model.d.ts +5 -0
  141. package/types/src/models/vscode-settings.model.d.ts +7 -0
  142. package/types/src/program.d.ts +3 -0
  143. package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
  144. package/types/src/services/scaffold.service.d.ts +22 -0
  145. package/types/src/version.d.ts +6 -0
  146. package/types/src/webapi/api-name.d.ts +14 -0
  147. package/types/src/webapi/cli-options.d.ts +6 -0
  148. package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +7 -21
  149. package/types/src/webapi/create-api.d.ts +15 -0
  150. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
  151. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  152. package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
  153. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  154. package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
  155. package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
  156. package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
  157. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  158. package/types/src/webapi/features/auth.feature.d.ts +10 -0
  159. package/types/src/webapi/features/auth0-cli.d.ts +80 -0
  160. package/types/src/webapi/features/biome-format.d.ts +5 -0
  161. package/types/src/webapi/features/database.feature.d.ts +7 -0
  162. package/types/src/webapi/features/finalize.feature.d.ts +2 -0
  163. package/types/src/webapi/features/postman.feature.d.ts +16 -0
  164. package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
  165. package/types/src/webapi/models/auth-config.model.d.ts +26 -0
  166. package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
  167. package/types/src/webapi/models/github-ci.model.d.ts +9 -0
  168. package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
  169. package/types/src/webapi/models/production-dockerfile.model.d.ts +23 -0
  170. package/types/src/webapi/models/sample-files.model.d.ts +7 -0
  171. package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
  172. package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
  173. package/types/src/webapi/postman/local-view.d.ts +17 -0
  174. package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
  175. package/types/src/webapi/workspace-readme.d.ts +18 -0
  176. package/types/src/webapi.action.d.ts +14 -0
  177. package/types/src/webapi.command.d.ts +11 -0
  178. package/src/add-api.action.ts +0 -25
  179. package/src/add-api.command.ts +0 -31
  180. package/src/add.command.ts +0 -20
  181. package/src/add.module.ts +0 -11
  182. package/src/cli.ts +0 -4
  183. package/src/create.command.ts +0 -20
  184. package/src/create.module.ts +0 -11
  185. package/src/domain/prompt.ts +0 -50
  186. package/src/models/compose.model.ts +0 -205
  187. package/src/models/config-yaml.model.ts +0 -83
  188. package/src/models/const.ts +0 -20
  189. package/src/models/dev-container.model.ts +0 -136
  190. package/src/models/docker-image.model.ts +0 -343
  191. package/src/models/env-file.model.ts +0 -45
  192. package/src/models/gitignore.model.ts +0 -35
  193. package/src/models/launch-json.model.ts +0 -60
  194. package/src/models/package-json.model.ts +0 -169
  195. package/src/models/project.model.ts +0 -141
  196. package/src/models/tsconfig.model.ts +0 -29
  197. package/src/models/vitest-config.model.ts +0 -26
  198. package/src/models/vscode-settings.model.ts +0 -24
  199. package/src/program.ts +0 -63
  200. package/src/services/scaffold.service.ts +0 -89
  201. package/src/version.ts +0 -56
  202. package/src/webapi/api-name.ts +0 -73
  203. package/src/webapi/cli-options.ts +0 -56
  204. package/src/webapi/create-api.ts +0 -275
  205. package/src/webapi/devcontainer/host-scripts.ts +0 -35
  206. package/src/webapi/features/auth.feature.ts +0 -454
  207. package/src/webapi/features/auth0-cli.ts +0 -421
  208. package/src/webapi/features/biome-format.ts +0 -34
  209. package/src/webapi/features/database.feature.ts +0 -522
  210. package/src/webapi/features/finalize.feature.ts +0 -112
  211. package/src/webapi/features/postman.feature.ts +0 -287
  212. package/src/webapi/models/api-package-json.model.ts +0 -60
  213. package/src/webapi/models/auth-config.model.ts +0 -36
  214. package/src/webapi/models/controller-source-file.model.ts +0 -232
  215. package/src/webapi/models/github-ci.model.ts +0 -188
  216. package/src/webapi/models/main-source-file.model.ts +0 -208
  217. package/src/webapi/models/production-dockerfile.model.ts +0 -162
  218. package/src/webapi/models/sample-files.model.ts +0 -45
  219. package/src/webapi/models/webapi-image.model.ts +0 -21
  220. package/src/webapi/postman/hello-openapi.ts +0 -92
  221. package/src/webapi/postman/local-view.ts +0 -195
  222. package/src/webapi/postman/openapi-to-postman.ts +0 -208
  223. package/src/webapi/workspace-readme.ts +0 -192
  224. package/src/webapi.action.ts +0 -22
  225. package/src/webapi.command.ts +0 -41
  226. package/tests/auth-postman.wiring.spec.ts +0 -263
  227. package/tests/auth0-cli.spec.ts +0 -232
  228. package/tests/biome-format.spec.ts +0 -49
  229. package/tests/cli.integration.spec.ts +0 -658
  230. package/tests/compose.model.spec.ts +0 -51
  231. package/tests/container-image.model.spec.ts +0 -162
  232. package/tests/dev-container.model.spec.ts +0 -29
  233. package/tests/devcontainer-base-image.spec.ts +0 -32
  234. package/tests/hello-di-inject.scaffold.spec.ts +0 -18
  235. package/tests/hello-route.contract.spec.ts +0 -94
  236. package/tests/package-json.order.spec.ts +0 -59
  237. package/tests/postman-from-openapi.spec.ts +0 -197
  238. package/tests/seed-devcontainer-templates.ts +0 -40
  239. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
  240. /package/{src/index.ts → types/src/index.d.ts} +0 -0
@@ -1,188 +0,0 @@
1
- import { WELL_KNOWN_FILES } from "../../models/const.js";
2
- import type { KeyedModel, SourceFile } from "../../models/source-file.js";
3
-
4
- /**
5
- * Minimal GitHub Actions for scaffolded apps: lint ∥ test, build gate on default branch,
6
- * npm publish after CI (skips private packages). No default forge docker --push.
7
- */
8
- export class GithubCiWorkflows implements KeyedModel {
9
- public readonly key = WELL_KNOWN_FILES.GITHUB_CI;
10
-
11
- public files(): SourceFile[] {
12
- return [
13
- {
14
- filePath: ".github/actions/setup-node-pnpm/action.yml",
15
- content: `name: Setup Node + pnpm
16
- description: Install pnpm and Node, configure npm registry auth, install deps
17
- inputs:
18
- node-version:
19
- description: Node.js version
20
- required: false
21
- default: "24"
22
- pnpm-version:
23
- description: pnpm version
24
- required: false
25
- default: "10"
26
- install:
27
- description: Run pnpm install --frozen-lockfile
28
- required: false
29
- default: "true"
30
-
31
- runs:
32
- using: composite
33
- steps:
34
- - uses: pnpm/action-setup@v4
35
- with:
36
- version: \${{ inputs.pnpm-version }}
37
-
38
- - uses: actions/setup-node@v4
39
- with:
40
- node-version: \${{ inputs.node-version }}
41
- cache: pnpm
42
- cache-dependency-path: pnpm-lock.yaml
43
-
44
- - name: Configure npm auth
45
- shell: bash
46
- run: |
47
- if [[ -n "\${NODE_AUTH_TOKEN:-}" ]]; then
48
- echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" >> .npmrc
49
- fi
50
-
51
- - name: Install dependencies
52
- if: inputs.install == 'true'
53
- shell: bash
54
- run: |
55
- if [[ -f pnpm-lock.yaml ]]; then
56
- pnpm install --frozen-lockfile
57
- else
58
- pnpm install
59
- fi
60
- `,
61
- },
62
- {
63
- filePath: ".github/workflows/ci.yml",
64
- content: `name: CI
65
- on:
66
- push:
67
- branches:
68
- - "*"
69
- pull_request:
70
- workflow_dispatch:
71
- concurrency:
72
- group: ci-\${{ github.ref }}
73
- cancel-in-progress: true
74
- env:
75
- NODE_AUTH_TOKEN: \${{ secrets.NODE_AUTH_TOKEN }}
76
- SCRIBE_LOG_LEVEL: info
77
-
78
- jobs:
79
- lint:
80
- runs-on: ubuntu-latest
81
- timeout-minutes: 15
82
- permissions:
83
- contents: read
84
- steps:
85
- - uses: actions/checkout@v4
86
- with:
87
- fetch-depth: 1
88
-
89
- - uses: ./.github/actions/setup-node-pnpm
90
-
91
- - name: Lint and typecheck
92
- run: |
93
- set +e
94
- pnpm run lint:check &
95
- lint_pid=$!
96
- pnpm run typecheck &
97
- tsc_pid=$!
98
- wait "$lint_pid"
99
- lint_code=$?
100
- wait "$tsc_pid"
101
- tsc_code=$?
102
- set -e
103
- if [[ "$lint_code" -ne 0 || "$tsc_code" -ne 0 ]]; then
104
- exit 1
105
- fi
106
-
107
- test:
108
- runs-on: ubuntu-latest
109
- timeout-minutes: 20
110
- permissions:
111
- contents: read
112
- steps:
113
- - uses: actions/checkout@v4
114
- with:
115
- fetch-depth: 1
116
-
117
- - uses: ./.github/actions/setup-node-pnpm
118
-
119
- - name: Test
120
- run: pnpm test
121
-
122
- - name: Build (default branch)
123
- if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
124
- run: pnpm run build
125
-
126
- ci:
127
- needs: [lint, test]
128
- if: always()
129
- runs-on: ubuntu-latest
130
- timeout-minutes: 5
131
- steps:
132
- - name: Aggregate
133
- run: |
134
- if [[ "\${{ needs.lint.result }}" != "success" || "\${{ needs.test.result }}" != "success" ]]; then
135
- echo "CI failed (lint=\${{ needs.lint.result }} test=\${{ needs.test.result }})"
136
- exit 1
137
- fi
138
- echo "CI passed"
139
- `,
140
- },
141
- {
142
- filePath: ".github/workflows/publish.yml",
143
- content: `name: Publish
144
-
145
- on:
146
- workflow_run:
147
- workflows: ["CI"]
148
- types: [completed]
149
-
150
- concurrency:
151
- group: publish-\${{ github.event.workflow_run.head_branch }}
152
- cancel-in-progress: false
153
-
154
- env:
155
- NODE_AUTH_TOKEN: \${{ secrets.NODE_AUTH_TOKEN }}
156
- SCRIBE_LOG_LEVEL: info
157
-
158
- jobs:
159
- publish:
160
- if: >
161
- github.event.workflow_run.conclusion == 'success' &&
162
- github.event.workflow_run.head_branch == github.event.repository.default_branch
163
- runs-on: ubuntu-latest
164
- timeout-minutes: 30
165
- permissions:
166
- contents: read
167
- id-token: write
168
- steps:
169
- - uses: actions/checkout@v4
170
- with:
171
- ref: \${{ github.event.workflow_run.head_sha }}
172
- fetch-depth: 1
173
-
174
- - uses: ./.github/actions/setup-node-pnpm
175
-
176
- - name: Build release
177
- run: pnpm run build
178
-
179
- - name: Publish packages
180
- run: pnpm exec forge publish --configuration release
181
- # Skips packages with "private": true (e.g. the webapi app).
182
- # Optional registry images: add \`pnpm exec forge docker --configuration release --push\`
183
- # after configuring GHCR/OIDC — not enabled by default (PaaS builds the Dockerfile).
184
- `,
185
- },
186
- ];
187
- }
188
- }
@@ -1,208 +0,0 @@
1
- import { WELL_KNOWN_FILES } from "../../models/const.js";
2
- import type { KeyedModel, SourceFile } from "../../models/source-file.js";
3
-
4
- export type TypeOrmImport = { className: string; importPath: string };
5
- export type ServiceImport = TypeOrmImport & { scope?: "singleton" | "request" };
6
-
7
- /** Tracks named imports from a single module path, split into values and types. */
8
- class ImportGroup {
9
- private readonly _values: string[] = [];
10
- private readonly _types: string[] = [];
11
-
12
- public add(name: string, typeOnly = false): this {
13
- if (typeOnly) {
14
- // Value import already present — value wins, skip
15
- if (!this._values.includes(name) && !this._types.includes(name)) {
16
- this._types.push(name);
17
- }
18
- } else {
19
- // Promote: remove from types if it was previously type-only
20
- const typeIdx = this._types.indexOf(name);
21
- if (typeIdx !== -1) this._types.splice(typeIdx, 1);
22
- if (!this._values.includes(name)) this._values.push(name);
23
- }
24
- return this;
25
- }
26
-
27
- public render(from: string): string {
28
- const values = [...this._values].sort();
29
- const types = [...this._types].sort().map((n) => `type ${n}`);
30
- return `import { ${[...values, ...types].join(", ")} } from "${from}";`;
31
- }
32
- }
33
-
34
- /**
35
- * Model for `projects/api/src/main.ts`.
36
- *
37
- * Starts with the minimal HTTP + YAML config setup. Features mutate the model
38
- * by calling addSideEffectImport / addImport / addBuilderCall / addEntity /
39
- * addRepository. files() renders whatever has been accumulated.
40
- */
41
- export class MainSourceFile implements KeyedModel {
42
- public readonly key = WELL_KNOWN_FILES.MAIN_TS;
43
-
44
- private readonly _sideEffectImports: string[] = [];
45
- private readonly _extraImports = new Map<string, ImportGroup>();
46
- private readonly _builderCalls: string[] = [];
47
- private readonly _entities: TypeOrmImport[] = [];
48
- private readonly _repositories: TypeOrmImport[] = [];
49
- private readonly _services: ServiceImport[] = [];
50
- private _dataSourceName: string | undefined;
51
- private _typeOrmSeed: string | undefined;
52
-
53
- public constructor(private readonly appName: string) {}
54
-
55
- /** Adds a bare side-effect import, e.g. `@decohere/cortex/auth`. */
56
- public addSideEffectImport(path: string): this {
57
- if (!this._sideEffectImports.includes(path)) this._sideEffectImports.push(path);
58
- return this;
59
- }
60
-
61
- /** Adds a named import, e.g. `import { Foo } from "bar"`. */
62
- public addImport(name: string, from: string, typeOnly = false): this {
63
- const group = this._extraImports.get(from) ?? new ImportGroup();
64
- group.add(name, typeOnly);
65
- this._extraImports.set(from, group);
66
- return this;
67
- }
68
-
69
- /**
70
- * Adds a builder call inserted after `.withConfiguration()` and before
71
- * `.withServices()`. Pass the call without leading dot, e.g. `.withAuth()`.
72
- */
73
- public addBuilderCall(call: string): this {
74
- this._builderCalls.push(call);
75
- return this;
76
- }
77
-
78
- /** Adds a TypeORM entity — import and entry in `withTypeOrm({ entities })`. */
79
- public addEntity(entity: TypeOrmImport): this {
80
- this._entities.push(entity);
81
- return this;
82
- }
83
-
84
- /** Adds a TypeORM repository — import and entry in `withTypeOrm({ repositories })`. */
85
- public addRepository(repo: TypeOrmImport): this {
86
- this._repositories.push(repo);
87
- return this;
88
- }
89
-
90
- /** Adds a service registered in `.withServices()`. */
91
- public addService(service: ServiceImport): this {
92
- this._services.push(service);
93
- return this;
94
- }
95
-
96
- /**
97
- * Datasource name passed to `withTypeOrm({ name })`. Must match
98
- * `database.dataSources[].name` in config.yaml.
99
- */
100
- public setDataSourceName(name: string): this {
101
- this._dataSourceName = name;
102
- return this;
103
- }
104
-
105
- /**
106
- * Inline seed callback body for `withTypeOrm({ seed })`.
107
- * Receives `dataSource` in scope. Example: `await dataSource.getRepository(User).save(...)`.
108
- */
109
- public setTypeOrmSeed(seedBody: string): this {
110
- this._typeOrmSeed = seedBody;
111
- return this;
112
- }
113
-
114
- public files(): SourceFile[] {
115
- const imports: string[] = [
116
- 'import "@decohere/scribe-pino";',
117
- 'import "@decohere/cortex/http";',
118
- ];
119
-
120
- for (const path of this._sideEffectImports) {
121
- imports.push(`import "${path}";`);
122
- }
123
-
124
- imports.push(
125
- 'import "@decohere/protocol";',
126
- 'import { YamlDataSource } from "@decohere/protocol";',
127
- 'import { Builder } from "@decohere/cortex";',
128
- 'import { FastifyProvider } from "@decohere/cortex/providers";',
129
- 'import { scribe } from "@decohere/scribe";',
130
- 'import { SampleService } from "./services/sample.service.js";',
131
- );
132
-
133
- for (const e of this._entities) {
134
- imports.push(`import { ${e.className} } from "${e.importPath}";`);
135
- }
136
-
137
- for (const r of this._repositories) {
138
- imports.push(`import { ${r.className} } from "${r.importPath}";`);
139
- }
140
-
141
- for (const s of this._services) {
142
- imports.push(`import { ${s.className} } from "${s.importPath}";`);
143
- }
144
-
145
- for (const [from, group] of this._extraImports) {
146
- imports.push(group.render(from));
147
- }
148
-
149
- imports.push('import { HelloController } from "./controllers/hello.controller.js";');
150
-
151
- const serviceRegs = [
152
- "cb.registerSingleton(SampleService);",
153
- ...this._services.map((s) =>
154
- s.scope === "request"
155
- ? `cb.registerRequestScoped(${s.className});`
156
- : `cb.registerSingleton(${s.className});`,
157
- ),
158
- ].join(" ");
159
-
160
- const builderCalls: string[] = [
161
- `\t.withHttp(FastifyProvider)`,
162
- `\t.withConfiguration(YamlDataSource)`,
163
- `\t.withHealth()`,
164
- `\t.withOpenApi({ info: { title: "${this.appName}", version: "1.0.0" } })`,
165
- ...this._builderCalls.map((c) => `\t${c}`),
166
- `\t.withServices((cb) => { ${serviceRegs} })`,
167
- ];
168
-
169
- if (this._entities.length > 0) {
170
- const entities = this._entities.map((e) => e.className).join(", ");
171
- const repos = this._repositories.map((r) => r.className).join(", ");
172
- const named = this._dataSourceName != null ? `name: "${this._dataSourceName}", ` : "";
173
- if (this._typeOrmSeed) {
174
- builderCalls.push(
175
- `\t.withTypeOrm({ ${named}entities: [${entities}], repositories: [${repos}], seed: async (dataSource) => { ${this._typeOrmSeed} } })`,
176
- );
177
- } else {
178
- builderCalls.push(
179
- `\t.withTypeOrm({ ${named}entities: [${entities}], repositories: [${repos}] })`,
180
- );
181
- }
182
- }
183
-
184
- builderCalls.push(`\t.withControllers(HelloController)`);
185
-
186
- const content = [
187
- ...imports,
188
- "",
189
- `scribe.withPinoPretty().and.withPinoRoll({ file: "logs/${this.appName}.log" });`,
190
- "",
191
- `const app = await Builder.create({ applicationName: "${this.appName}" })`,
192
- ...builderCalls,
193
- `\t.build();`,
194
- "",
195
- "await app.start({ port: Number(process.env.PORT ?? 3000) });",
196
- "",
197
- "export default app;",
198
- "",
199
- ].join("\n");
200
-
201
- return [
202
- {
203
- filePath: "src/main.ts",
204
- content,
205
- },
206
- ];
207
- }
208
- }
@@ -1,162 +0,0 @@
1
- import { WELL_KNOWN_FILES } from "../../models/const.js";
2
- import type { KeyedModel, SourceFile } from "../../models/source-file.js";
3
- import { DEFAULT_WEBAPI_NAME, webapiProjectDir, webapiReleaseEntry } from "../const.js";
4
-
5
- export type ProductionDockerfileOptions = {
6
- /** When true, only write `Dockerfile.<name>` (existing workspace — do not replace root Dockerfile). */
7
- namedOnly?: boolean;
8
- };
9
-
10
- /**
11
- * Production Dockerfiles for PaaS (Railway/Fly) and local `docker build`.
12
- *
13
- * Context is always the **repo root** (`docker build -f Dockerfile.billing .`).
14
- * Root `Dockerfile` is the first API (`pnpm start`). Extra or custom-named APIs
15
- * also get `Dockerfile.<name>` so each service can be deployed on its own.
16
- *
17
- * Runtime `node_modules` come from a **prod-deps** stage (`pnpm install --prod`),
18
- * not the full build install (Forge, TypeScript, Biome, Vitest).
19
- */
20
- export class WebapiProductionDockerfile implements KeyedModel {
21
- public readonly key = WELL_KNOWN_FILES.PRODUCTION_DOCKERFILE;
22
-
23
- public constructor(
24
- private readonly apiNames: readonly string[] = [DEFAULT_WEBAPI_NAME],
25
- private readonly options: ProductionDockerfileOptions = {},
26
- ) {}
27
-
28
- public files(): SourceFile[] {
29
- const names = this.apiNames.length > 0 ? this.apiNames : [DEFAULT_WEBAPI_NAME];
30
- const first = names[0] ?? DEFAULT_WEBAPI_NAME;
31
- const files: SourceFile[] = [];
32
-
33
- if (!this.options.namedOnly) {
34
- files.push({
35
- filePath: ".dockerignore",
36
- content: dockerignoreContent(),
37
- });
38
- files.push({
39
- filePath: "Dockerfile",
40
- content: renderProductionDockerfile(first, "start"),
41
- });
42
- }
43
-
44
- const emitNamed =
45
- this.options.namedOnly || names.length > 1 || first !== DEFAULT_WEBAPI_NAME;
46
- if (emitNamed) {
47
- for (const name of names) {
48
- files.push({
49
- filePath: `Dockerfile.${name}`,
50
- content: renderProductionDockerfile(name, `start:${name}`),
51
- });
52
- }
53
- }
54
-
55
- return files;
56
- }
57
- }
58
-
59
- export function renderProductionDockerfile(apiName: string, startScript: string): string {
60
- const projectDir = webapiProjectDir(apiName);
61
- const startCmd =
62
- startScript === "start"
63
- ? 'CMD ["pnpm", "start"]'
64
- : `CMD ["pnpm", "run", "${startScript}"]`;
65
- const startEntry = webapiReleaseEntry(apiName);
66
- const dockerfileFlag = startScript === "start" ? "" : `.${apiName}`;
67
-
68
- return `# syntax=docker/dockerfile:1
69
- # Self-contained production image: install + forge build happen inside docker build.
70
- # Hosts (Railway/Fly/etc.) clone git and run \`docker build\` — they do not receive CI dist/.
71
- # Build from the repo root: docker build -f Dockerfile${dockerfileFlag} .
72
- #
73
- # Runtime node_modules are a production-only install (prod-deps). The build stage's
74
- # full install (Forge, TypeScript, Biome, Vitest) is not copied into the run image.
75
- #
76
- # Private @decohere/* packages: pass a BuildKit secret named npm_token (npm auth token), e.g.
77
- # docker build --secret id=npm_token,env=NODE_AUTH_TOKEN .
78
- # Or set NODE_AUTH_TOKEN as a build-arg on hosts that only support build-args.
79
-
80
- FROM node:24-bookworm AS build
81
- WORKDIR /app
82
- RUN corepack enable && corepack prepare pnpm@10 --activate
83
-
84
- COPY package.json ./
85
- COPY pnpm-lock.yaml* ./
86
- COPY tsconfig.json biome.json ./
87
- COPY config ./config
88
- COPY ${projectDir} ./${projectDir}
89
-
90
- ${renderPnpmInstall("all")}
91
-
92
- RUN pnpm run build
93
-
94
- FROM node:24-bookworm AS prod-deps
95
- WORKDIR /app
96
- ENV NODE_ENV=production
97
- RUN corepack enable && corepack prepare pnpm@10 --activate
98
-
99
- COPY package.json ./
100
- COPY pnpm-lock.yaml* ./
101
- COPY ${projectDir}/package.json ./${projectDir}/package.json
102
-
103
- ${renderPnpmInstall("prod")}
104
-
105
- FROM node:24-bookworm-slim AS run
106
- WORKDIR /app
107
- ENV NODE_ENV=production
108
- RUN corepack enable && corepack prepare pnpm@10 --activate
109
-
110
- COPY --from=prod-deps /app/package.json ./
111
- COPY --from=prod-deps /app/pnpm-lock.yaml* ./
112
- COPY --from=build /app/config ./config
113
- COPY --from=build /app/dist ./dist
114
- COPY --from=prod-deps /app/node_modules ./node_modules
115
- COPY --from=prod-deps /app/${projectDir}/package.json ./${projectDir}/package.json
116
-
117
- EXPOSE 3000
118
- ${startCmd}
119
- # Entry resolved by package.json "${startScript}" → node ${startEntry}
120
- `;
121
- }
122
-
123
- /** npm auth + pnpm install. Token is required again in prod-deps (separate stage, no store). */
124
- function renderPnpmInstall(mode: "all" | "prod"): string {
125
- const flags = mode === "prod" ? " --prod" : "";
126
- return `ARG NODE_AUTH_TOKEN=
127
- RUN --mount=type=secret,id=npm_token,required=false \\
128
- if [ -f /run/secrets/npm_token ]; then \\
129
- TOKEN="$(cat /run/secrets/npm_token)"; \\
130
- else \\
131
- TOKEN="\${NODE_AUTH_TOKEN}"; \\
132
- fi; \\
133
- if [ -n "$TOKEN" ]; then \\
134
- echo "//registry.npmjs.org/:_authToken=\${TOKEN}" > .npmrc; \\
135
- fi; \\
136
- if [ -f pnpm-lock.yaml ]; then \\
137
- pnpm install --frozen-lockfile${flags}; \\
138
- else \\
139
- pnpm install${flags}; \\
140
- fi; \\
141
- rm -f .npmrc`;
142
- }
143
-
144
- function dockerignoreContent(): string {
145
- return [
146
- "node_modules",
147
- "dist",
148
- ".git",
149
- ".devcontainer",
150
- ".vscode",
151
- ".github",
152
- "postman",
153
- ".postman",
154
- ".env",
155
- ".env.*",
156
- "!.env.example",
157
- "coverage",
158
- "**/*.spec.ts",
159
- "**/*.test.ts",
160
- "",
161
- ].join("\n");
162
- }
@@ -1,45 +0,0 @@
1
- import type { SourceFile, SourceFileProvider } from "../../models/source-file.js";
2
-
3
- const SAMPLE_CONFIG_CONTENT = `export class SampleConfig {\n\tpublic greeting: string = "";\n}\n`;
4
-
5
- const SAMPLE_SERVICE_CONTENT = [
6
- `import { inject, injectable } from "@decohere/crucible/decorators";`,
7
- `import { ConfigurationServiceToken } from "@decohere/nexus/configuration";`,
8
- `import type { IConfigurationService } from "@decohere/nexus/configuration";`,
9
- `import { SampleConfig } from "../sample.config.js";`,
10
- ``,
11
- `@injectable()`,
12
- `export class SampleService {`,
13
- `\tpublic constructor(`,
14
- `\t\t@inject(ConfigurationServiceToken) private readonly config: IConfigurationService,`,
15
- `\t) {}`,
16
- ``,
17
- `\tpublic async getGreeting(): Promise<string> {`,
18
- `\t\tconst config = await this.config.get(SampleConfig);`,
19
- `\t\treturn config.greeting;`,
20
- `\t}`,
21
- `}`,
22
- ``,
23
- ].join("\n");
24
-
25
- export class SampleConfigModel implements SourceFileProvider {
26
- public files(): SourceFile[] {
27
- return [
28
- {
29
- filePath: "src/sample.config.ts",
30
- content: SAMPLE_CONFIG_CONTENT,
31
- },
32
- ];
33
- }
34
- }
35
-
36
- export class SampleServiceModel implements SourceFileProvider {
37
- public files(): SourceFile[] {
38
- return [
39
- {
40
- filePath: "src/services/sample.service.ts",
41
- content: SAMPLE_SERVICE_CONTENT,
42
- },
43
- ];
44
- }
45
- }
@@ -1,21 +0,0 @@
1
- import { DockerfileModel } from "../../models/docker-image.model.js";
2
- import { CREATE_VERSION, DEVCONTAINER_IMAGE_REPOSITORY } from "../../version.js";
3
-
4
- /**
5
- * Scaffolded app `.devcontainer/Dockerfile` — thin `FROM` the published base image.
6
- * Optional feature layers (e.g. postgresql-client) append via `addLayer`.
7
- */
8
- export class WebapiDevContainer extends DockerfileModel {
9
- public constructor(baseImage = `${DEVCONTAINER_IMAGE_REPOSITORY}:${CREATE_VERSION}`) {
10
- super(baseImage);
11
- }
12
-
13
- public files() {
14
- return [
15
- {
16
- filePath: ".devcontainer/Dockerfile",
17
- content: this.build(),
18
- },
19
- ];
20
- }
21
- }