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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/package.json +9 -19
  2. package/src/add-api.action.ts +25 -0
  3. package/src/add-api.command.ts +31 -0
  4. package/src/add.command.ts +20 -0
  5. package/src/add.module.ts +11 -0
  6. package/src/cli.ts +4 -0
  7. package/src/create.command.ts +20 -0
  8. package/src/create.module.ts +11 -0
  9. package/src/domain/prompt.ts +50 -0
  10. package/{types/src/index.d.ts → src/index.ts} +0 -4
  11. package/src/models/compose.model.ts +205 -0
  12. package/src/models/config-yaml.model.ts +83 -0
  13. package/src/models/const.ts +20 -0
  14. package/src/models/dev-container.model.ts +136 -0
  15. package/src/models/docker-image.model.ts +343 -0
  16. package/src/models/env-file.model.ts +45 -0
  17. package/src/models/gitignore.model.ts +35 -0
  18. package/src/models/launch-json.model.ts +60 -0
  19. package/src/models/package-json.model.ts +169 -0
  20. package/src/models/project.model.ts +141 -0
  21. package/{types/src/models/source-file.d.ts → src/models/source-file.ts} +6 -4
  22. package/src/models/tsconfig.model.ts +29 -0
  23. package/src/models/vitest-config.model.ts +26 -0
  24. package/src/models/vscode-settings.model.ts +24 -0
  25. package/src/program.ts +63 -0
  26. package/{types/src/resolve-target-dir.d.ts → src/resolve-target-dir.ts} +4 -1
  27. package/src/services/scaffold.service.ts +89 -0
  28. package/src/version.ts +56 -0
  29. package/src/webapi/api-name.ts +73 -0
  30. package/src/webapi/cli-options.ts +56 -0
  31. package/{types/src/webapi/const.d.ts → src/webapi/const.ts} +21 -9
  32. package/src/webapi/create-api.ts +275 -0
  33. package/src/webapi/devcontainer/host-scripts.ts +35 -0
  34. package/src/webapi/features/auth.feature.ts +454 -0
  35. package/src/webapi/features/auth0-cli.ts +421 -0
  36. package/src/webapi/features/biome-format.ts +34 -0
  37. package/src/webapi/features/database.feature.ts +522 -0
  38. package/src/webapi/features/finalize.feature.ts +112 -0
  39. package/src/webapi/features/postman.feature.ts +287 -0
  40. package/src/webapi/models/api-package-json.model.ts +60 -0
  41. package/src/webapi/models/auth-config.model.ts +36 -0
  42. package/src/webapi/models/controller-source-file.model.ts +232 -0
  43. package/{es6/src/webapi/models/github-ci.model.js → src/webapi/models/github-ci.model.ts} +29 -27
  44. package/src/webapi/models/main-source-file.model.ts +208 -0
  45. package/src/webapi/models/production-dockerfile.model.ts +162 -0
  46. package/src/webapi/models/sample-files.model.ts +45 -0
  47. package/src/webapi/models/webapi-image.model.ts +21 -0
  48. package/src/webapi/postman/hello-openapi.ts +92 -0
  49. package/src/webapi/postman/local-view.ts +195 -0
  50. package/src/webapi/postman/openapi-to-postman.ts +208 -0
  51. package/src/webapi/workspace-readme.ts +192 -0
  52. package/src/webapi.action.ts +22 -0
  53. package/src/webapi.command.ts +41 -0
  54. package/tests/auth-postman.wiring.spec.ts +263 -0
  55. package/tests/auth0-cli.spec.ts +232 -0
  56. package/tests/biome-format.spec.ts +49 -0
  57. package/tests/cli.integration.spec.ts +658 -0
  58. package/tests/compose.model.spec.ts +51 -0
  59. package/tests/container-image.model.spec.ts +162 -0
  60. package/tests/dev-container.model.spec.ts +29 -0
  61. package/tests/devcontainer-base-image.spec.ts +32 -0
  62. package/tests/hello-di-inject.scaffold.spec.ts +18 -0
  63. package/tests/hello-route.contract.spec.ts +94 -0
  64. package/tests/package-json.order.spec.ts +59 -0
  65. package/tests/postman-from-openapi.spec.ts +197 -0
  66. package/tests/seed-devcontainer-templates.ts +40 -0
  67. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +96 -0
  68. package/LICENSE +0 -15
  69. package/commonjs/src/add-api.action.js +0 -37
  70. package/commonjs/src/add-api.command.js +0 -39
  71. package/commonjs/src/add.command.js +0 -22
  72. package/commonjs/src/add.module.js +0 -24
  73. package/commonjs/src/auth0-configure.action.js +0 -28
  74. package/commonjs/src/auth0-configure.command.js +0 -24
  75. package/commonjs/src/auth0-login.action.js +0 -28
  76. package/commonjs/src/auth0-login.command.js +0 -23
  77. package/commonjs/src/auth0.command.js +0 -22
  78. package/commonjs/src/auth0.module.js +0 -28
  79. package/commonjs/src/cli.js +0 -8
  80. package/commonjs/src/create.command.js +0 -22
  81. package/commonjs/src/create.module.js +0 -24
  82. package/commonjs/src/domain/prompt.js +0 -62
  83. package/commonjs/src/index.js +0 -48
  84. package/commonjs/src/models/compose.model.js +0 -258
  85. package/commonjs/src/models/config-yaml.model.js +0 -86
  86. package/commonjs/src/models/const.js +0 -30
  87. package/commonjs/src/models/dev-container.model.js +0 -172
  88. package/commonjs/src/models/docker-image.model.js +0 -277
  89. package/commonjs/src/models/env-file.model.js +0 -48
  90. package/commonjs/src/models/gitignore.model.js +0 -48
  91. package/commonjs/src/models/launch-json.model.js +0 -70
  92. package/commonjs/src/models/package-json.model.js +0 -156
  93. package/commonjs/src/models/project.model.js +0 -132
  94. package/commonjs/src/models/source-file.js +0 -4
  95. package/commonjs/src/models/tsconfig.model.js +0 -32
  96. package/commonjs/src/models/vitest-config.model.js +0 -33
  97. package/commonjs/src/models/vscode-settings.model.js +0 -29
  98. package/commonjs/src/program.js +0 -64
  99. package/commonjs/src/resolve-target-dir.js +0 -17
  100. package/commonjs/src/services/scaffold.service.js +0 -60
  101. package/commonjs/src/version.js +0 -55
  102. package/commonjs/src/webapi/api-name.js +0 -93
  103. package/commonjs/src/webapi/auth0-commands.js +0 -250
  104. package/commonjs/src/webapi/authorize-scopes.js +0 -79
  105. package/commonjs/src/webapi/cli-options.js +0 -99
  106. package/commonjs/src/webapi/const.js +0 -54
  107. package/commonjs/src/webapi/create-api.js +0 -265
  108. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +0 -164
  109. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  110. package/commonjs/src/webapi/devcontainer/host-scripts.js +0 -50
  111. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  112. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +0 -169
  113. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +0 -35
  114. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +0 -65
  115. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  116. package/commonjs/src/webapi/features/auth.feature.js +0 -408
  117. package/commonjs/src/webapi/features/auth0-cli.js +0 -472
  118. package/commonjs/src/webapi/features/biome-format.js +0 -47
  119. package/commonjs/src/webapi/features/database.feature.js +0 -433
  120. package/commonjs/src/webapi/features/finalize.feature.js +0 -93
  121. package/commonjs/src/webapi/features/postman.feature.js +0 -322
  122. package/commonjs/src/webapi/models/api-package-json.model.js +0 -58
  123. package/commonjs/src/webapi/models/auth-config.model.js +0 -24
  124. package/commonjs/src/webapi/models/controller-source-file.model.js +0 -190
  125. package/commonjs/src/webapi/models/github-ci.model.js +0 -193
  126. package/commonjs/src/webapi/models/main-source-file.model.js +0 -165
  127. package/commonjs/src/webapi/models/production-dockerfile.model.js +0 -75
  128. package/commonjs/src/webapi/models/sample-files.model.js +0 -58
  129. package/commonjs/src/webapi/models/webapi-image.model.js +0 -25
  130. package/commonjs/src/webapi/postman/hello-openapi.js +0 -173
  131. package/commonjs/src/webapi/postman/local-view.js +0 -182
  132. package/commonjs/src/webapi/postman/openapi-to-postman.js +0 -238
  133. package/commonjs/src/webapi/workspace-readme.js +0 -109
  134. package/commonjs/src/webapi.action.js +0 -28
  135. package/commonjs/src/webapi.command.js +0 -43
  136. package/es6/src/add-api.action.js +0 -27
  137. package/es6/src/add-api.command.js +0 -29
  138. package/es6/src/add.command.js +0 -12
  139. package/es6/src/add.module.js +0 -14
  140. package/es6/src/auth0-configure.action.js +0 -18
  141. package/es6/src/auth0-configure.command.js +0 -14
  142. package/es6/src/auth0-login.action.js +0 -18
  143. package/es6/src/auth0-login.command.js +0 -13
  144. package/es6/src/auth0.command.js +0 -12
  145. package/es6/src/auth0.module.js +0 -18
  146. package/es6/src/cli.js +0 -4
  147. package/es6/src/create.command.js +0 -12
  148. package/es6/src/create.module.js +0 -14
  149. package/es6/src/domain/prompt.js +0 -41
  150. package/es6/src/index.js +0 -9
  151. package/es6/src/models/compose.model.js +0 -254
  152. package/es6/src/models/config-yaml.model.js +0 -74
  153. package/es6/src/models/const.js +0 -20
  154. package/es6/src/models/dev-container.model.js +0 -162
  155. package/es6/src/models/docker-image.model.js +0 -235
  156. package/es6/src/models/env-file.model.js +0 -44
  157. package/es6/src/models/gitignore.model.js +0 -38
  158. package/es6/src/models/launch-json.model.js +0 -60
  159. package/es6/src/models/package-json.model.js +0 -141
  160. package/es6/src/models/project.model.js +0 -117
  161. package/es6/src/models/source-file.js +0 -1
  162. package/es6/src/models/tsconfig.model.js +0 -22
  163. package/es6/src/models/vitest-config.model.js +0 -23
  164. package/es6/src/models/vscode-settings.model.js +0 -19
  165. package/es6/src/program.js +0 -50
  166. package/es6/src/resolve-target-dir.js +0 -7
  167. package/es6/src/services/scaffold.service.js +0 -50
  168. package/es6/src/version.js +0 -34
  169. package/es6/src/webapi/api-name.js +0 -57
  170. package/es6/src/webapi/auth0-commands.js +0 -185
  171. package/es6/src/webapi/authorize-scopes.js +0 -55
  172. package/es6/src/webapi/cli-options.js +0 -34
  173. package/es6/src/webapi/const.js +0 -21
  174. package/es6/src/webapi/create-api.js +0 -245
  175. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +0 -154
  176. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  177. package/es6/src/webapi/devcontainer/host-scripts.js +0 -29
  178. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  179. package/es6/src/webapi/devcontainer/templates/initialize.sh +0 -169
  180. package/es6/src/webapi/devcontainer/templates/post-create.sh +0 -35
  181. package/es6/src/webapi/devcontainer/templates/post-start.sh +0 -65
  182. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  183. package/es6/src/webapi/features/auth.feature.js +0 -357
  184. package/es6/src/webapi/features/auth0-cli.js +0 -406
  185. package/es6/src/webapi/features/biome-format.js +0 -40
  186. package/es6/src/webapi/features/database.feature.js +0 -423
  187. package/es6/src/webapi/features/finalize.feature.js +0 -83
  188. package/es6/src/webapi/features/postman.feature.js +0 -256
  189. package/es6/src/webapi/models/api-package-json.model.js +0 -53
  190. package/es6/src/webapi/models/auth-config.model.js +0 -20
  191. package/es6/src/webapi/models/controller-source-file.model.js +0 -181
  192. package/es6/src/webapi/models/main-source-file.model.js +0 -161
  193. package/es6/src/webapi/models/production-dockerfile.model.js +0 -66
  194. package/es6/src/webapi/models/sample-files.model.js +0 -40
  195. package/es6/src/webapi/models/webapi-image.model.js +0 -18
  196. package/es6/src/webapi/postman/hello-openapi.js +0 -166
  197. package/es6/src/webapi/postman/local-view.js +0 -167
  198. package/es6/src/webapi/postman/openapi-to-postman.js +0 -179
  199. package/es6/src/webapi/workspace-readme.js +0 -88
  200. package/es6/src/webapi.action.js +0 -18
  201. package/es6/src/webapi.command.js +0 -33
  202. package/types/src/add-api.action.d.ts +0 -13
  203. package/types/src/add-api.command.d.ts +0 -7
  204. package/types/src/add.command.d.ts +0 -6
  205. package/types/src/add.module.d.ts +0 -14
  206. package/types/src/auth0-configure.action.d.ts +0 -7
  207. package/types/src/auth0-configure.command.d.ts +0 -6
  208. package/types/src/auth0-login.action.d.ts +0 -4
  209. package/types/src/auth0-login.command.d.ts +0 -6
  210. package/types/src/auth0.command.d.ts +0 -6
  211. package/types/src/auth0.module.d.ts +0 -18
  212. package/types/src/cli.d.ts +0 -2
  213. package/types/src/create.command.d.ts +0 -6
  214. package/types/src/create.module.d.ts +0 -14
  215. package/types/src/domain/prompt.d.ts +0 -4
  216. package/types/src/models/compose.model.d.ts +0 -73
  217. package/types/src/models/config-yaml.model.d.ts +0 -27
  218. package/types/src/models/const.d.ts +0 -20
  219. package/types/src/models/dev-container.model.d.ts +0 -49
  220. package/types/src/models/docker-image.model.d.ts +0 -90
  221. package/types/src/models/env-file.model.d.ts +0 -16
  222. package/types/src/models/gitignore.model.d.ts +0 -9
  223. package/types/src/models/launch-json.model.d.ts +0 -7
  224. package/types/src/models/package-json.model.d.ts +0 -36
  225. package/types/src/models/project.model.d.ts +0 -60
  226. package/types/src/models/tsconfig.model.d.ts +0 -5
  227. package/types/src/models/vitest-config.model.d.ts +0 -5
  228. package/types/src/models/vscode-settings.model.d.ts +0 -7
  229. package/types/src/program.d.ts +0 -8
  230. package/types/src/services/scaffold.service.d.ts +0 -22
  231. package/types/src/version.d.ts +0 -6
  232. package/types/src/webapi/api-name.d.ts +0 -14
  233. package/types/src/webapi/auth0-commands.d.ts +0 -11
  234. package/types/src/webapi/authorize-scopes.d.ts +0 -7
  235. package/types/src/webapi/cli-options.d.ts +0 -6
  236. package/types/src/webapi/create-api.d.ts +0 -15
  237. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +0 -32
  238. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
  239. package/types/src/webapi/devcontainer/host-scripts.d.ts +0 -7
  240. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
  241. package/types/src/webapi/devcontainer/templates/initialize.sh +0 -169
  242. package/types/src/webapi/devcontainer/templates/post-create.sh +0 -35
  243. package/types/src/webapi/devcontainer/templates/post-start.sh +0 -65
  244. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
  245. package/types/src/webapi/features/auth.feature.d.ts +0 -10
  246. package/types/src/webapi/features/auth0-cli.d.ts +0 -84
  247. package/types/src/webapi/features/biome-format.d.ts +0 -5
  248. package/types/src/webapi/features/database.feature.d.ts +0 -7
  249. package/types/src/webapi/features/finalize.feature.d.ts +0 -2
  250. package/types/src/webapi/features/postman.feature.d.ts +0 -16
  251. package/types/src/webapi/models/api-package-json.model.d.ts +0 -18
  252. package/types/src/webapi/models/auth-config.model.d.ts +0 -26
  253. package/types/src/webapi/models/controller-source-file.model.d.ts +0 -46
  254. package/types/src/webapi/models/github-ci.model.d.ts +0 -9
  255. package/types/src/webapi/models/main-source-file.model.d.ts +0 -54
  256. package/types/src/webapi/models/production-dockerfile.model.d.ts +0 -18
  257. package/types/src/webapi/models/sample-files.model.d.ts +0 -7
  258. package/types/src/webapi/models/webapi-image.model.d.ts +0 -12
  259. package/types/src/webapi/postman/hello-openapi.d.ts +0 -19
  260. package/types/src/webapi/postman/local-view.d.ts +0 -17
  261. package/types/src/webapi/postman/openapi-to-postman.d.ts +0 -62
  262. package/types/src/webapi/workspace-readme.d.ts +0 -18
  263. package/types/src/webapi.action.d.ts +0 -14
  264. package/types/src/webapi.command.d.ts +0 -11
