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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/LICENSE +15 -0
  2. package/commonjs/src/add-api.action.js +37 -0
  3. package/commonjs/src/add-api.command.js +39 -0
  4. package/commonjs/src/add.command.js +22 -0
  5. package/commonjs/src/add.module.js +24 -0
  6. package/commonjs/src/auth0-configure.action.js +28 -0
  7. package/commonjs/src/auth0-configure.command.js +24 -0
  8. package/commonjs/src/auth0-login.action.js +28 -0
  9. package/commonjs/src/auth0-login.command.js +23 -0
  10. package/commonjs/src/auth0.command.js +22 -0
  11. package/commonjs/src/auth0.module.js +28 -0
  12. package/commonjs/src/cli.js +8 -0
  13. package/commonjs/src/create.command.js +22 -0
  14. package/commonjs/src/create.module.js +24 -0
  15. package/commonjs/src/domain/prompt.js +62 -0
  16. package/commonjs/src/index.js +48 -0
  17. package/commonjs/src/models/compose.model.js +258 -0
  18. package/commonjs/src/models/config-yaml.model.js +86 -0
  19. package/commonjs/src/models/const.js +30 -0
  20. package/commonjs/src/models/dev-container.model.js +172 -0
  21. package/commonjs/src/models/docker-image.model.js +277 -0
  22. package/commonjs/src/models/env-file.model.js +48 -0
  23. package/commonjs/src/models/gitignore.model.js +48 -0
  24. package/commonjs/src/models/launch-json.model.js +70 -0
  25. package/commonjs/src/models/package-json.model.js +156 -0
  26. package/commonjs/src/models/project.model.js +132 -0
  27. package/commonjs/src/models/source-file.js +4 -0
  28. package/commonjs/src/models/tsconfig.model.js +32 -0
  29. package/commonjs/src/models/vitest-config.model.js +33 -0
  30. package/commonjs/src/models/vscode-settings.model.js +29 -0
  31. package/commonjs/src/program.js +64 -0
  32. package/commonjs/src/resolve-target-dir.js +17 -0
  33. package/commonjs/src/services/scaffold.service.js +60 -0
  34. package/commonjs/src/version.js +55 -0
  35. package/commonjs/src/webapi/api-name.js +93 -0
  36. package/commonjs/src/webapi/auth0-commands.js +250 -0
  37. package/commonjs/src/webapi/authorize-scopes.js +79 -0
  38. package/commonjs/src/webapi/cli-options.js +99 -0
  39. package/commonjs/src/webapi/const.js +54 -0
  40. package/commonjs/src/webapi/create-api.js +265 -0
  41. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +164 -0
  42. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  43. package/commonjs/src/webapi/devcontainer/host-scripts.js +50 -0
  44. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  45. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
  46. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
  47. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
  48. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  49. package/commonjs/src/webapi/features/auth.feature.js +408 -0
  50. package/commonjs/src/webapi/features/auth0-cli.js +472 -0
  51. package/commonjs/src/webapi/features/biome-format.js +47 -0
  52. package/commonjs/src/webapi/features/database.feature.js +433 -0
  53. package/commonjs/src/webapi/features/finalize.feature.js +93 -0
  54. package/commonjs/src/webapi/features/postman.feature.js +322 -0
  55. package/commonjs/src/webapi/models/api-package-json.model.js +58 -0
  56. package/commonjs/src/webapi/models/auth-config.model.js +24 -0
  57. package/commonjs/src/webapi/models/controller-source-file.model.js +190 -0
  58. package/commonjs/src/webapi/models/github-ci.model.js +193 -0
  59. package/commonjs/src/webapi/models/main-source-file.model.js +165 -0
  60. package/commonjs/src/webapi/models/production-dockerfile.model.js +75 -0
  61. package/commonjs/src/webapi/models/sample-files.model.js +58 -0
  62. package/commonjs/src/webapi/models/webapi-image.model.js +25 -0
  63. package/commonjs/src/webapi/postman/hello-openapi.js +173 -0
  64. package/commonjs/src/webapi/postman/local-view.js +182 -0
  65. package/commonjs/src/webapi/postman/openapi-to-postman.js +238 -0
  66. package/commonjs/src/webapi/workspace-readme.js +109 -0
  67. package/commonjs/src/webapi.action.js +28 -0
  68. package/commonjs/src/webapi.command.js +43 -0
  69. package/es6/src/add-api.action.js +27 -0
  70. package/es6/src/add-api.command.js +29 -0
  71. package/es6/src/add.command.js +12 -0
  72. package/es6/src/add.module.js +14 -0
  73. package/es6/src/auth0-configure.action.js +18 -0
  74. package/es6/src/auth0-configure.command.js +14 -0
  75. package/es6/src/auth0-login.action.js +18 -0
  76. package/es6/src/auth0-login.command.js +13 -0
  77. package/es6/src/auth0.command.js +12 -0
  78. package/es6/src/auth0.module.js +18 -0
  79. package/es6/src/cli.js +4 -0
  80. package/es6/src/create.command.js +12 -0
  81. package/es6/src/create.module.js +14 -0
  82. package/es6/src/domain/prompt.js +41 -0
  83. package/es6/src/index.js +9 -0
  84. package/es6/src/models/compose.model.js +254 -0
  85. package/es6/src/models/config-yaml.model.js +74 -0
  86. package/es6/src/models/const.js +20 -0
  87. package/es6/src/models/dev-container.model.js +162 -0
  88. package/es6/src/models/docker-image.model.js +235 -0
  89. package/es6/src/models/env-file.model.js +44 -0
  90. package/es6/src/models/gitignore.model.js +38 -0
  91. package/es6/src/models/launch-json.model.js +60 -0
  92. package/es6/src/models/package-json.model.js +141 -0
  93. package/es6/src/models/project.model.js +117 -0
  94. package/es6/src/models/source-file.js +1 -0
  95. package/es6/src/models/tsconfig.model.js +22 -0
  96. package/es6/src/models/vitest-config.model.js +23 -0
  97. package/es6/src/models/vscode-settings.model.js +19 -0
  98. package/es6/src/program.js +50 -0
  99. package/es6/src/resolve-target-dir.js +7 -0
  100. package/es6/src/services/scaffold.service.js +50 -0
  101. package/es6/src/version.js +34 -0
  102. package/es6/src/webapi/api-name.js +57 -0
  103. package/es6/src/webapi/auth0-commands.js +185 -0
  104. package/es6/src/webapi/authorize-scopes.js +55 -0
  105. package/es6/src/webapi/cli-options.js +34 -0
  106. package/es6/src/webapi/const.js +21 -0
  107. package/es6/src/webapi/create-api.js +245 -0
  108. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +154 -0
  109. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  110. package/es6/src/webapi/devcontainer/host-scripts.js +29 -0
  111. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  112. package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
  113. package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
  114. package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
  115. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  116. package/es6/src/webapi/features/auth.feature.js +357 -0
  117. package/es6/src/webapi/features/auth0-cli.js +406 -0
  118. package/es6/src/webapi/features/biome-format.js +40 -0
  119. package/es6/src/webapi/features/database.feature.js +423 -0
  120. package/es6/src/webapi/features/finalize.feature.js +83 -0
  121. package/es6/src/webapi/features/postman.feature.js +256 -0
  122. package/es6/src/webapi/models/api-package-json.model.js +53 -0
  123. package/es6/src/webapi/models/auth-config.model.js +20 -0
  124. package/es6/src/webapi/models/controller-source-file.model.js +181 -0
  125. package/{src/webapi/models/github-ci.model.ts → es6/src/webapi/models/github-ci.model.js} +27 -29
  126. package/es6/src/webapi/models/main-source-file.model.js +161 -0
  127. package/es6/src/webapi/models/production-dockerfile.model.js +66 -0
  128. package/es6/src/webapi/models/sample-files.model.js +40 -0
  129. package/es6/src/webapi/models/webapi-image.model.js +18 -0
  130. package/es6/src/webapi/postman/hello-openapi.js +166 -0
  131. package/es6/src/webapi/postman/local-view.js +167 -0
  132. package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
  133. package/es6/src/webapi/workspace-readme.js +88 -0
  134. package/es6/src/webapi.action.js +18 -0
  135. package/es6/src/webapi.command.js +33 -0
  136. package/package.json +19 -9
  137. package/types/src/add-api.action.d.ts +13 -0
  138. package/types/src/add-api.command.d.ts +7 -0
  139. package/types/src/add.command.d.ts +6 -0
  140. package/types/src/add.module.d.ts +14 -0
  141. package/types/src/auth0-configure.action.d.ts +7 -0
  142. package/types/src/auth0-configure.command.d.ts +6 -0
  143. package/types/src/auth0-login.action.d.ts +4 -0
  144. package/types/src/auth0-login.command.d.ts +6 -0
  145. package/types/src/auth0.command.d.ts +6 -0
  146. package/types/src/auth0.module.d.ts +18 -0
  147. package/types/src/cli.d.ts +2 -0
  148. package/types/src/create.command.d.ts +6 -0
  149. package/types/src/create.module.d.ts +14 -0
  150. package/types/src/domain/prompt.d.ts +4 -0
  151. package/{src/index.ts → types/src/index.d.ts} +4 -0
  152. package/types/src/models/compose.model.d.ts +73 -0
  153. package/types/src/models/config-yaml.model.d.ts +27 -0
  154. package/types/src/models/const.d.ts +20 -0
  155. package/types/src/models/dev-container.model.d.ts +49 -0
  156. package/types/src/models/docker-image.model.d.ts +90 -0
  157. package/types/src/models/env-file.model.d.ts +16 -0
  158. package/types/src/models/gitignore.model.d.ts +9 -0
  159. package/types/src/models/launch-json.model.d.ts +7 -0
  160. package/types/src/models/package-json.model.d.ts +36 -0
  161. package/types/src/models/project.model.d.ts +60 -0
  162. package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
  163. package/types/src/models/tsconfig.model.d.ts +5 -0
  164. package/types/src/models/vitest-config.model.d.ts +5 -0
  165. package/types/src/models/vscode-settings.model.d.ts +7 -0
  166. package/types/src/program.d.ts +8 -0
  167. package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
  168. package/types/src/services/scaffold.service.d.ts +22 -0
  169. package/types/src/version.d.ts +6 -0
  170. package/types/src/webapi/api-name.d.ts +14 -0
  171. package/types/src/webapi/auth0-commands.d.ts +11 -0
  172. package/types/src/webapi/authorize-scopes.d.ts +7 -0
  173. package/types/src/webapi/cli-options.d.ts +6 -0
  174. package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +9 -21
  175. package/types/src/webapi/create-api.d.ts +15 -0
  176. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
  177. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  178. package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
  179. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  180. package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
  181. package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
  182. package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
  183. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  184. package/types/src/webapi/features/auth.feature.d.ts +10 -0
  185. package/types/src/webapi/features/auth0-cli.d.ts +84 -0
  186. package/types/src/webapi/features/biome-format.d.ts +5 -0
  187. package/types/src/webapi/features/database.feature.d.ts +7 -0
  188. package/types/src/webapi/features/finalize.feature.d.ts +2 -0
  189. package/types/src/webapi/features/postman.feature.d.ts +16 -0
  190. package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
  191. package/types/src/webapi/models/auth-config.model.d.ts +26 -0
  192. package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
  193. package/types/src/webapi/models/github-ci.model.d.ts +9 -0
  194. package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
  195. package/types/src/webapi/models/production-dockerfile.model.d.ts +18 -0
  196. package/types/src/webapi/models/sample-files.model.d.ts +7 -0
  197. package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
  198. package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
  199. package/types/src/webapi/postman/local-view.d.ts +17 -0
  200. package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
  201. package/types/src/webapi/workspace-readme.d.ts +18 -0
  202. package/types/src/webapi.action.d.ts +14 -0
  203. package/types/src/webapi.command.d.ts +11 -0
  204. package/src/add-api.action.ts +0 -25
  205. package/src/add-api.command.ts +0 -31
  206. package/src/add.command.ts +0 -20
  207. package/src/add.module.ts +0 -11
  208. package/src/cli.ts +0 -4
  209. package/src/create.command.ts +0 -20
  210. package/src/create.module.ts +0 -11
  211. package/src/domain/prompt.ts +0 -50
  212. package/src/models/compose.model.ts +0 -205
  213. package/src/models/config-yaml.model.ts +0 -83
  214. package/src/models/const.ts +0 -20
  215. package/src/models/dev-container.model.ts +0 -136
  216. package/src/models/docker-image.model.ts +0 -343
  217. package/src/models/env-file.model.ts +0 -45
  218. package/src/models/gitignore.model.ts +0 -35
  219. package/src/models/launch-json.model.ts +0 -60
  220. package/src/models/package-json.model.ts +0 -169
  221. package/src/models/project.model.ts +0 -141
  222. package/src/models/tsconfig.model.ts +0 -29
  223. package/src/models/vitest-config.model.ts +0 -26
  224. package/src/models/vscode-settings.model.ts +0 -24
  225. package/src/program.ts +0 -63
  226. package/src/services/scaffold.service.ts +0 -89
  227. package/src/version.ts +0 -56
  228. package/src/webapi/api-name.ts +0 -73
  229. package/src/webapi/cli-options.ts +0 -56
  230. package/src/webapi/create-api.ts +0 -275
  231. package/src/webapi/devcontainer/host-scripts.ts +0 -35
  232. package/src/webapi/features/auth.feature.ts +0 -454
  233. package/src/webapi/features/auth0-cli.ts +0 -421
  234. package/src/webapi/features/biome-format.ts +0 -34
  235. package/src/webapi/features/database.feature.ts +0 -522
  236. package/src/webapi/features/finalize.feature.ts +0 -112
  237. package/src/webapi/features/postman.feature.ts +0 -287
  238. package/src/webapi/models/api-package-json.model.ts +0 -60
  239. package/src/webapi/models/auth-config.model.ts +0 -36
  240. package/src/webapi/models/controller-source-file.model.ts +0 -232
  241. package/src/webapi/models/main-source-file.model.ts +0 -208
  242. package/src/webapi/models/production-dockerfile.model.ts +0 -162
  243. package/src/webapi/models/sample-files.model.ts +0 -45
  244. package/src/webapi/models/webapi-image.model.ts +0 -21
  245. package/src/webapi/postman/hello-openapi.ts +0 -92
  246. package/src/webapi/postman/local-view.ts +0 -195
  247. package/src/webapi/postman/openapi-to-postman.ts +0 -208
  248. package/src/webapi/workspace-readme.ts +0 -192
  249. package/src/webapi.action.ts +0 -22
  250. package/src/webapi.command.ts +0 -41
  251. package/tests/auth-postman.wiring.spec.ts +0 -263
  252. package/tests/auth0-cli.spec.ts +0 -232
  253. package/tests/biome-format.spec.ts +0 -49
  254. package/tests/cli.integration.spec.ts +0 -658
  255. package/tests/compose.model.spec.ts +0 -51
  256. package/tests/container-image.model.spec.ts +0 -162
  257. package/tests/dev-container.model.spec.ts +0 -29
  258. package/tests/devcontainer-base-image.spec.ts +0 -32
  259. package/tests/hello-di-inject.scaffold.spec.ts +0 -18
  260. package/tests/hello-route.contract.spec.ts +0 -94
  261. package/tests/package-json.order.spec.ts +0 -59
  262. package/tests/postman-from-openapi.spec.ts +0 -197
  263. package/tests/seed-devcontainer-templates.ts +0 -40
  264. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
