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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/LICENSE +15 -0
  2. package/commonjs/src/add-api.action.js +37 -0
  3. package/commonjs/src/add-api.command.js +39 -0
  4. package/commonjs/src/add.command.js +22 -0
  5. package/commonjs/src/add.module.js +24 -0
  6. package/commonjs/src/auth0-configure.action.js +28 -0
  7. package/commonjs/src/auth0-configure.command.js +24 -0
  8. package/commonjs/src/auth0-login.action.js +28 -0
  9. package/commonjs/src/auth0-login.command.js +23 -0
  10. package/commonjs/src/auth0.command.js +22 -0
  11. package/commonjs/src/auth0.module.js +28 -0
  12. package/commonjs/src/cli.js +8 -0
  13. package/commonjs/src/create.command.js +22 -0
  14. package/commonjs/src/create.module.js +24 -0
  15. package/commonjs/src/domain/prompt.js +62 -0
  16. package/commonjs/src/index.js +48 -0
  17. package/commonjs/src/models/compose.model.js +258 -0
  18. package/commonjs/src/models/config-yaml.model.js +86 -0
  19. package/commonjs/src/models/const.js +30 -0
  20. package/commonjs/src/models/dev-container.model.js +172 -0
  21. package/commonjs/src/models/docker-image.model.js +277 -0
  22. package/commonjs/src/models/env-file.model.js +48 -0
  23. package/commonjs/src/models/gitignore.model.js +48 -0
  24. package/commonjs/src/models/launch-json.model.js +70 -0
  25. package/commonjs/src/models/package-json.model.js +156 -0
  26. package/commonjs/src/models/project.model.js +132 -0
  27. package/commonjs/src/models/source-file.js +4 -0
  28. package/commonjs/src/models/tsconfig.model.js +32 -0
  29. package/commonjs/src/models/vitest-config.model.js +33 -0
  30. package/commonjs/src/models/vscode-settings.model.js +29 -0
  31. package/commonjs/src/program.js +64 -0
  32. package/commonjs/src/resolve-target-dir.js +17 -0
  33. package/commonjs/src/services/scaffold.service.js +60 -0
  34. package/commonjs/src/version.js +55 -0
  35. package/commonjs/src/webapi/api-name.js +93 -0
  36. package/commonjs/src/webapi/auth0-commands.js +250 -0
  37. package/commonjs/src/webapi/authorize-scopes.js +79 -0
  38. package/commonjs/src/webapi/cli-options.js +99 -0
  39. package/commonjs/src/webapi/const.js +54 -0
  40. package/commonjs/src/webapi/create-api.js +265 -0
  41. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +164 -0
  42. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  43. package/commonjs/src/webapi/devcontainer/host-scripts.js +50 -0
  44. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  45. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
  46. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
  47. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
  48. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  49. package/commonjs/src/webapi/features/auth.feature.js +408 -0
  50. package/commonjs/src/webapi/features/auth0-cli.js +472 -0
  51. package/commonjs/src/webapi/features/biome-format.js +47 -0
  52. package/commonjs/src/webapi/features/database.feature.js +433 -0
  53. package/commonjs/src/webapi/features/finalize.feature.js +93 -0
  54. package/commonjs/src/webapi/features/postman.feature.js +322 -0
  55. package/commonjs/src/webapi/models/api-package-json.model.js +58 -0
  56. package/commonjs/src/webapi/models/auth-config.model.js +24 -0
  57. package/commonjs/src/webapi/models/controller-source-file.model.js +190 -0
  58. package/commonjs/src/webapi/models/github-ci.model.js +193 -0
  59. package/commonjs/src/webapi/models/main-source-file.model.js +165 -0
  60. package/commonjs/src/webapi/models/production-dockerfile.model.js +75 -0
  61. package/commonjs/src/webapi/models/sample-files.model.js +58 -0
  62. package/commonjs/src/webapi/models/webapi-image.model.js +25 -0
  63. package/commonjs/src/webapi/postman/hello-openapi.js +173 -0
  64. package/commonjs/src/webapi/postman/local-view.js +182 -0
  65. package/commonjs/src/webapi/postman/openapi-to-postman.js +238 -0
  66. package/commonjs/src/webapi/workspace-readme.js +109 -0
  67. package/commonjs/src/webapi.action.js +28 -0
  68. package/commonjs/src/webapi.command.js +43 -0
  69. package/es6/src/add-api.action.js +27 -0
  70. package/es6/src/add-api.command.js +29 -0
  71. package/es6/src/add.command.js +12 -0
  72. package/es6/src/add.module.js +14 -0
  73. package/es6/src/auth0-configure.action.js +18 -0
  74. package/es6/src/auth0-configure.command.js +14 -0
  75. package/es6/src/auth0-login.action.js +18 -0
  76. package/es6/src/auth0-login.command.js +13 -0
  77. package/es6/src/auth0.command.js +12 -0
  78. package/es6/src/auth0.module.js +18 -0
  79. package/es6/src/cli.js +4 -0
  80. package/es6/src/create.command.js +12 -0
  81. package/es6/src/create.module.js +14 -0
  82. package/es6/src/domain/prompt.js +41 -0
  83. package/es6/src/index.js +9 -0
  84. package/es6/src/models/compose.model.js +254 -0
  85. package/es6/src/models/config-yaml.model.js +74 -0
  86. package/es6/src/models/const.js +20 -0
  87. package/es6/src/models/dev-container.model.js +162 -0
  88. package/es6/src/models/docker-image.model.js +235 -0
  89. package/es6/src/models/env-file.model.js +44 -0
  90. package/es6/src/models/gitignore.model.js +38 -0
  91. package/es6/src/models/launch-json.model.js +60 -0
  92. package/es6/src/models/package-json.model.js +141 -0
  93. package/es6/src/models/project.model.js +117 -0
  94. package/es6/src/models/source-file.js +1 -0
  95. package/es6/src/models/tsconfig.model.js +22 -0
  96. package/es6/src/models/vitest-config.model.js +23 -0
  97. package/es6/src/models/vscode-settings.model.js +19 -0
  98. package/es6/src/program.js +50 -0
  99. package/es6/src/resolve-target-dir.js +7 -0
  100. package/es6/src/services/scaffold.service.js +50 -0
  101. package/es6/src/version.js +34 -0
  102. package/es6/src/webapi/api-name.js +57 -0
  103. package/es6/src/webapi/auth0-commands.js +185 -0
  104. package/es6/src/webapi/authorize-scopes.js +55 -0
  105. package/es6/src/webapi/cli-options.js +34 -0
  106. package/es6/src/webapi/const.js +21 -0
  107. package/es6/src/webapi/create-api.js +245 -0
  108. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +154 -0
  109. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  110. package/es6/src/webapi/devcontainer/host-scripts.js +29 -0
  111. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  112. package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
  113. package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
  114. package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
  115. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  116. package/es6/src/webapi/features/auth.feature.js +357 -0
  117. package/es6/src/webapi/features/auth0-cli.js +406 -0
  118. package/es6/src/webapi/features/biome-format.js +40 -0
  119. package/es6/src/webapi/features/database.feature.js +423 -0
  120. package/es6/src/webapi/features/finalize.feature.js +83 -0
  121. package/es6/src/webapi/features/postman.feature.js +256 -0
  122. package/es6/src/webapi/models/api-package-json.model.js +53 -0
  123. package/es6/src/webapi/models/auth-config.model.js +20 -0
  124. package/es6/src/webapi/models/controller-source-file.model.js +181 -0
  125. package/{src/webapi/models/github-ci.model.ts → es6/src/webapi/models/github-ci.model.js} +27 -29
  126. package/es6/src/webapi/models/main-source-file.model.js +161 -0
  127. package/es6/src/webapi/models/production-dockerfile.model.js +66 -0
  128. package/es6/src/webapi/models/sample-files.model.js +40 -0
  129. package/es6/src/webapi/models/webapi-image.model.js +18 -0
  130. package/es6/src/webapi/postman/hello-openapi.js +166 -0
  131. package/es6/src/webapi/postman/local-view.js +167 -0
  132. package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
  133. package/es6/src/webapi/workspace-readme.js +88 -0
  134. package/es6/src/webapi.action.js +18 -0
  135. package/es6/src/webapi.command.js +33 -0
  136. package/package.json +19 -9
  137. package/types/src/add-api.action.d.ts +13 -0
  138. package/types/src/add-api.command.d.ts +7 -0
  139. package/types/src/add.command.d.ts +6 -0
  140. package/types/src/add.module.d.ts +14 -0
  141. package/types/src/auth0-configure.action.d.ts +7 -0
  142. package/types/src/auth0-configure.command.d.ts +6 -0
  143. package/types/src/auth0-login.action.d.ts +4 -0
  144. package/types/src/auth0-login.command.d.ts +6 -0
  145. package/types/src/auth0.command.d.ts +6 -0
  146. package/types/src/auth0.module.d.ts +18 -0
  147. package/types/src/cli.d.ts +2 -0
  148. package/types/src/create.command.d.ts +6 -0
  149. package/types/src/create.module.d.ts +14 -0
  150. package/types/src/domain/prompt.d.ts +4 -0
  151. package/{src/index.ts → types/src/index.d.ts} +4 -0
  152. package/types/src/models/compose.model.d.ts +73 -0
  153. package/types/src/models/config-yaml.model.d.ts +27 -0
  154. package/types/src/models/const.d.ts +20 -0
  155. package/types/src/models/dev-container.model.d.ts +49 -0
  156. package/types/src/models/docker-image.model.d.ts +90 -0
  157. package/types/src/models/env-file.model.d.ts +16 -0
  158. package/types/src/models/gitignore.model.d.ts +9 -0
  159. package/types/src/models/launch-json.model.d.ts +7 -0
  160. package/types/src/models/package-json.model.d.ts +36 -0
  161. package/types/src/models/project.model.d.ts +60 -0
  162. package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
  163. package/types/src/models/tsconfig.model.d.ts +5 -0
  164. package/types/src/models/vitest-config.model.d.ts +5 -0
  165. package/types/src/models/vscode-settings.model.d.ts +7 -0
  166. package/types/src/program.d.ts +8 -0
  167. package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
  168. package/types/src/services/scaffold.service.d.ts +22 -0
  169. package/types/src/version.d.ts +6 -0
  170. package/types/src/webapi/api-name.d.ts +14 -0
  171. package/types/src/webapi/auth0-commands.d.ts +11 -0
  172. package/types/src/webapi/authorize-scopes.d.ts +7 -0
  173. package/types/src/webapi/cli-options.d.ts +6 -0
  174. package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +9 -21
  175. package/types/src/webapi/create-api.d.ts +15 -0
  176. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
  177. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  178. package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
  179. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  180. package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
  181. package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
  182. package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
  183. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  184. package/types/src/webapi/features/auth.feature.d.ts +10 -0
  185. package/types/src/webapi/features/auth0-cli.d.ts +84 -0
  186. package/types/src/webapi/features/biome-format.d.ts +5 -0
  187. package/types/src/webapi/features/database.feature.d.ts +7 -0
  188. package/types/src/webapi/features/finalize.feature.d.ts +2 -0
  189. package/types/src/webapi/features/postman.feature.d.ts +16 -0
  190. package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
  191. package/types/src/webapi/models/auth-config.model.d.ts +26 -0
  192. package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
  193. package/types/src/webapi/models/github-ci.model.d.ts +9 -0
  194. package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
  195. package/types/src/webapi/models/production-dockerfile.model.d.ts +18 -0
  196. package/types/src/webapi/models/sample-files.model.d.ts +7 -0
  197. package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
  198. package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
  199. package/types/src/webapi/postman/local-view.d.ts +17 -0
  200. package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
  201. package/types/src/webapi/workspace-readme.d.ts +18 -0
  202. package/types/src/webapi.action.d.ts +14 -0
  203. package/types/src/webapi.command.d.ts +11 -0
  204. package/src/add-api.action.ts +0 -25
  205. package/src/add-api.command.ts +0 -31
  206. package/src/add.command.ts +0 -20
  207. package/src/add.module.ts +0 -11
  208. package/src/cli.ts +0 -4
  209. package/src/create.command.ts +0 -20
  210. package/src/create.module.ts +0 -11
  211. package/src/domain/prompt.ts +0 -50
  212. package/src/models/compose.model.ts +0 -205
  213. package/src/models/config-yaml.model.ts +0 -83
  214. package/src/models/const.ts +0 -20
  215. package/src/models/dev-container.model.ts +0 -136
  216. package/src/models/docker-image.model.ts +0 -343
  217. package/src/models/env-file.model.ts +0 -45
  218. package/src/models/gitignore.model.ts +0 -35
  219. package/src/models/launch-json.model.ts +0 -60
  220. package/src/models/package-json.model.ts +0 -169
  221. package/src/models/project.model.ts +0 -141
  222. package/src/models/tsconfig.model.ts +0 -29
  223. package/src/models/vitest-config.model.ts +0 -26
  224. package/src/models/vscode-settings.model.ts +0 -24
  225. package/src/program.ts +0 -63
  226. package/src/services/scaffold.service.ts +0 -89
  227. package/src/version.ts +0 -56
  228. package/src/webapi/api-name.ts +0 -73
  229. package/src/webapi/cli-options.ts +0 -56
  230. package/src/webapi/create-api.ts +0 -275
  231. package/src/webapi/devcontainer/host-scripts.ts +0 -35
  232. package/src/webapi/features/auth.feature.ts +0 -454
  233. package/src/webapi/features/auth0-cli.ts +0 -421
  234. package/src/webapi/features/biome-format.ts +0 -34
  235. package/src/webapi/features/database.feature.ts +0 -522
  236. package/src/webapi/features/finalize.feature.ts +0 -112
  237. package/src/webapi/features/postman.feature.ts +0 -287
  238. package/src/webapi/models/api-package-json.model.ts +0 -60
  239. package/src/webapi/models/auth-config.model.ts +0 -36
  240. package/src/webapi/models/controller-source-file.model.ts +0 -232
  241. package/src/webapi/models/main-source-file.model.ts +0 -208
  242. package/src/webapi/models/production-dockerfile.model.ts +0 -162
  243. package/src/webapi/models/sample-files.model.ts +0 -45
  244. package/src/webapi/models/webapi-image.model.ts +0 -21
  245. package/src/webapi/postman/hello-openapi.ts +0 -92
  246. package/src/webapi/postman/local-view.ts +0 -195
  247. package/src/webapi/postman/openapi-to-postman.ts +0 -208
  248. package/src/webapi/workspace-readme.ts +0 -192
  249. package/src/webapi.action.ts +0 -22
  250. package/src/webapi.command.ts +0 -41
  251. package/tests/auth-postman.wiring.spec.ts +0 -263
  252. package/tests/auth0-cli.spec.ts +0 -232
  253. package/tests/biome-format.spec.ts +0 -49
  254. package/tests/cli.integration.spec.ts +0 -658
  255. package/tests/compose.model.spec.ts +0 -51
  256. package/tests/container-image.model.spec.ts +0 -162
  257. package/tests/dev-container.model.spec.ts +0 -29
  258. package/tests/devcontainer-base-image.spec.ts +0 -32
  259. package/tests/hello-di-inject.scaffold.spec.ts +0 -18
  260. package/tests/hello-route.contract.spec.ts +0 -94
  261. package/tests/package-json.order.spec.ts +0 -59
  262. package/tests/postman-from-openapi.spec.ts +0 -197
  263. package/tests/seed-devcontainer-templates.ts +0 -40
  264. package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
