zudojs-cli 2.0.1 → 2.1.1

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 (248) hide show
  1. package/README.md +59 -7
  2. package/dist/src/adapters/frontend/angular.adapter.d.ts +0 -1
  3. package/dist/src/adapters/frontend/angular.adapter.js +14 -97
  4. package/dist/src/adapters/frontend/astro.adapter.d.ts +0 -1
  5. package/dist/src/adapters/frontend/astro.adapter.js +4 -64
  6. package/dist/src/adapters/frontend/fallback/fallback.helper.d.ts +41 -0
  7. package/dist/src/adapters/frontend/fallback/fallback.helper.js +57 -0
  8. package/dist/src/adapters/frontend/fallback/framework/angular/angular.fallback.d.ts +18 -0
  9. package/dist/src/adapters/frontend/fallback/framework/angular/angular.fallback.js +107 -0
  10. package/dist/src/adapters/frontend/fallback/framework/angular/angularWorkspace.fallback.d.ts +23 -0
  11. package/dist/src/adapters/frontend/fallback/framework/angular/angularWorkspace.fallback.js +125 -0
  12. package/dist/src/adapters/frontend/fallback/framework/angular/index.d.ts +8 -0
  13. package/dist/src/adapters/frontend/fallback/framework/angular/index.js +8 -0
  14. package/dist/src/adapters/frontend/fallback/framework/astro.fallback.d.ts +14 -0
  15. package/dist/src/adapters/frontend/fallback/framework/astro.fallback.js +76 -0
  16. package/dist/src/adapters/frontend/fallback/framework/index.d.ts +13 -0
  17. package/dist/src/adapters/frontend/fallback/framework/index.js +13 -0
  18. package/dist/src/adapters/frontend/fallback/framework/next.fallback.d.ts +13 -0
  19. package/dist/src/adapters/frontend/fallback/framework/next.fallback.js +105 -0
  20. package/dist/src/adapters/frontend/fallback/framework/nuxt.fallback.d.ts +14 -0
  21. package/dist/src/adapters/frontend/fallback/framework/nuxt.fallback.js +56 -0
  22. package/dist/src/adapters/frontend/fallback/framework/sveltekit.fallback.d.ts +15 -0
  23. package/dist/src/adapters/frontend/fallback/framework/sveltekit.fallback.js +94 -0
  24. package/dist/src/adapters/frontend/fallback/index.d.ts +12 -0
  25. package/dist/src/adapters/frontend/fallback/index.js +12 -0
  26. package/dist/src/adapters/frontend/fallback/vite/index.d.ts +11 -0
  27. package/dist/src/adapters/frontend/fallback/vite/index.js +11 -0
  28. package/dist/src/adapters/frontend/fallback/vite/react.fallback.d.ts +13 -0
  29. package/dist/src/adapters/frontend/fallback/vite/react.fallback.js +77 -0
  30. package/dist/src/adapters/frontend/fallback/vite/svelte.fallback.d.ts +14 -0
  31. package/dist/src/adapters/frontend/fallback/vite/svelte.fallback.js +95 -0
  32. package/dist/src/adapters/frontend/fallback/vite/vanilla.fallback.d.ts +13 -0
  33. package/dist/src/adapters/frontend/fallback/vite/vanilla.fallback.js +71 -0
  34. package/dist/src/adapters/frontend/fallback/vite/vue.fallback.d.ts +14 -0
  35. package/dist/src/adapters/frontend/fallback/vite/vue.fallback.js +78 -0
  36. package/dist/src/adapters/frontend/next.adapter.d.ts +0 -1
  37. package/dist/src/adapters/frontend/next.adapter.js +16 -95
  38. package/dist/src/adapters/frontend/nuxt.adapter.d.ts +1 -2
  39. package/dist/src/adapters/frontend/nuxt.adapter.js +32 -43
  40. package/dist/src/adapters/frontend/react-native.adapter.js +1 -1
  41. package/dist/src/adapters/frontend/react.adapter.d.ts +0 -1
  42. package/dist/src/adapters/frontend/react.adapter.js +7 -89
  43. package/dist/src/adapters/frontend/svelte.adapter.d.ts +0 -1
  44. package/dist/src/adapters/frontend/svelte.adapter.js +4 -86
  45. package/dist/src/adapters/frontend/sveltekit.adapter.d.ts +0 -1
  46. package/dist/src/adapters/frontend/sveltekit.adapter.js +4 -57
  47. package/dist/src/adapters/frontend/vanilla.adapter.d.ts +0 -1
  48. package/dist/src/adapters/frontend/vanilla.adapter.js +4 -47
  49. package/dist/src/adapters/frontend/vue.adapter.d.ts +0 -1
  50. package/dist/src/adapters/frontend/vue.adapter.js +4 -76
  51. package/dist/src/bin/bin.menu.d.ts +22 -0
  52. package/dist/src/bin/bin.menu.js +40 -0
  53. package/dist/src/bin/bin.runner.d.ts +38 -0
  54. package/dist/src/bin/bin.runner.js +51 -0
  55. package/dist/src/bin/index.d.ts +9 -0
  56. package/dist/src/bin/index.js +9 -0
  57. package/dist/src/bin/zudojs.js +15 -4
  58. package/dist/src/cliApplication/cliApplication.core.d.ts +4 -2
  59. package/dist/src/cliApplication/cliApplication.core.js +48 -36
  60. package/dist/src/cliApplication/cliApplication.logger.d.ts +5 -0
  61. package/dist/src/cliApplication/cliApplication.logger.js +5 -3
  62. package/dist/src/cliApplication/invocation/index.d.ts +12 -0
  63. package/dist/src/cliApplication/invocation/index.js +12 -0
  64. package/dist/src/cliApplication/invocation/invocation.hint.d.ts +30 -0
  65. package/dist/src/cliApplication/invocation/invocation.hint.js +74 -0
  66. package/dist/src/cliApplication/invocation/invocation.locate.d.ts +34 -0
  67. package/dist/src/cliApplication/invocation/invocation.locate.js +53 -0
  68. package/dist/src/cliApplication/invocation/invocation.name.d.ts +37 -0
  69. package/dist/src/cliApplication/invocation/invocation.name.js +69 -0
  70. package/dist/src/cliApplication/invocation/invocation.sanitize.d.ts +19 -0
  71. package/dist/src/cliApplication/invocation/invocation.sanitize.js +32 -0
  72. package/dist/src/cliParser/cliParser.core.js +3 -1
  73. package/dist/src/cliParser/cliParser.helper.d.ts +7 -0
  74. package/dist/src/cliParser/cliParser.helper.js +19 -2
  75. package/dist/src/cliParser/cliParser.longOption.js +2 -2
  76. package/dist/src/commands/add.command.d.ts +7 -3
  77. package/dist/src/commands/add.command.js +96 -57
  78. package/dist/src/commands/create.command.js +29 -42
  79. package/dist/src/commands/doctor.command.d.ts +2 -1
  80. package/dist/src/commands/doctor.command.js +52 -4
  81. package/dist/src/commands/generate.command.js +31 -16
  82. package/dist/src/commands/info.command.js +11 -1
  83. package/dist/src/constants/index.d.ts +21 -3
  84. package/dist/src/constants/index.js +40 -5
  85. package/dist/src/constants/zudojsVersions.generated.d.ts +9 -0
  86. package/dist/src/constants/zudojsVersions.generated.js +48 -0
  87. package/dist/src/constants/zudojsVersions.helper.d.ts +21 -0
  88. package/dist/src/constants/zudojsVersions.helper.js +29 -0
  89. package/dist/src/generators/frontend/frontendGenerator.core.js +9 -10
  90. package/dist/src/generators/frontend/frontendPipeline.d.ts +6 -1
  91. package/dist/src/generators/frontend/frontendPipeline.js +9 -1
  92. package/dist/src/generators/fullstack/fullstackComposer.core.js +7 -1
  93. package/dist/src/generators/index.d.ts +1 -2
  94. package/dist/src/generators/index.js +1 -2
  95. package/dist/src/generators/infrastructure/infrastructure.generator.js +13 -5
  96. package/dist/src/generators/integration/integrationGenerator.core.d.ts +2 -0
  97. package/dist/src/generators/integration/integrationGenerator.core.js +31 -2
  98. package/dist/src/generators/module/module.generator.js +36 -4
  99. package/dist/src/generators/resource/index.d.ts +8 -0
  100. package/dist/src/generators/resource/index.js +8 -0
  101. package/dist/src/generators/resource/resource.generator.d.ts +27 -0
  102. package/dist/src/generators/resource/resource.generator.js +87 -0
  103. package/dist/src/generators/resource/resource.layout.d.ts +26 -0
  104. package/dist/src/generators/resource/resource.layout.js +46 -0
  105. package/dist/src/generators/resource/resource.plan.d.ts +27 -0
  106. package/dist/src/generators/resource/resource.plan.js +46 -0
  107. package/dist/src/generators/resource/resource.run.d.ts +25 -0
  108. package/dist/src/generators/resource/resource.run.js +52 -0
  109. package/dist/src/prompts/cancel.prompt.d.ts +5 -1
  110. package/dist/src/prompts/cancel.prompt.js +4 -0
  111. package/dist/src/prompts/menu/index.d.ts +13 -0
  112. package/dist/src/prompts/menu/index.js +13 -0
  113. package/dist/src/prompts/menu/menu.adapter.d.ts +16 -0
  114. package/dist/src/prompts/menu/menu.adapter.js +56 -0
  115. package/dist/src/prompts/menu/menu.constant.d.ts +20 -0
  116. package/dist/src/prompts/menu/menu.constant.js +29 -0
  117. package/dist/src/prompts/menu/menu.flow.d.ts +18 -0
  118. package/dist/src/prompts/menu/menu.flow.js +82 -0
  119. package/dist/src/prompts/menu/menu.select.d.ts +26 -0
  120. package/dist/src/prompts/menu/menu.select.js +118 -0
  121. package/dist/src/prompts/menu/menu.type.d.ts +66 -0
  122. package/dist/src/prompts/menu/menu.type.js +8 -0
  123. package/dist/src/recipes/apply/index.d.ts +9 -0
  124. package/dist/src/recipes/apply/index.js +9 -0
  125. package/dist/src/recipes/apply/recipeApply.app.d.ts +20 -0
  126. package/dist/src/recipes/apply/recipeApply.app.js +79 -0
  127. package/dist/src/recipes/apply/recipeApply.context.d.ts +14 -0
  128. package/dist/src/recipes/apply/recipeApply.context.js +76 -0
  129. package/dist/src/recipes/apply/recipeApply.files.d.ts +39 -0
  130. package/dist/src/recipes/apply/recipeApply.files.js +84 -0
  131. package/dist/src/recipes/apply/recipeApply.project.d.ts +11 -0
  132. package/dist/src/recipes/apply/recipeApply.project.js +50 -0
  133. package/dist/src/recipes/apply/recipeApply.report.d.ts +20 -0
  134. package/dist/src/recipes/apply/recipeApply.report.js +51 -0
  135. package/dist/src/recipes/catalog/database/database.files.d.ts +15 -0
  136. package/dist/src/recipes/catalog/database/database.files.js +98 -0
  137. package/dist/src/recipes/catalog/database/database.recipe.d.ts +9 -0
  138. package/dist/src/recipes/catalog/database/database.recipe.js +50 -0
  139. package/dist/src/recipes/catalog/database/index.d.ts +6 -0
  140. package/dist/src/recipes/catalog/database/index.js +6 -0
  141. package/dist/src/recipes/catalog/email.recipe.d.ts +9 -0
  142. package/dist/src/recipes/catalog/email.recipe.js +122 -0
  143. package/dist/src/recipes/catalog/index.d.ts +11 -0
  144. package/dist/src/recipes/catalog/index.js +11 -0
  145. package/dist/src/recipes/catalog/openapi.recipe.d.ts +8 -0
  146. package/dist/src/recipes/catalog/openapi.recipe.js +25 -0
  147. package/dist/src/recipes/catalog/platform/cache.recipe.d.ts +7 -0
  148. package/dist/src/recipes/catalog/platform/cache.recipe.js +55 -0
  149. package/dist/src/recipes/catalog/platform/index.d.ts +9 -0
  150. package/dist/src/recipes/catalog/platform/index.js +9 -0
  151. package/dist/src/recipes/catalog/platform/messaging.recipe.d.ts +7 -0
  152. package/dist/src/recipes/catalog/platform/messaging.recipe.js +46 -0
  153. package/dist/src/recipes/catalog/platform/observability.recipe.d.ts +8 -0
  154. package/dist/src/recipes/catalog/platform/observability.recipe.js +49 -0
  155. package/dist/src/recipes/catalog/platform/queue.recipe.d.ts +7 -0
  156. package/dist/src/recipes/catalog/platform/queue.recipe.js +61 -0
  157. package/dist/src/recipes/catalog/platform/scheduler.recipe.d.ts +7 -0
  158. package/dist/src/recipes/catalog/platform/scheduler.recipe.js +56 -0
  159. package/dist/src/recipes/catalog/redis.recipe.d.ts +8 -0
  160. package/dist/src/recipes/catalog/redis.recipe.js +79 -0
  161. package/dist/src/recipes/catalog/storage.recipe.d.ts +7 -0
  162. package/dist/src/recipes/catalog/storage.recipe.js +49 -0
  163. package/dist/src/recipes/catalog/websockets.recipe.d.ts +10 -0
  164. package/dist/src/recipes/catalog/websockets.recipe.js +84 -0
  165. package/dist/src/recipes/docker/docker.compose.d.ts +19 -0
  166. package/dist/src/recipes/docker/docker.compose.js +90 -0
  167. package/dist/src/recipes/docker/docker.recipe.d.ts +10 -0
  168. package/dist/src/recipes/docker/docker.recipe.js +51 -0
  169. package/dist/src/recipes/docker/docker.refresh.d.ts +13 -0
  170. package/dist/src/recipes/docker/docker.refresh.js +37 -0
  171. package/dist/src/recipes/docker/index.d.ts +7 -0
  172. package/dist/src/recipes/docker/index.js +7 -0
  173. package/dist/src/recipes/index.d.ts +8 -0
  174. package/dist/src/recipes/index.js +8 -0
  175. package/dist/src/recipes/recipe.registry.d.ts +17 -0
  176. package/dist/src/recipes/recipe.registry.js +46 -0
  177. package/dist/src/recipes/recipe.type.d.ts +93 -0
  178. package/dist/src/recipes/recipe.type.js +12 -0
  179. package/dist/src/resolvers/dependency/dependencyVersions.constant.d.ts +100 -5
  180. package/dist/src/resolvers/dependency/dependencyVersions.constant.js +103 -34
  181. package/dist/src/resolvers/dependency/index.d.ts +1 -1
  182. package/dist/src/resolvers/dependency/index.js +1 -1
  183. package/dist/src/resolvers/project.resolver.d.ts +14 -1
  184. package/dist/src/resolvers/project.resolver.js +11 -3
  185. package/dist/src/scaffolders/scaffolder.helper.js +12 -1
  186. package/dist/src/templates/backendApp/backendApp.core.d.ts +45 -0
  187. package/dist/src/templates/backendApp/backendApp.core.js +66 -0
  188. package/dist/src/templates/backendApp/backendApp.modules.d.ts +26 -0
  189. package/dist/src/templates/backendApp/backendApp.modules.js +25 -0
  190. package/dist/src/templates/backendApp/backendApp.project.d.ts +26 -0
  191. package/dist/src/templates/backendApp/backendApp.project.js +87 -0
  192. package/dist/src/templates/backendApp/index.d.ts +8 -0
  193. package/dist/src/templates/backendApp/index.js +8 -0
  194. package/dist/src/templates/backendApp/parts/config.template.d.ts +28 -0
  195. package/dist/src/templates/backendApp/parts/config.template.js +118 -0
  196. package/dist/src/templates/backendApp/parts/httpUtils.template.d.ts +10 -0
  197. package/dist/src/templates/backendApp/parts/httpUtils.template.js +103 -0
  198. package/dist/src/templates/backendApp/parts/index.d.ts +9 -0
  199. package/dist/src/templates/backendApp/parts/index.js +9 -0
  200. package/dist/src/templates/backendApp/parts/integrations.template.d.ts +16 -0
  201. package/dist/src/templates/backendApp/parts/integrations.template.js +115 -0
  202. package/dist/src/templates/backendApp/parts/wiring.template.d.ts +26 -0
  203. package/dist/src/templates/backendApp/parts/wiring.template.js +111 -0
  204. package/dist/src/templates/microservice/microservice.template.js +61 -87
  205. package/dist/src/templates/modular-monolith/modularMonolith.template.js +25 -77
  206. package/dist/src/templates/monolith/monolith.template.d.ts +11 -26
  207. package/dist/src/templates/monolith/monolith.template.js +49 -146
  208. package/dist/src/templates/resource/index.d.ts +9 -0
  209. package/dist/src/templates/resource/index.js +9 -0
  210. package/dist/src/templates/resource/layers/index.d.ts +9 -0
  211. package/dist/src/templates/resource/layers/index.js +9 -0
  212. package/dist/src/templates/resource/layers/resourceData.template.d.ts +9 -0
  213. package/dist/src/templates/resource/layers/resourceData.template.js +108 -0
  214. package/dist/src/templates/resource/layers/resourcePrisma.template.d.ts +15 -0
  215. package/dist/src/templates/resource/layers/resourcePrisma.template.js +89 -0
  216. package/dist/src/templates/resource/layers/resourceRoutes.template.d.ts +13 -0
  217. package/dist/src/templates/resource/layers/resourceRoutes.template.js +128 -0
  218. package/dist/src/templates/resource/layers/resourceService.template.d.ts +13 -0
  219. package/dist/src/templates/resource/layers/resourceService.template.js +95 -0
  220. package/dist/src/templates/resource/resource.files.d.ts +33 -0
  221. package/dist/src/templates/resource/resource.files.js +99 -0
  222. package/dist/src/templates/resource/resource.names.d.ts +38 -0
  223. package/dist/src/templates/resource/resource.names.js +60 -0
  224. package/dist/src/templates/resource/resource.type.d.ts +19 -0
  225. package/dist/src/templates/resource/resource.type.js +15 -0
  226. package/dist/src/templates/shared/appRuntime.template.js +28 -11
  227. package/dist/src/templates/shared/capability.template.d.ts +13 -4
  228. package/dist/src/templates/shared/capability.template.js +21 -5
  229. package/dist/src/templates/shared/dockerfile.template.d.ts +12 -4
  230. package/dist/src/templates/shared/dockerfile.template.js +68 -29
  231. package/dist/src/templates/shared/pnpm.template.d.ts +8 -2
  232. package/dist/src/templates/shared/pnpm.template.js +9 -1
  233. package/dist/src/templates/shared/server.template.d.ts +23 -22
  234. package/dist/src/templates/shared/server.template.js +79 -53
  235. package/dist/src/types/scaffold.type.d.ts +6 -0
  236. package/dist/src/types/scaffold.type.js +2 -1
  237. package/dist/src/utils/utils.name.js +6 -0
  238. package/dist/src/utils/utils.writeGuard.d.ts +5 -0
  239. package/dist/src/utils/utils.writeGuard.js +23 -0
  240. package/dist/src/wiring/index.d.ts +10 -0
  241. package/dist/src/wiring/index.js +10 -0
  242. package/dist/src/wiring/wiring.dependencies.d.ts +29 -0
  243. package/dist/src/wiring/wiring.dependencies.js +83 -0
  244. package/dist/src/wiring/wiring.edits.d.ts +51 -0
  245. package/dist/src/wiring/wiring.edits.js +90 -0
  246. package/dist/src/wiring/wiring.markers.d.ts +53 -0
  247. package/dist/src/wiring/wiring.markers.js +87 -0
  248. package/package.json +20 -15