package/package.json CHANGED
@@ -1,20 +1,15 @@
1
1
  {
2
2
  "name": "@decohere/scaffold",
3
- "version": "0.0.1-dev.126",
3
+ "version": "0.0.1-dev.18",
4
+ "decohere": {
5
+ "dockerImageRepository": "ghcr.io/typesmiths/decohere/create",
6
+ "devcontainerImageRepository": "ghcr.io/typesmiths/decohere/devcontainer"
7
+ },
4
8
  "description": "Decohere project scaffolder (runs inside ghcr.io/typesmiths/decohere/create)",
5
9
  "keywords": [
6
10
  "decohere",
7
11
  "scaffold"
8
12
  ],
9
- "homepage": "https://github.com/typesmiths/decohere/tree/main/projects/scaffold#readme",
10
- "bugs": {
11
- "url": "https://github.com/typesmiths/decohere/issues"
12
- },
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/typesmiths/decohere.git",
16
- "directory": "projects/scaffold"
17
- },
18
13
  "license": "ISC",
19
14
  "author": "Typesmiths",
20
15
  "type": "module",
@@ -31,15 +26,10 @@
31
26
  "test": "echo \"Error: no test specified\" && exit 1"
32
27
  },
33
28
  "dependencies": {
34
- "@decohere/crucible": "0.0.1-dev.126",
35
- "@decohere/devkit": "0.0.1-dev.126",
29
+ "@decohere/crucible": "0.0.1-dev.5",
30
+ "@decohere/nexus": "0.0.1-dev.5",
36
31
  "commander": "^13.1.0",
37
- "openapi-to-postmanv2": "^5.0.0",
38
- "yaml": "^2.9.1"
32
+ "openapi-to-postmanv2": "^5.0.0"
39
33
  },