@@ -0,0 +1,406 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ /** Management API scopes needed for API, SPA, connections, and admin users. */ const REQUIRED_SCOPES = [
4
+ "create:clients",
5
+ "read:clients",
6
+ "update:clients",
7
+ "create:resource_servers",
8
+ "read:resource_servers",
9
+ "update:resource_servers",
10
+ "read:connections",
11
+ "update:connections",
12
+ "create:users",
13
+ "read:users",
14
+ "update:users"
15
+ ].join(",");
16
+ /** Hard timeout for any Management API call. Prevents indefinite hangs on token re-auth. */ const API_TIMEOUT_MS = 8_000;
17
+ /** Default API scopes matching Hello CASL Authorize decorators. */ export const HELLO_API_SCOPES = [
18
+ "read:Hello",
19
+ "create:Hello",
20
+ "update:Hello",
21
+ "delete:Hello"
22
+ ];
23
+ export const DB_CONNECTION_NAME = "Username-Password-Authentication";
24
+ export class Auth0AuthError extends Error {
25
+ constructor(message){
26
+ super(message);
27
+ this.name = "Auth0AuthError";
28
+ }
29
+ }
30
+ /** Returns true if an error or stderr output looks like an expired/missing token. */ function isAuthError(text) {
31
+ return text.includes("renew access token") || text.includes("log in") || text.includes("Unauthorized");
32
+ }
33
+ /** Strip Auth0 CLI banner lines before JSON. */ function parseJsonOutput(stdout) {
34
+ const start = stdout.indexOf("{");
35
+ const arrayStart = stdout.indexOf("[");
36
+ let idx = -1;
37
+ if (start === -1) idx = arrayStart;
38
+ else if (arrayStart === -1) idx = start;
39
+ else idx = Math.min(start, arrayStart);
40
+ if (idx < 0) throw new Error(`Expected JSON from auth0 CLI, got: ${stdout.slice(0, 200)}`);
41
+ return JSON.parse(stdout.slice(idx));
42
+ }
43
+ /** Run an auth0 CLI command with a hard timeout. Returns stdout on success, throws on failure. */ function runAuth0(args) {
44
+ const result = spawnSync("auth0", args, {
45
+ encoding: "utf8",
46
+ timeout: API_TIMEOUT_MS,
47
+ input: "",
48
+ stdio: "pipe"
49
+ });
50
+ if (result.error) {
51
+ const msg = result.error.message;
52
+ if (isAuthError(msg) || result.error.code === "ETIMEDOUT") {
53
+ throw new Auth0AuthError(msg);
54
+ }
55
+ throw result.error;
56
+ }
57
+ if (result.status !== 0) {
58
+ const stderr = result.stderr ?? "";
59
+ if (isAuthError(stderr)) throw new Auth0AuthError(stderr);
60
+ throw new Error(stderr || `auth0 exited with code ${result.status}`);
61
+ }
62
+ return result.stdout;
63
+ }
64
+ export function isInstalled() {
65
+ try {
66
+ execFileSync("auth0", [
67
+ "--version"
68
+ ], {
69
+ stdio: "pipe"
70
+ });
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ export function listTenants() {
77
+ try {
78
+ const out = execFileSync("auth0", [
79
+ "tenants",
80
+ "list",
81
+ "--json"
82
+ ], {
83
+ encoding: "utf8",
84
+ stdio: "pipe"
85
+ });
86
+ return JSON.parse(out);
87
+ } catch {
88
+ return [];
89
+ }
90
+ }
91
+ /** Device/URL login (`--no-input`) so Docker can print a host-browser URL. */ export function login() {
92
+ const result = spawnSync("auth0", [
93
+ "login",
94
+ "--scopes",
95
+ REQUIRED_SCOPES,
96
+ "--no-input"
97
+ ], {
98
+ stdio: "inherit"
99
+ });
100
+ return result.status === 0;
101
+ }
102
+ export function listApis() {
103
+ try {
104
+ const out = runAuth0([
105
+ "apis",
106
+ "list",
107
+ "--json",
108
+ "--no-input"
109
+ ]);
110
+ return parseJsonOutput(out);
111
+ } catch (err) {
112
+ if (err instanceof Auth0AuthError) throw err;
113
+ return [];
114
+ }
115
+ }
116
+ export function createApi(name, identifier, scopes = HELLO_API_SCOPES) {
117
+ const out = runAuth0([
118
+ "apis",
119
+ "create",
120
+ "--name",
121
+ name,
122
+ "--identifier",
123
+ identifier,
124
+ "--scopes",
125
+ scopes.join(","),
126
+ // RBAC: only permissions assigned to the user/role appear on the token.
127
+ "--enforce-policies",
128
+ // Include Auth0 `permissions[]` claim (consumed by sample principalMapper).
129
+ "--token-dialect",
130
+ "access_token_authz",
131
+ "--json",
132
+ "--no-input"
133
+ ]);
134
+ return parseJsonOutput(out);
135
+ }
136
+ export function listApiScopes(identifier) {
137
+ try {
138
+ const out = runAuth0([
139
+ "apis",
140
+ "show",
141
+ identifier,
142
+ "--json",
143
+ "--no-input"
144
+ ]);
145
+ const api = parseJsonOutput(out);
146
+ return (api.scopes ?? []).map((scope)=>typeof scope === "string" ? scope : scope.value ?? scope.name ?? "").filter((scope)=>scope.length > 0);
147
+ } catch (err) {
148
+ if (err instanceof Auth0AuthError) throw err;
149
+ return [];
150
+ }
151
+ }
152
+ /** Assign API permissions to a user (Authorization Core). Idempotent when already granted. */ export function assignUserPermissions(userId, audience, permissions) {
153
+ if (permissions.length === 0) return;
154
+ runAuth0([
155
+ "api",
156
+ "post",
157
+ `users/${encodeURIComponent(userId)}/permissions`,
158
+ "--data",
159
+ JSON.stringify({
160
+ permissions: permissions.map((permission_name)=>({
161
+ resource_server_identifier: audience,
162
+ permission_name
163
+ }))
164
+ }),
165
+ "--no-input"
166
+ ]);
167
+ }
168
+ /**
169
+ * Replace an API's scopes and enable RBAC policy enforcement.
170
+ * Auth0 CLI `--scopes` replaces the full set.
171
+ */ export function updateApiScopes(identifier, scopes = HELLO_API_SCOPES) {
172
+ runAuth0([
173
+ "apis",
174
+ "update",
175
+ identifier,
176
+ "--scopes",
177
+ scopes.join(","),
178
+ "--enforce-policies",
179
+ "--token-dialect",
180
+ "access_token_authz",
181
+ "--no-input"
182
+ ]);
183
+ }
184
+ /** True when Auth0 refuses create because the tenant hit its Applications quota. */ export function isAuth0EntityLimitError(err) {
185
+ const text = err instanceof Error ? err.message : String(err);
186
+ return /limit of entities|reached the limit|tenant application limit reached/i.test(text);
187
+ }
188
+ /**
189
+ * Actionable message when Auth0 will not create another application.
190
+ * Does not suggest reusing unrelated apps — free tenants must delete unused ones.
191
+ */ export function formatSpaEntityLimitError(appName, apps) {
192
+ const listed = apps.length === 0 ? "Could not list existing applications." : `This tenant already has ${apps.length} application(s):\n${apps.map((a)=>` - ${a.name} (${a.client_id})`).join("\n")}`;
193
+ return [
194
+ `Auth0 refused to create SPA "${appName}": tenant application limit reached.`,
195
+ listed,
196
+ "",
197
+ "Fix: Auth0 Dashboard → Applications → Applications → delete unused apps,",
198
+ `then re-run the scaffold (or choose Postman "none" to skip SPA creation).`,
199
+ "Re-scaffold of the same project name reuses an exact-name match when it still exists."
200
+ ].join("\n");
201
+ }
202
+ export function createSpaApp(name, callbackUrls) {
203
+ const callbacks = typeof callbackUrls === "string" ? callbackUrls : callbackUrls.join(",");
204
+ const out = runAuth0([
205
+ "apps",
206
+ "create",
207
+ "--name",
208
+ name,
209
+ "--type",
210
+ "spa",
211
+ "--callbacks",
212
+ callbacks,
213
+ "--json",
214
+ "--no-input"
215
+ ]);
216
+ const app = parseJsonOutput(out);
217
+ const clientId = app.client_id ?? app.clientId;
218
+ if (!clientId) {
219
+ throw new Error(`Auth0 create SPA response missing client_id. stdout: ${out.slice(0, 300)}`);
220
+ }
221
+ return {
222
+ client_id: clientId
223
+ };
224
+ }
225
+ export function listApps() {
226
+ try {
227
+ const out = runAuth0([
228
+ "apps",
229
+ "list",
230
+ "--json",
231
+ "--no-input"
232
+ ]);
233
+ const apps = parseJsonOutput(out);
234
+ return apps.map((a)=>({
235
+ client_id: a.client_id ?? a.clientId ?? "",
236
+ name: a.name ?? ""
237
+ })).filter((a)=>a.client_id && a.name);
238
+ } catch (err) {
239
+ if (err instanceof Auth0AuthError) throw err;
240
+ return [];
241
+ }
242
+ }
243
+ export function updateSpaCallbacks(clientId, callbackUrls) {
244
+ const callbacks = typeof callbackUrls === "string" ? callbackUrls : callbackUrls.join(",");
245
+ runAuth0([
246
+ "apps",
247
+ "update",
248
+ clientId,
249
+ "--callbacks",
250
+ callbacks,
251
+ "--no-input"
252
+ ]);
253
+ }
254
+ /**
255
+ * Create a Postman SPA, or reuse an existing app with the same name (re-scaffold).
256
+ * Always returns a client_id suitable for Postman auth0ClientId.
257
+ * On Auth0 application-quota errors, throws a clear delete-apps message (no cross-app reuse).
258
+ */ export function ensureSpaApp(name, callbackUrls) {
259
+ const apps = listApps();
260
+ const existing = apps.find((a)=>a.name === name);
261
+ if (existing) {
262
+ try {
263
+ updateSpaCallbacks(existing.client_id, callbackUrls);
264
+ } catch {
265
+ // Callbacks may already match; still usable for Postman.
266
+ }
267
+ return {
268
+ client_id: existing.client_id,
269
+ reused: true
270
+ };
271
+ }
272
+ try {
273
+ return {
274
+ ...createSpaApp(name, callbackUrls),
275
+ reused: false
276
+ };
277
+ } catch (err) {
278
+ if (isAuth0EntityLimitError(err)) {
279
+ throw new Error(formatSpaEntityLimitError(name, apps));
280
+ }
281
+ throw err;
282
+ }
283
+ }
284
+ /** Enable a database connection for an application (required for username/password login). */ export function enableConnectionForClient(connectionName, clientId) {
285
+ const listOut = runAuth0([
286
+ "api",
287
+ "get",
288
+ "connections",
289
+ "--no-input"
290
+ ]);
291
+ const connections = parseJsonOutput(listOut);
292
+ const connection = connections.find((c)=>c.name === connectionName);
293
+ if (!connection) {
294
+ throw new Error(`Auth0 connection not found: ${connectionName}`);
295
+ }
296
+ const clientsOut = runAuth0([
297
+ "api",
298
+ "get",
299
+ `connections/${connection.id}/clients`,
300
+ "--no-input"
301
+ ]);
302
+ const { clients } = parseJsonOutput(clientsOut);
303
+ if (clients.some((c)=>c.client_id === clientId)) return;
304
+ // Dedicated enabled-clients endpoint (enabled_clients on connection PATCH is deprecated).
305
+ runAuth0([
306
+ "api",
307
+ "patch",
308
+ `connections/${connection.id}/clients`,
309
+ "--data",
310
+ JSON.stringify([
311
+ {
312
+ client_id: clientId,
313
+ status: true
314
+ }
315
+ ]),
316
+ "--no-input"
317
+ ]);
318
+ }
319
+ /** Strong default password when the scaffolder caller leaves password blank. */ export function generateAdminPassword() {
320
+ return `${randomBytes(12).toString("base64url")}aA1!`;
321
+ }
322
+ function isUserAlreadyExistsError(err) {
323
+ const text = err instanceof Error ? err.message : String(err);
324
+ return /already exists|user exists|Conflict|409/i.test(text);
325
+ }
326
+ /** Look up a user by email (Management API users-by-email). */ export function findUserByEmail(email) {
327
+ const out = runAuth0([
328
+ "api",
329
+ "get",
330
+ `users-by-email?email=${encodeURIComponent(email)}`,
331
+ "--no-input"
332
+ ]);
333
+ const users = parseJsonOutput(out);
334
+ const user = users.find((u)=>u.user_id && u.email);
335
+ if (!user?.user_id || !user.email) return null;
336
+ return {
337
+ user_id: user.user_id,
338
+ email: user.email
339
+ };
340
+ }
341
+ /** Reset password so a re-scaffold can write a known password to .env. */ export function updateUserPassword(userId, password) {
342
+ runAuth0([
343
+ "api",
344
+ "patch",
345
+ `users/${encodeURIComponent(userId)}`,
346
+ "--data",
347
+ JSON.stringify({
348
+ password,
349
+ connection: DB_CONNECTION_NAME
350
+ }),
351
+ "--no-input"
352
+ ]);
353
+ }
354
+ /**
355
+ * Create a DB connection root-admin user for local / Postman login.
356
+ * If the user already exists, look them up, reset password to `input.password`,
357
+ * and return that identity so callers can still write .env credentials.
358
+ */ export function createAdminDatabaseUser(input) {
359
+ const body = {
360
+ email: input.email,
361
+ connection: DB_CONNECTION_NAME,
362
+ password: input.password,
363
+ name: input.username,
364
+ email_verified: true,
365
+ verify_email: false,
366
+ app_metadata: {
367
+ roles: [
368
+ "admin"
369
+ ]
370
+ }
371
+ };
372
+ try {
373
+ const out = runAuth0([
374
+ "api",
375
+ "post",
376
+ "users",
377
+ "--data",
378
+ JSON.stringify(body),
379
+ "--no-input"
380
+ ]);
381
+ const user = parseJsonOutput(out);
382
+ if (!user.user_id || !user.email) {
383
+ throw new Error(`Auth0 create user response missing user_id/email. stdout: ${out.slice(0, 300)}`);
384
+ }
385
+ return {
386
+ user_id: user.user_id,
387
+ email: user.email,
388
+ password: input.password,
389
+ username: input.username
390
+ };
391
+ } catch (err) {
392
+ if (!isUserAlreadyExistsError(err)) throw err;
393
+ const existing = findUserByEmail(input.email);
394
+ if (!existing) {
395
+ throw new Error(`Auth0 user already exists for ${input.email}, but lookup by email returned no user.`);
396
+ }
397
+ updateUserPassword(existing.user_id, input.password);
398
+ return {
399
+ user_id: existing.user_id,
400
+ email: existing.email,
401
+ password: input.password,
402
+ username: input.username
403
+ };
404
+ }
405
+ }
406
+ export { REQUIRED_SCOPES };
@@ -0,0 +1,40 @@
1
+ import { spawnSync } from "node:child_process";
2
+ /**
3
+ * Format/lint-fix all files in the scaffolded workspace with Biome.
4
+ * Prefer a global `biome` binary (builder image); fall back to `npx`.
5
+ */ export function formatScaffoldedFiles(targetDir, paths = [
6
+ "."
7
+ ]) {
8
+ process.stdout.write("\n Formatting scaffolded files with Biome...");
9
+ let result = spawnSync("biome", [
10
+ "check",
11
+ "--write",
12
+ ...paths
13
+ ], {
14
+ cwd: targetDir,
15
+ encoding: "utf8",
16
+ stdio: "pipe"
17
+ });
18
+ if (result.error && result.error.code === "ENOENT") {
19
+ result = spawnSync("npx", [
20
+ "--yes",
21
+ "@biomejs/biome",
22
+ "check",
23
+ "--write",
24
+ ...paths
25
+ ], {
26
+ cwd: targetDir,
27
+ encoding: "utf8",
28
+ stdio: "pipe"
29
+ });
30
+ }
31
+ if (result.status === 0) {
32
+ process.stdout.write(" done.\n");
33
+ return;
34
+ }
35
+ const detail = (result.stderr || result.stdout || result.error?.message || "").trim();
36
+ process.stdout.write(" failed.\n");
37
+ if (detail) {
38
+ console.error(` ✗ Biome: ${detail.slice(0, 400)}`);
39
+ }
40
+ }