@@ -0,0 +1,19 @@
1
+ /**
2
+ * zudojs-cli — Terminal-Safe Text
3
+ *
4
+ * Neutralises control characters in text that echoes user input back to
5
+ * the terminal.
6
+ */
7
+ /**
8
+ * Replaces terminal control characters with a visible escape.
9
+ *
10
+ * Error messages quote what the user passed (`Command "…" was not found.`,
11
+ * `Unknown or invalid option "…"`). An argument carrying ESC sequences —
12
+ * from a script that forwards untrusted input — could otherwise recolour
13
+ * the terminal, rewrite its title, hide text or emit OSC 8 links. The
14
+ * escaped form (`\x1b`) still shows the user what was received.
15
+ *
16
+ * @param text - Text about to be written to the terminal.
17
+ */
18
+ export declare function escapeControlCharacters(text: string): string;
19
+ //# sourceMappingURL=invocation.sanitize.d.ts.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * zudojs-cli — Terminal-Safe Text
3
+ *
4
+ * Neutralises control characters in text that echoes user input back to
5
+ * the terminal.
6
+ */
7
+ /**
8
+ * C0 and C1 control characters other than tab and newline, plus the Unicode
9
+ * bidirectional overrides that can make a line read differently from what
10
+ * it contains.
11
+ */
12
+ const UNSAFE_CHARACTERS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F‎‏‪-‮⁦-⁩]/g;
13
+ /**
14
+ * Replaces terminal control characters with a visible escape.
15
+ *
16
+ * Error messages quote what the user passed (`Command "…" was not found.`,
17
+ * `Unknown or invalid option "…"`). An argument carrying ESC sequences —
18
+ * from a script that forwards untrusted input — could otherwise recolour
19
+ * the terminal, rewrite its title, hide text or emit OSC 8 links. The
20
+ * escaped form (`\x1b`) still shows the user what was received.
21
+ *
22
+ * @param text - Text about to be written to the terminal.
23
+ */
24
+ export function escapeControlCharacters(text) {
25
+ return text.replace(UNSAFE_CHARACTERS, (character) => {
26
+ const code = character.codePointAt(0);
27
+ return code <= 0xff
28
+ ? `\\x${code.toString(16).padStart(2, "0")}`
29
+ : `\\u${code.toString(16).padStart(4, "0")}`;
30
+ });
31
+ }
32
+ //# sourceMappingURL=invocation.sanitize.js.map
@@ -28,7 +28,9 @@ export class CLIParser {
28
28
  const tokens = Array.from(args);
29
29
  const commands = [];
30
30
  const positional = [];
31
- const options = {};
31
+ // No prototype: an undeclared `--constructor` (allowUnknownOptions) must
32
+ // not read or shadow Object.prototype members.
33
+ const options = Object.create(null);
32
34
  const definitions = command?.options ?? [];
33
35
  const argumentsDefinitions = command?.arguments ?? [];
34
36
  let index = 0;
@@ -11,6 +11,13 @@ export declare function parseCLIArguments(args: CLIArguments, command?: CLIComma
11
11
  export declare function parseOptionValue(definition: CLIOption, value: string): CLIValue;
12
12
  /** Parses a string into a boolean. */
13
13
  export declare function parseBoolean(value: string, option?: string): boolean;
14
+ /**
15
+ * Returns whether an option name would touch an object's prototype chain.
16
+ *
17
+ * Only reachable when `allowUnknownOptions` is on: undeclared names are then
18
+ * written to the values object as typed, so `--__proto__` must be refused.
19
+ */
20
+ export declare function isReservedOptionKey(name: string): boolean;
14
21
  /** Returns whether a token is any option (short or long). */
15
22
  export declare function isOption(token: string): boolean;
16
23
  /** Returns whether a token is a long option (e.g. `--verbose`). */
@@ -19,8 +19,10 @@ export function parseOptionValue(definition, value) {
19
19
  case "boolean":
20
20
  return parseBoolean(value, definition.name);
21
21
  case "number": {
22
- const parsed = Number(value);
23
- if (Number.isNaN(parsed)) {
22
+ // `Number("")` is 0 and `Number("Infinity")` is Infinity, so
23
+ // `--port=` used to start the server with PORT=0 instead of failing.
24
+ const parsed = value.trim() === "" ? Number.NaN : Number(value);
25
+ if (!Number.isFinite(parsed)) {
24
26
  throw new InvalidArgumentsError(`Option "${definition.name}" expects a number, received "${value}".`);
25
27
  }
26
28
  return parsed;
@@ -42,6 +44,21 @@ export function parseBoolean(value, option = "option") {
42
44
  /* -------------------------------------------------------------------------- */
43
45
  /* Token Classification */
44
46
  /* -------------------------------------------------------------------------- */
47
+ /** Keys that must never be written from the command line. */
48
+ const RESERVED_OPTION_KEYS = new Set([
49
+ "__proto__",
50
+ "constructor",
51
+ "prototype",
52
+ ]);
53
+ /**
54
+ * Returns whether an option name would touch an object's prototype chain.
55
+ *
56
+ * Only reachable when `allowUnknownOptions` is on: undeclared names are then
57
+ * written to the values object as typed, so `--__proto__` must be refused.
58
+ */
59
+ export function isReservedOptionKey(name) {
60
+ return RESERVED_OPTION_KEYS.has(name);
61
+ }
45
62
  /** Returns whether a token is any option (short or long). */
46
63
  export function isOption(token) {
47
64
  return ((token.startsWith(CLI_OPTION_PREFIXES.LONG) && token !== "--") ||
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { CLI_OPTION_PREFIXES } from "../cliConstant/cliConstant.value.js";
7
7
  import { InvalidOptionError, MissingOptionValueError, } from "../cliError/cliError.option.js";
8
- import { isOption, isOptionValueToken, parseBoolean, parseOptionValue, } from "./cliParser.helper.js";
8
+ import { isOption, isOptionValueToken, isReservedOptionKey, parseBoolean, parseOptionValue, } from "./cliParser.helper.js";
9
9
  /** Parses a long option token from the token stream. */
10
10
  export function parseLongOption(tokens, index, definitions, values, allowUnknownOptions) {
11
11
  const token = tokens[index];
@@ -26,7 +26,7 @@ export function parseLongOption(tokens, index, definitions, values, allowUnknown
26
26
  }
27
27
  }
28
28
  const definition = findOption(name, definitions);
29
- if (!definition && !allowUnknownOptions) {
29
+ if (!definition && (!allowUnknownOptions || isReservedOptionKey(name))) {
30
30
  throw new InvalidOptionError(token);
31
31
  }
32
32
  if (!definition) {
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * zudojs-cli — Add Command
3
3
  *
4
- * The `zudojs add` command for adding feature packages.
4
+ * `zudojs add <feature>` writes a working recipe for the feature (see
5
+ * `src/recipes/`): the integration code, its registration, config, env
6
+ * variables and dependencies — or, for `docker`, the project's container
7
+ * files — then records the feature and installs.
5
8
  */
6
9
  import type { CLIContext } from "../cliType/cliType.type.js";
7
10
  import { type ProjectLayout } from "../resolvers/layout/projectLayout.core.js";
@@ -29,9 +32,10 @@ export declare function selectAddTargets(layout: ProjectLayout, service: string
29
32
  * them contains the framework packages, so the next install failed with
30
33
  * "not found in workspace". The protocol is now used only when the target
31
34
  * already links a framework package that way — i.e. inside the Zudojs
32
- * monorepo itself.
35
+ * monorepo itself. Otherwise the package gets a caret range on the version
36
+ * this CLI build targets.
33
37
  */
34
- export declare function versionForNewDependency(pkg: PackageJsonShape): string;
38
+ export declare function versionForNewDependency(pkg: PackageJsonShape, name?: string): string;
35
39
  export declare function runAddCommand(context: CLIContext): Promise<void>;
36
40
  export {};
37
41
  //# sourceMappingURL=add.command.d.ts.map
@@ -1,17 +1,22 @@
1
1
  /**
2
2
  * zudojs-cli — Add Command
3
3
  *
4
- * The `zudojs add` command for adding feature packages.
4
+ * `zudojs add <feature>` writes a working recipe for the feature (see
5
+ * `src/recipes/`): the integration code, its registration, config, env
6
+ * variables and dependencies — or, for `docker`, the project's container
7
+ * files — then records the feature and installs.
5
8
  */
6
- import { join, relative } from "node:path";
7
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
8
10
  import { runStreaming } from "../utils/utils.exec.js";
9
11
  import { assertSafePathSegment } from "../utils/utils.name.js";
10
12
  import { CLIValidationError, CLIGenerationError } from "../errors/index.js";
11
13
  import { ManifestManager } from "../manifest/manifestManager.core.js";
12
14
  import { getInstallCommand } from "../installers/dependency.installer.js";
13
- import { FEATURE_PACKAGES, ZUDOJS_PACKAGES_VERSION, } from "../constants/index.js";
15
+ import { FEATURE_NAMES, zudojsVersionRange } from "../constants/index.js";
14
16
  import { resolveProjectLayout, } from "../resolvers/layout/projectLayout.core.js";
17
+ import { applyAppRecipe, applyProjectRecipe, applyProjectSettings, projectRecipeContext, recipeContextFor, recordFeature, recordProjectFeature, refreshGeneratedDockerfiles, reportOutcomes, resolveFeatureRecipe, } from "../recipes/index.js";
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { findProjectRoot } from "../resolvers/project.resolver.js";
15
20
  /**
16
21
  * Picks the `package.json` files a feature is added to.
17
22
  *
@@ -48,34 +53,39 @@ export function selectAddTargets(layout, service) {
48
53
  * them contains the framework packages, so the next install failed with
49
54
  * "not found in workspace". The protocol is now used only when the target
50
55
  * already links a framework package that way — i.e. inside the Zudojs
51
- * monorepo itself.
56
+ * monorepo itself. Otherwise the package gets a caret range on the version
57
+ * this CLI build targets.
52
58
  */
53
- export function versionForNewDependency(pkg) {
59
+ export function versionForNewDependency(pkg, name = "@zudojs/core") {
54
60
  const linksFramework = Object.entries({
55
61
  ...pkg.dependencies,
56
62
  ...pkg.devDependencies,
57
- }).some(([name, version]) => name.startsWith("@zudojs/") && version.startsWith("workspace:"));
58
- return linksFramework ? "workspace:*" : ZUDOJS_PACKAGES_VERSION;
63
+ }).some(([dep, version]) => dep.startsWith("@zudojs/") && version.startsWith("workspace:"));
64
+ return linksFramework ? "workspace:*" : zudojsVersionRange(name);
65
+ }
66
+ /** A recipe with its `@zudojs/*` ranges swapped for `workspace:*` when the app links the framework. */
67
+ function forApp(recipe, pkg) {
68
+ const dependencies = Object.fromEntries(Object.entries(recipe.dependencies).map(([name, range]) => [
69
+ name,
70
+ name.startsWith("@zudojs/") ? versionForNewDependency(pkg, name) : range,
71
+ ]));
72
+ return { ...recipe, dependencies };
59
73
  }
60
74
  export async function runAddCommand(context) {
61
- const feature = context.values.feature;
75
+ const requested = context.values.feature;
62
76
  const service = context.values.service;
63
- const available = Object.keys(FEATURE_PACKAGES).join(", ");
64
- if (!feature) {
65
- throw new CLIValidationError(`Feature name is required. Available: ${available}`);
66
- }
67
- // Own keys only: `constructor` or `__proto__` resolved to Object.prototype
68
- // members and crashed with "packages.join is not a function".
69
- const packages = Object.hasOwn(FEATURE_PACKAGES, feature)
70
- ? FEATURE_PACKAGES[feature]
71
- : undefined;
72
- if (!packages) {
73
- throw new CLIValidationError(`Unknown feature: "${feature}". Available: ${available}`);
77
+ if (!requested) {
78
+ throw new CLIValidationError(`Feature name is required. Available: ${FEATURE_NAMES.join(", ")}`);
74
79
  }
80
+ const recipe = resolveFeatureRecipe(requested);
81
+ const feature = recipe.feature;
75
82
  if (service !== undefined) {
76
83
  assertSafePathSegment(service, "--service");
77
84
  }
78
- const layout = resolveProjectLayout(context.cwd);
85
+ // Walks up, so `add` works from any directory inside the project; an app
86
+ // of a workspace resolves to the workspace, whose manifest is updated.
87
+ const root = findProjectRoot(context.cwd, { workspace: true });
88
+ const layout = root === null ? null : resolveProjectLayout(root);
79
89
  if (!layout) {
80
90
  throw new CLIValidationError("No Zudojs project found in this directory. Run `zudojs create` first.");
81
91
  }
@@ -86,8 +96,6 @@ export async function runAddCommand(context) {
86
96
  if (targets.length === 0) {
87
97
  throw new CLIValidationError("No backend app with a package.json was found in this project.");
88
98
  }
89
- context.logger.info(`Adding feature: ${feature}`);
90
- context.logger.info(`Packages: ${packages.join(", ")}`);
91
99
  // The manifest is read and validated before anything is written: an
92
100
  // unusable manifest used to be discovered after every package.json had
93
101
  // already been rewritten, leaving the project half-updated.
@@ -96,45 +104,48 @@ export async function runAddCommand(context) {
96
104
  if (manifestState.status === "invalid") {
97
105
  throw new CLIGenerationError(`The project manifest at ${manifest.path} is unusable: ${manifestState.reason ?? "unknown reason"}. Fix or delete it, then re-run; nothing has been changed.`);
98
106
  }
107
+ for (const pkgPath of targets) {
108
+ if (!existsSync(pkgPath)) {
109
+ throw new CLIGenerationError(`Could not find ${pkgPath}`);
110
+ }
111
+ }
112
+ context.logger.info(`Adding feature: ${feature} — ${recipe.summary}`);
113
+ const outcomes = [];
114
+ const nextSteps = [];
99
115
  try {
100
- for (const pkgPath of targets) {
101
- if (!existsSync(pkgPath)) {
102
- throw new CLIGenerationError(`Could not find ${pkgPath}`);
116
+ if (recipe.scope === "app") {
117
+ const apps = targets.map((pkgPath) => join(pkgPath, ".."));
118
+ // Validate every app before touching any of them.
119
+ for (const dir of apps)
120
+ recipe.validate?.(recipeContextFor(layout.root, dir));
121
+ const gitignore = new Set();
122
+ for (const dir of apps) {
123
+ const recipeContext = recipeContextFor(layout.root, dir);
124
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
125
+ outcomes.push(await applyAppRecipe(forApp(recipe, pkg), recipeContext, layout.root));
126
+ for (const line of recipe.gitignore?.(recipeContext) ?? [])
127
+ gitignore.add(line);
128
+ nextSteps.push(...(recipe.nextSteps?.(recipeContext) ?? []));
103
129
  }
104
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
105
- const version = versionForNewDependency(pkg);
106
- pkg.dependencies ??= {};
107
- pkg.zudojs ??= { features: [] };
108
- const block = pkg.zudojs;
109
- const features = new Set(Array.isArray(block.features)
110
- ? block.features.filter((f) => typeof f === "string")
111
- : []);
112
- features.add(feature);
113
- block.features = [...features];
114
- for (const name of packages) {
115
- if (!(name in pkg.dependencies)) {
116
- pkg.dependencies[name] = version;
117
- }
118
- }
119
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
120
- context.logger.info(`Updated ${relative(context.cwd, pkgPath) || "package.json"}`);
121
- }
122
- // The manifest records capabilities for the whole project. A project
123
- // described by a legacy zudojs.config.ts or a package.json block has
124
- // none; that is said out loud rather than passed over in silence.
125
- if (manifestState.status === "ok") {
126
- await manifest.addCapability(feature);
130
+ const settings = await applyProjectSettings(layout.root, [...gitignore], recipe.allowBuilds ?? []);
131
+ // A Dockerfile written before Prisma was added cannot build the app.
132
+ const dockerfiles = await refreshGeneratedDockerfiles(projectRecipeContext(layout, feature, []));
133
+ outcomes.push({ written: [], kept: [], edited: [...settings, ...dockerfiles], manualSteps: [] });
127
134
  }
128
135
  else {
129
- context.logger.warn(`No .zudojs/manifest.json in ${layout.root}; "${feature}" was recorded in package.json only.`);
130
- }
131
- if (context.values["skip-install"] !== true) {
132
- const [file, ...args] = getInstallCommand(layout.packageManager);
133
- context.logger.info(`Installing dependencies with ${file}...`);
134
- await runStreaming(file, args, layout.root);
136
+ const capabilities = manifestState.manifest?.capabilities ?? [];
137
+ const projectContext = projectRecipeContext(layout, feature, capabilities);
138
+ const refreshed = await refreshGeneratedDockerfiles(projectContext);
139
+ const outcome = await applyProjectRecipe(recipe, projectContext);
140
+ outcomes.push({
141
+ ...outcome,
142
+ edited: [...outcome.edited, ...refreshed],
143
+ kept: outcome.kept.filter((path) => !refreshed.includes(path)),
144
+ });
145
+ for (const pkgPath of targets)
146
+ recordFeature(pkgPath, feature);
147
+ nextSteps.push(...(recipe.nextSteps?.(projectContext) ?? []));
135
148
  }
136
- context.logger.info(`Feature "${feature}" added successfully.`);
137
- context.logger.info(`Import from ${packages.map((p) => `"${p}"`).join(", ")} in your modules.`);
138
149
  }
139
150
  catch (error) {
140
151
  if (error instanceof CLIValidationError) {
@@ -143,5 +154,33 @@ export async function runAddCommand(context) {
143
154
  const message = error instanceof Error ? error.message : String(error);
144
155
  throw new CLIGenerationError(`Failed to add feature "${feature}": ${message}`, error);
145
156
  }
157
+ // The manifest (and a microservice root's package.json) record
158
+ // capabilities for the whole project, under the same name the apps got.
159
+ // A project described by a legacy zudojs.config.ts or a package.json
160
+ // block has no manifest; that is said out loud rather than passed over.
161
+ if (recordProjectFeature(layout.root, feature, targets)) {
162
+ outcomes.push({ written: [], kept: [], edited: ["package.json"], manualSteps: [] });
163
+ }
164
+ if (manifestState.status === "ok") {
165
+ await manifest.addCapability(feature);
166
+ }
167
+ else {
168
+ context.logger.warn(`No .zudojs/manifest.json in ${layout.root}; "${feature}" was recorded in package.json only.`);
169
+ }
170
+ reportOutcomes(context.logger, context.cwd, layout.root, outcomes);
171
+ if (context.values["skip-install"] !== true) {
172
+ const [file, ...args] = getInstallCommand(layout.packageManager);
173
+ context.logger.info(`Installing dependencies with ${file}...`);
174
+ try {
175
+ await runStreaming(file, args, layout.root);
176
+ }
177
+ catch (error) {
178
+ const message = error instanceof Error ? error.message : String(error);
179
+ throw new CLIGenerationError(`Feature "${feature}" was added, but installing dependencies failed: ${message}. Run "${file} ${args.join(" ")}" to retry.`, error);
180
+ }
181
+ }
182
+ context.logger.info(`Feature "${feature}" added successfully.`);
183
+ for (const step of new Set(nextSteps))
184
+ context.logger.info(`Next: ${step}`);
146
185
  }
147
186
  //# sourceMappingURL=add.command.js.map
@@ -21,12 +21,14 @@ import { promptFramework, promptFrontendArchitecture, } from "../prompts/fronten
21
21
  import { promptPackageManager } from "../prompts/workspace/index.js";
22
22
  import { promptCapabilities } from "../prompts/capabilities/index.js";
23
23
  import { CLIValidationError, CLIGenerationError } from "../errors/index.js";
24
+ import { InvalidArgumentsError } from "../cliError/cliError.argument.js";
24
25
  import { execCommand, runStreaming } from "../utils/utils.exec.js";
25
26
  import { writeFileTree } from "../utils/utils.fileSystem.js";
26
27
  import { normalizeName } from "../utils/utils.name.js";
27
28
  import { resolveMicroserviceServices } from "../templates/microservice/index.js";
29
+ import { capabilityPackages, resolveProjectCapabilities, } from "../templates/shared/index.js";
28
30
  import { ManifestManager } from "../manifest/manifestManager.core.js";
29
- import { CLI_VERSION, FEATURE_PACKAGES, PACKAGE_MANAGERS, ZUDOJS_PACKAGES_VERSION, } from "../constants/index.js";
31
+ import { CLI_VERSION, PACKAGE_MANAGERS, zudojsVersionRange, } from "../constants/index.js";
30
32
  import { registerCLIInterruptHandler } from "../cliApplication/cliApplication.writer.js";
31
33
  import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
32
34
  import { CapabilityResolver } from "../resolvers/capability/capabilityResolver.core.js";
@@ -253,7 +255,6 @@ export async function runCreateCommand(context) {
253
255
  }
254
256
  const isInteractive = process.stdin.isTTY;
255
257
  let answers;
256
- let selectedCapabilities;
257
258
  if (isInteractive) {
258
259
  p.intro("Zudojs");
259
260
  const name = (await promptProjectName(projectName ?? undefined));
@@ -285,7 +286,6 @@ export async function runCreateCommand(context) {
285
286
  // `--capabilities` seeds the prompt so both branches start from the
286
287
  // same selection.
287
288
  const capabilities = await promptCapabilities(capabilitiesExplicit ? requestedCapabilities : []);
288
- selectedCapabilities = capabilities;
289
289
  const flags = capabilityFlags(capabilities);
290
290
  const enableDocker = arch === "microservice";
291
291
  const confirmed = await promptConfirmation(`Create project "${name}"?`, true);
@@ -307,6 +307,7 @@ export async function runCreateCommand(context) {
307
307
  "typescript"),
308
308
  services: interactiveServices,
309
309
  ...flags,
310
+ capabilities,
310
311
  enableDocker,
311
312
  installDeps: !noInstall,
312
313
  initGit: !noGit,
@@ -315,7 +316,7 @@ export async function runCreateCommand(context) {
315
316
  else {
316
317
  // Without --capabilities this is the set the branch has always
317
318
  // hard-coded; with it, the same project the prompts would produce.
318
- selectedCapabilities = capabilitiesExplicit
319
+ const selectedCapabilities = capabilitiesExplicit
319
320
  ? requestedCapabilities
320
321
  : DEFAULT_CAPABILITIES;
321
322
  answers = {
@@ -333,6 +334,7 @@ export async function runCreateCommand(context) {
333
334
  language: languageValue,
334
335
  services,
335
336
  ...capabilityFlags(selectedCapabilities),
337
+ capabilities: selectedCapabilities,
336
338
  // Match the interactive branch, which enables Docker for microservices.
337
339
  enableDocker: architectureValue === "microservice",
338
340
  installDeps: !noInstall,
@@ -340,7 +342,9 @@ export async function runCreateCommand(context) {
340
342
  };
341
343
  }
342
344
  if (!answers.projectName) {
343
- throw new CLIValidationError("Project name is required.");
345
+ // A usage error (exit 2), like any other missing positional; `zudojs
346
+ // new` is the same command, so the example names the canonical form.
347
+ throw new InvalidArgumentsError('Missing required argument "project-name". Pass one (zudojs create my-api) or run in an interactive terminal to be prompted.');
344
348
  }
345
349
  // Backend templates only emit TypeScript. `--language javascript` used to
346
350
  // be accepted and ignored (tooling/CLI-09); in a fullstack project it
@@ -361,8 +365,11 @@ export async function runCreateCommand(context) {
361
365
  answers.projectType === "backend") {
362
366
  throw new CLIValidationError(`--frontend ${frontendValue} applies to --type frontend or --type fullstack; a backend project has no frontend. Use --type fullstack to get one.`);
363
367
  }
368
+ if (capabilitiesExplicit && answers.projectType === "frontend") {
369
+ context.logger.warn("--capabilities applies to backend apps; a frontend project records none.");
370
+ }
364
371
  assertCompatibleOptions(answers, context);
365
- await createProject(answers, context, selectedCapabilities);
372
+ await createProject(answers, context);
366
373
  }
367
374
  /**
368
375
  * Refuses option combinations the generators cannot honour, and reports the
@@ -393,8 +400,10 @@ function assertCompatibleOptions(options, context) {
393
400
  .join("\n")}`);
394
401
  }
395
402
  }
396
- async function createProject(options, context, capabilities) {
403
+ async function createProject(options, context) {
397
404
  const { projectName, packageManager } = options;
405
+ // One list for the manifest and every backend package.json.
406
+ const capabilities = resolveProjectCapabilities(options);
398
407
  const targetPath = join(context.cwd, projectName);
399
408
  if (existsSync(targetPath)) {
400
409
  throw new CLIValidationError(`Directory "${projectName}" already exists in ${context.cwd}`);
@@ -534,12 +543,16 @@ function installCommandFor(packageManager) {
534
543
  }
535
544
  }
536
545
  /**
537
- * Adds the packages behind capabilities the templates do not wire
538
- * themselves.
546
+ * Makes every backend app declare `capabilities` in `zudojs.features` and
547
+ * depend on the packages behind them — the list the manifest records.
539
548
  *
540
549
  * The capability prompt offers `events` and `security`, but only six of its
541
550
  * eight options reached ScaffoldOptions, so ticking **Security** produced
542
- * no dependency, no manifest capability and no message.
551
+ * no dependency, no manifest capability and no message. Later this function
552
+ * returned early when no capability had an entry in FEATURE_PACKAGES, so
553
+ * `--capabilities events,cqrs` put `events` in the manifest but not in
554
+ * package.json; and it mapped packages differently from `zudojs doctor`, so
555
+ * a microservice gateway declared `cqrs` without `@zudojs/cqrs`.
543
556
  */
544
557
  async function applyCapabilityPackages(targetPath, capabilities, context) {
545
558
  if (capabilities.length === 0)
@@ -547,14 +560,7 @@ async function applyCapabilityPackages(targetPath, capabilities, context) {
547
560
  const layout = resolveProjectLayout(targetPath);
548
561
  if (!layout || layout.backendDirs.length === 0)
549
562
  return;
550
- const required = new Set();
551
- for (const capability of capabilities) {
552
- for (const name of FEATURE_PACKAGES[capability] ?? []) {
553
- required.add(name);
554
- }
555
- }
556
- if (required.size === 0)
557
- return;
563
+ const required = capabilityPackages(capabilities);
558
564
  const added = new Set();
559
565
  for (const pkgPath of selectAddTargets(layout, undefined)) {
560
566
  if (!existsSync(pkgPath))
@@ -564,7 +570,7 @@ async function applyCapabilityPackages(targetPath, capabilities, context) {
564
570
  let changed = false;
565
571
  for (const name of required) {
566
572
  if (!(name in pkg.dependencies)) {
567
- pkg.dependencies[name] = ZUDOJS_PACKAGES_VERSION;
573
+ pkg.dependencies[name] = zudojsVersionRange(name);
568
574
  added.add(name);
569
575
  changed = true;
570
576
  }
@@ -614,26 +620,7 @@ async function collectProjectProblems(targetPath, options, installed) {
614
620
  * Writes the machine-managed .zudojs/manifest.json so follow-up commands
615
621
  * (`zudojs dev`, `generate`, `add`) recognize the project immediately.
616
622
  */
617
- async function writeProjectManifest(options, targetPath, selected = []) {
618
- const capabilities = [];
619
- if (options.enableCQRS)
620
- capabilities.push("cqrs");
621
- if (options.enableMessaging)
622
- capabilities.push("messaging");
623
- if (options.enableObservability)
624
- capabilities.push("observability");
625
- if (options.enableOpenAPI)
626
- capabilities.push("openapi");
627
- if (options.enableDatabase)
628
- capabilities.push("database");
629
- if (options.enableQueue)
630
- capabilities.push("queue");
631
- // Capabilities with no ScaffoldOptions flag of their own (events,
632
- // security) were selected and then dropped on the floor.
633
- for (const capability of selected) {
634
- if (!capabilities.includes(capability))
635
- capabilities.push(capability);
636
- }
623
+ async function writeProjectManifest(options, targetPath, capabilities) {
637
624
  // Record the services actually generated, not the raw request: the
638
625
  // microservice template drops reserved names (gateway) and duplicates, and
639
626
  // the modular-monolith template normalizes each name into a directory
@@ -670,7 +657,7 @@ async function writeProjectManifest(options, targetPath, selected = []) {
670
657
  : {}),
671
658
  ...(options.database ? { database: { provider: options.database } } : {}),
672
659
  workspace: { packageManager: options.packageManager },
673
- capabilities,
660
+ capabilities: [...capabilities],
674
661
  ...(services ? { services: [...services] } : {}),
675
662
  });
676
663
  }
@@ -695,7 +682,7 @@ async function generateFullstackProject(options, projectPath, rollback) {
695
682
  workspace: {
696
683
  packageManager: options.packageManager,
697
684
  },
698
- features: options.services,
685
+ features: resolveProjectCapabilities(options),
699
686
  },
700
687
  projectPath,
701
688
  installDeps: options.installDeps,
@@ -730,7 +717,7 @@ async function generateFullstackProject(options, projectPath, rollback) {
730
717
  workspace: {
731
718
  packageManager: options.packageManager,
732
719
  },
733
- features: options.services,
720
+ features: resolveProjectCapabilities(options),
734
721
  },
735
722
  projectPath,
736
723
  backendPort: 3000,
@@ -16,7 +16,8 @@ export interface DoctorCheck {
16
16
  readonly severity: "error" | "warning";
17
17
  }
18
18
  /**
19
- * Runs every diagnostic for the project at `cwd`.
19
+ * Runs every diagnostic for the project containing `cwd` (any directory
20
+ * inside it: the root is found by walking up, as `generate` does).
20
21
  *
21
22
  * Exported so the checks can be run against a scaffolded directory in tests
22
23
  * without going through the logger.