@@ -0,0 +1,21 @@
1
+ /** Default API project slug when `--name` is omitted. */ export const DEFAULT_WEBAPI_NAME = "api";
2
+ /** Relative path of an API project inside the monorepo. */ export function webapiProjectDir(name) {
3
+ return `projects/${name}`;
4
+ }
5
+ /**
6
+ * Forge release entry for a scaffolded API (`pack: true` → per-target outDir).
7
+ * Keep in sync with root `package.json` `"start"` / `"start:<name>"` and Dockerfiles.
8
+ */ export function webapiReleaseEntry(name) {
9
+ return `dist/release/${webapiProjectDir(name)}/${webapiPackagedEntry()}`;
10
+ }
11
+ /** The same entry inside `.package/` (`forge package`), which is what the Dockerfile runs. */ export function webapiPackagedEntry() {
12
+ return "es6/src/main.js";
13
+ }
14
+ /** npm package name for a scaffolded API (`flux serve --project`). */ export function webapiPackageName(workspaceName, apiName) {
15
+ return `@${workspaceName}/${apiName}`;
16
+ }
17
+ /** Local listen port for the nth API (0-based). Production uses `PORT`. */ export function webapiPort(index) {
18
+ return 3000 + index;
19
+ }
20
+ /** @deprecated Use `webapiProjectDir(name)`. Default path for the first API. */ export const WEBAPI_PROJECT_DIR = webapiProjectDir(DEFAULT_WEBAPI_NAME);
21
+ /** @deprecated Use `webapiReleaseEntry(name)`. Default start entry for the first API. */ export const WEBAPI_RELEASE_ENTRY = webapiReleaseEntry(DEFAULT_WEBAPI_NAME);
@@ -0,0 +1,245 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { DockerCompose } from "../models/compose.model.js";
4
+ import { ConfigYaml } from "../models/config-yaml.model.js";
5
+ import { WELL_KNOWN_FILES } from "../models/const.js";
6
+ import { DevContainer } from "../models/dev-container.model.js";
7
+ import { commandHistory, DockerfileModel, withWorkspaceNodeModulesOwnership } from "../models/docker-image.model.js";
8
+ import { EnvFile } from "../models/env-file.model.js";
9
+ import { Gitignore } from "../models/gitignore.model.js";
10
+ import { LaunchJson } from "../models/launch-json.model.js";
11
+ import { PackageJson, packageScript } from "../models/package-json.model.js";
12
+ import { MonoRepoScaffold, ProjectScaffold } from "../models/project.model.js";
13
+ import { TsConfig } from "../models/tsconfig.model.js";
14
+ import { VitestConfig } from "../models/vitest-config.model.js";
15
+ import { VSCodeSettings } from "../models/vscode-settings.model.js";
16
+ import { ScaffoldService } from "../services/scaffold.service.js";
17
+ import { CreateCliError, isDecohereWorkspace, listExistingApiNames, parseApiNames } from "./api-name.js";
18
+ import { DEFAULT_WEBAPI_NAME, webapiPackageName, webapiPort, webapiProjectDir, webapiReleaseEntry } from "./const.js";
19
+ import { DevContainerHostScripts } from "./devcontainer/host-scripts.js";
20
+ import { AuthFeature } from "./features/auth.feature.js";
21
+ import { DatabaseFeature } from "./features/database.feature.js";
22
+ import { FinalizeFeature } from "./features/finalize.feature.js";
23
+ import { PostmanFeature } from "./features/postman.feature.js";
24
+ import { ApiPackageJson } from "./models/api-package-json.model.js";
25
+ import { ControllerSourceFile } from "./models/controller-source-file.model.js";
26
+ import { GithubCiWorkflows } from "./models/github-ci.model.js";
27
+ import { MainSourceFile } from "./models/main-source-file.model.js";
28
+ import { WebapiProductionDockerfile } from "./models/production-dockerfile.model.js";
29
+ import { SampleConfigModel, SampleServiceModel } from "./models/sample-files.model.js";
30
+ import { WebapiDevContainer } from "./models/webapi-image.model.js";
31
+ export { WEBAPI_PROJECT_DIR, WEBAPI_RELEASE_ENTRY } from "./const.js";
32
+ const features = [
33
+ DatabaseFeature,
34
+ AuthFeature,
35
+ PostmanFeature,
36
+ FinalizeFeature
37
+ ];
38
+ export async function scaffoldApi(cliOptions) {
39
+ const targetDir = resolve(cliOptions.d ?? ".");
40
+ const projectName = cliOptions.projectName?.trim() || process.env.DECOHERE_PROJECT_NAME?.trim() || basename(targetDir);
41
+ const names = parseApiNames(cliOptions.names);
42
+ const workspace = cliOptions.add ? createAddWorkspace(targetDir, projectName, names) : createWorkspace(targetDir, projectName, names);
43
+ await ScaffoldService.scaffold(workspace, {
44
+ ...cliOptions,
45
+ names
46
+ }, features);
47
+ }
48
+ function createWorkspace(targetDir, projectName, apiNames) {
49
+ const monoRepo = new MonoRepoScaffold();
50
+ monoRepo.targetDir = targetDir;
51
+ monoRepo.setName(projectName);
52
+ const name = monoRepo.name;
53
+ // Project-named root so Cursor Agents (and other tools) label the workspace by name.
54
+ const workspaceRoot = `/${name}`;
55
+ const compose = new DockerCompose().setProjectName(name).addHeaderComment("Merge .cursor/mcp.json in the service command. postStartCommand runs after Cursor's first MCP snapshot.").addNetwork(name, {
56
+ name: `samples-${name}`
57
+ }).addService("devcontainer", {
58
+ build: ".",
59
+ volumes: [
60
+ // Repo bind in Compose (survives CLI recreates; Cursor-only mounts do not).
61
+ `..:${workspaceRoot}`,
62
+ // Workspace-local (created by initialize.sh as the host user).
63
+ // Do NOT bind ${HOME}/.npm/... — Docker creates missing host paths as root.
64
+ "./npm-auth:/home/node/.npm-config",
65
+ "./gh-auth:/home/node/.config/gh",
66
+ `webapi-node-modules:${workspaceRoot}/node_modules`,
67
+ // Named volume (not a Cursor-only mount) so CLI recreates keep Up-arrow history.
68
+ `${commandHistory.volumeName}:${commandHistory.dir}`
69
+ ],
70
+ env_file: [
71
+ "./gh.env"
72
+ ],
73
+ environment: {
74
+ NPM_CONFIG_USERCONFIG: "/home/node/.npm-config/.npmrc"
75
+ },
76
+ command: `'bash -c "CODEBASE_RAG_WORKSPACE=${workspaceRoot} node --experimental-strip-types ${workspaceRoot}/.devcontainer/codebase-rag-mcp.ts merge || true && exec sleep infinity"'`,
77
+ networks: [
78
+ name
79
+ ]
80
+ }).addVolume("webapi-node-modules").addVolume(commandHistory.volumeName);
81
+ const installCmd = "bash .devcontainer/post-create.sh";
82
+ const devContainer = new DevContainer().setWorkspaceFolder(workspaceRoot).addExtension("biomejs.biome", "vitest.explorer").setInitializeCommand("bash .devcontainer/initialize.sh").setPostCreateCommand(installCmd).setUpdateContentCommand(installCmd).setPostStartCommand("bash .devcontainer/post-start.sh").useCompose();
83
+ forwardApiPorts(devContainer, apiNames, 0);
84
+ // Workspace-level models (repo root)
85
+ monoRepo.addModel(compose);
86
+ monoRepo.addModel(devContainer);
87
+ const appDevImage = new WebapiDevContainer();
88
+ appDevImage.addLayer(withWorkspaceNodeModulesOwnership({
89
+ workspaceDir: workspaceRoot
90
+ }));
91
+ monoRepo.addModel(appDevImage);
92
+ monoRepo.addModel(new WebapiProductionDockerfile(apiNames));
93
+ monoRepo.addModel(new GithubCiWorkflows());
94
+ monoRepo.addModel(new VSCodeSettings(name));
95
+ monoRepo.addModel(new LaunchJson(apiNames));
96
+ monoRepo.addModel(new TsConfig());
97
+ monoRepo.addModel(new VitestConfig());
98
+ monoRepo.addModel(new PackageJson(`${name}.api`, {
99
+ apiNames,
100
+ workspaceName: name
101
+ }));
102
+ monoRepo.addModel(new ConfigYaml(WELL_KNOWN_FILES.CONFIG_YAML, "config/config.yaml", ""));
103
+ monoRepo.addModel(new ConfigYaml(WELL_KNOWN_FILES.CONFIG_DEV_YAML, "config/config.dev.yaml", ""));
104
+ monoRepo.addModel(new Gitignore());
105
+ monoRepo.addModel(new EnvFile());
106
+ monoRepo.addFile(new DevContainerHostScripts());
107
+ // Biome formatter/linter config
108
+ monoRepo.addFile({
109
+ files: ()=>[
110
+ {
111
+ filePath: "biome.json",
112
+ content: JSON.stringify({
113
+ $schema: "https://biomejs.dev/schemas/2.0.0/schema.json",
114
+ files: {
115
+ includes: [
116
+ "**/*.ts",
117
+ "**/*.js",
118
+ "**/*.json",
119
+ "**/*.yml",
120
+ "**/*.yaml"
121
+ ]
122
+ },
123
+ assist: {
124
+ actions: {
125
+ source: {
126
+ organizeImports: "on"
127
+ }
128
+ }
129
+ },
130
+ vcs: {
131
+ enabled: true,
132
+ clientKind: "git",
133
+ useIgnoreFile: true
134
+ },
135
+ formatter: {
136
+ enabled: true,
137
+ formatWithErrors: true,
138
+ indentStyle: "tab",
139
+ lineWidth: 95
140
+ },
141
+ javascript: {
142
+ parser: {
143
+ unsafeParameterDecoratorsEnabled: true
144
+ }
145
+ },
146
+ linter: {
147
+ rules: {
148
+ style: {
149
+ useConsistentMemberAccessibility: {
150
+ level: "error",
151
+ options: {
152
+ accessibility: "explicit"
153
+ }
154
+ }
155
+ }
156
+ }
157
+ },
158
+ overrides: [
159
+ {
160
+ includes: [
161
+ "**/*.json"
162
+ ],
163
+ formatter: {
164
+ indentStyle: "space",
165
+ indentWidth: 2
166
+ }
167
+ }
168
+ ]
169
+ }, null, 2)
170
+ }
171
+ ]
172
+ });
173
+ for (const apiName of apiNames){
174
+ addApiProject(monoRepo, name, apiName);
175
+ }
176
+ return monoRepo;
177
+ }
178
+ function createAddWorkspace(targetDir, projectName, apiNames) {
179
+ if (!isDecohereWorkspace(targetDir)) {
180
+ throw new CreateCliError(`Not a Decohere workspace: ${targetDir}. Scaffold one first with \`npx @decohere/create webapi\`.`);
181
+ }
182
+ const existing = listExistingApiNames(targetDir);
183
+ for (const apiName of apiNames){
184
+ if (existing.includes(apiName)) {
185
+ throw new CreateCliError(`API "${apiName}" already exists at ${webapiProjectDir(apiName)}.`);
186
+ }
187
+ }
188
+ const monoRepo = new MonoRepoScaffold();
189
+ monoRepo.targetDir = targetDir;
190
+ monoRepo.setName(projectName);
191
+ const name = monoRepo.name;
192
+ const pkg = PackageJson.fromFile(join(targetDir, "package.json"));
193
+ monoRepo.addModel(pkg);
194
+ monoRepo.addModel(existsSync(join(targetDir, ".env")) ? EnvFile.fromFile(join(targetDir, ".env")) : new EnvFile());
195
+ monoRepo.addModel(existsSync(join(targetDir, "config/config.yaml")) ? ConfigYaml.fromFile(WELL_KNOWN_FILES.CONFIG_YAML, "config/config.yaml", join(targetDir, "config/config.yaml")) : new ConfigYaml(WELL_KNOWN_FILES.CONFIG_YAML, "config/config.yaml", ""));
196
+ monoRepo.addModel(existsSync(join(targetDir, "config/config.dev.yaml")) ? ConfigYaml.fromFile(WELL_KNOWN_FILES.CONFIG_DEV_YAML, "config/config.dev.yaml", join(targetDir, "config/config.dev.yaml")) : new ConfigYaml(WELL_KNOWN_FILES.CONFIG_DEV_YAML, "config/config.dev.yaml", ""));
197
+ // Load the workspace files features merge into, so e.g. --database on a workspace
198
+ // created without one adds the Postgres service instead of only the db:* scripts.
199
+ const compose = loadIfExists(targetDir, ".devcontainer/docker-compose.yml", (path)=>DockerCompose.fromFile(path));
200
+ if (compose) monoRepo.addModel(compose);
201
+ const devContainer = loadIfExists(targetDir, ".devcontainer/devcontainer.json", (path)=>DevContainer.fromFile(path));
202
+ if (devContainer) {
203
+ forwardApiPorts(devContainer, apiNames, existing.length);
204
+ monoRepo.addModel(devContainer);
205
+ }
206
+ const devImage = loadIfExists(targetDir, ".devcontainer/Dockerfile", (path)=>DockerfileModel.fromFile(path, ".devcontainer/Dockerfile"));
207
+ if (devImage) monoRepo.addModel(devImage);
208
+ const gitignore = loadIfExists(targetDir, ".gitignore", (path)=>Gitignore.fromFile(path));
209
+ if (gitignore) monoRepo.addModel(gitignore);
210
+ if (!pkg.hasScript("package")) {
211
+ pkg.addScript("package", "pnpm run build && forge package");
212
+ }
213
+ for (const [offset, apiName] of apiNames.entries()){
214
+ const port = webapiPort(existing.length + offset);
215
+ pkg.addScript(`start:${apiName}`, `node ${webapiReleaseEntry(apiName)}`);
216
+ pkg.addScript(`package:${apiName}`, packageScript(name, apiName));
217
+ pkg.addScript(`dev:${apiName}`, `PORT=${port} flux serve --env-file-if-exists .env --project ${webapiPackageName(name, apiName)}`);
218
+ addApiProject(monoRepo, name, apiName);
219
+ }
220
+ monoRepo.addModel(new WebapiProductionDockerfile(apiNames));
221
+ return monoRepo;
222
+ }
223
+ function addApiProject(monoRepo, workspaceName, apiName) {
224
+ const apiProject = new ProjectScaffold(webapiProjectDir(apiName));
225
+ apiProject.addModel(new ApiPackageJson(workspaceName, apiName));
226
+ apiProject.addModel(new MainSourceFile(`${workspaceName}.${apiName}`));
227
+ apiProject.addModel(new ControllerSourceFile());
228
+ apiProject.addFile(new SampleConfigModel());
229
+ apiProject.addFile(new SampleServiceModel());
230
+ monoRepo.addProject(apiProject);
231
+ }
232
+ function forwardApiPorts(devContainer, apiNames, firstIndex) {
233
+ for (const [offset, apiName] of apiNames.entries()){
234
+ const port = webapiPort(firstIndex + offset);
235
+ devContainer.forwardPort(port);
236
+ devContainer.setPortAttributes(port, {
237
+ label: apiName === DEFAULT_WEBAPI_NAME ? "API" : apiName,
238
+ requireLocalPort: true
239
+ });
240
+ }
241
+ }
242
+ function loadIfExists(targetDir, relativePath, load) {
243
+ const absolutePath = join(targetDir, relativePath);
244
+ return existsSync(absolutePath) ? load(absolutePath) : undefined;
245
+ }
@@ -0,0 +1,154 @@
1
+ import { chmodSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ function isRecord(value) {
5
+ return value != null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ function loadDotenv(path, env) {
8
+ let raw;
9
+ try {
10
+ raw = readFileSync(path, "utf8");
11
+ } catch {
12
+ return;
13
+ }
14
+ for (const line of raw.split(/\r?\n/)){
15
+ const trimmed = line.trim();
16
+ if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
17
+ const eq = trimmed.indexOf("=");
18
+ const key = trimmed.slice(0, eq).trim();
19
+ const value = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
20
+ if (key && env[key] === undefined) env[key] = value;
21
+ }
22
+ }
23
+ function seedFromMcpJson(raw) {
24
+ let data;
25
+ try {
26
+ data = JSON.parse(raw);
27
+ } catch {
28
+ return null;
29
+ }
30
+ if (!isRecord(data)) return null;
31
+ const servers = data.mcpServers;
32
+ if (!isRecord(servers)) return null;
33
+ const server = servers["codebase-rag"];
34
+ if (!isRecord(server)) return null;
35
+ const url = typeof server.url === "string" ? server.url.trim() : "";
36
+ const headers = isRecord(server.headers) ? server.headers : {};
37
+ const auth = typeof headers.Authorization === "string" ? headers.Authorization.trim() : "";
38
+ const token = auth.toLowerCase().startsWith("bearer ") ? auth.slice(7).trim() : "";
39
+ if (!url.startsWith("http") || token.length === 0) return null;
40
+ return {
41
+ url,
42
+ token
43
+ };
44
+ }
45
+ function mergeIntoMcpJson(input) {
46
+ const { mcpPath, url, token, hostRoot } = input;
47
+ if (!url || !token) {
48
+ return {
49
+ skipped: "no URL/token"
50
+ };
51
+ }
52
+ const xRoot = hostRoot || "${workspaceFolder}";
53
+ mkdirSync(dirname(mcpPath), {
54
+ recursive: true
55
+ });
56
+ let data = {
57
+ mcpServers: {}
58
+ };
59
+ try {
60
+ if (statSync(mcpPath).size > 0) {
61
+ const loaded = JSON.parse(readFileSync(mcpPath, "utf8"));
62
+ if (isRecord(loaded)) data = loaded;
63
+ }
64
+ } catch (err) {
65
+ const code = isRecord(err) && typeof err.code === "string" ? err.code : "";
66
+ if (code !== "ENOENT") {
67
+ console.log("codebase-rag MCP: existing mcp.json is invalid JSON; replacing rag entry only after backup");
68
+ renameSync(mcpPath, `${mcpPath}.bak`);
69
+ }
70
+ }
71
+ const servers = isRecord(data.mcpServers) ? data.mcpServers : {};
72
+ data.mcpServers = servers;
73
+ servers["codebase-rag"] = {
74
+ url,
75
+ headers: {
76
+ Authorization: `Bearer ${token}`,
77
+ "X-Codebase-Root": xRoot
78
+ }
79
+ };
80
+ writeFileSync(mcpPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
81
+ try {
82
+ chmodSync(mcpPath, 0o600);
83
+ } catch {
84
+ // memfs and some hosts omit chmod
85
+ }
86
+ return {
87
+ path: mcpPath,
88
+ root: xRoot
89
+ };
90
+ }
91
+ function workspaceRoot(env = process.env) {
92
+ const fromEnv = env.CODEBASE_RAG_WORKSPACE?.trim();
93
+ if (fromEnv) return fromEnv;
94
+ // Scaffolded copy lives at <repo>/.devcontainer/codebase-rag-mcp.ts
95
+ return dirname(dirname(fileURLToPath(import.meta.url)));
96
+ }
97
+ function mergeFromEnv(env = process.env) {
98
+ const root = workspaceRoot(env);
99
+ const cursorDir = join(root, ".cursor");
100
+ loadDotenv(join(cursorDir, "codebase-rag.env"), env);
101
+ const url = (env.CODEBASE_RAG_MCP_URL ?? "").trim();
102
+ const token = (env.CODEBASE_RAG_MCP_TOKEN ?? "").trim();
103
+ const hostRoot = (env.CODEBASE_RAG_MCP_ROOT ?? "").trim() || root;
104
+ const result = mergeIntoMcpJson({
105
+ mcpPath: join(cursorDir, "mcp.json"),
106
+ url,
107
+ token,
108
+ hostRoot
109
+ });
110
+ if (result.skipped != null) {
111
+ console.log(`codebase-rag MCP: skipped (${result.skipped})`);
112
+ return 0;
113
+ }
114
+ console.log(`codebase-rag MCP: wrote ${result.path} url=${url} root=${result.root}`);
115
+ return 0;
116
+ }
117
+ function seedPrint(mcpPath) {
118
+ let raw;
119
+ try {
120
+ raw = readFileSync(mcpPath, "utf8");
121
+ } catch {
122
+ return 1;
123
+ }
124
+ const creds = seedFromMcpJson(raw);
125
+ if (creds == null) return 1;
126
+ console.log(creds.url);
127
+ console.log(creds.token);
128
+ return 0;
129
+ }
130
+ function main(argv = process.argv.slice(2)) {
131
+ const command = argv[0] ?? "merge";
132
+ if (command === "seed-print") {
133
+ const mcpPath = argv[1];
134
+ if (mcpPath == null || mcpPath.length === 0) return 1;
135
+ return seedPrint(mcpPath);
136
+ }
137
+ if (command === "merge") {
138
+ return mergeFromEnv();
139
+ }
140
+ console.error(`codebase-rag MCP: unknown command ${command}`);
141
+ return 1;
142
+ }
143
+ const invoked = (process.argv[1] ?? "").replaceAll("\\", "/");
144
+ if (invoked.endsWith("/codebase-rag-mcp.ts") || invoked.endsWith("/codebase-rag-mcp.js")) {
145
+ process.exit(main());
146
+ }
147
+ export const codebaseRagMcp = {
148
+ loadDotenv,
149
+ seedFromMcpJson,
150
+ mergeIntoMcpJson,
151
+ mergeFromEnv,
152
+ seedPrint,
153
+ main
154
+ };
@@ -0,0 +1,182 @@
1
+ import {
2
+ chmodSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ statSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ export type CodebaseRagCredentials = {
13
+ url: string;
14
+ token: string;
15
+ };
16
+
17
+ export type McpJson = {
18
+ mcpServers?: Record<string, unknown>;
19
+ [key: string]: unknown;
20
+ };
21
+
22
+ function isRecord(value: unknown): value is Record<string, unknown> {
23
+ return value != null && typeof value === "object" && !Array.isArray(value);
24
+ }
25
+
26
+ function loadDotenv(path: string, env: NodeJS.ProcessEnv): void {
27
+ let raw: string;
28
+ try {
29
+ raw = readFileSync(path, "utf8");
30
+ } catch {
31
+ return;
32
+ }
33
+ for (const line of raw.split(/\r?\n/)) {
34
+ const trimmed = line.trim();
35
+ if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
36
+ const eq = trimmed.indexOf("=");
37
+ const key = trimmed.slice(0, eq).trim();
38
+ const value = trimmed
39
+ .slice(eq + 1)
40
+ .trim()
41
+ .replace(/^["']|["']$/g, "");
42
+ if (key && env[key] === undefined) env[key] = value;
43
+ }
44
+ }
45
+
46
+ function seedFromMcpJson(raw: string): CodebaseRagCredentials | null {
47
+ let data: unknown;
48
+ try {
49
+ data = JSON.parse(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ if (!isRecord(data)) return null;
54
+ const servers = data.mcpServers;
55
+ if (!isRecord(servers)) return null;
56
+ const server = servers["codebase-rag"];
57
+ if (!isRecord(server)) return null;
58
+ const url = typeof server.url === "string" ? server.url.trim() : "";
59
+ const headers = isRecord(server.headers) ? server.headers : {};
60
+ const auth = typeof headers.Authorization === "string" ? headers.Authorization.trim() : "";
61
+ const token = auth.toLowerCase().startsWith("bearer ") ? auth.slice(7).trim() : "";
62
+ if (!url.startsWith("http") || token.length === 0) return null;
63
+ return { url, token };
64
+ }
65
+
66
+ function mergeIntoMcpJson(input: {
67
+ mcpPath: string;
68
+ url: string;
69
+ token: string;
70
+ hostRoot: string;
71
+ }): { skipped?: string; path?: string; root?: string } {
72
+ const { mcpPath, url, token, hostRoot } = input;
73
+ if (!url || !token) {
74
+ return { skipped: "no URL/token" };
75
+ }
76
+
77
+ const xRoot = hostRoot || "${workspaceFolder}";
78
+ mkdirSync(dirname(mcpPath), { recursive: true });
79
+
80
+ let data: McpJson = { mcpServers: {} };
81
+ try {
82
+ if (statSync(mcpPath).size > 0) {
83
+ const loaded: unknown = JSON.parse(readFileSync(mcpPath, "utf8"));
84
+ if (isRecord(loaded)) data = loaded as McpJson;
85
+ }
86
+ } catch (err) {
87
+ const code = isRecord(err) && typeof err.code === "string" ? err.code : "";
88
+ if (code !== "ENOENT") {
89
+ console.log(
90
+ "codebase-rag MCP: existing mcp.json is invalid JSON; replacing rag entry only after backup",
91
+ );
92
+ renameSync(mcpPath, `${mcpPath}.bak`);
93
+ }
94
+ }
95
+
96
+ const servers = isRecord(data.mcpServers) ? data.mcpServers : {};
97
+ data.mcpServers = servers;
98
+ servers["codebase-rag"] = {
99
+ url,
100
+ headers: {
101
+ Authorization: `Bearer ${token}`,
102
+ "X-Codebase-Root": xRoot,
103
+ },
104
+ };
105
+ writeFileSync(mcpPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
106
+ try {
107
+ chmodSync(mcpPath, 0o600);
108
+ } catch {
109
+ // memfs and some hosts omit chmod
110
+ }
111
+ return { path: mcpPath, root: xRoot };
112
+ }
113
+
114
+ function workspaceRoot(env: NodeJS.ProcessEnv = process.env): string {
115
+ const fromEnv = env.CODEBASE_RAG_WORKSPACE?.trim();
116
+ if (fromEnv) return fromEnv;
117
+ // Scaffolded copy lives at <repo>/.devcontainer/codebase-rag-mcp.ts
118
+ return dirname(dirname(fileURLToPath(import.meta.url)));
119
+ }
120
+
121
+ function mergeFromEnv(env: NodeJS.ProcessEnv = process.env): number {
122
+ const root = workspaceRoot(env);
123
+ const cursorDir = join(root, ".cursor");
124
+ loadDotenv(join(cursorDir, "codebase-rag.env"), env);
125
+ const url = (env.CODEBASE_RAG_MCP_URL ?? "").trim();
126
+ const token = (env.CODEBASE_RAG_MCP_TOKEN ?? "").trim();
127
+ const hostRoot = (env.CODEBASE_RAG_MCP_ROOT ?? "").trim() || root;
128
+ const result = mergeIntoMcpJson({
129
+ mcpPath: join(cursorDir, "mcp.json"),
130
+ url,
131
+ token,
132
+ hostRoot,
133
+ });
134
+ if (result.skipped != null) {
135
+ console.log(`codebase-rag MCP: skipped (${result.skipped})`);
136
+ return 0;
137
+ }
138
+ console.log(`codebase-rag MCP: wrote ${result.path} url=${url} root=${result.root}`);
139
+ return 0;
140
+ }
141
+
142
+ function seedPrint(mcpPath: string): number {
143
+ let raw: string;
144
+ try {
145
+ raw = readFileSync(mcpPath, "utf8");
146
+ } catch {
147
+ return 1;
148
+ }
149
+ const creds = seedFromMcpJson(raw);
150
+ if (creds == null) return 1;
151
+ console.log(creds.url);
152
+ console.log(creds.token);
153
+ return 0;
154
+ }
155
+
156
+ function main(argv = process.argv.slice(2)): number {
157
+ const command = argv[0] ?? "merge";
158
+ if (command === "seed-print") {
159
+ const mcpPath = argv[1];
160
+ if (mcpPath == null || mcpPath.length === 0) return 1;
161
+ return seedPrint(mcpPath);
162
+ }
163
+ if (command === "merge") {
164
+ return mergeFromEnv();
165
+ }
166
+ console.error(`codebase-rag MCP: unknown command ${command}`);
167
+ return 1;
168
+ }
169
+
170
+ const invoked = (process.argv[1] ?? "").replaceAll("\\", "/");
171
+ if (invoked.endsWith("/codebase-rag-mcp.ts") || invoked.endsWith("/codebase-rag-mcp.js")) {
172
+ process.exit(main());
173
+ }
174
+
175
+ export const codebaseRagMcp = {
176
+ loadDotenv,
177
+ seedFromMcpJson,
178
+ mergeIntoMcpJson,
179
+ mergeFromEnv,
180
+ seedPrint,
181
+ main,
182
+ };
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ const TEMPLATE_FILES = [
4
+ "initialize.sh",
5
+ "post-create.sh",
6
+ "post-start.sh",
7
+ "tty-prompt.sh",
8
+ "codebase-rag.env.example"
9
+ ];
10
+ export function devcontainerTemplatePath(name) {
11
+ return fileURLToPath(new URL(`./templates/${name}`, import.meta.url));
12
+ }
13
+ export function codebaseRagMcpSourcePath() {
14
+ return fileURLToPath(new URL("./codebase-rag-mcp.ts", import.meta.url));
15
+ }
16
+ /** Host + container scripts for the scaffolded Dev Container. */ export class DevContainerHostScripts {
17
+ files() {
18
+ return [
19
+ ...TEMPLATE_FILES.map((name)=>({
20
+ filePath: `.devcontainer/${name}`,
21
+ content: readFileSync(devcontainerTemplatePath(name), "utf8")
22
+ })),
23
+ {
24
+ filePath: ".devcontainer/codebase-rag-mcp.ts",
25
+ content: readFileSync(codebaseRagMcpSourcePath(), "utf8")
26
+ }
27
+ ];
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ # Optional. Written to .cursor/codebase-rag.env by initialize.sh (gitignored).
2
+ # CODEBASE_RAG_MCP_URL=https://rag.example.com/mcp
3
+ # CODEBASE_RAG_MCP_TOKEN=