@decohere/scaffold 0.0.1-dev.126 → 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,169 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Runs on the host before the container is created (devcontainer initializeCommand).
3
- # Creates bind-mount sources as the host user so Docker does not create them as root.
4
- # Optional npm / gh / MCP setup is y/N on a TTY; no TTY skips.
5
- set -euo pipefail
6
-
7
- root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
- here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
- # shellcheck source=tty-prompt.sh
10
- source "$here/tty-prompt.sh"
11
-
12
- npm_dir="$root/.devcontainer/npm-auth"
13
- gh_dir="$root/.devcontainer/gh-auth"
14
- gh_env="$root/.devcontainer/gh.env"
15
- cursor_dir="$root/.cursor"
16
- RAG_ENV="$cursor_dir/codebase-rag.env"
17
-
18
- run_codebase_rag_mcp() {
19
- local script="$here/codebase-rag-mcp.ts"
20
- CODEBASE_RAG_WORKSPACE="$root" node --experimental-strip-types "$script" "$@"
21
- }
22
-
23
- load_rag_env() {
24
- RAG_URL=""
25
- RAG_TOKEN=""
26
- [[ -f "$RAG_ENV" ]] || return 0
27
- # shellcheck disable=SC1090
28
- set -a
29
- # shellcheck disable=SC1090
30
- source "$RAG_ENV"
31
- set +a
32
- RAG_URL="${CODEBASE_RAG_MCP_URL:-}"
33
- RAG_TOKEN="${CODEBASE_RAG_MCP_TOKEN:-}"
34
- }
35
-
36
- write_rag_env() {
37
- mkdir -p "$(dirname "$RAG_ENV")"
38
- umask 077
39
- cat >"$RAG_ENV" <<EOF
40
- # Written by .devcontainer/initialize.sh. Not in git.
41
- CODEBASE_RAG_MCP_URL=${RAG_URL}
42
- CODEBASE_RAG_MCP_TOKEN=${RAG_TOKEN}
43
- CODEBASE_RAG_MCP_ROOT=${root}
44
- EOF
45
- chmod 600 "$RAG_ENV"
46
- if [[ -n "$RAG_URL" && -n "$RAG_TOKEN" ]]; then
47
- run_codebase_rag_mcp merge || true
48
- fi
49
- }
50
-
51
- seed_rag_from_host_mcp() {
52
- local mcp="${HOME}/.cursor/mcp.json"
53
- [[ -f "$mcp" ]] || return 1
54
- command -v node >/dev/null 2>&1 || return 1
55
- run_codebase_rag_mcp seed-print "$mcp"
56
- }
57
-
58
- ensure_cursor_dir() {
59
- mkdir -p "$cursor_dir"
60
- }
61
-
62
- ensure_auth_dir() {
63
- local dir="$1"
64
- if ! mkdir -p "$dir" 2>/dev/null; then
65
- echo "Cannot create $dir (permission denied)." >&2
66
- echo "The scaffold container likely left .devcontainer root-owned. Fix with:" >&2
67
- echo " sudo chown -R \"$USER:$USER\" \"$root\"" >&2
68
- exit 1
69
- fi
70
- {
71
- echo "*"
72
- echo "!.gitignore"
73
- } >"$dir/.gitignore"
74
- }
75
-
76
- ensure_gh_env() {
77
- if [[ -d "$gh_env" ]]; then
78
- echo "Refusing to start: $gh_env is a directory (left by a prior bind mount). Remove it and rebuild." >&2
79
- exit 1
80
- fi
81
- touch "$gh_env"
82
- }
83
-
84
- setup_npm_auth() {
85
- if [[ -f "$npm_dir/.npmrc" ]]; then
86
- echo "npm auth: using $npm_dir/.npmrc"
87
- return 0
88
- fi
89
- if ! prompt_yn "Configure npm auth for this workspace?"; then
90
- echo "npm auth: skipped"
91
- return 0
92
- fi
93
- if [[ -f "${HOME}/.npmrc" ]]; then
94
- cp -a "${HOME}/.npmrc" "$npm_dir/.npmrc"
95
- echo "npm auth: copied ~/.npmrc → $npm_dir/.npmrc"
96
- return 0
97
- fi
98
- echo "npm auth: no ~/.npmrc — log in inside the container if prompted"
99
- }
100
-
101
- setup_gh_auth() {
102
- if [[ -f "$gh_dir/hosts.yml" ]]; then
103
- echo "gh auth: using $gh_dir/hosts.yml"
104
- return 0
105
- fi
106
- if ! prompt_yn "Configure GitHub CLI auth for this workspace?"; then
107
- echo "gh auth: skipped"
108
- return 0
109
- fi
110
- if [[ -f "${HOME}/.config/gh/hosts.yml" ]]; then
111
- cp -a "${HOME}/.config/gh/hosts.yml" "$gh_dir/hosts.yml"
112
- [[ -f "${HOME}/.config/gh/config.yml" ]] &&
113
- cp -a "${HOME}/.config/gh/config.yml" "$gh_dir/config.yml"
114
- echo "gh auth: copied ~/.config/gh → $gh_dir"
115
- return 0
116
- fi
117
- echo "gh auth: no ~/.config/gh/hosts.yml — log in inside the container if prompted"
118
- }
119
-
120
- setup_codebase_rag_env() {
121
- load_rag_env
122
- if [[ -n "$RAG_URL" && -n "$RAG_TOKEN" ]]; then
123
- write_rag_env
124
- echo "codebase-rag MCP: using $RAG_ENV"
125
- return 0
126
- fi
127
-
128
- local seeded
129
- if seeded="$(seed_rag_from_host_mcp 2>/dev/null)"; then
130
- if prompt_yn "Found codebase-rag in ~/.cursor/mcp.json. Use it for this workspace?" y; then
131
- RAG_URL="$(printf '%s\n' "$seeded" | sed -n '1p')"
132
- RAG_TOKEN="$(printf '%s\n' "$seeded" | sed -n '2p')"
133
- write_rag_env
134
- echo "codebase-rag MCP: imported HTTP server from ~/.cursor/mcp.json → $RAG_ENV"
135
- return 0
136
- fi
137
- fi
138
-
139
- if ! prompt_yn "Configure codebase RAG MCP (semantic search)?"; then
140
- RAG_URL=""
141
- RAG_TOKEN=""
142
- write_rag_env
143
- echo "codebase-rag MCP: skipped"
144
- return 0
145
- fi
146
-
147
- printf '%s\n' "Empty URL skips. Example: https://rag.example.com/mcp" >/dev/tty
148
- printf '%s' "MCP URL: " >/dev/tty
149
- read_tty RAG_URL
150
- RAG_URL="$(printf '%s' "$RAG_URL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
151
- if [[ -z "$RAG_URL" ]]; then
152
- RAG_TOKEN=""
153
- write_rag_env
154
- echo "codebase-rag MCP: skipped"
155
- return 0
156
- fi
157
- printf '%s' "Bearer token (hidden): " >/dev/tty
158
- read_tty RAG_TOKEN hidden
159
- write_rag_env
160
- echo "codebase-rag MCP: saved $RAG_ENV"
161
- }
162
-
163
- ensure_auth_dir "$npm_dir"
164
- ensure_auth_dir "$gh_dir"
165
- ensure_gh_env
166
- ensure_cursor_dir
167
- setup_npm_auth
168
- setup_gh_auth
169
- setup_codebase_rag_env
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Runs after container create and when workspace content updates
3
- # (devcontainer postCreateCommand / updateContentCommand).
4
- # Installs deps when missing or when the lockfile changed; skips otherwise.
5
- set -euo pipefail
6
-
7
- root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
- cd "$root"
9
-
10
- lockfile="pnpm-lock.yaml"
11
- stamp="node_modules/.decohere-pnpm-lock.sha"
12
-
13
- current_hash=""
14
- if [[ -f "$lockfile" ]]; then
15
- current_hash="$(sha256sum "$lockfile" | awk '{print $1}')"
16
- elif [[ -f package.json ]]; then
17
- current_hash="$(sha256sum package.json | awk '{print $1}')"
18
- fi
19
-
20
- if [[ -d node_modules/.bin ]] && [[ -n "$current_hash" ]] && [[ -f "$stamp" ]] && [[ "$(cat "$stamp")" == "$current_hash" ]]; then
21
- echo "node_modules: up to date, skipping pnpm install"
22
- exit 0
23
- fi
24
-
25
- echo "node_modules: installing dependencies (first create may take a while)..."
26
- if [[ -f "$lockfile" ]]; then
27
- pnpm install --frozen-lockfile
28
- else
29
- pnpm install
30
- fi
31
-
32
- if [[ -n "$current_hash" ]]; then
33
- mkdir -p node_modules
34
- printf '%s\n' "$current_hash" >"$stamp"
35
- fi
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Runs on every container start (devcontainer postStartCommand).
3
- # Optional npm / gh login is y/N on a TTY. Install is post-create.sh.
4
- # MCP merge also runs here for later reconnects; the Compose service command
5
- # merges before Cursor attaches (postStart is too late for the first snapshot).
6
- set -euo pipefail
7
-
8
- here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
- root="$(cd "$here/.." && pwd)"
10
- # shellcheck source=tty-prompt.sh
11
- source "$here/tty-prompt.sh"
12
-
13
- CODEBASE_RAG_WORKSPACE="$root" node --experimental-strip-types "$here/codebase-rag-mcp.ts" merge ||
14
- echo "codebase-rag MCP: merge skipped" >&2
15
-
16
- AUTH_REGISTRY="https://registry.npmjs.org"
17
-
18
- ensure_npm_auth() {
19
- if npm whoami --registry "$AUTH_REGISTRY" >/dev/null 2>&1; then
20
- echo "npm: logged in as $(npm whoami --registry "$AUTH_REGISTRY")"
21
- return 0
22
- fi
23
-
24
- if prompt_yn "Log in to npm?"; then
25
- echo "npm: starting login..."
26
- npm login --registry "$AUTH_REGISTRY" </dev/tty >/dev/tty
27
- return 0
28
- fi
29
-
30
- echo "npm: skipped. Open a terminal and run: npm login" >&2
31
- echo " (creds persist in .devcontainer/npm-auth/)" >&2
32
- return 1
33
- }
34
-
35
- ensure_gh_auth() {
36
- if ! command -v gh >/dev/null 2>&1; then
37
- echo "gh: not installed in this container" >&2
38
- return 1
39
- fi
40
-
41
- if gh auth status >/dev/null 2>&1; then
42
- echo "gh: $(gh auth status 2>&1 | head -n1)"
43
- return 0
44
- fi
45
-
46
- local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
47
- if [[ -n "$token" ]]; then
48
- echo "gh: authenticating from GH_TOKEN..."
49
- printf '%s\n' "$token" | gh auth login --with-token
50
- return 0
51
- fi
52
-
53
- if prompt_yn "Log in to GitHub CLI (gh)?"; then
54
- echo "gh: starting login..."
55
- gh auth login </dev/tty >/dev/tty
56
- return 0
57
- fi
58
-
59
- echo "gh: skipped. Open a terminal and run: gh auth login" >&2
60
- echo " (creds persist in .devcontainer/gh-auth/; or set GH_TOKEN in .devcontainer/gh.env)" >&2
61
- return 1
62
- }
63
-
64
- ensure_npm_auth || true
65
- ensure_gh_auth || true
@@ -1,38 +0,0 @@
1
- can_prompt() {
2
- [[ -r /dev/tty && -w /dev/tty ]]
3
- }
4
-
5
- read_tty() {
6
- local var="$1"
7
- local hidden="${2:-}"
8
- local value=""
9
- if [[ -n "$hidden" ]]; then
10
- read -r -s value </dev/tty || true
11
- printf '\n' >/dev/tty
12
- else
13
- read -r value </dev/tty || true
14
- fi
15
- printf -v "$var" '%s' "$value"
16
- }
17
-
18
- # Returns 0 for yes. Default is n unless the second argument is y.
19
- prompt_yn() {
20
- local question="$1"
21
- local default="${2:-n}"
22
- local reply=""
23
- if ! can_prompt; then
24
- return 1
25
- fi
26
- local hint="y/N"
27
- if [[ "$default" == "y" ]]; then
28
- hint="Y/n"
29
- fi
30
- printf '%s [%s] ' "$question" "$hint" >/dev/tty
31
- read_tty reply
32
- reply="$(printf '%s' "$reply" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
33
- if [[ -z "$reply" ]]; then
34
- [[ "$default" == "y" ]]
35
- return
36
- fi
37
- [[ "$reply" == "y" || "$reply" == "yes" ]]
38
- }
@@ -1,357 +0,0 @@
1
- import { ask, pick } from "../../domain/prompt.js";
2
- import { WELL_KNOWN_FILES } from "../../models/const.js";
3
- import { ensureLoggedIn } from "../auth0-commands.js";
4
- import { AuthConfigModel } from "../models/auth-config.model.js";
5
- import { ControllerSourceFile } from "../models/controller-source-file.model.js";
6
- import * as auth0Cli from "./auth0-cli.js";
7
- const ADMIN_ROLE = "admin";
8
- const USER_ENTITY_TS = `import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
9
- import type { Entity as IEntity } from "@decohere/data";
10
-
11
- @Entity()
12
- export class UserEntity implements IEntity {
13
- \t@PrimaryGeneratedColumn()
14
- \tpublic id!: number;
15
-
16
- \t@Column({ unique: true })
17
- \tpublic sub!: string;
18
-
19
- \t@Column({ type: "varchar", nullable: true })
20
- \tpublic email!: string | null;
21
-
22
- \t@Column()
23
- \tpublic displayName!: string;
24
-
25
- \t/** App-owned roles. The \`admin\` role grants CASL \`manage\`/\`all\` (wildcard). */
26
- \t@Column({ type: "simple-json", default: "[]" })
27
- \tpublic roles!: string[];
28
- }
29
- `;
30
- const USER_REPOSITORY_TS = `import { injectable } from "@decohere/crucible/decorators";
31
- import { injectUnitOfWork } from "@decohere/data";
32
- import { TypeOrmRepository, TypeOrmUnitOfWork } from "@decohere/data-typeorm";
33
- import { UserEntity } from "../entities/user.entity.js";
34
-
35
- @injectable()
36
- export class UserEntityRepository extends TypeOrmRepository<UserEntity> {
37
- \tpublic constructor(@injectUnitOfWork() unitOfWork: TypeOrmUnitOfWork) {
38
- \t\tsuper(unitOfWork, UserEntity);
39
- \t}
40
-
41
- \tpublic findBySub(sub: string): Promise<UserEntity | null> {
42
- \t\treturn this.firstOrDefault({ where: { sub } });
43
- \t}
44
- }
45
- `;
46
- const USER_SERVICE_TS = `import { injectable } from "@decohere/crucible/decorators";
47
- import type { Principal } from "@decohere/http";
48
- import { UserEntity } from "../entities/user.entity.js";
49
- import { UserEntityRepository } from "../repositories/user.repository.js";
50
-
51
- function claimString(principal: Principal | undefined, key: string): string | undefined {
52
- \tconst value = principal?.[key];
53
- \treturn typeof value === "string" && value.length > 0 ? value : undefined;
54
- }
55
-
56
- @injectable()
57
- export class UserService {
58
- \tpublic constructor(private readonly users: UserEntityRepository) {}
59
-
60
- \t/**
61
- \t * Upsert a local profile keyed by JWT \`sub\`.
62
- \t * Profile fields from the token fill gaps; seeded rows keep displayName/email/roles.
63
- \t */
64
- \tpublic async ensureFromPrincipal(principal: Principal | undefined): Promise<UserEntity> {
65
- \t\tconst sub = principal?.sub;
66
- \t\tif (!sub) {
67
- \t\t\tthrow new Error("Authenticated request is missing principal.sub");
68
- \t\t}
69
-
70
- \t\tconst email =
71
- \t\t\tclaimString(principal, "email") ?? claimString(principal, "preferred_username");
72
- \t\tconst displayFromToken =
73
- \t\t\tclaimString(principal, "name") ??
74
- \t\t\tclaimString(principal, "nickname") ??
75
- \t\t\temail;
76
-
77
- \t\tconst existing = await this.users.findBySub(sub);
78
- \t\tif (existing) {
79
- \t\t\tlet dirty = false;
80
- \t\t\tif (!existing.email && email) {
81
- \t\t\t\texisting.email = email;
82
- \t\t\t\tdirty = true;
83
- \t\t\t}
84
- \t\t\tif (existing.displayName === existing.sub && displayFromToken) {
85
- \t\t\t\texisting.displayName = displayFromToken;
86
- \t\t\t\tdirty = true;
87
- \t\t\t}
88
- \t\t\treturn dirty ? this.users.save(existing) : existing;
89
- \t\t}
90
-
91
- \t\tconst created = new UserEntity();
92
- \t\tcreated.sub = sub;
93
- \t\tcreated.email = email ?? null;
94
- \t\tcreated.displayName = displayFromToken ?? sub;
95
- \t\tcreated.roles = Array.isArray(principal.roles)
96
- \t\t\t? principal.roles.filter((r): r is string => typeof r === "string")
97
- \t\t\t: [];
98
- \t\treturn this.users.save(created);
99
- \t}
100
- }
101
- `;
102
- const ABILITY_FACTORY_TS = `import type { Principal } from "@decohere/http";
103
- import {
104
- \tADMIN_ROLE,
105
- \tcreatePrincipalFromPayload,
106
- \tdefineAbilitiesFor as defaultDefineAbilitiesFor,
107
- } from "@decohere/http";
108
-
109
- /**
110
- * Customize CASL authorization here (product-specific rules).
111
- *
112
- * Default delegates to \`@decohere/http\`: \`admin\` role → \`manage\`/\`all\`; IdP scopes
113
- * (\`action:Subject\`) → abilities. Replace the body when you need app rules
114
- * beyond that — this file is the intentional extension point, not framework
115
- * infrastructure to copy.
116
- */
117
- export function defineAbilitiesFor(principal: Principal) {
118
- \treturn defaultDefineAbilitiesFor(principal);
119
- }
120
-
121
- /**
122
- * Customize JWT → Principal mapping here (IdP-specific claims).
123
- *
124
- * Elevates \`AUTH0_ADMIN_USER_SUB\` to the admin role so the seeded admin works
125
- * before/without roles on the access token.
126
- */
127
- export function mapPrincipal(payload: Record<string, unknown>): Principal {
128
- \tconst principal = createPrincipalFromPayload(payload);
129
- \tconst sub = principal.sub;
130
- \tif (sub && sub === process.env.AUTH0_ADMIN_USER_SUB) {
131
- \t\treturn {
132
- \t\t\t...principal,
133
- \t\t\troles: [...new Set([ADMIN_ROLE, ...(principal.roles ?? [])])],
134
- \t\t};
135
- \t}
136
- \treturn principal;
137
- }
138
- `;
139
- async function getOptions(workspace, cliOptions) {
140
- if (cliOptions.authProvider === "auth0" && !cliOptions.issuerBaseUrl) {
141
- return getAuth0Options(workspace, cliOptions);
142
- }
143
- if (cliOptions.authProvider != null) return {};
144
- const choices = [
145
- {
146
- label: "none"
147
- },
148
- {
149
- label: "auth0"
150
- }
151
- ];
152
- const selected = await pick("\n Auth provider: ", choices, (c)=>c.label);
153
- const provider = selected?.label ?? "none";
154
- if (provider !== "auth0") return {
155
- authProvider: "none"
156
- };
157
- return getAuth0Options(workspace, cliOptions);
158
- }
159
- async function getAuth0Options(workspace, cliOptions) {
160
- const active = ensureLoggedIn();
161
- const issuerBaseUrl = `https://${active.name}`;
162
- console.log(`\n ✓ Auth0 tenant: ${issuerBaseUrl}`);
163
- process.stdout.write("\n Fetching Auth0 APIs...");
164
- const userApis = auth0Cli.listApis().filter((a)=>!a.name.includes("Management API"));
165
- process.stdout.write(` found ${userApis.length}.\n`);
166
- const projectName = workspace.name;
167
- const apiChoices = [
168
- {
169
- label: `<new> https://api.${projectName}`,
170
- existing: null
171
- },
172
- ...userApis.map((a)=>({
173
- label: `${a.name} — ${a.identifier}`,
174
- existing: a.identifier
175
- }))
176
- ];
177
- const apiSelected = await pick("\n Select Auth0 API: ", apiChoices, (c)=>c.label);
178
- let audience;
179
- if (!apiSelected || apiSelected.existing === null) {
180
- const defaultId = `https://api.${projectName}`;
181
- audience = await ask(` API Identifier [${defaultId}]: `, defaultId);
182
- console.log("\n Creating Auth0 API...");
183
- audience = auth0Cli.createApi(`${projectName} API`, audience).identifier;
184
- console.log(` \u2713 API created: ${audience}`);
185
- } else {
186
- audience = apiSelected.existing;
187
- console.log(` \u2713 Selected: ${audience}`);
188
- try {
189
- auth0Cli.updateApiScopes(audience);
190
- console.log(` \u2713 API scopes ensured (${auth0Cli.HELLO_API_SCOPES.join(", ")})`);
191
- } catch {
192
- console.error(" ✗ Failed to update API scopes — add Hello scopes in the Auth0 dashboard.");
193
- }
194
- }
195
- const withLocalUsers = cliOptions.databaseProvider === "postgres/typeorm";
196
- let adminUsername;
197
- let adminPassword;
198
- if (withLocalUsers) {
199
- adminUsername = cliOptions.adminUsername ?? await ask("\n Admin username [admin]: ", "admin");
200
- adminPassword = cliOptions.adminPassword;
201
- if (adminPassword == null) {
202
- adminPassword = await ask(" Admin password (leave blank to generate): ");
203
- if (!adminPassword) {
204
- adminPassword = auth0Cli.generateAdminPassword();
205
- console.log(` ✓ Generated admin password (written to .env): ${adminPassword}`);
206
- }
207
- }
208
- }
209
- return {
210
- authProvider: "auth0",
211
- issuerBaseUrl,
212
- audience,
213
- ...withLocalUsers ? {
214
- adminUsername,
215
- adminPassword
216
- } : {}
217
- };
218
- }
219
- function adminEmail(workspaceName, username) {
220
- if (username.includes("@")) return username;
221
- const slug = workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
222
- return `${username}@${slug}.example.com`;
223
- }
224
- function createAuth0AdminUser(workspace, options, authConfig) {
225
- const username = options.adminUsername ?? "admin";
226
- const password = options.adminPassword;
227
- if (!password) return;
228
- const email = adminEmail(workspace.name, username);
229
- try {
230
- const admin = auth0Cli.createAdminDatabaseUser({
231
- email,
232
- password,
233
- username
234
- });
235
- authConfig.setAdminUser({
236
- sub: admin.user_id,
237
- email: admin.email,
238
- password: admin.password,
239
- username: admin.username
240
- });
241
- workspace.getModel(WELL_KNOWN_FILES.ENV_FILE)?.addEntry("AUTH0_ADMIN_USER_SUB", admin.user_id).addEntry("AUTH0_ADMIN_USER_EMAIL", admin.email).addEntry("AUTH0_ADMIN_USER_PASSWORD", admin.password).addEntry("AUTH0_ADMIN_USERNAME", admin.username);
242
- console.log(` ✓ Auth0 admin user ready: ${admin.email} (role: ${ADMIN_ROLE})`);
243
- console.log(` Login with EMAIL (not username): ${admin.email}`);
244
- console.log(` Password: see AUTH0_ADMIN_USER_PASSWORD in .env`);
245
- } catch (err) {
246
- console.error(` ✗ Failed to ensure Auth0 admin user (${email}): ${err instanceof Error ? err.message : err}`);
247
- }
248
- }
249
- function scaffoldLocalUserProfile(workspace) {
250
- for (const project of workspace.getProjects()){
251
- project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addEntity({
252
- className: "UserEntity",
253
- importPath: "./entities/user.entity.js"
254
- }).addRepository({
255
- className: "UserEntityRepository",
256
- importPath: "./repositories/user.repository.js"
257
- }).addService({
258
- className: "UserService",
259
- importPath: "./services/user.service.js",
260
- scope: "request"
261
- }).addImport("defineAbilitiesFor", "./auth/ability.factory.js").addImport("mapPrincipal", "./auth/ability.factory.js").setTypeOrmSeed([
262
- "const sub = process.env.AUTH0_ADMIN_USER_SUB;",
263
- "if (!sub) return;",
264
- "const repo = dataSource.getRepository(UserEntity);",
265
- "const existing = await repo.findOne({ where: { sub } });",
266
- "if (existing) return;",
267
- "await repo.save(repo.create({",
268
- " sub,",
269
- " email: process.env.AUTH0_ADMIN_USER_EMAIL ?? null,",
270
- ' displayName: process.env.AUTH0_ADMIN_USERNAME ?? "admin",',
271
- ' roles: ["admin"],',
272
- "}));"
273
- ].join(" "));
274
- project.addFile({
275
- files: ()=>[
276
- {
277
- filePath: "src/entities/user.entity.ts",
278
- content: USER_ENTITY_TS
279
- },
280
- {
281
- filePath: "src/repositories/user.repository.ts",
282
- content: USER_REPOSITORY_TS
283
- },
284
- {
285
- filePath: "src/services/user.service.ts",
286
- content: USER_SERVICE_TS
287
- },
288
- {
289
- filePath: "src/auth/ability.factory.ts",
290
- content: ABILITY_FACTORY_TS
291
- }
292
- ]
293
- });
294
- }
295
- for (const controller of workspace.getModels(ControllerSourceFile)){
296
- controller.addImport("UserService", "../services/user.service.js").addImport("inject", "@decohere/crucible/decorators").addConstructorParam("@inject(UserService) private readonly users: UserService");
297
- controller.getMethod("getOne").setAsync(true).setBody([
298
- "const user = await this.users.ensureFromPrincipal(this.principalProvider.currentPrincipal);",
299
- "return { id, message: `Hello ${user.displayName}!`, username: user.displayName };"
300
- ]);
301
- }
302
- }
303
- function scaffold(workspace, options) {
304
- if (options.authProvider !== "auth0") return;
305
- const resource = "Hello";
306
- const withLocalUsers = options.databaseProvider === "postgres/typeorm";
307
- for (const controller of workspace.getModels(ControllerSourceFile)){
308
- controller.addDecorator(`@Authorize("read", "${resource}")`).addCortexImport("AllowAnonymous").addCortexImport("Authorize").addCortexImport("IPrincipalProviderToken").addCortexImport("IPrincipalProvider", true).addImport("inject", "@decohere/crucible/decorators").addConstructorParam(`@inject(IPrincipalProviderToken) private readonly principalProvider: IPrincipalProvider`);
309
- controller.getMethod("getAll").addDecorator("@AllowAnonymous()");
310
- if (!withLocalUsers) {
311
- controller.getMethod("getOne").setBody([
312
- "const principal = this.principalProvider.currentPrincipal;",
313
- "const username =",
314
- '\t(typeof principal?.name === "string" && principal.name) ||',
315
- '\t(typeof principal?.nickname === "string" && principal.nickname) ||',
316
- '\t(typeof principal?.email === "string" && principal.email) ||',
317
- '\t(typeof principal?.preferred_username === "string" && principal.preferred_username) ||',
318
- "\tprincipal?.sub ||",
319
- '\t"unknown";',
320
- "return { id, message: `Hello ${username}!`, username };"
321
- ]);
322
- }
323
- controller.getMethod("create").addDecorator(`@Authorize("create", "${resource}")`);
324
- controller.getMethod("update").addDecorator(`@Authorize("update", "${resource}")`);
325
- controller.getMethod("remove").addDecorator(`@Authorize("delete", "${resource}")`);
326
- }
327
- for (const project of workspace.getProjects()){
328
- project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addSideEffectImport("@decohere/http/auth");
329
- }
330
- if (withLocalUsers) {
331
- scaffoldLocalUserProfile(workspace);
332
- for (const project of workspace.getProjects()){
333
- project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addBuilderCall(".withAuth(defineAbilitiesFor, { principalMapper: mapPrincipal })");
334
- }
335
- } else {
336
- for (const project of workspace.getProjects()){
337
- project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addBuilderCall(".withAuth()");
338
- }
339
- }
340
- const issuerPlaceholder = "${AUTH0_ISSUER_BASE_URL}";
341
- const audPlaceholder = "${AUTH0_AUDIENCE}";
342
- const configSection = `auth:\n discoveryUrl: ${issuerPlaceholder}\n audience: ${audPlaceholder}\n`;
343
- workspace.getModel(WELL_KNOWN_FILES.CONFIG_YAML)?.addSection(configSection);
344
- workspace.getModel(WELL_KNOWN_FILES.ENV_FILE)?.addEntry("AUTH0_ISSUER_BASE_URL", options.issuerBaseUrl ?? "").addEntry("AUTH0_AUDIENCE", options.audience ?? "");
345
- const authConfig = new AuthConfigModel({
346
- issuerBaseUrl: options.issuerBaseUrl ?? "",
347
- audience: options.audience ?? ""
348
- });
349
- workspace.addModel(authConfig);
350
- if (withLocalUsers && options.adminPassword) {
351
- createAuth0AdminUser(workspace, options, authConfig);
352
- }
353
- }
354
- export const AuthFeature = {
355
- getOptions,
356
- scaffold
357
- };