40
- "devDependencies": {},
41
- "decohere": {
42
- "dockerImageRepository": "ghcr.io/typesmiths/decohere/create",
43
- "devcontainerImageRepository": "ghcr.io/typesmiths/decohere/devcontainer"
44
- }
34
+ "devDependencies": {}
45
35
  }
@@ -0,0 +1,25 @@
1
+ import { injectable } from "@decohere/crucible/decorators";
2
+ import { type Action, ActionResponse } from "@decohere/nexus/cli";
3
+ import { runScaffoldApi, toApiOptions } from "./webapi/cli-options.js";
4
+
5
+ export type AddApiOptions = {
6
+ apiName?: string;
7
+ d?: string;
8
+ database?: string;
9
+ auth?: string;
10
+ postman?: string;
11
+ yes?: boolean;
12
+ install?: string;
13
+ };
14
+
15
+ @injectable()
16
+ export class AddApiAction implements Action<AddApiOptions> {
17
+ public async run(options: AddApiOptions): Promise<ActionResponse> {
18
+ const name = options.apiName;
19
+ if (name == null || name === "") {
20
+ return ActionResponse.Error("api", "API name is required");
21
+ }
22
+ await runScaffoldApi(toApiOptions(undefined, options, { names: [name], add: true }));
23
+ return ActionResponse.Success("api");
24
+ }
25
+ }
@@ -0,0 +1,31 @@
1
+ import { DecohereCommand } from "@decohere/nexus/cli";
2
+ import type { OptionValues } from "commander";
3
+ import { AddApiAction } from "./add-api.action.js";
4
+ import { addWebapiFeatureOptions } from "./webapi/cli-options.js";
5
+
6
+ export class AddApiCommand extends DecohereCommand {
7
+ public override get actions() {
8
+ return [AddApiAction];
9
+ }
10
+
11
+ public constructor(name: string) {
12
+ super(name);
13
+ this.description("Add an API to an existing Decohere workspace");
14
+ this.argument("<name>", "API name under projects/");
15
+ this.option("-d <directory>", "Workspace directory (default: current directory)", ".");
16
+ addWebapiFeatureOptions(this);
17
+ }
18
+
19
+ public override action(fn: (...args: unknown[]) => void | Promise<void>): this {
20
+ return super.action((...args: unknown[]) => {
21
+ const cmd = args.at(-1) as DecohereCommand;
22
+ const first = args[0];
23
+ const hasPositional = typeof first === "string" || first === undefined;
24
+ const opts = hasPositional ? (args[1] as OptionValues) : (args[0] as OptionValues);
25
+ if (typeof first === "string" && first !== "") {
26
+ cmd.setOptionValue("apiName", first);
27
+ }
28
+ return fn(opts, cmd);
29
+ });
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ import type { Actions } from "@decohere/nexus/cli";
2
+ import {
3
+ type CommandHandler,
4
+ type CommandResponse,
5
+ type CommandType,
6
+ DecohereCommand,
7
+ } from "@decohere/nexus/cli";
8
+
9
+ export class AddCommand extends DecohereCommand {
10
+ public constructor(commands: Record<string, CommandType>, handler: CommandHandler) {
11
+ super(AddCommand);
12
+ this.description("Add to an existing Decohere workspace");
13
+ this.exitOverride();
14
+ this.registerSubcommand(commands, handler);
15
+ }
16
+
17
+ public run(_actions: Actions, _options: never): Promise<CommandResponse> {
18
+ return this.help();
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ import { ModuleManifest } from "@decohere/nexus/cli";
2
+ import { AddCommand } from "./add.command.js";
3
+ import { AddApiAction } from "./add-api.action.js";
4
+ import { AddApiCommand } from "./add-api.command.js";
5
+
6
+ export class AddModule extends ModuleManifest {
7
+ public readonly id = "add";
8
+ public readonly command = AddCommand;
9
+ public readonly commands = { api: AddApiCommand };
10
+ public readonly actions = { api: AddApiAction };
11
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "./program.js";
3
+
4
+ void runCli();
@@ -0,0 +1,20 @@
1
+ import type { Actions } from "@decohere/nexus/cli";
2
+ import {
3
+ type CommandHandler,
4
+ type CommandResponse,
5
+ type CommandType,
6
+ DecohereCommand,
7
+ } from "@decohere/nexus/cli";
8
+
9
+ export class CreateCommand extends DecohereCommand {
10
+ public constructor(commands: Record<string, CommandType>, handler: CommandHandler) {
11
+ super(CreateCommand);
12
+ this.description("Create a new Decohere workspace");
13
+ this.exitOverride();
14
+ this.registerSubcommand(commands, handler);
15
+ }
16
+
17
+ public run(_actions: Actions, _options: never): Promise<CommandResponse> {
18
+ return this.help();
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ import { ModuleManifest } from "@decohere/nexus/cli";
2
+ import { CreateCommand } from "./create.command.js";
3
+ import { WebapiAction } from "./webapi.action.js";
4
+ import { WebapiCommand } from "./webapi.command.js";
5
+
6
+ export class CreateModule extends ModuleManifest {
7
+ public readonly id = "create";
8
+ public readonly command = CreateCommand;
9
+ public readonly commands = { webapi: WebapiCommand };
10
+ public readonly actions = { webapi: WebapiAction };
11
+ }
@@ -0,0 +1,50 @@
1
+ import { createInterface, type Interface } from "node:readline";
2
+
3
+ // readline/promises rl.question() has a race condition with piped (non-TTY) stdin
4
+ // in Node ≥ 22: after the first line is read, stdin emits 'end', readline closes,
5
+ // and all subsequent question() calls resolve to "".
6
+ // Fix: use the async-iterator API, which buffers all lines before the close event fires.
7
+
8
+ let _rl: Interface | null = null;
9
+ let _lines: AsyncIterator<string> | null = null;
10
+
11
+ function getLines(): AsyncIterator<string> {
12
+ if (_lines == null) {
13
+ _rl = createInterface({ input: process.stdin, terminal: false });
14
+ _lines = _rl[Symbol.asyncIterator]();
15
+ }
16
+ return _lines;
17
+ }
18
+
19
+ /** Release stdin so the process can exit after interactive prompts. */
20
+ export function closePrompt(): void {
21
+ _rl?.close();
22
+ _rl = null;
23
+ _lines = null;
24
+ }
25
+
26
+ async function readLine(prompt: string): Promise<string> {
27
+ process.stdout.write(prompt);
28
+ const { value, done } = await getLines().next();
29
+ return done ? "" : value.trim();
30
+ }
31
+
32
+ export async function ask(question: string, fallback = ""): Promise<string> {
33
+ const answer = await readLine(question);
34
+ return answer || fallback;
35
+ }
36
+
37
+ export async function pick<T>(
38
+ label: string,
39
+ items: T[],
40
+ display: (item: T) => string,
41
+ ): Promise<T | null> {
42
+ console.log(label.trimEnd());
43
+ for (const [i, item] of items.entries()) {
44
+ console.log(` ${i + 1}. ${display(item)}`);
45
+ }
46
+ const answer = await readLine(" > ");
47
+ const idx = Number.parseInt(answer, 10);
48
+ if (Number.isNaN(idx) || idx < 1 || idx > items.length) return null;
49
+ return items[idx - 1];
50
+ }
@@ -1,10 +1,6 @@
1
1
  export { AddModule } from "./add.module.js";
2
2
  export type { AddApiOptions } from "./add-api.action.js";
3
3
  export { AddApiAction } from "./add-api.action.js";
4
- export { Auth0Module } from "./auth0.module.js";
5
- export type { Auth0ConfigureOptions } from "./auth0-configure.action.js";
6
- export { Auth0ConfigureAction } from "./auth0-configure.action.js";
7
- export { Auth0LoginAction } from "./auth0-login.action.js";
8
4
  export { CreateModule } from "./create.module.js";
9
5
  export { runCli } from "./program.js";
10
6
  export type { ApiOptions } from "./webapi/create-api.js";
@@ -0,0 +1,205 @@
1
+ import { WELL_KNOWN_FILES } from "./const.js";
2
+ import type { KeyedModel } from "./source-file.js";
3
+
4
+ export type ComposeNetworkRef =
5
+ | string
6
+ | {
7
+ /** Network key under `networks:` on this service */
8
+ [networkKey: string]: { aliases?: string[] };
9
+ };
10
+
11
+ export interface ComposeHealthcheck {
12
+ test: string[];
13
+ interval?: string;
14
+ timeout?: string;
15
+ retries?: number;
16
+ }
17
+
18
+ export interface ComposeService {
19
+ build?: string;
20
+ image?: string;
21
+ container_name?: string;
22
+ /** Quoted in YAML (`0:0` must not parse as a number). */
23
+ user?: string;
24
+ env_file?: string[];
25
+ environment?: Record<string, string>;
26
+ ports?: string[];
27
+ volumes?: string[];
28
+ command?: string;
29
+ restart?: string;
30
+ depends_on?: string[];
31
+ /** Compose profiles (service only starts when profile is enabled). */
32
+ profiles?: string[];
33
+ /** Attach to compose networks (string name or map with aliases). */
34
+ networks?: ComposeNetworkRef[];
35
+ healthcheck?: ComposeHealthcheck;
36
+ }
37
+
38
+ export type ComposeNetwork = {
39
+ /** Fixed Docker network name (shareable across compose projects). */
40
+ name: string;
41
+ external?: boolean;
42
+ };
43
+
44
+ /**
45
+ * Live builder for `docker-compose.yml`.
46
+ *
47
+ * Features call `addService` / `addVolume` / `addNetwork` in their scaffold phases.
48
+ */
49
+ export class DockerCompose implements KeyedModel {
50
+ public readonly key = WELL_KNOWN_FILES.DOCKER_COMPOSE;
51
+ private _projectName: string | undefined;
52
+ private readonly _headerComments: string[] = [];
53
+ private readonly _services: Map<string, ComposeService> = new Map();
54
+ private readonly _volumes: Set<string> = new Set();
55
+ private readonly _networks: Map<string, ComposeNetwork> = new Map();
56
+
57
+ /** Compose project name (`name:` top-level) — isolates containers/volumes. */
58
+ public setProjectName(name: string): this {
59
+ this._projectName = name;
60
+ return this;
61
+ }
62
+
63
+ /** File-level `#` comments rendered above `name:` / `services:`. */
64
+ public addHeaderComment(...lines: string[]): this {
65
+ this._headerComments.push(...lines);
66
+ return this;
67
+ }
68
+
69
+ public addService(name: string, config: ComposeService): this {
70
+ this._services.set(name, config);
71
+ return this;
72
+ }
73
+
74
+ public files() {
75
+ return [
76
+ {
77
+ filePath: ".devcontainer/docker-compose.yml",
78
+ content: this.getContent(),
79
+ },
80
+ ];
81
+ }
82
+
83
+ public addVolume(name: string): this {
84
+ this._volumes.add(name);
85
+ return this;
86
+ }
87
+
88
+ public addNetwork(key: string, network: ComposeNetwork): this {
89
+ this._networks.set(key, network);
90
+ return this;
91
+ }
92
+
93
+ /** Append an env_file path to an existing service. */
94
+ public addEnvFile(serviceName: string, filePath: string): this {
95
+ const svc = this._services.get(serviceName);
96
+ if (svc) svc.env_file = [...(svc.env_file ?? []), filePath];
97
+ return this;
98
+ }
99
+
100
+ public getContent(): string {
101
+ const lines: string[] = [];
102
+ for (const comment of this._headerComments) {
103
+ lines.push(comment.startsWith("#") ? comment : `# ${comment}`);
104
+ }
105
+ if (this._headerComments.length > 0) {
106
+ lines.push("");
107
+ }
108
+ if (this._projectName) {
109
+ lines.push(`name: ${this._projectName}`, "");
110
+ }
111
+ lines.push("services:");
112
+
113
+ for (const [name, svc] of this._services) {
114
+ lines.push(` ${name}:`);
115
+ if (svc.build) lines.push(` build: ${svc.build}`);
116
+ if (svc.image) lines.push(` image: ${svc.image}`);
117
+ if (svc.container_name) lines.push(` container_name: ${svc.container_name}`);
118
+ if (svc.command) lines.push(` command: ${svc.command}`);
119
+ if (svc.user) lines.push(` user: "${svc.user}"`);
120
+ if (svc.restart) lines.push(` restart: ${svc.restart}`);
121
+
122
+ if (svc.profiles && svc.profiles.length > 0) {
123
+ lines.push(" profiles:");
124
+ for (const p of svc.profiles) lines.push(` - ${p}`);
125
+ }
126
+
127
+ if (svc.env_file && svc.env_file.length > 0) {
128
+ lines.push(" env_file:");
129
+ for (const f of svc.env_file) lines.push(` - ${f}`);
130
+ }
131
+
132
+ if (svc.environment && Object.keys(svc.environment).length > 0) {
133
+ lines.push(" environment:");
134
+ for (const [k, v] of Object.entries(svc.environment)) {
135
+ lines.push(` ${k}: ${v}`);
136
+ }
137
+ }
138
+
139
+ if (svc.ports && svc.ports.length > 0) {
140
+ lines.push(" ports:");
141
+ for (const p of svc.ports) lines.push(` - "${p}"`);
142
+ }
143
+
144
+ if (svc.volumes && svc.volumes.length > 0) {
145
+ lines.push(" volumes:");
146
+ for (const v of svc.volumes) lines.push(` - ${v}`);
147
+ }
148
+
149
+ if (svc.networks && svc.networks.length > 0) {
150
+ const onlyStrings = svc.networks.every((n) => typeof n === "string");
151
+ if (onlyStrings) {
152
+ lines.push(" networks:");
153
+ for (const n of svc.networks) lines.push(` - ${n as string}`);
154
+ } else {
155
+ lines.push(" networks:");
156
+ for (const n of svc.networks) {
157
+ if (typeof n === "string") {
158
+ lines.push(` ${n}:`);
159
+ } else {
160
+ for (const [netKey, cfg] of Object.entries(n)) {
161
+ lines.push(` ${netKey}:`);
162
+ if (cfg.aliases && cfg.aliases.length > 0) {
163
+ lines.push(" aliases:");
164
+ for (const a of cfg.aliases) lines.push(` - ${a}`);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ if (svc.depends_on && svc.depends_on.length > 0) {
173
+ lines.push(" depends_on:");
174
+ for (const d of svc.depends_on) lines.push(` - ${d}`);
175
+ }
176
+
177
+ if (svc.healthcheck) {
178
+ const hc = svc.healthcheck;
179
+ lines.push(" healthcheck:");
180
+ lines.push(` test: ${JSON.stringify(hc.test)}`);
181
+ if (hc.interval) lines.push(` interval: ${hc.interval}`);
182
+ if (hc.timeout) lines.push(` timeout: ${hc.timeout}`);
183
+ if (hc.retries != null) lines.push(` retries: ${hc.retries}`);
184
+ }
185
+ }
186
+
187
+ if (this._networks.size > 0) {
188
+ lines.push("");
189
+ lines.push("networks:");
190
+ for (const [key, net] of this._networks) {
191
+ lines.push(` ${key}:`);
192
+ lines.push(` name: ${net.name}`);
193
+ if (net.external) lines.push(" external: true");
194
+ }
195
+ }
196
+
197
+ if (this._volumes.size > 0) {
198
+ lines.push("");
199
+ lines.push("volumes:");
200
+ for (const v of this._volumes) lines.push(` ${v}:`);
201
+ }
202
+
203
+ return `${lines.join("\n")}\n`;
204
+ }
205
+ }
@@ -0,0 +1,83 @@
1
+ import { readFileSync } from "node:fs";
2
+ import type { KeyedModel, SourceFile } from "./source-file.js";
3
+
4
+ /**
5
+ * Builder for a YAML config file (e.g. config/config.yaml or config/config.dev.yaml).
6
+ *
7
+ * Starts with a base section passed to the constructor. Features call
8
+ * `addSection(yaml)` during their scaffold phase to append their own block.
9
+ * `files()` joins all sections with a blank line separator.
10
+ */
11
+ export class ConfigYaml implements KeyedModel {
12
+ private readonly _sections: string[] = [];
13
+
14
+ public constructor(
15
+ public readonly key: string,
16
+ private readonly filePath: string,
17
+ baseContent: string,
18
+ ) {
19
+ this._sections.push(baseContent);
20
+ }
21
+
22
+ public addSection(yaml: string): this {
23
+ this._sections.push(yaml);
24
+ return this;
25
+ }
26
+
27
+ /** True when a datasource with this `name:` already appears in the file. */
28
+ public hasDataSource(name: string): boolean {
29
+ return this._sections.join("\n").includes(`name: ${name}\n`);
30
+ }
31
+
32
+ public hasDataSources(): boolean {
33
+ return this._sections.join("\n").includes("dataSources:");
34
+ }
35
+
36
+ /**
37
+ * Insert a `dataSources` list item. Used by `webapi add` so we do not write a
38
+ * second top-level `database:` key (YAML last-key-wins).
39
+ */
40
+ public appendDataSource(item: string): this {
41
+ const current = this._sections.join("\n");
42
+ this._sections.length = 0;
43
+ this._sections.push(injectYamlDataSource(current, item));
44
+ return this;
45
+ }
46
+
47
+ public static fromFile(key: string, relativePath: string, absolutePath: string): ConfigYaml {
48
+ const content = readFileSync(absolutePath, "utf8");
49
+ return new ConfigYaml(key, relativePath, content);
50
+ }
51
+
52
+ public files(): SourceFile[] {
53
+ return [{ filePath: this.filePath, content: this._sections.join("\n") }];
54
+ }
55
+ }
56
+
57
+ /** Insert a YAML list item under `dataSources:`; append a new `database:` block if missing. */
58
+ export function injectYamlDataSource(yaml: string, item: string): string {
59
+ const block = item.replace(/\n$/, "");
60
+ if (!yaml.includes("dataSources:")) {
61
+ const prefix = yaml.trimEnd();
62
+ const section = `database:\n dataSources:\n${block}\n`;
63
+ return prefix.length > 0 ? `${prefix}\n\n${section}` : section;
64
+ }
65
+ const lines = yaml.split("\n");
66
+ const dsIndex = lines.findIndex((line) => /^\s*dataSources:\s*$/.test(line));
67
+ if (dsIndex < 0) {
68
+ return `${yaml.trimEnd()}\n\ndatabase:\n dataSources:\n${block}\n`;
69
+ }
70
+ let insertAt = lines.length;
71
+ for (let i = dsIndex + 1; i < lines.length; i++) {
72
+ if (/^[A-Za-z]/.test(lines[i] ?? "")) {
73
+ insertAt = i;
74
+ break;
75
+ }
76
+ }
77
+ const insertLines = block.split("\n");
78
+ if (insertAt < lines.length && lines[insertAt - 1] !== "") {
79
+ insertLines.push("");
80
+ }
81
+ lines.splice(insertAt, 0, ...insertLines);
82
+ return lines.join("\n");
83
+ }
@@ -0,0 +1,20 @@
1
+ export const WELL_KNOWN_FILES = {
2
+ VSCODE_SETTINGS: "VSCODE_SETTINGS",
3
+ LAUNCH_JSON: "LAUNCH_JSON",
4
+ DOCKER_COMPOSE: "DOCKER_COMPOSE",
5
+ DEVCONTAINER_JSON: "DEVCONTAINER_JSON",
6
+ DOCKERFILE: "DOCKERFILE",
7
+ PRODUCTION_DOCKERFILE: "PRODUCTION_DOCKERFILE",
8
+ GITHUB_CI: "GITHUB_CI",
9
+ PACKAGE_JSON: "PACKAGE_JSON",
10
+ API_PACKAGE_JSON: "API_PACKAGE_JSON",
11
+ TSCONFIG: "TSCONFIG",
12
+ VITEST_CONFIG: "VITEST_CONFIG",
13
+ MAIN_TS: "MAIN_TS",
14
+ CONTROLLER_TS: "CONTROLLER_TS",
15
+ CONFIG_YAML: "CONFIG_YAML",
16
+ CONFIG_DEV_YAML: "CONFIG_DEV_YAML",
17
+ GITIGNORE: "GITIGNORE",
18
+ AUTH_CONFIG: "AUTH_CONFIG",
19
+ ENV_FILE: "ENV_FILE",
20
+ };
@@ -0,0 +1,136 @@
1
+ import { WELL_KNOWN_FILES } from "./const.js";
2
+ import type { KeyedModel, SourceFile } from "./source-file.js";
3
+
4
+ /**
5
+ * Model for `.devcontainer/devcontainer.json`.
6
+ *
7
+ * Features call builder methods (`addExtension`, `forwardPort`, etc.) during
8
+ * their configure phase. `MonoRepoScaffold` calls `files()` at render time.
9
+ */
10
+
11
+ export type PortAttributes = {
12
+ label?: string;
13
+ /** When true, host port must match the container port (no random remap). */
14
+ requireLocalPort?: boolean;
15
+ onAutoForward?: "notify" | "openBrowser" | "openPreview" | "silent" | "ignore";
16
+ };
17
+
18
+ export class DevContainer implements KeyedModel {
19
+ public readonly key = WELL_KNOWN_FILES.DEVCONTAINER_JSON;
20
+ private readonly _extensions: string[] = [];
21
+ private readonly _ports: number[] = [];
22
+ private readonly _portAttributes = new Map<number, PortAttributes>();
23
+ private readonly _mounts: string[];
24
+ private _postCreateCommand?: string;
25
+ private _updateContentCommand?: string;
26
+ private _postStartCommand?: string;
27
+ private _initializeCommand?: string;
28
+ private _remoteUser = "node";
29
+ private _useCompose = false;
30
+
31
+ public constructor() {
32
+ this._mounts = ["source=..,target=/workspace,type=bind,consistency=cached"];
33
+ }
34
+
35
+ public addExtension(...ids: string[]): this {
36
+ this._extensions.push(...ids);
37
+ return this;
38
+ }
39
+
40
+ public forwardPort(...ports: number[]): this {
41
+ this._ports.push(...ports);
42
+ return this;
43
+ }
44
+
45
+ public setPortAttributes(port: number, attrs: PortAttributes): this {
46
+ this._portAttributes.set(port, attrs);
47
+ return this;
48
+ }
49
+
50
+ public setPostCreateCommand(cmd: string): this {
51
+ this._postCreateCommand = cmd;
52
+ return this;
53
+ }
54
+
55
+ public setUpdateContentCommand(cmd: string): this {
56
+ this._updateContentCommand = cmd;
57
+ return this;
58
+ }
59
+
60
+ public setPostStartCommand(cmd: string): this {
61
+ this._postStartCommand = cmd;
62
+ return this;
63
+ }
64
+
65
+ public setInitializeCommand(cmd: string): this {
66
+ this._initializeCommand = cmd;
67
+ return this;
68
+ }
69
+
70
+ public setRemoteUser(user: string): this {
71
+ this._remoteUser = user;
72
+ return this;
73
+ }
74
+
75
+ public useCompose(): this {
76
+ this._useCompose = true;
77
+ return this;
78
+ }
79
+
80
+ public files(): SourceFile[] {
81
+ const config: Record<string, unknown> = {};
82
+
83
+ if (this._useCompose) {
84
+ config.dockerComposeFile = "docker-compose.yml";
85
+ config.service = "devcontainer";
86
+ config.workspaceFolder = "/workspace";
87
+ }
88
+
89
+ if (this._initializeCommand) {
90
+ config.initializeCommand = this._initializeCommand;
91
+ }
92
+
93
+ if (this._mounts) {
94
+ config.mounts = this._mounts;
95
+ }
96
+
97
+ if (this._extensions.length > 0) {
98
+ config.customizations = { vscode: { extensions: this._extensions } };
99
+ }
100
+
101
+ if (this._ports.length > 0) {
102
+ config.forwardPorts = this._ports;
103
+ }
104
+
105
+ if (this._portAttributes.size > 0) {
106
+ const portsAttributes: Record<string, PortAttributes> = {};
107
+ for (const [port, attrs] of this._portAttributes) {
108
+ portsAttributes[String(port)] = attrs;
109
+ }
110
+ config.portsAttributes = portsAttributes;
111
+ }
112
+
113
+ if (this._postCreateCommand) {
114
+ config.postCreateCommand = this._postCreateCommand;
115
+ }
116
+
117
+ if (this._updateContentCommand) {
118
+ config.updateContentCommand = this._updateContentCommand;
119
+ }
120
+
121
+ if (this._postStartCommand) {
122
+ config.postStartCommand = this._postStartCommand;
123
+ }
124
+
125
+ if (this._remoteUser) {
126
+ config.remoteUser = this._remoteUser;
127
+ }
128
+
129
+ return [
130
+ {
131
+ filePath: ".devcontainer/devcontainer.json",
132
+ content: JSON.stringify(config, null, 2),
133
+ },
134
+ ];
135
+ }
136
+ }