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

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/package.json +9 -19
  2. package/src/add-api.action.ts +25 -0
  3. package/src/add-api.command.ts +31 -0
  4. package/src/add.command.ts +20 -0
  5. package/src/add.module.ts +11 -0
  6. package/src/cli.ts +4 -0
  7. package/src/create.command.ts +20 -0
  8. package/src/create.module.ts +11 -0
  9. package/src/domain/prompt.ts +50 -0
  10. package/{types/src/index.d.ts → src/index.ts} +0 -4
  11. package/src/models/compose.model.ts +205 -0
  12. package/src/models/config-yaml.model.ts +83 -0
  13. package/src/models/const.ts +20 -0
  14. package/src/models/dev-container.model.ts +136 -0
  15. package/src/models/docker-image.model.ts +343 -0
  16. package/src/models/env-file.model.ts +45 -0
  17. package/src/models/gitignore.model.ts +35 -0
  18. package/src/models/launch-json.model.ts +60 -0
  19. package/src/models/package-json.model.ts +169 -0
  20. package/src/models/project.model.ts +141 -0
  21. package/{types/src/models/source-file.d.ts → src/models/source-file.ts} +6 -4
  22. package/src/models/tsconfig.model.ts +29 -0
  23. package/src/models/vitest-config.model.ts +26 -0
  24. package/src/models/vscode-settings.model.ts +24 -0
  25. package/src/program.ts +63 -0
  26. package/{types/src/resolve-target-dir.d.ts → src/resolve-target-dir.ts} +4 -1
  27. package/src/services/scaffold.service.ts +89 -0
  28. package/src/version.ts +56 -0
  29. package/src/webapi/api-name.ts +73 -0
  30. package/src/webapi/cli-options.ts +56 -0
  31. package/{types/src/webapi/const.d.ts → src/webapi/const.ts} +21 -9
  32. package/src/webapi/create-api.ts +275 -0
  33. package/src/webapi/devcontainer/host-scripts.ts +35 -0
  34. package/src/webapi/features/auth.feature.ts +454 -0
  35. package/src/webapi/features/auth0-cli.ts +421 -0
  36. package/src/webapi/features/biome-format.ts +34 -0
  37. package/src/webapi/features/database.feature.ts +522 -0
  38. package/src/webapi/features/finalize.feature.ts +112 -0
  39. package/src/webapi/features/postman.feature.ts +287 -0
  40. package/src/webapi/models/api-package-json.model.ts +60 -0
  41. package/src/webapi/models/auth-config.model.ts +36 -0
  42. package/src/webapi/models/controller-source-file.model.ts +232 -0
  43. package/{es6/src/webapi/models/github-ci.model.js → src/webapi/models/github-ci.model.ts} +29 -27
  44. package/src/webapi/models/main-source-file.model.ts +208 -0
  45. package/src/webapi/models/production-dockerfile.model.ts +162 -0
  46. package/src/webapi/models/sample-files.model.ts +45 -0
  47. package/src/webapi/models/webapi-image.model.ts +21 -0
  48. package/src/webapi/postman/hello-openapi.ts +92 -0
  49. package/src/webapi/postman/local-view.ts +195 -0
  50. package/src/webapi/postman/openapi-to-postman.ts +208 -0
  51. package/src/webapi/workspace-readme.ts +192 -0
  52. package/src/webapi.action.ts +22 -0
  53. package/src/webapi.command.ts +41 -0
  54. package/tests/auth-postman.wiring.spec.ts +263 -0
  55. package/tests/auth0-cli.spec.ts +232 -0
  56. package/tests/biome-format.spec.ts +49 -0
  57. package/tests/cli.integration.spec.ts +658 -0
  58. package/tests/compose.model.spec.ts +51 -0
  59. package/tests/container-image.model.spec.ts +162 -0
  60. package/tests/dev-container.model.spec.ts +29 -0
  61. package/tests/devcontainer-base-image.spec.ts +32 -0
  62. package/tests/hello-di-inject.scaffold.spec.ts +18 -0
  63. package/tests/hello-route.contract.spec.ts +94 -0
  64. package/tests/package-json.order.spec.ts +59 -0
  65. package/tests/postman-from-openapi.spec.ts +197 -0
  66. package/tests/seed-devcontainer-templates.ts +40 -0
  67. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +96 -0
  68. package/LICENSE +0 -15
  69. package/commonjs/src/add-api.action.js +0 -37
  70. package/commonjs/src/add-api.command.js +0 -39
  71. package/commonjs/src/add.command.js +0 -22
  72. package/commonjs/src/add.module.js +0 -24
  73. package/commonjs/src/auth0-configure.action.js +0 -28
  74. package/commonjs/src/auth0-configure.command.js +0 -24
  75. package/commonjs/src/auth0-login.action.js +0 -28
  76. package/commonjs/src/auth0-login.command.js +0 -23
  77. package/commonjs/src/auth0.command.js +0 -22
  78. package/commonjs/src/auth0.module.js +0 -28
  79. package/commonjs/src/cli.js +0 -8
  80. package/commonjs/src/create.command.js +0 -22
  81. package/commonjs/src/create.module.js +0 -24
  82. package/commonjs/src/domain/prompt.js +0 -62
  83. package/commonjs/src/index.js +0 -48
  84. package/commonjs/src/models/compose.model.js +0 -258
  85. package/commonjs/src/models/config-yaml.model.js +0 -86
  86. package/commonjs/src/models/const.js +0 -30
  87. package/commonjs/src/models/dev-container.model.js +0 -172
  88. package/commonjs/src/models/docker-image.model.js +0 -277
  89. package/commonjs/src/models/env-file.model.js +0 -48
  90. package/commonjs/src/models/gitignore.model.js +0 -48
  91. package/commonjs/src/models/launch-json.model.js +0 -70
  92. package/commonjs/src/models/package-json.model.js +0 -156
  93. package/commonjs/src/models/project.model.js +0 -132
  94. package/commonjs/src/models/source-file.js +0 -4
  95. package/commonjs/src/models/tsconfig.model.js +0 -32
  96. package/commonjs/src/models/vitest-config.model.js +0 -33
  97. package/commonjs/src/models/vscode-settings.model.js +0 -29
  98. package/commonjs/src/program.js +0 -64
  99. package/commonjs/src/resolve-target-dir.js +0 -17
  100. package/commonjs/src/services/scaffold.service.js +0 -60
  101. package/commonjs/src/version.js +0 -55
  102. package/commonjs/src/webapi/api-name.js +0 -93
  103. package/commonjs/src/webapi/auth0-commands.js +0 -250
  104. package/commonjs/src/webapi/authorize-scopes.js +0 -79
  105. package/commonjs/src/webapi/cli-options.js +0 -99
  106. package/commonjs/src/webapi/const.js +0 -54
  107. package/commonjs/src/webapi/create-api.js +0 -265
  108. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +0 -164
  109. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  110. package/commonjs/src/webapi/devcontainer/host-scripts.js +0 -50
  111. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  112. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +0 -169
  113. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +0 -35
  114. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +0 -65
  115. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  116. package/commonjs/src/webapi/features/auth.feature.js +0 -408
  117. package/commonjs/src/webapi/features/auth0-cli.js +0 -472
  118. package/commonjs/src/webapi/features/biome-format.js +0 -47
  119. package/commonjs/src/webapi/features/database.feature.js +0 -433
  120. package/commonjs/src/webapi/features/finalize.feature.js +0 -93
  121. package/commonjs/src/webapi/features/postman.feature.js +0 -322
  122. package/commonjs/src/webapi/models/api-package-json.model.js +0 -58
  123. package/commonjs/src/webapi/models/auth-config.model.js +0 -24
  124. package/commonjs/src/webapi/models/controller-source-file.model.js +0 -190
  125. package/commonjs/src/webapi/models/github-ci.model.js +0 -193
  126. package/commonjs/src/webapi/models/main-source-file.model.js +0 -165
  127. package/commonjs/src/webapi/models/production-dockerfile.model.js +0 -75
  128. package/commonjs/src/webapi/models/sample-files.model.js +0 -58
  129. package/commonjs/src/webapi/models/webapi-image.model.js +0 -25
  130. package/commonjs/src/webapi/postman/hello-openapi.js +0 -173
  131. package/commonjs/src/webapi/postman/local-view.js +0 -182
  132. package/commonjs/src/webapi/postman/openapi-to-postman.js +0 -238
  133. package/commonjs/src/webapi/workspace-readme.js +0 -109
  134. package/commonjs/src/webapi.action.js +0 -28
  135. package/commonjs/src/webapi.command.js +0 -43
  136. package/es6/src/add-api.action.js +0 -27
  137. package/es6/src/add-api.command.js +0 -29
  138. package/es6/src/add.command.js +0 -12
  139. package/es6/src/add.module.js +0 -14
  140. package/es6/src/auth0-configure.action.js +0 -18
  141. package/es6/src/auth0-configure.command.js +0 -14
  142. package/es6/src/auth0-login.action.js +0 -18
  143. package/es6/src/auth0-login.command.js +0 -13
  144. package/es6/src/auth0.command.js +0 -12
  145. package/es6/src/auth0.module.js +0 -18
  146. package/es6/src/cli.js +0 -4
  147. package/es6/src/create.command.js +0 -12
  148. package/es6/src/create.module.js +0 -14
  149. package/es6/src/domain/prompt.js +0 -41
  150. package/es6/src/index.js +0 -9
  151. package/es6/src/models/compose.model.js +0 -254
  152. package/es6/src/models/config-yaml.model.js +0 -74
  153. package/es6/src/models/const.js +0 -20
  154. package/es6/src/models/dev-container.model.js +0 -162
  155. package/es6/src/models/docker-image.model.js +0 -235
  156. package/es6/src/models/env-file.model.js +0 -44
  157. package/es6/src/models/gitignore.model.js +0 -38
  158. package/es6/src/models/launch-json.model.js +0 -60
  159. package/es6/src/models/package-json.model.js +0 -141
  160. package/es6/src/models/project.model.js +0 -117
  161. package/es6/src/models/source-file.js +0 -1
  162. package/es6/src/models/tsconfig.model.js +0 -22
  163. package/es6/src/models/vitest-config.model.js +0 -23
  164. package/es6/src/models/vscode-settings.model.js +0 -19
  165. package/es6/src/program.js +0 -50
  166. package/es6/src/resolve-target-dir.js +0 -7
  167. package/es6/src/services/scaffold.service.js +0 -50
  168. package/es6/src/version.js +0 -34
  169. package/es6/src/webapi/api-name.js +0 -57
  170. package/es6/src/webapi/auth0-commands.js +0 -185
  171. package/es6/src/webapi/authorize-scopes.js +0 -55
  172. package/es6/src/webapi/cli-options.js +0 -34
  173. package/es6/src/webapi/const.js +0 -21
  174. package/es6/src/webapi/create-api.js +0 -245
  175. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +0 -154
  176. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  177. package/es6/src/webapi/devcontainer/host-scripts.js +0 -29
  178. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  179. package/es6/src/webapi/devcontainer/templates/initialize.sh +0 -169
  180. package/es6/src/webapi/devcontainer/templates/post-create.sh +0 -35
  181. package/es6/src/webapi/devcontainer/templates/post-start.sh +0 -65
  182. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  183. package/es6/src/webapi/features/auth.feature.js +0 -357
  184. package/es6/src/webapi/features/auth0-cli.js +0 -406
  185. package/es6/src/webapi/features/biome-format.js +0 -40
  186. package/es6/src/webapi/features/database.feature.js +0 -423
  187. package/es6/src/webapi/features/finalize.feature.js +0 -83
  188. package/es6/src/webapi/features/postman.feature.js +0 -256
  189. package/es6/src/webapi/models/api-package-json.model.js +0 -53
  190. package/es6/src/webapi/models/auth-config.model.js +0 -20
  191. package/es6/src/webapi/models/controller-source-file.model.js +0 -181
  192. package/es6/src/webapi/models/main-source-file.model.js +0 -161
  193. package/es6/src/webapi/models/production-dockerfile.model.js +0 -66
  194. package/es6/src/webapi/models/sample-files.model.js +0 -40
  195. package/es6/src/webapi/models/webapi-image.model.js +0 -18
  196. package/es6/src/webapi/postman/hello-openapi.js +0 -166
  197. package/es6/src/webapi/postman/local-view.js +0 -167
  198. package/es6/src/webapi/postman/openapi-to-postman.js +0 -179
  199. package/es6/src/webapi/workspace-readme.js +0 -88
  200. package/es6/src/webapi.action.js +0 -18
  201. package/es6/src/webapi.command.js +0 -33
  202. package/types/src/add-api.action.d.ts +0 -13
  203. package/types/src/add-api.command.d.ts +0 -7
  204. package/types/src/add.command.d.ts +0 -6
  205. package/types/src/add.module.d.ts +0 -14
  206. package/types/src/auth0-configure.action.d.ts +0 -7
  207. package/types/src/auth0-configure.command.d.ts +0 -6
  208. package/types/src/auth0-login.action.d.ts +0 -4
  209. package/types/src/auth0-login.command.d.ts +0 -6
  210. package/types/src/auth0.command.d.ts +0 -6
  211. package/types/src/auth0.module.d.ts +0 -18
  212. package/types/src/cli.d.ts +0 -2
  213. package/types/src/create.command.d.ts +0 -6
  214. package/types/src/create.module.d.ts +0 -14
  215. package/types/src/domain/prompt.d.ts +0 -4
  216. package/types/src/models/compose.model.d.ts +0 -73
  217. package/types/src/models/config-yaml.model.d.ts +0 -27
  218. package/types/src/models/const.d.ts +0 -20
  219. package/types/src/models/dev-container.model.d.ts +0 -49
  220. package/types/src/models/docker-image.model.d.ts +0 -90
  221. package/types/src/models/env-file.model.d.ts +0 -16
  222. package/types/src/models/gitignore.model.d.ts +0 -9
  223. package/types/src/models/launch-json.model.d.ts +0 -7
  224. package/types/src/models/package-json.model.d.ts +0 -36
  225. package/types/src/models/project.model.d.ts +0 -60
  226. package/types/src/models/tsconfig.model.d.ts +0 -5
  227. package/types/src/models/vitest-config.model.d.ts +0 -5
  228. package/types/src/models/vscode-settings.model.d.ts +0 -7
  229. package/types/src/program.d.ts +0 -8
  230. package/types/src/services/scaffold.service.d.ts +0 -22
  231. package/types/src/version.d.ts +0 -6
  232. package/types/src/webapi/api-name.d.ts +0 -14
  233. package/types/src/webapi/auth0-commands.d.ts +0 -11
  234. package/types/src/webapi/authorize-scopes.d.ts +0 -7
  235. package/types/src/webapi/cli-options.d.ts +0 -6
  236. package/types/src/webapi/create-api.d.ts +0 -15
  237. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +0 -32
  238. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  239. package/types/src/webapi/devcontainer/host-scripts.d.ts +0 -7
  240. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  241. package/types/src/webapi/devcontainer/templates/initialize.sh +0 -169
  242. package/types/src/webapi/devcontainer/templates/post-create.sh +0 -35
  243. package/types/src/webapi/devcontainer/templates/post-start.sh +0 -65
  244. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  245. package/types/src/webapi/features/auth.feature.d.ts +0 -10
  246. package/types/src/webapi/features/auth0-cli.d.ts +0 -84
  247. package/types/src/webapi/features/biome-format.d.ts +0 -5
  248. package/types/src/webapi/features/database.feature.d.ts +0 -7
  249. package/types/src/webapi/features/finalize.feature.d.ts +0 -2
  250. package/types/src/webapi/features/postman.feature.d.ts +0 -16
  251. package/types/src/webapi/models/api-package-json.model.d.ts +0 -18
  252. package/types/src/webapi/models/auth-config.model.d.ts +0 -26
  253. package/types/src/webapi/models/controller-source-file.model.d.ts +0 -46
  254. package/types/src/webapi/models/github-ci.model.d.ts +0 -9
  255. package/types/src/webapi/models/main-source-file.model.d.ts +0 -54
  256. package/types/src/webapi/models/production-dockerfile.model.d.ts +0 -18
  257. package/types/src/webapi/models/sample-files.model.d.ts +0 -7
  258. package/types/src/webapi/models/webapi-image.model.d.ts +0 -12
  259. package/types/src/webapi/postman/hello-openapi.d.ts +0 -19
  260. package/types/src/webapi/postman/local-view.d.ts +0 -17
  261. package/types/src/webapi/postman/openapi-to-postman.d.ts +0 -62
  262. package/types/src/webapi/workspace-readme.d.ts +0 -18
  263. package/types/src/webapi.action.d.ts +0 -14
  264. package/types/src/webapi.command.d.ts +0 -11
