@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,162 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { WELL_KNOWN_FILES } from "./const.js";
3
- export class DevContainer {
4
- key = WELL_KNOWN_FILES.DEVCONTAINER_JSON;
5
- _extensions = [];
6
- _ports = [];
7
- _portAttributes = new Map();
8
- _mounts;
9
- _workspaceFolder = "/workspace";
10
- _postCreateCommand;
11
- _updateContentCommand;
12
- _postStartCommand;
13
- _initializeCommand;
14
- _remoteUser = "node";
15
- _useCompose = false;
16
- _existing;
17
- /**
18
- * Load an existing devcontainer.json so `decohere add api` can merge into it.
19
- * Only extensions, forwarded ports and port attributes are merged; everything else is kept.
20
- */ static fromFile(absolutePath) {
21
- const devContainer = new DevContainer();
22
- try {
23
- devContainer._existing = JSON.parse(readFileSync(absolutePath, "utf8"));
24
- } catch (error) {
25
- throw new Error(`Can't update ${absolutePath}: it isn't plain JSON (${error.message}). Remove any comments or trailing commas and try again.`);
26
- }
27
- return devContainer;
28
- }
29
- constructor(){
30
- this._mounts = [
31
- this.bindMount(this._workspaceFolder)
32
- ];
33
- }
34
- /** In-container path Cursor/VS Code opens (Agents window uses the basename). */ setWorkspaceFolder(folder) {
35
- this._workspaceFolder = folder;
36
- this._mounts = [
37
- this.bindMount(folder)
38
- ];
39
- return this;
40
- }
41
- bindMount(target) {
42
- return `source=..,target=${target},type=bind,consistency=cached`;
43
- }
44
- addExtension(...ids) {
45
- this._extensions.push(...ids);
46
- return this;
47
- }
48
- forwardPort(...ports) {
49
- this._ports.push(...ports);
50
- return this;
51
- }
52
- setPortAttributes(port, attrs) {
53
- this._portAttributes.set(port, attrs);
54
- return this;
55
- }
56
- setPostCreateCommand(cmd) {
57
- this._postCreateCommand = cmd;
58
- return this;
59
- }
60
- setUpdateContentCommand(cmd) {
61
- this._updateContentCommand = cmd;
62
- return this;
63
- }
64
- setPostStartCommand(cmd) {
65
- this._postStartCommand = cmd;
66
- return this;
67
- }
68
- setInitializeCommand(cmd) {
69
- this._initializeCommand = cmd;
70
- return this;
71
- }
72
- setRemoteUser(user) {
73
- this._remoteUser = user;
74
- return this;
75
- }
76
- useCompose() {
77
- this._useCompose = true;
78
- return this;
79
- }
80
- files() {
81
- const config = this._existing ? this.mergeIntoExisting(this._existing) : this.build();
82
- return [
83
- {
84
- filePath: ".devcontainer/devcontainer.json",
85
- content: JSON.stringify(config, null, 2)
86
- }
87
- ];
88
- }
89
- mergeIntoExisting(existing) {
90
- const config = structuredClone(existing);
91
- if (this._extensions.length > 0) {
92
- const customizations = objectAt(config, "customizations");
93
- const vscode = objectAt(customizations, "vscode");
94
- vscode.extensions = union(vscode.extensions, this._extensions);
95
- }
96
- if (this._ports.length > 0) {
97
- config.forwardPorts = union(config.forwardPorts, this._ports);
98
- }
99
- if (this._portAttributes.size > 0) {
100
- const attributes = objectAt(config, "portsAttributes");
101
- for (const [port, attrs] of this._portAttributes){
102
- attributes[String(port)] ??= attrs;
103
- }
104
- }
105
- return config;
106
- }
107
- build() {
108
- const config = {};
109
- if (this._useCompose) {
110
- config.dockerComposeFile = "docker-compose.yml";
111
- config.service = "devcontainer";
112
- config.workspaceFolder = this._workspaceFolder;
113
- }
114
- if (this._initializeCommand) {
115
- config.initializeCommand = this._initializeCommand;
116
- }
117
- if (this._mounts) {
118
- config.mounts = this._mounts;
119
- }
120
- if (this._extensions.length > 0) {
121
- config.customizations = {
122
- vscode: {
123
- extensions: this._extensions
124
- }
125
- };
126
- }
127
- if (this._ports.length > 0) {
128
- config.forwardPorts = this._ports;
129
- }
130
- if (this._portAttributes.size > 0) {
131
- const portsAttributes = {};
132
- for (const [port, attrs] of this._portAttributes){
133
- portsAttributes[String(port)] = attrs;
134
- }
135
- config.portsAttributes = portsAttributes;
136
- }
137
- if (this._postCreateCommand) {
138
- config.postCreateCommand = this._postCreateCommand;
139
- }
140
- if (this._updateContentCommand) {
141
- config.updateContentCommand = this._updateContentCommand;
142
- }
143
- if (this._postStartCommand) {
144
- config.postStartCommand = this._postStartCommand;
145
- }
146
- if (this._remoteUser) {
147
- config.remoteUser = this._remoteUser;
148
- }
149
- return config;
150
- }
151
- }
152
- /** The object at `parent[key]`, created when missing. */ function objectAt(parent, key) {
153
- parent[key] ??= {};
154
- return parent[key];
155
- }
156
- function union(existing, additions) {
157
- const items = Array.isArray(existing) ? [
158
- ...existing
159
- ] : [];
160
- for (const item of additions)if (!items.includes(item)) items.push(item);
161
- return items;
162
- }
@@ -1,235 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { WELL_KNOWN_FILES } from "./const.js";
3
- export class DockerStep {
4
- key;
5
- content;
6
- order;
7
- constructor(key, content, order = 0){
8
- this.key = key;
9
- this.content = content;
10
- this.order = order;
11
- }
12
- static run(key, command, order = 0) {
13
- return new DockerStep(key, `RUN ${command}`, order);
14
- }
15
- static raw(key, content, order = 0) {
16
- return new DockerStep(key, content, order);
17
- }
18
- static entrypoint(key, value, order = 0) {
19
- const content = typeof value === "string" ? `ENTRYPOINT ${value}` : `ENTRYPOINT ${JSON.stringify(value)}`;
20
- return new DockerStep(key, content, order);
21
- }
22
- }
23
- const StepOrder = {
24
- base: 1_000,
25
- features: 5_000,
26
- user: 9_000,
27
- entrypoint: 10_000
28
- };
29
- const VSCODE_BOOTLOADER_FIX_BASH_SNIPPET = `if [[ -n "\${NODE_OPTIONS:-}" && "\${NODE_OPTIONS}" == *"bootloader.js"* ]]; then
30
- _bootloader_path="$(printf '%s\\n' "$NODE_OPTIONS" | sed -n 's/.*--require[= ]\\([^[:space:]]*bootloader\\.js\\).*/\\1/p')"
31
- if [[ -n "\${_bootloader_path}" && ! -f "\${_bootloader_path}" ]]; then
32
- _stable_bootloader="$(find /vscode/vscode-server /home/node/.vscode-server/extensions -name "bootloader.js" -path "*/ms-vscode.js-debug/*" 2>/dev/null | head -1)"
33
- if [[ -n "\${_stable_bootloader}" ]]; then
34
- NODE_OPTIONS="\${NODE_OPTIONS//\${_bootloader_path}/\${_stable_bootloader}}"
35
- else
36
- NODE_OPTIONS="$(printf '%s\\n' "$NODE_OPTIONS" | sed -E 's#(^| )--require(=| )[[:graph:]]*bootloader\\.js( |$)# #g; s# +# #g; s#^ ##; s# $##')"
37
- unset VSCODE_INSPECTOR_OPTIONS
38
- fi
39
- export NODE_OPTIONS
40
- if [[ -z "$NODE_OPTIONS" ]]; then
41
- unset NODE_OPTIONS
42
- fi
43
- fi
44
- unset _bootloader_path _stable_bootloader
45
- fi`;
46
- /** Named volume + paths for interactive bash history in app Dev Containers. */ export const commandHistory = {
47
- volumeName: "bashhistory",
48
- dir: "/commandhistory",
49
- fileName: ".bash_history",
50
- histFile: "/commandhistory/.bash_history"
51
- };
52
- export function withCommandHistory(options) {
53
- const historyDir = options?.historyDir ?? commandHistory.dir;
54
- const historyFileName = options?.historyFileName ?? commandHistory.fileName;
55
- const username = options?.username ?? "node";
56
- const order = options?.order ?? StepOrder.base;
57
- const historyFile = `${historyDir}/${historyFileName}`;
58
- return (dockerfile)=>{
59
- dockerfile.addStep(DockerStep.run("history-dir", `mkdir -p ${historyDir} && touch ${historyFile} && chown -R ${username} ${historyDir}`, order)).addStep(DockerStep.raw("history-env", `ENV HISTFILE=${historyFile}`, order + 1)).addStep(DockerStep.raw("history-bashrc", `RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=${historyFile}" \\\n\t&& echo "$SNIPPET" >> "/home/${username}/.bashrc"`, order + 2));
60
- };
61
- }
62
- export function withNodeUser(options) {
63
- const username = options?.username ?? "node";
64
- const includeUserInstruction = options?.includeUserInstruction ?? true;
65
- const order = options?.order ?? StepOrder.features;
66
- return (dockerfile)=>{
67
- dockerfile.addStep(DockerStep.raw("arg-username", `ARG USERNAME=${username}`, order));
68
- if (includeUserInstruction) {
69
- dockerfile.addStep(DockerStep.raw("user", "USER ${USERNAME}", StepOrder.user));
70
- }
71
- };
72
- }
73
- export function withPnpm(options) {
74
- const version = options?.version ?? "10";
75
- const usernameExpr = options?.usernameExpr ?? "${USERNAME}";
76
- const order = options?.order ?? StepOrder.features + 100;
77
- return (dockerfile)=>{
78
- dockerfile.addStep(DockerStep.run("pnpm-install", `npm install --global npm@latest && npm add --global pnpm@${version}`, order)).addStep(DockerStep.run("pnpm-config", `mkdir -p /home/${usernameExpr}/.config/pnpm /home/${usernameExpr}/.pnpm-store && chown -R ${usernameExpr}:${usernameExpr} /home/${usernameExpr}/.config /home/${usernameExpr}/.pnpm-store && printf "store-dir=/home/${usernameExpr}/.pnpm-store\\n" > /home/${usernameExpr}/.config/pnpm/rc`, order + 1));
79
- };
80
- }
81
- /**
82
- * Client-only (`psql`, etc.). Escalates to root so this also works when layered on
83
- * a published base image that already switched to `USER node`.
84
- */ export function withPostgresqlClient(options) {
85
- const order = options?.order ?? StepOrder.features + 150;
86
- const username = options?.username ?? "node";
87
- return (dockerfile)=>{
88
- dockerfile.addStep(DockerStep.raw("postgresql-client-root", "USER root", order)).addStep(DockerStep.run("postgresql-client", "apt-get update && apt-get install -y --no-install-recommends postgresql-client && rm -rf /var/lib/apt/lists/*", order + 1)).addStep(DockerStep.raw("postgresql-client-user", `USER ${username}`, order + 2));
89
- };
90
- }
91
- /** GitHub CLI — install before USER switches away from root. */ export function withGithubCli(options) {
92
- const order = options?.order ?? StepOrder.features + 140;
93
- return (dockerfile)=>{
94
- dockerfile.addStep(DockerStep.run("github-cli", "apt-get update && apt-get install -y --no-install-recommends gh && rm -rf /var/lib/apt/lists/*", order));
95
- };
96
- }
97
- export function withWorkspaceNodeModulesOwnership(options) {
98
- const workspaceDir = options?.workspaceDir ?? "/workspace";
99
- const usernameExpr = options?.usernameExpr ?? "${USERNAME}";
100
- const order = options?.order ?? StepOrder.features + 200;
101
- return (dockerfile)=>{
102
- dockerfile.addStep(DockerStep.raw("workspace-node-modules", `RUN mkdir -p ${workspaceDir}/node_modules && chown ${usernameExpr}:${usernameExpr} ${workspaceDir}/node_modules`, order));
103
- };
104
- }
105
- export function withVsCodeJsDebugBootloaderFix(options) {
106
- const bashrcPath = options?.bashrcPath ?? "/home/${USERNAME}/.bashrc";
107
- const order = options?.order ?? StepOrder.features + 300;
108
- return (dockerfile)=>{
109
- dockerfile.addStep(DockerStep.raw("vscode-bootloader-fix", [
110
- "# VS Code / Cursor js-debug Auto Attach injects --require …/bootloader.js into",
111
- "# NODE_OPTIONS. On a fresh Dev Container that path often does not exist yet",
112
- "# (workspaceStorage file is only created after toggling Auto Attach), which crashes",
113
- "# every Node command. If the injected path is missing, prefer the stable",
114
- "# bootloader from the server extension install; otherwise strip the bad require.",
115
- `RUN cat <<'EOF' >> "${bashrcPath}"`,
116
- VSCODE_BOOTLOADER_FIX_BASH_SNIPPET,
117
- "EOF"
118
- ].join("\n"), order));
119
- };
120
- }
121
- export class DockerfileModel {
122
- key = WELL_KNOWN_FILES.DOCKERFILE;
123
- _steps = new Map();
124
- _layerWriters = [];
125
- _fromImage;
126
- _layerCounter = 0;
127
- _existing;
128
- constructor(baseImage){
129
- this._fromImage = baseImage;
130
- this.registerBaseLayers();
131
- }
132
- /**
133
- * Load an existing Dockerfile so `decohere add api` can append feature layers.
134
- * The new steps are appended as one block, unless the file already contains it.
135
- */ static fromFile(absolutePath, filePath) {
136
- const dockerfile = new DockerfileModel("");
137
- dockerfile._existing = {
138
- content: readFileSync(absolutePath, "utf8"),
139
- filePath
140
- };
141
- return dockerfile;
142
- }
143
- from(baseImage) {
144
- this._fromImage = baseImage;
145
- return this;
146
- }
147
- addLayer(layer, order = StepOrder.features) {
148
- if (typeof layer === "string") {
149
- const idx = ++this._layerCounter;
150
- const key = `layer-run-${idx}`;
151
- this._layerWriters.push((dockerfile)=>{
152
- dockerfile.addStep(DockerStep.run(key, layer, order + idx));
153
- });
154
- return this;
155
- }
156
- this._layerWriters.push(layer);
157
- return this;
158
- }
159
- addStep(step) {
160
- this._steps.set(step.key, step);
161
- return this;
162
- }
163
- removeStep(key) {
164
- this._steps.delete(key);
165
- return this;
166
- }
167
- entrypoint(value, order = StepOrder.entrypoint) {
168
- this.addStep(DockerStep.entrypoint("entrypoint", value, order));
169
- return this;
170
- }
171
- addRunStep(cmd) {
172
- return this.addLayer(cmd);
173
- }
174
- build() {
175
- for (const layerWriter of this._layerWriters){
176
- layerWriter(this);
177
- }
178
- const sortedSteps = [
179
- ...this._steps.values()
180
- ].sort((left, right)=>{
181
- if (left.order !== right.order) {
182
- return left.order - right.order;
183
- }
184
- return left.key.localeCompare(right.key, "en");
185
- });
186
- if (this._existing) {
187
- // All or nothing: skipping one step of a layer (e.g. its `USER root`) would break the rest.
188
- const { content } = this._existing;
189
- const block = sortedSteps.map((step)=>step.content).join("\n\n");
190
- if (block === "" || content.includes(block)) return content;
191
- return `${content.replace(/\n*$/, "\n")}\n${block}\n`;
192
- }
193
- const lines = [
194
- `FROM ${this._fromImage}`
195
- ];
196
- for (const step of sortedSteps){
197
- lines.push("", step.content);
198
- }
199
- return `${lines.join("\n")}\n`;
200
- }
201
- registerBaseLayers() {
202
- // Generic base model intentionally has no default layers.
203
- }
204
- files() {
205
- return [
206
- {
207
- filePath: this._existing?.filePath ?? "./Dockerfile",
208
- content: this.build()
209
- }
210
- ];
211
- }
212
- }
213
- /**
214
- * Shared layers for the published `ghcr.io/typesmiths/decohere/devcontainer` image.
215
- * Checked in at `projects/devcontainer/Dockerfile` — keep in sync via the unit test.
216
- */ export class AppDevContainerBaseImage extends DockerfileModel {
217
- registerBaseLayers() {
218
- super.registerBaseLayers();
219
- this.addLayer(withCommandHistory()).addLayer(withNodeUser()).addLayer(withPnpm()).addLayer(withGithubCli()).addLayer(withWorkspaceNodeModulesOwnership()).addLayer(withVsCodeJsDebugBootloaderFix());
220
- }
221
- }
222
- /** Generic Dev Container image helpers (history + bootloader) without webapi defaults. */ export class DevContainerImageModel extends DockerfileModel {
223
- registerBaseLayers() {
224
- super.registerBaseLayers();
225
- this.addLayer(withCommandHistory()).addLayer(withVsCodeJsDebugBootloaderFix());
226
- }
227
- files() {
228
- return [
229
- {
230
- filePath: ".devcontainer/Dockerfile",
231
- content: this.build()
232
- }
233
- ];
234
- }
235
- }
@@ -1,44 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { WELL_KNOWN_FILES } from "./const.js";
3
- /**
4
- * Accumulates key=value pairs and renders them as a `.env` file.
5
- *
6
- * Register once in the workspace; features call `addEntry` during their
7
- * scaffold phase to contribute environment variables. The final file is
8
- * written at render time when `workspace.scaffold()` is called.
9
- */ export class EnvFile {
10
- key = WELL_KNOWN_FILES.ENV_FILE;
11
- _entries = new Map();
12
- addEntry(key, value) {
13
- this._entries.set(key, value);
14
- return this;
15
- }
16
- hasEntry(key) {
17
- return this._entries.has(key);
18
- }
19
- static fromFile(absolutePath) {
20
- const env = new EnvFile();
21
- const raw = readFileSync(absolutePath, "utf8");
22
- for (const line of raw.split("\n")){
23
- const trimmed = line.trim();
24
- if (trimmed === "" || trimmed.startsWith("#")) continue;
25
- const eq = line.indexOf("=");
26
- if (eq <= 0) continue;
27
- env.addEntry(line.slice(0, eq).trim(), line.slice(eq + 1));
28
- }
29
- return env;
30
- }
31
- files() {
32
- if (this._entries.size === 0) return [];
33
- const body = [
34
- ...this._entries.entries()
35
- ].map(([k, v])=>`${k}=${v}`).join("\n");
36
- const content = `${body}\n`;
37
- return [
38
- {
39
- filePath: ".env",
40
- content
41
- }
42
- ];
43
- }
44
- }
@@ -1,38 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { WELL_KNOWN_FILES } from "./const.js";
3
- export class Gitignore {
4
- key = WELL_KNOWN_FILES.GITIGNORE;
5
- ignoreList = [
6
- "node_modules",
7
- "dist",
8
- ".package",
9
- ".pnpm-store",
10
- ".env",
11
- ".devcontainer/gh.env",
12
- ".cursor/codebase-rag.env",
13
- ".cursor/mcp.json",
14
- ".DS_Store",
15
- "logs"
16
- ];
17
- /** Load an existing .gitignore so `decohere add api` appends instead of replacing. */ static fromFile(absolutePath) {
18
- const gitignore = new Gitignore();
19
- gitignore.ignoreList = readFileSync(absolutePath, "utf8").replace(/\n+$/, "").split("\n");
20
- return gitignore;
21
- }
22
- add(...patterns) {
23
- for (const pattern of patterns){
24
- if (!this.ignoreList.includes(pattern)) {
25
- this.ignoreList.push(pattern);
26
- }
27
- }
28
- return this;
29
- }
30
- files() {
31
- return [
32
- {
33
- filePath: ".gitignore",
34
- content: `${this.ignoreList.join("\n")}\n`
35
- }
36
- ];
37
- }
38
- }
@@ -1,60 +0,0 @@
1
- import { WELL_KNOWN_FILES } from "./const.js";
2
- export class LaunchJson {
3
- apiNames;
4
- key = WELL_KNOWN_FILES.LAUNCH_JSON;
5
- constructor(apiNames = [
6
- "api"
7
- ]){
8
- this.apiNames = apiNames;
9
- }
10
- files() {
11
- const configurations = [
12
- {
13
- name: "Build",
14
- type: "node",
15
- request: "launch",
16
- runtimeExecutable: "pnpm",
17
- runtimeArgs: [
18
- "build"
19
- ],
20
- console: "integratedTerminal",
21
- cwd: "${workspaceFolder}"
22
- }
23
- ];
24
- const first = this.apiNames[0] ?? "api";
25
- const defaultDevArgs = first === "api" && this.apiNames.length === 1 ? [
26
- "dev"
27
- ] : [
28
- "run",
29
- `dev:${first}`
30
- ];
31
- configurations.push(serveConfig("Serve / Dev", defaultDevArgs));
32
- for (const apiName of this.apiNames){
33
- if (apiName === first && this.apiNames.length === 1 && apiName === "api") continue;
34
- configurations.push(serveConfig(`Serve / Dev (${apiName})`, [
35
- "run",
36
- `dev:${apiName}`
37
- ]));
38
- }
39
- return [
40
- {
41
- filePath: ".vscode/launch.json",
42
- content: JSON.stringify({
43
- version: "0.2.0",
44
- configurations
45
- }, null, 2)
46
- }
47
- ];
48
- }
49
- }
50
- function serveConfig(name, runtimeArgs) {
51
- return {
52
- name,
53
- type: "node",
54
- request: "launch",
55
- runtimeExecutable: "pnpm",
56
- runtimeArgs,
57
- console: "integratedTerminal",
58
- cwd: "${workspaceFolder}"
59
- };
60
- }
@@ -1,141 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { CREATE_VERSION } from "../version.js";
3
- import { DEFAULT_WEBAPI_NAME, webapiPackageName, webapiPort, webapiReleaseEntry } from "../webapi/const.js";
4
- import { WELL_KNOWN_FILES } from "./const.js";
5
- /**
6
- * Builder for the root `package.json`.
7
- *
8
- * Starts with the core API dependencies. Features call `addDependency` /
9
- * `addDevDependency` during their scaffold phase to inject their own deps
10
- * before `files()` is called at render time.
11
- */ export class PackageJson {
12
- name;
13
- key = WELL_KNOWN_FILES.PACKAGE_JSON;
14
- _dependencies;
15
- _devDependencies;
16
- _scripts;
17
- _version = "0.0.1";
18
- _type = "module";
19
- _rest = {};
20
- constructor(name, options){
21
- this.name = name;
22
- const apiNames = options?.apiNames ?? [
23
- DEFAULT_WEBAPI_NAME
24
- ];
25
- const first = apiNames[0] ?? DEFAULT_WEBAPI_NAME;
26
- const startEntry = options?.startEntry ?? webapiReleaseEntry(first);
27
- const workspaceName = options?.workspaceName ?? stripApiSuffix(name);
28
- const namedOrMany = apiNames.length > 1 || first !== DEFAULT_WEBAPI_NAME;
29
- this._scripts = {
30
- build: "forge build --configuration release",
31
- "build:watch": "forge build --watch",
32
- // Release build, then `.package/` in every API (what its Dockerfile copies).
33
- package: "pnpm run build && forge package",
34
- start: `node ${startEntry}`,
35
- dev: namedOrMany ? fluxDevScript(workspaceName, first, webapiPort(0)) : "flux serve --env-file-if-exists .env",
36
- "lint:check": "biome check .",
37
- typecheck: "forge typecheck",
38
- test: "vitest run --passWithNoTests"
39
- };
40
- for (const [index, apiName] of apiNames.entries()){
41
- this._scripts[`start:${apiName}`] = `node ${webapiReleaseEntry(apiName)}`;
42
- this._scripts[`package:${apiName}`] = packageScript(workspaceName, apiName);
43
- this._scripts[`dev:${apiName}`] = fluxDevScript(workspaceName, apiName, webapiPort(index));
44
- }
45
- this._dependencies = {
46
- "@decohere/http": CREATE_VERSION,
47
- "@decohere/crucible": CREATE_VERSION,
48
- "@decohere/core": CREATE_VERSION,
49
- "@decohere/config": CREATE_VERSION,
50
- "@decohere/scribe": CREATE_VERSION,
51
- "@decohere/scribe-pino": CREATE_VERSION,
52
- "pino-pretty": "^13.0.0",
53
- "pino-roll": "^4.0.0"
54
- };
55
- this._devDependencies = {
56
- "@decohere/forge": CREATE_VERSION,
57
- "@decohere/cli": CREATE_VERSION,
58
- typescript: "^6.0.3",
59
- "@biomejs/biome": "^2.0.0",
60
- vitest: "^3.2.4"
61
- };
62
- }
63
- /** Load an existing root package.json so `webapi add` can merge scripts/deps. */ static fromFile(filePath) {
64
- const raw = JSON.parse(readFileSync(filePath, "utf8"));
65
- const pkg = new PackageJson(typeof raw.name === "string" ? raw.name : "app", {
66
- apiNames: []
67
- });
68
- pkg._version = typeof raw.version === "string" ? raw.version : "0.0.1";
69
- pkg._type = typeof raw.type === "string" ? raw.type : "module";
70
- pkg._scripts = {
71
- ...asStringMap(raw.scripts)
72
- };
73
- pkg._dependencies = {
74
- ...asStringMap(raw.dependencies)
75
- };
76
- pkg._devDependencies = {
77
- ...asStringMap(raw.devDependencies)
78
- };
79
- const reserved = new Set([
80
- "name",
81
- "version",
82
- "type",
83
- "scripts",
84
- "dependencies",
85
- "devDependencies"
86
- ]);
87
- pkg._rest = Object.fromEntries(Object.entries(raw).filter(([key])=>!reserved.has(key)));
88
- return pkg;
89
- }
90
- addDependency(name, version) {
91
- this._dependencies[name] = version;
92
- return this;
93
- }
94
- addDevDependency(name, version) {
95
- this._devDependencies[name] = version;
96
- return this;
97
- }
98
- addScript(name, command) {
99
- this._scripts[name] = command;
100
- return this;
101
- }
102
- hasScript(name) {
103
- return typeof this._scripts[name] === "string";
104
- }
105
- files() {
106
- return [
107
- {
108
- filePath: "package.json",
109
- content: JSON.stringify({
110
- name: this.name,
111
- version: this._version,
112
- type: this._type,
113
- ...this._rest,
114
- scripts: this._scripts,
115
- dependencies: sortStringMap(this._dependencies),
116
- devDependencies: sortStringMap(this._devDependencies)
117
- }, null, 2)
118
- }
119
- ];
120
- }
121
- }
122
- function stripApiSuffix(name) {
123
- return name.endsWith(".api") ? name.slice(0, -".api".length) : name;
124
- }
125
- function asStringMap(value) {
126
- if (value == null || typeof value !== "object") return {};
127
- const result = {};
128
- for (const [key, entry] of Object.entries(value)){
129
- if (typeof entry === "string") result[key] = entry;
130
- }
131
- return result;
132
- }
133
- /** Stable A→Z key order for package.json dependency maps. */ export function sortStringMap(map) {
134
- return Object.fromEntries(Object.entries(map).sort(([left], [right])=>left.localeCompare(right, "en")));
135
- }
136
- export function packageScript(workspaceName, apiName) {
137
- return `pnpm run build && forge package ${webapiPackageName(workspaceName, apiName)}`;
138
- }
139
- function fluxDevScript(workspaceName, apiName, port) {
140
- return `PORT=${port} flux serve --env-file-if-exists .env --project ${webapiPackageName(workspaceName, apiName)}`;
141
- }