@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,21 +0,0 @@
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);
@@ -1,245 +0,0 @@
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
- }
@@ -1,154 +0,0 @@
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
- };
@@ -1,182 +0,0 @@
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
- };
@@ -1,29 +0,0 @@
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
- }
@@ -1,3 +0,0 @@
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=