@@ -1,433 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "DatabaseFeature", {
6
- enumerable: true,
7
- get: function() {
8
- return DatabaseFeature;
9
- }
10
- });
11
- const _nodecrypto = require("node:crypto");
12
- const _nodefs = require("node:fs");
13
- const _nodepath = require("node:path");
14
- const _prompt = require("../../domain/prompt.js");
15
- const _const = require("../../models/const.js");
16
- const _dockerimagemodel = require("../../models/docker-image.model.js");
17
- const _version = require("../../version.js");
18
- const _apiname = require("../api-name.js");
19
- /** Bind path relative to `.devcontainer/docker-compose.yml` (dies with the working tree). */ const POSTGRES_DATA_BIND = "./postgres-data:/var/lib/postgresql/data";
20
- const POSTGRES_DATA_GITIGNORE = ".devcontainer/postgres-data";
21
- const DB_RESET_SCRIPT = "rm -rf .devcontainer/postgres-data && docker compose -f .devcontainer/docker-compose.yml down && docker compose -f .devcontainer/docker-compose.yml up -d postgres";
22
- /** Read a single KEY= from an existing target `.env` (if any). */ function readExistingEnvValue(workspace, key) {
23
- try {
24
- const raw = (0, _nodefs.readFileSync)((0, _nodepath.join)(workspace.targetDir, ".env"), "utf8");
25
- const line = raw.split("\n").find((l)=>l.startsWith(`${key}=`));
26
- const value = line?.slice(key.length + 1).trim();
27
- return value || undefined;
28
- } catch {
29
- return undefined;
30
- }
31
- }
32
- function randomPassword() {
33
- return (0, _nodecrypto.randomBytes)(16).toString("hex");
34
- }
35
- /** Safe Postgres identifier fragment from the project name. */ function toSqlIdentFragment(name) {
36
- const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_").replace(/^([^A-Za-z_])/, "p_$1");
37
- return cleaned.length > 0 ? cleaned.slice(0, 40) : "app";
38
- }
39
- function passwordOrExisting(workspace, key, fallbackExistingKeys = []) {
40
- const existing = readExistingEnvValue(workspace, key);
41
- if (existing) return existing;
42
- for (const alt of fallbackExistingKeys){
43
- const v = readExistingEnvValue(workspace, alt);
44
- if (v) return v;
45
- }
46
- return randomPassword();
47
- }
48
- // Production: runtime app role only — no bootstrap, no admin/migrate secrets required.
49
- function renderBaseDataSource(apiName) {
50
- const p = (0, _apiname.apiEnvPrefix)(apiName);
51
- return [
52
- ` - name: ${apiName}`,
53
- ` provider: postgres`,
54
- ` host: \${${p}_DB_HOST}`,
55
- ` port: \${${p}_DB_PORT}`,
56
- ` database: \${${p}_DB_NAME}`,
57
- ` username: \${${p}_DB_USER}`,
58
- ` password: \${${p}_DB_PASSWORD}`,
59
- ` synchronize: false`,
60
- ` seed: false`,
61
- ` bootstrap: false`
62
- ].join("\n");
63
- }
64
- // Dev overlay: @decohere/config merges it into config.yaml by data source name, so it only states
65
- // what development changes. Enable bootstrap + migrate role.
66
- function renderDevDataSource(apiName) {
67
- const p = (0, _apiname.apiEnvPrefix)(apiName);
68
- return [
69
- ` - name: ${apiName}`,
70
- ` synchronize: true`,
71
- ` seed: true`,
72
- ` bootstrap: true`,
73
- ` adminUser: \${DB_ADMIN_USER}`,
74
- ` adminPassword: \${DB_ADMIN_PASSWORD}`,
75
- ` migrateUsername: \${${p}_DB_MIGRATE_USER}`,
76
- ` migratePassword: \${${p}_DB_MIGRATE_PASSWORD}`
77
- ].join("\n");
78
- }
79
- function renderDatabaseSection(apiNames, items) {
80
- return `database:\n dataSources:\n${apiNames.map(items).join("\n")}\n`;
81
- }
82
- /** Create-or-sync bootstrap role; runs on every Postgres container start (not only empty volume). */ const POSTGRES_ENSURE_BOOTSTRAP_SH = `#!/bin/bash
83
- set -euo pipefail
84
- # Superuser aligns decohere_bootstrap with DB_ADMIN_* from env_file on every start.
85
- # Initdb.d alone is insufficient: passwords in .env can change while the volume persists.
86
- : "\${POSTGRES_USER:?POSTGRES_USER is required}"
87
- : "\${DB_ADMIN_USER:?DB_ADMIN_USER is required}"
88
- : "\${DB_ADMIN_PASSWORD:?DB_ADMIN_PASSWORD is required}"
89
-
90
- psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres \\
91
- \t--set=bootstrap_user="$DB_ADMIN_USER" \\
92
- \t--set=bootstrap_password="$DB_ADMIN_PASSWORD" <<'EOSQL'
93
- SELECT format(
94
- \t'CREATE ROLE %I LOGIN PASSWORD %L CREATEDB CREATEROLE NOSUPERUSER NOBYPASSRLS',
95
- \t:'bootstrap_user',
96
- \t:'bootstrap_password'
97
- )
98
- WHERE NOT EXISTS (
99
- \tSELECT 1 FROM pg_catalog.pg_roles WHERE rolname = :'bootstrap_user'
100
- )\\gexec
101
-
102
- SELECT format(
103
- \t'ALTER ROLE %I WITH LOGIN PASSWORD %L CREATEDB CREATEROLE NOSUPERUSER NOBYPASSRLS',
104
- \t:'bootstrap_user',
105
- \t:'bootstrap_password'
106
- )\\gexec
107
-
108
- GRANT CONNECT ON DATABASE postgres TO :"bootstrap_user";
109
- EOSQL
110
- `;
111
- const POSTGRES_ENTRYPOINT_SH = `#!/bin/bash
112
- set -euo pipefail
113
- (
114
- \tfor _ in $(seq 1 60); do
115
- \t\tif pg_isready -U "\${POSTGRES_USER:-postgres}" >/dev/null 2>&1; then
116
- \t\t\t/usr/local/bin/decohere-ensure-bootstrap.sh
117
- \t\t\texit 0
118
- \t\tfi
119
- \t\tsleep 0.5
120
- \tdone
121
- \techo "decohere: timed out waiting for Postgres before ensuring bootstrap role" >&2
122
- \texit 1
123
- ) &
124
- exec docker-entrypoint.sh "$@"
125
- `;
126
- const POSTGRES_IMAGE_DOCKERFILE = `FROM postgres:16
127
- COPY ensure-bootstrap-role.sh /usr/local/bin/decohere-ensure-bootstrap.sh
128
- COPY entrypoint.sh /usr/local/bin/decohere-postgres-entrypoint.sh
129
- RUN chmod +x /usr/local/bin/decohere-ensure-bootstrap.sh /usr/local/bin/decohere-postgres-entrypoint.sh
130
- ENTRYPOINT ["/usr/local/bin/decohere-postgres-entrypoint.sh"]
131
- CMD ["postgres"]
132
- `;
133
- const SAMPLE_ENTITY_TS = `import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
134
- import type { Entity as IEntity } from "@decohere/data";
135
-
136
- @Entity()
137
- export class SampleEntity implements IEntity {
138
- \t@PrimaryGeneratedColumn()
139
- \tpublic id!: number;
140
-
141
- \t@Column()
142
- \tpublic name!: string;
143
- }
144
- `;
145
- const SAMPLE_REPOSITORY_TS = `import { injectable } from "@decohere/crucible/decorators";
146
- import { injectUnitOfWork } from "@decohere/data";
147
- import { TypeOrmRepository, TypeOrmUnitOfWork } from "@decohere/data-typeorm";
148
- import { SampleEntity } from "../entities/sample.entity.js";
149
-
150
- @injectable()
151
- export class SampleEntityRepository extends TypeOrmRepository<SampleEntity> {
152
- \tpublic constructor(@injectUnitOfWork() unitOfWork: TypeOrmUnitOfWork) {
153
- \t\tsuper(unitOfWork, SampleEntity);
154
- \t}
155
- }
156
- `;
157
- /** TypeORM CLI DataSource — connects as the migrate role; never enables synchronize. */ function renderTypeOrmDataSource(apiName) {
158
- const p = (0, _apiname.apiEnvPrefix)(apiName);
159
- return `import { dirname, join } from "node:path";
160
- import { fileURLToPath } from "node:url";
161
- import { DataSource } from "typeorm";
162
-
163
- const root = dirname(fileURLToPath(import.meta.url));
164
- const required = (key: string): string => {
165
- \tconst value = process.env[key];
166
- \tif (value == null || value === "") {
167
- \t\tthrow new Error(\`Missing env \${key} (load .env via --env-file or export it)\`);
168
- \t}
169
- \treturn value;
170
- };
171
-
172
- /**
173
- * CLI-only DataSource for \`pnpm db:migrate\` / \`pnpm db:migrate:generate\`.
174
- * Uses the migrate role (DDL). Runtime API pods use the app role with synchronize: false.
175
- */
176
- export default new DataSource({
177
- \ttype: "postgres",
178
- \thost: required("${p}_DB_HOST"),
179
- \tport: Number(required("${p}_DB_PORT")),
180
- \tdatabase: required("${p}_DB_NAME"),
181
- \tusername: required("${p}_DB_MIGRATE_USER"),
182
- \tpassword: required("${p}_DB_MIGRATE_PASSWORD"),
183
- \tsynchronize: false,
184
- \tentities: [join(root, "entities", "*.{ts,js}")],
185
- \tmigrations: [join(root, "migrations", "*.{ts,js}")],
186
- });
187
- `;
188
- }
189
- const DATABASE_DOCS_MD = `# Database (Postgres / TypeORM)
190
-
191
- ## Roles
192
-
193
- | Role | Env | Purpose |
194
- |---|---|---|
195
- | App | \`{PREFIX}_DB_USER\` | Runtime API (DML only in production) |
196
- | Migrate | \`{PREFIX}_DB_MIGRATE_USER\` | Schema changes / TypeORM migrations (CI/deploy) |
197
- | Bootstrap | \`DB_ADMIN_USER\` | Create DB + roles when \`bootstrap: true\` (dev) |
198
-
199
- \`{PREFIX}\` is the API slug in SCREAMING_SNAKE (\`api\` → \`API\`, \`billing\` → \`BILLING\`).
200
-
201
- ## Development (fast iteration)
202
-
203
- \`config/config.dev.yaml\` sets \`synchronize: true\` and \`seed: true\` so the API can create tables on startup. You can keep using sync while prototyping.
204
-
205
- Alternatively, turn sync off in the overlay and use migrations the same way as production:
206
-
207
- \`\`\`bash
208
- pnpm db:up
209
- pnpm db:migrate
210
- pnpm db:migrate:generate -- ./projects/api/src/migrations/AddSomething
211
- \`\`\`
212
-
213
- Do **not** mix sync and migrations for the same schema change (pick one path per change).
214
-
215
- ## Production (migrations only)
216
-
217
- \`config/config.yaml\` keeps \`synchronize: false\` / \`seed: false\` / \`bootstrap: false\`. Deploy steps:
218
-
219
- 1. Ensure the app database and roles already exist (provisioner / ops — not the API pod).
220
- 2. Run migrations with the migrate role: \`pnpm db:migrate\` (or \`pnpm db:migrate:<api>\` for multi-API).
221
- 3. Start the API with the app role only — no migrate/admin secrets on the pod.
222
-
223
- ## Scripts
224
-
225
- | Script | What it does |
226
- |---|---|
227
- | \`db:up\` / \`db:down\` / \`db:reset\` | Local Compose Postgres |
228
- | \`db:migrate\` | Run pending migrations (\`projects/api\` by default) |
229
- | \`db:migrate:generate\` | Generate a migration from entity drift (pass path after \`--\`) |
230
- | \`db:migrate:<api>\` | Same for a named API under \`projects/<api>\` |
231
-
232
- CLI entry: \`projects/<api>/src/typeorm.data-source.ts\` (migrate credentials, \`synchronize: false\`).
233
-
234
- ## Browse the database
235
-
236
- Prefer the Dev Container **SQLTools** extension (Postgres driver) or any desktop client against \`127.0.0.1:5433\` (see \`.env\` for credentials).
237
-
238
- Adminer is **not** scaffolded. If you want a browser UI in Compose, add this under \`services:\` in \`.devcontainer/docker-compose.yml\` (profile \`tools\` so it stays off by default):
239
-
240
- \`\`\`yaml
241
- adminer:
242
- image: adminer
243
- profiles:
244
- - tools
245
- ports:
246
- - "127.0.0.1:8081:8080"
247
- networks:
248
- - <compose-network-key> # same key as postgres / devcontainer
249
- \`\`\`
250
-
251
- Then: \`docker compose -f .devcontainer/docker-compose.yml --profile tools up -d adminer\`
252
- `;
253
- function typeormCli(args) {
254
- return `node --import tsx --env-file=.env ./node_modules/typeorm/cli.js ${args}`;
255
- }
256
- function migrateRunScript(apiName) {
257
- return typeormCli(`migration:run -d ./projects/${apiName}/src/typeorm.data-source.ts`);
258
- }
259
- function migrateGenerateScript(apiName) {
260
- return typeormCli(`migration:generate --esm -d ./projects/${apiName}/src/typeorm.data-source.ts`);
261
- }
262
- async function getOptions(_workspace, cliOptions) {
263
- if (cliOptions.databaseProvider != null) return {};
264
- const choices = [
265
- {
266
- label: "none"
267
- },
268
- {
269
- label: "postgres/typeorm"
270
- }
271
- ];
272
- const selected = await (0, _prompt.pick)("\n Database: ", choices, (c)=>c.label);
273
- return {
274
- databaseProvider: selected?.label ?? "none"
275
- };
276
- }
277
- function scaffold(workspace, options) {
278
- if (options.databaseProvider !== "postgres/typeorm") return;
279
- const name = workspace.name;
280
- const ident = toSqlIdentFragment(name);
281
- const apiProjects = workspace.getProjects();
282
- const apiNames = apiProjects.map((project)=>(0, _apiname.apiNameFromProjectDir)(project.dir));
283
- // Container init superuser — never reused as an API runtime role.
284
- const postgresSuperuser = "postgres";
285
- const postgresPassword = passwordOrExisting(workspace, "POSTGRES_PASSWORD");
286
- const bootstrapUser = "decohere_bootstrap";
287
- const bootstrapPassword = passwordOrExisting(workspace, "DB_ADMIN_PASSWORD", [
288
- "POSTGRES_PASSWORD"
289
- ]);
290
- const devContainer = workspace.getModel(_const.WELL_KNOWN_FILES.DEVCONTAINER_JSON);
291
- const compose = workspace.getModel(_const.WELL_KNOWN_FILES.DOCKER_COMPOSE);
292
- const networkKey = name;
293
- // Skip when the workspace already runs Postgres (`decohere add api` on a database workspace).
294
- if (compose && !compose.hasService("postgres")) {
295
- // One shared instance. Each API owns a database on it (created at bootstrap).
296
- // Point `{PREFIX}_DB_HOST` / `{PREFIX}_DB_PORT` at another server to split later.
297
- // Custom image: sync bootstrap role password on every start (initdb.d is once-only).
298
- // Default data is a bind mount under the project so `rm -rf` the tree removes it.
299
- compose.addHeaderComment("Postgres data defaults to ./postgres-data under this folder (bind mount; dies with the working tree).", `For data that outlives the checkout, replace the postgres volumes entry with a named volume`, `e.g. "${name}-postgres-data:/var/lib/postgresql/data" and declare it under top-level volumes:.`).addService("postgres", {
300
- build: "./postgres",
301
- container_name: `samples-${name}-postgres`,
302
- restart: "unless-stopped",
303
- ports: [
304
- "127.0.0.1:5433:5432"
305
- ],
306
- volumes: [
307
- POSTGRES_DATA_BIND
308
- ],
309
- networks: [
310
- {
311
- [networkKey]: {
312
- aliases: [
313
- "postgres"
314
- ]
315
- }
316
- }
317
- ],
318
- healthcheck: {
319
- // Ensure role/password match .env before reporting healthy.
320
- test: [
321
- "CMD-SHELL",
322
- `pg_isready -U ${postgresSuperuser} && /usr/local/bin/decohere-ensure-bootstrap.sh`
323
- ],
324
- interval: "2s",
325
- timeout: "5s",
326
- retries: 15
327
- }
328
- }).addEnvFile("postgres", "../.env");
329
- workspace.getModel(_const.WELL_KNOWN_FILES.GITIGNORE)?.add(POSTGRES_DATA_GITIGNORE);
330
- workspace.addFile({
331
- files: ()=>[
332
- {
333
- filePath: ".devcontainer/postgres/Dockerfile",
334
- content: POSTGRES_IMAGE_DOCKERFILE
335
- },
336
- {
337
- filePath: ".devcontainer/postgres/entrypoint.sh",
338
- content: POSTGRES_ENTRYPOINT_SH
339
- },
340
- {
341
- filePath: ".devcontainer/postgres/ensure-bootstrap-role.sh",
342
- content: POSTGRES_ENSURE_BOOTSTRAP_SH
343
- }
344
- ]
345
- });
346
- compose.addEnvFile("devcontainer", "../.env");
347
- }
348
- devContainer?.addExtension("mtxr.sqltools", "mtxr.sqltools-driver-pg");
349
- workspace.getModel(_const.WELL_KNOWN_FILES.DOCKERFILE)?.addLayer((0, _dockerimagemodel.withPostgresqlClient)());
350
- const pkg = workspace.getModel(_const.WELL_KNOWN_FILES.PACKAGE_JSON)?.addDependency("@decohere/data", _version.CREATE_VERSION).addDependency("typeorm", "^0.3.28").addDependency("@decohere/data-typeorm", _version.CREATE_VERSION).addDependency("tsx", "^4.20.5").addScript("db:up", "docker compose -f .devcontainer/docker-compose.yml up -d postgres").addScript("db:down", "docker compose -f .devcontainer/docker-compose.yml down").addScript("db:reset", DB_RESET_SCRIPT);
351
- const primaryApi = apiNames[0];
352
- if (pkg != null) {
353
- for (const apiName of apiNames){
354
- pkg.addScript(`db:migrate:${apiName}`, migrateRunScript(apiName)).addScript(`db:migrate:generate:${apiName}`, migrateGenerateScript(apiName));
355
- }
356
- // Default aliases target the first API of a new workspace only — never overwrite on `webapi add`.
357
- if (!options.add && primaryApi != null) {
358
- pkg.addScript("db:migrate", migrateRunScript(primaryApi)).addScript("db:migrate:generate", migrateGenerateScript(primaryApi));
359
- }
360
- }
361
- workspace.addFile({
362
- files: ()=>[
363
- {
364
- filePath: "docs/database.md",
365
- content: DATABASE_DOCS_MD
366
- }
367
- ]
368
- });
369
- writeDatabaseConfig(workspace, apiNames);
370
- for (const project of apiProjects){
371
- wireApiDatabase(project);
372
- }
373
- const env = workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE);
374
- env?.addEntry("POSTGRES_DB", ident).addEntry("POSTGRES_USER", postgresSuperuser).addEntry("POSTGRES_PASSWORD", postgresPassword).addEntry("DB_ADMIN_USER", bootstrapUser).addEntry("DB_ADMIN_PASSWORD", bootstrapPassword);
375
- for (const apiName of apiNames){
376
- writeApiDatabaseEnv(workspace, ident, apiName);
377
- }
378
- }
379
- function writeDatabaseConfig(workspace, apiNames) {
380
- const config = workspace.getModel(_const.WELL_KNOWN_FILES.CONFIG_YAML);
381
- const devConfig = workspace.getModel(_const.WELL_KNOWN_FILES.CONFIG_DEV_YAML);
382
- const pending = apiNames.filter((apiName)=>!config?.hasDataSource(apiName));
383
- if (pending.length === 0) return;
384
- if (config?.hasDataSources()) {
385
- for (const apiName of pending){
386
- config.appendDataSource(renderBaseDataSource(apiName));
387
- devConfig?.appendDataSource(renderDevDataSource(apiName));
388
- }
389
- return;
390
- }
391
- config?.addSection(renderDatabaseSection(pending, renderBaseDataSource));
392
- devConfig?.addSection(renderDatabaseSection(pending, renderDevDataSource));
393
- }
394
- function writeApiDatabaseEnv(workspace, ident, apiName) {
395
- const prefix = (0, _apiname.apiEnvPrefix)(apiName);
396
- const sqlApi = toSqlIdentFragment(apiName);
397
- workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE)?.addEntry(`${prefix}_DB_HOST`, "postgres").addEntry(`${prefix}_DB_PORT`, "5432").addEntry(`${prefix}_DB_NAME`, `${ident}_${sqlApi}_dev`).addEntry(`${prefix}_DB_USER`, `${ident}_${sqlApi}_app`).addEntry(`${prefix}_DB_PASSWORD`, passwordOrExisting(workspace, `${prefix}_DB_PASSWORD`)).addEntry(`${prefix}_DB_MIGRATE_USER`, `${ident}_${sqlApi}_migrate`).addEntry(`${prefix}_DB_MIGRATE_PASSWORD`, passwordOrExisting(workspace, `${prefix}_DB_MIGRATE_PASSWORD`));
398
- }
399
- function wireApiDatabase(project) {
400
- const apiName = (0, _apiname.apiNameFromProjectDir)(project.dir);
401
- project.getModel(_const.WELL_KNOWN_FILES.MAIN_TS)?.setDataSourceName(apiName).addSideEffectImport("@decohere/data-typeorm").addEntity({
402
- className: "SampleEntity",
403
- importPath: "./entities/sample.entity.js"
404
- }).addRepository({
405
- className: "SampleEntityRepository",
406
- importPath: "./repositories/sample.repository.js"
407
- });
408
- project.getModel(_const.WELL_KNOWN_FILES.API_PACKAGE_JSON)?.addDependency("@decohere/data", _version.CREATE_VERSION).addDependency("@decohere/data-typeorm", _version.CREATE_VERSION);
409
- project.addFile({
410
- files: ()=>[
411
- {
412
- filePath: "src/entities/sample.entity.ts",
413
- content: SAMPLE_ENTITY_TS
414
- },
415
- {
416
- filePath: "src/repositories/sample.repository.ts",
417
- content: SAMPLE_REPOSITORY_TS
418
- },
419
- {
420
- filePath: "src/typeorm.data-source.ts",
421
- content: renderTypeOrmDataSource(apiName)
422
- },
423
- {
424
- filePath: "src/migrations/.gitkeep",
425
- content: ""
426
- }
427
- ]
428
- });
429
- }
430
- const DatabaseFeature = {
431
- getOptions,
432
- scaffold
433
- };
@@ -1,93 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "FinalizeFeature", {
6
- enumerable: true,
7
- get: function() {
8
- return FinalizeFeature;
9
- }
10
- });
11
- const _nodefs = require("node:fs");
12
- const _nodepath = require("node:path");
13
- const _interop = require("@decohere/devkit/interop");
14
- const _prompt = require("../../domain/prompt.js");
15
- const _const = require("../../models/const.js");
16
- const _workspacereadme = require("../workspace-readme.js");
17
- const _biomeformat = require("./biome-format.js");
18
- function readExistingEnvValue(workspace, key) {
19
- try {
20
- const raw = (0, _nodefs.readFileSync)((0, _nodepath.join)(workspace.targetDir, ".env"), "utf8");
21
- const line = raw.split("\n").find((l)=>l.startsWith(`${key}=`));
22
- const value = line?.slice(key.length + 1).trim();
23
- return value || undefined;
24
- } catch {
25
- return undefined;
26
- }
27
- }
28
- // State resolved during scaffold(), consumed by finalize().
29
- let _pm = null;
30
- let _shouldInstall = true;
31
- async function getOptions(_workspace, cliOptions) {
32
- if (cliOptions.installDependencies != null) {
33
- _shouldInstall = cliOptions.installDependencies === "yes";
34
- }
35
- return {};
36
- }
37
- async function scaffold(workspace, options) {
38
- // Local scaffold default — production deploys must set NODE_ENV explicitly.
39
- const nodeEnv = readExistingEnvValue(workspace, "NODE_ENV") ?? "dev";
40
- workspace.getModel(_const.WELL_KNOWN_FILES.ENV_FILE)?.addEntry("NODE_ENV", nodeEnv);
41
- // Durable day-2 CLI examples (also refreshed on `webapi add`).
42
- (0, _workspacereadme.writeWorkspaceReadme)(workspace, options);
43
- if (process.env.DECOHERE_BUILDER === "1") {
44
- _shouldInstall = false;
45
- return;
46
- }
47
- _pm = (0, _interop.detectPackageManager)(workspace.targetDir) ?? await (0, _interop.promptForPackageManager)();
48
- if (options.installDependencies != null) {
49
- _shouldInstall = options.installDependencies === "yes";
50
- return;
51
- }
52
- const answer = await (0, _prompt.ask)("\nInstall dependencies now? [yes/no] (yes): ", "yes");
53
- _shouldInstall = answer.toLowerCase() !== "no";
54
- }
55
- async function finalize(workspace, options) {
56
- const formatPaths = options.add ? workspace.getProjects().map((project)=>project.dir) : [
57
- "."
58
- ];
59
- (0, _biomeformat.formatScaffoldedFiles)(workspace.targetDir, formatPaths.length > 0 ? formatPaths : [
60
- "."
61
- ]);
62
- if (_shouldInstall) {
63
- const pm = _pm;
64
- console.log(`\nScaffolded ${workspace.name}. Installing dependencies...\n`);
65
- await pm.install({
66
- cwd: workspace.targetDir
67
- });
68
- }
69
- const apiNames = workspace.getProjects().map((project)=>project.dir.replace(/^projects\//, ""));
70
- console.log(`\nDone! ${_shouldInstall ? "Next" : "To get started:"} steps:\n`);
71
- if (!options.add) {
72
- console.log(` cd ${workspace.name}`);
73
- }
74
- if (!_shouldInstall && options.add) {
75
- const pmName = _pm?.name ?? "pnpm";
76
- console.log(` ${pmName} install`);
77
- }
78
- if (options.add && apiNames[0] != null) {
79
- console.log(` pnpm run start:${apiNames[0]}`);
80
- console.log(` pnpm run package:${apiNames[0]} && docker build projects/${apiNames[0]}`);
81
- } else {
82
- console.log(" code . # open in VS Code");
83
- console.log(" # then: Reopen in Container (first create runs pnpm install)\n");
84
- }
85
- console.log(" See README.md for CLI examples.\n");
86
- // Drop readline's stdin hold so the CLI / container can exit.
87
- (0, _prompt.closePrompt)();
88
- }
89
- const FinalizeFeature = {
90
- getOptions,
91
- scaffold,
92
- finalize
93
- };