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,128 @@
1
+ /**
2
+ * zudojs-cli — A resource's CRUD routes (with OpenAPI metadata) and test.
3
+ */
4
+ /** `routes/<slug>.routes.ts`: `register<Pascal>Routes(router, controller)`. */
5
+ export function renderResourceRoutes(n) {
6
+ const schemas = `Create${n.entity}Schema, ${n.entity}ParamsSchema, ${n.entity}Schema, Update${n.entity}Schema`;
7
+ return `import type { HttpRouter } from "@zudojs/http";
8
+ import { schema } from "@zudojs/schema";
9
+
10
+ import type { ${n.pascal}Controller } from "../controllers/${n.slug}.controller.js";
11
+ import { ${schemas} } from "../dtos/${n.slug}.dto.js";
12
+
13
+ const BASE_PATH = "${n.routePath}";
14
+ const TAGS = ["${n.slug}"];
15
+ const invalid = { description: "The request failed validation" };
16
+ const missing = { description: "No ${n.label} has this id" };
17
+
18
+ /** Registers the ${n.slug} CRUD routes. */
19
+ export function register${n.pascal}Routes(
20
+ router: HttpRouter,
21
+ controller: ${n.pascal}Controller,
22
+ ): void {
23
+ router.get(BASE_PATH, controller.list, {
24
+ openapi: {
25
+ summary: "List ${n.slug}",
26
+ tags: TAGS,
27
+ responses: { "200": { description: "Every ${n.label}", schema: schema.array(${n.entity}Schema) } },
28
+ },
29
+ });
30
+
31
+ router.get(\`\${BASE_PATH}/:id\`, controller.get, {
32
+ openapi: {
33
+ summary: "Get a ${n.label}",
34
+ tags: TAGS,
35
+ params: ${n.entity}ParamsSchema,
36
+ responses: { "200": { description: "The ${n.label}", schema: ${n.entity}Schema }, "400": invalid, "404": missing },
37
+ },
38
+ });
39
+
40
+ router.post(BASE_PATH, controller.create, {
41
+ openapi: {
42
+ summary: "Create a ${n.label}",
43
+ tags: TAGS,
44
+ body: Create${n.entity}Schema,
45
+ responses: { "201": { description: "Created", schema: ${n.entity}Schema }, "400": invalid },
46
+ },
47
+ });
48
+
49
+ router.patch(\`\${BASE_PATH}/:id\`, controller.update, {
50
+ openapi: {
51
+ summary: "Update a ${n.label}",
52
+ tags: TAGS,
53
+ params: ${n.entity}ParamsSchema,
54
+ body: Update${n.entity}Schema,
55
+ responses: { "200": { description: "Updated", schema: ${n.entity}Schema }, "400": invalid, "404": missing },
56
+ },
57
+ });
58
+
59
+ router.delete(\`\${BASE_PATH}/:id\`, controller.remove, {
60
+ openapi: {
61
+ summary: "Delete a ${n.label}",
62
+ tags: TAGS,
63
+ params: ${n.entity}ParamsSchema,
64
+ responses: { "204": { description: "Deleted" }, "400": invalid, "404": missing },
65
+ },
66
+ });
67
+ }
68
+ `;
69
+ }
70
+ /**
71
+ * `tests/<slug>.test.ts`: drives the routes over real HTTP with
72
+ * `createHttpTestClient`, backed by the in-memory repository.
73
+ * `baseFromTests` is the import prefix from the tests folder to the layers.
74
+ */
75
+ export function renderResourceTest(n, baseFromTests) {
76
+ const b = baseFromTests;
77
+ return `import { afterAll, describe, it } from "vitest";
78
+ import { createRouter } from "@zudojs/http";
79
+ import { createHttpTestClient } from "@zudojs/testing";
80
+
81
+ import { ${n.pascal}Controller } from "${b}/controllers/${n.slug}.controller.js";
82
+ import { InMemory${n.pascal}Repository } from "${b}/repositories/${n.slug}.repository.js";
83
+ import { register${n.pascal}Routes } from "${b}/routes/${n.slug}.routes.js";
84
+ import { ${n.pascal}Service } from "${b}/services/${n.slug}.service.js";
85
+
86
+ const router = createRouter();
87
+ register${n.pascal}Routes(
88
+ router,
89
+ new ${n.pascal}Controller(new ${n.pascal}Service(new InMemory${n.pascal}Repository())),
90
+ );
91
+ const client = createHttpTestClient(router);
92
+ afterAll(() => client.close());
93
+
94
+ describe("${n.routePath}", () => {
95
+ it("creates, reads, updates and deletes a ${n.label}", async () => {
96
+ const created = await client
97
+ .post("${n.routePath}")
98
+ .send({ name: "Ada" })
99
+ .expect(201)
100
+ .expectJson({ name: "Ada" });
101
+ const { id } = created.json<{ id: string }>();
102
+
103
+ await client.get(\`${n.routePath}/\${id}\`).expect(200).expectJson({ id, name: "Ada" });
104
+ await client.get("${n.routePath}").expect(200);
105
+ await client
106
+ .patch(\`${n.routePath}/\${id}\`)
107
+ .send({ name: "Grace" })
108
+ .expect(200)
109
+ .expectJson({ name: "Grace" });
110
+ await client.delete(\`${n.routePath}/\${id}\`).expect(204);
111
+ await client.get(\`${n.routePath}/\${id}\`).expect(404);
112
+ });
113
+
114
+ it("rejects an invalid body with 400", async () => {
115
+ await client
116
+ .post("${n.routePath}")
117
+ .send({ name: "" })
118
+ .expect(400)
119
+ .expectJson({ error: "Validation failed" });
120
+ });
121
+
122
+ it("rejects an id that is not a UUID with 400", async () => {
123
+ await client.get("${n.routePath}/not-a-uuid").expect(400);
124
+ });
125
+ });
126
+ `;
127
+ }
128
+ //# sourceMappingURL=resourceRoutes.template.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * zudojs-cli — A resource's service and controller.
3
+ */
4
+ import type { ResourceNames } from "../resource.names.js";
5
+ /** `services/<slug>.service.ts`: CRUD with NotFoundError for missing ids. */
6
+ export declare function renderResourceService(n: ResourceNames): string;
7
+ /**
8
+ * `controllers/<slug>.controller.ts`: validates input with the DTO schemas
9
+ * and maps service results to responses. Handlers are arrow properties so
10
+ * they can be passed to the router unbound.
11
+ */
12
+ export declare function renderResourceController(n: ResourceNames, appSrcFromControllers: string): string;
13
+ //# sourceMappingURL=resourceService.template.d.ts.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * zudojs-cli — A resource's service and controller.
3
+ */
4
+ /** `services/<slug>.service.ts`: CRUD with NotFoundError for missing ids. */
5
+ export function renderResourceService(n) {
6
+ const types = `Create${n.entity}Input, ${n.entity}, Update${n.entity}Input`;
7
+ return `import { NotFoundError } from "@zudojs/errors";
8
+
9
+ import type { ${types} } from "../dtos/${n.slug}.dto.js";
10
+ import type { ${n.pascal}Repository } from "../repositories/${n.slug}.repository.js";
11
+
12
+ /** ${n.entity} use cases. Throws NotFoundError (404) for an unknown id. */
13
+ export class ${n.pascal}Service {
14
+ public constructor(private readonly repository: ${n.pascal}Repository) {}
15
+
16
+ public list(): Promise<readonly ${n.entity}[]> {
17
+ return this.repository.findAll();
18
+ }
19
+
20
+ public async get(id: string): Promise<${n.entity}> {
21
+ const record = await this.repository.findById(id);
22
+ if (record === undefined) throw notFound(id);
23
+ return record;
24
+ }
25
+
26
+ public create(input: Create${n.entity}Input): Promise<${n.entity}> {
27
+ return this.repository.create(input);
28
+ }
29
+
30
+ public async update(id: string, input: Update${n.entity}Input): Promise<${n.entity}> {
31
+ const record = await this.repository.update(id, input);
32
+ if (record === undefined) throw notFound(id);
33
+ return record;
34
+ }
35
+
36
+ public async remove(id: string): Promise<void> {
37
+ if (!(await this.repository.delete(id))) throw notFound(id);
38
+ }
39
+ }
40
+
41
+ function notFound(id: string): NotFoundError {
42
+ return new NotFoundError(\`${n.entity} "\${id}" was not found.\`);
43
+ }
44
+ `;
45
+ }
46
+ /**
47
+ * `controllers/<slug>.controller.ts`: validates input with the DTO schemas
48
+ * and maps service results to responses. Handlers are arrow properties so
49
+ * they can be passed to the router unbound.
50
+ */
51
+ export function renderResourceController(n, appSrcFromControllers) {
52
+ const schemas = `Create${n.entity}Schema, ${n.entity}ParamsSchema, Update${n.entity}Schema`;
53
+ return `import type { HttpResponseContext, HttpRouterContext } from "@zudojs/http";
54
+
55
+ import { ${schemas} } from "../dtos/${n.slug}.dto.js";
56
+ import type { ${n.pascal}Service } from "../services/${n.slug}.service.js";
57
+ import { empty, json, readJsonBody, validationFailed } from "${appSrcFromControllers}/utils/http.js";
58
+
59
+ /** HTTP handlers for ${n.routePath}. */
60
+ export class ${n.pascal}Controller {
61
+ public constructor(private readonly service: ${n.pascal}Service) {}
62
+
63
+ public readonly list = async (): Promise<HttpResponseContext> =>
64
+ json(200, await this.service.list());
65
+
66
+ public readonly get = async (ctx: HttpRouterContext): Promise<HttpResponseContext> => {
67
+ const params = ${n.entity}ParamsSchema.safeParse(ctx.params);
68
+ if (!params.success) return validationFailed(params.issues);
69
+ return json(200, await this.service.get(params.data.id));
70
+ };
71
+
72
+ public readonly create = async (ctx: HttpRouterContext): Promise<HttpResponseContext> => {
73
+ const body = Create${n.entity}Schema.safeParse(readJsonBody(ctx));
74
+ if (!body.success) return validationFailed(body.issues);
75
+ return json(201, await this.service.create(body.data));
76
+ };
77
+
78
+ public readonly update = async (ctx: HttpRouterContext): Promise<HttpResponseContext> => {
79
+ const params = ${n.entity}ParamsSchema.safeParse(ctx.params);
80
+ if (!params.success) return validationFailed(params.issues);
81
+ const body = Update${n.entity}Schema.safeParse(readJsonBody(ctx));
82
+ if (!body.success) return validationFailed(body.issues);
83
+ return json(200, await this.service.update(params.data.id, body.data));
84
+ };
85
+
86
+ public readonly remove = async (ctx: HttpRouterContext): Promise<HttpResponseContext> => {
87
+ const params = ${n.entity}ParamsSchema.safeParse(ctx.params);
88
+ if (!params.success) return validationFailed(params.issues);
89
+ await this.service.remove(params.data.id);
90
+ return empty(204);
91
+ };
92
+ }
93
+ `;
94
+ }
95
+ //# sourceMappingURL=resourceService.template.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * zudojs-cli — The files and registrations of one resource.
3
+ *
4
+ * Shared by `zudojs create` (the example resource every new app ships
5
+ * with) and `zudojs generate resource|route|controller|repository|dto`, so
6
+ * a generated resource and the scaffolded one are identical.
7
+ */
8
+ import type { ResourceNames } from "./resource.names.js";
9
+ import { type ResourceLayout } from "./resource.type.js";
10
+ /** A resource layer; each is one file (the repository may add a second). */
11
+ export type ResourceLayer = "dto" | "repository" | "service" | "controller" | "routes" | "test";
12
+ /** Layers in dependency order: each needs the ones before it. */
13
+ export declare const RESOURCE_LAYERS: readonly ResourceLayer[];
14
+ /** Path of each layer's file(s), relative to the project root. */
15
+ export declare function resourceLayerPaths(n: ResourceNames, layout: ResourceLayout, layer: ResourceLayer): readonly string[];
16
+ /** Renders `layers` of a resource as `{ path: content }`. */
17
+ export declare function renderResourceLayers(n: ResourceNames, layout: ResourceLayout, layers: readonly ResourceLayer[]): Record<string, string>;
18
+ /** The lines that register a resource in routes and the container. */
19
+ export interface ResourceWiring {
20
+ /** routes/index.ts next to the resource's routes file. */
21
+ readonly routesIndex: string;
22
+ readonly routeImport: string;
23
+ readonly routeEntry: string;
24
+ /** The app's src/container.ts. */
25
+ readonly container: string;
26
+ readonly containerImports: readonly string[];
27
+ readonly containerEntry: string;
28
+ /** The key the controller is stored under in the container. */
29
+ readonly containerKey: string;
30
+ }
31
+ /** Describes how a resource is registered (see {@link ResourceWiring}). */
32
+ export declare function resourceWiring(n: ResourceNames, layout: ResourceLayout): ResourceWiring;
33
+ //# sourceMappingURL=resource.files.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * zudojs-cli — The files and registrations of one resource.
3
+ *
4
+ * Shared by `zudojs create` (the example resource every new app ships
5
+ * with) and `zudojs generate resource|route|controller|repository|dto`, so
6
+ * a generated resource and the scaffolded one are identical.
7
+ */
8
+ import { posix } from "node:path";
9
+ import { renderPrismaRepository, renderResourceController, renderResourceDto, renderResourceRepository, renderResourceRoutes, renderResourceService, renderResourceTest, } from "./layers/index.js";
10
+ import { joinPath, toAppSrc } from "./resource.type.js";
11
+ /** Layers in dependency order: each needs the ones before it. */
12
+ export const RESOURCE_LAYERS = [
13
+ "dto",
14
+ "repository",
15
+ "service",
16
+ "controller",
17
+ "routes",
18
+ "test",
19
+ ];
20
+ /** Path of each layer's file(s), relative to the project root. */
21
+ export function resourceLayerPaths(n, layout, layer) {
22
+ switch (layer) {
23
+ case "dto":
24
+ return [joinPath(layout.base, "dtos", `${n.slug}.dto.ts`)];
25
+ case "repository":
26
+ return [
27
+ joinPath(layout.base, "repositories", `${n.slug}.repository.ts`),
28
+ ...(layout.prisma
29
+ ? [joinPath(layout.base, "repositories", `${n.slug}.prisma.repository.ts`)]
30
+ : []),
31
+ ];
32
+ case "service":
33
+ return [joinPath(layout.base, "services", `${n.slug}.service.ts`)];
34
+ case "controller":
35
+ return [joinPath(layout.base, "controllers", `${n.slug}.controller.ts`)];
36
+ case "routes":
37
+ return [joinPath(layout.base, "routes", `${n.slug}.routes.ts`)];
38
+ case "test":
39
+ return [joinPath(layout.appRoot, "tests", `${n.slug}.test.ts`)];
40
+ }
41
+ }
42
+ /** Renders `layers` of a resource as `{ path: content }`. */
43
+ export function renderResourceLayers(n, layout, layers) {
44
+ const files = {};
45
+ for (const layer of layers) {
46
+ const [path, second] = resourceLayerPaths(n, layout, layer);
47
+ if (path === undefined)
48
+ continue;
49
+ switch (layer) {
50
+ case "dto":
51
+ files[path] = renderResourceDto(n);
52
+ break;
53
+ case "repository":
54
+ files[path] = renderResourceRepository(n);
55
+ if (second !== undefined) {
56
+ files[second] = renderPrismaRepository(n, toAppSrc(layout, "repositories"));
57
+ }
58
+ break;
59
+ case "service":
60
+ files[path] = renderResourceService(n);
61
+ break;
62
+ case "controller":
63
+ files[path] = renderResourceController(n, toAppSrc(layout, "controllers"));
64
+ break;
65
+ case "routes":
66
+ files[path] = renderResourceRoutes(n);
67
+ break;
68
+ case "test": {
69
+ const tests = joinPath(layout.appRoot, "tests");
70
+ files[path] = renderResourceTest(n, posix.relative(tests, layout.base));
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ return files;
76
+ }
77
+ /** Describes how a resource is registered (see {@link ResourceWiring}). */
78
+ export function resourceWiring(n, layout) {
79
+ const fromSrc = posix.relative(layout.appSrc, layout.base);
80
+ const prefix = fromSrc === "" ? "." : `./${fromSrc}`;
81
+ const repository = layout.prisma
82
+ ? { name: `Prisma${n.pascal}Repository`, file: `${n.slug}.prisma.repository.js` }
83
+ : { name: `InMemory${n.pascal}Repository`, file: `${n.slug}.repository.js` };
84
+ const key = `${n.camel}Controller`;
85
+ return {
86
+ routesIndex: joinPath(layout.base, "routes", "index.ts"),
87
+ routeImport: `import { register${n.pascal}Routes } from "./${n.slug}.routes.js";`,
88
+ routeEntry: `register${n.pascal}Routes(router, deps.${key});`,
89
+ container: joinPath(layout.appSrc, "container.ts"),
90
+ containerImports: [
91
+ `import { ${n.pascal}Controller } from "${prefix}/controllers/${n.slug}.controller.js";`,
92
+ `import { ${repository.name} } from "${prefix}/repositories/${repository.file}";`,
93
+ `import { ${n.pascal}Service } from "${prefix}/services/${n.slug}.service.js";`,
94
+ ],
95
+ containerEntry: `${key}: new ${n.pascal}Controller(new ${n.pascal}Service(new ${repository.name}())),`,
96
+ containerKey: key,
97
+ };
98
+ }
99
+ //# sourceMappingURL=resource.files.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * zudojs-cli — Names derived from a resource name.
3
+ *
4
+ * `users` gives the file slug `users`, the classes `UsersController`,
5
+ * `UsersService` and `UsersRepository`, the record type `User`, the Prisma
6
+ * model `User` (delegate `user`, table `users`) and the path
7
+ * `/api/v1/users`.
8
+ */
9
+ /** Every identifier and path a resource's files use. */
10
+ export interface ResourceNames {
11
+ /** kebab-case slug: file names, table name, OpenAPI tag. */
12
+ readonly slug: string;
13
+ /** PascalCase of the name as given: class prefix (`UsersService`). */
14
+ readonly pascal: string;
15
+ /** camelCase of the name as given: container key prefix. */
16
+ readonly camel: string;
17
+ /** PascalCase singular: record type and schema prefix (`User`). */
18
+ readonly entity: string;
19
+ /** camelCase singular: Prisma delegate (`prisma.user`). */
20
+ readonly entityCamel: string;
21
+ /** Human label for messages and summaries (`user`). */
22
+ readonly label: string;
23
+ /** Collection path (`/api/v1/users`). */
24
+ readonly routePath: string;
25
+ }
26
+ /** Naive English singular, enough for resource names (`users` → `user`). */
27
+ export declare function singularize(slug: string): string;
28
+ /**
29
+ * Derives {@link ResourceNames} from a raw name.
30
+ *
31
+ * A name containing a path separator or `..` is refused outright rather
32
+ * than normalized: `generate resource ../../x` used to be accepted and
33
+ * silently became `x`, which hides a typo or an attack in a script.
34
+ *
35
+ * @throws {CLIValidationError} For unusable or reserved names.
36
+ */
37
+ export declare function resourceNames(raw: string): ResourceNames;
38
+ //# sourceMappingURL=resource.names.d.ts.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * zudojs-cli — Names derived from a resource name.
3
+ *
4
+ * `users` gives the file slug `users`, the classes `UsersController`,
5
+ * `UsersService` and `UsersRepository`, the record type `User`, the Prisma
6
+ * model `User` (delegate `user`, table `users`) and the path
7
+ * `/api/v1/users`.
8
+ */
9
+ import { CLIValidationError } from "../../errors/index.js";
10
+ import { assertGeneratableName, toCamelCase, toPascalCase, } from "../../utils/utils.name.js";
11
+ /**
12
+ * Names that would shadow a global the generated files rely on (a `Record`
13
+ * entity type hides TypeScript's `Record<K, V>`, `Promise` breaks every
14
+ * async signature).
15
+ */
16
+ const RESERVED_ENTITY_NAMES = new Set([
17
+ "Array", "Boolean", "Date", "Error", "Function", "Headers", "Map", "Number",
18
+ "Object", "Promise", "Record", "Request", "Response", "Set", "String",
19
+ "Symbol", "URL",
20
+ ]);
21
+ /** Naive English singular, enough for resource names (`users` → `user`). */
22
+ export function singularize(slug) {
23
+ if (/ies$/.test(slug) && slug.length > 4)
24
+ return `${slug.slice(0, -3)}y`;
25
+ if (/(ses|xes|zes|ches|shes)$/.test(slug))
26
+ return slug.slice(0, -2);
27
+ if (/[^s]s$/.test(slug))
28
+ return slug.slice(0, -1);
29
+ return slug;
30
+ }
31
+ /**
32
+ * Derives {@link ResourceNames} from a raw name.
33
+ *
34
+ * A name containing a path separator or `..` is refused outright rather
35
+ * than normalized: `generate resource ../../x` used to be accepted and
36
+ * silently became `x`, which hides a typo or an attack in a script.
37
+ *
38
+ * @throws {CLIValidationError} For unusable or reserved names.
39
+ */
40
+ export function resourceNames(raw) {
41
+ if (/[\\/]|\.\./.test(raw)) {
42
+ throw new CLIValidationError(`Invalid resource name: "${raw}". Use a plain name such as "users"; paths are not allowed.`);
43
+ }
44
+ const slug = assertGeneratableName(raw, "resource name");
45
+ const singular = singularize(slug);
46
+ const entity = toPascalCase(singular);
47
+ if (RESERVED_ENTITY_NAMES.has(entity)) {
48
+ throw new CLIValidationError(`Invalid resource name: "${raw}". "${entity}" would shadow a JavaScript global; choose another name (for example "${slug}-items").`);
49
+ }
50
+ return {
51
+ slug,
52
+ pascal: toPascalCase(slug),
53
+ camel: toCamelCase(slug),
54
+ entity,
55
+ entityCamel: toCamelCase(singular),
56
+ label: singular.replace(/-/g, " "),
57
+ routePath: `/api/v1/${slug}`,
58
+ };
59
+ }
60
+ //# sourceMappingURL=resource.names.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * zudojs-cli — Where a resource's files go.
3
+ */
4
+ /** Placement of a resource inside a generated project (POSIX, relative). */
5
+ export interface ResourceLayout {
6
+ /** Directory the layer folders live in: `src`, `src/modules/billing`. */
7
+ readonly base: string;
8
+ /** The app's source root: `src`, `apps/gateway/src`. */
9
+ readonly appSrc: string;
10
+ /** The app's root (holds package.json): `""`, `apps/gateway`. */
11
+ readonly appRoot: string;
12
+ /** Whether the app has Prisma set up (prisma/schema.prisma exists). */
13
+ readonly prisma: boolean;
14
+ }
15
+ /** Import prefix from a layer folder of `layout` to the app's src root. */
16
+ export declare function toAppSrc(layout: ResourceLayout, layerDir: string): string;
17
+ /** Joins path segments, dropping empty ones. */
18
+ export declare function joinPath(...parts: readonly string[]): string;
19
+ //# sourceMappingURL=resource.type.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * zudojs-cli — Where a resource's files go.
3
+ */
4
+ import { posix } from "node:path";
5
+ /** Import prefix from a layer folder of `layout` to the app's src root. */
6
+ export function toAppSrc(layout, layerDir) {
7
+ const from = posix.join(layout.base, layerDir);
8
+ const rel = posix.relative(from, layout.appSrc);
9
+ return rel === "" ? "." : rel;
10
+ }
11
+ /** Joins path segments, dropping empty ones. */
12
+ export function joinPath(...parts) {
13
+ return posix.join(...parts.filter((part) => part.length > 0));
14
+ }
15
+ //# sourceMappingURL=resource.type.js.map
@@ -41,39 +41,56 @@ export function renderAppFile(options) {
41
41
  const imports = [...options.modules]
42
42
  .map((m) => `import { ${m.className} } from ${literal(m.importPath)};`)
43
43
  .join("\n");
44
- // An empty array literal would make the loop variable `never`, so the
45
- // registration loop is only emitted when there is at least one module.
46
- const registration = options.modules.length > 0
47
- ? ` for (const module of [
48
- ${options.modules.map((m) => ` new ${m.className}(),`).join("\n")}
49
- ]) {
44
+ // The list always exists, even when empty, so `zudojs generate module`
45
+ // can register into it; the cast keeps an empty literal from being
46
+ // typed `never[]`.
47
+ const registration = ` for (const module of [
48
+ ${options.modules.map((m) => ` new ${m.className}(),`).join("\n")}${options.modules.length > 0 ? "\n" : ""} ] as Module[]) {
50
49
  modules.set(module.id, module);
51
50
  }
52
- `
53
- : "";
51
+ `;
54
52
  const metadata = options.port === undefined
55
53
  ? ""
56
54
  : `\n metadata: { port: ${options.port} },`;
57
- return `import { resolveEnvironment } from "@zudojs/constants";
55
+ return `import type { Server } from "node:http";
56
+
57
+ import { resolveEnvironment } from "@zudojs/constants";
58
58
  import { createContainer } from "@zudojs/container";
59
59
  import type { Module } from "@zudojs/core";
60
60
  import { createEventBus } from "@zudojs/events";
61
61
  import { createLogger } from "@zudojs/logger";
62
62
  import { createRuntime, type Runtime } from "@zudojs/runtime";
63
+
64
+ import type { AppConfig } from "./configs/index.js";
65
+ import { IntegrationsModule, integrations } from "./integrations/index.js";
63
66
  ${imports}
64
67
 
68
+ /** What {@link createApp} needs. */
69
+ export interface AppOptions {
70
+ readonly config: AppConfig;
71
+ /** The Node HTTP server, for integrations that attach to it. */
72
+ readonly httpServer?: Server;
73
+ }
74
+
65
75
  /**
66
76
  * Assembles the application runtime.
67
77
  *
68
78
  * \`createRuntime\` takes two arguments: the dependencies the runtime and its
69
- * modules share, and the options describing this application.
79
+ * modules share, and the options describing this application. Integrations
80
+ * (\`src/integrations\`) are registered first so they start before, and stop
81
+ * after, every other module.
70
82
  */
71
- export function createApp(): Runtime {
83
+ export function createApp(options: AppOptions): Runtime {
72
84
  const logger = createLogger({ name: ${literal(options.applicationName)} });
73
85
  const container = createContainer();
74
86
  const eventBus = createEventBus();
75
87
 
76
88
  const modules = new Map<string, Module>();
89
+ const integrationsModule = new IntegrationsModule(integrations, {
90
+ config: options.config,
91
+ ...(options.httpServer === undefined ? {} : { httpServer: options.httpServer }),
92
+ });
93
+ modules.set(integrationsModule.id, integrationsModule);
77
94
  ${registration}
78
95
  const runtime = createRuntime(
79
96
  { modules, logger, container, eventBus },
@@ -8,13 +8,22 @@
8
8
  * every project the CLI created, and `zudojs add` was the only thing that
9
9
  * could ever put a value there.
10
10
  *
11
- * The list produced here is the same one `zudojs create` records as
12
- * `capabilities` in `.zudojs/manifest.json`, and {@link capabilityPackages}
13
- * gives the packages that back it, so a declared feature is always installed.
11
+ * {@link resolveProjectCapabilities} is the single source of truth for what
12
+ * a new project was created with: `zudojs create` records exactly this list
13
+ * as `capabilities` in `.zudojs/manifest.json` and as `zudojs.features` in
14
+ * every backend app's package.json, and {@link capabilityPackages} gives the
15
+ * packages that back it, so a declared feature is always installed.
14
16
  */
15
17
  import type { ScaffoldOptions } from "../../types/index.js";
16
18
  /**
17
- * The capability ids a scaffolded project was created with.
19
+ * The capability ids a scaffolded project was created with: the `enable*`
20
+ * flags in manifest order, then the selected capabilities that have no flag
21
+ * (`events`, `security`) in the order they were chosen.
22
+ *
23
+ * Those two used to reach the manifest but not package.json, so
24
+ * `--capabilities events,cqrs` recorded `["cqrs","events"]` in one and
25
+ * `["cqrs"]` in the other. A frontend-only project has no backend app to
26
+ * carry a capability and records none.
18
27
  */
19
28
  export declare function resolveProjectCapabilities(options: ScaffoldOptions): readonly string[];
20
29
  /**
@@ -8,9 +8,11 @@
8
8
  * every project the CLI created, and `zudojs add` was the only thing that
9
9
  * could ever put a value there.
10
10
  *
11
- * The list produced here is the same one `zudojs create` records as
12
- * `capabilities` in `.zudojs/manifest.json`, and {@link capabilityPackages}
13
- * gives the packages that back it, so a declared feature is always installed.
11
+ * {@link resolveProjectCapabilities} is the single source of truth for what
12
+ * a new project was created with: `zudojs create` records exactly this list
13
+ * as `capabilities` in `.zudojs/manifest.json` and as `zudojs.features` in
14
+ * every backend app's package.json, and {@link capabilityPackages} gives the
15
+ * packages that back it, so a declared feature is always installed.
14
16
  */
15
17
  import { FEATURE_PACKAGES } from "../../constants/index.js";
16
18
  /** Capability flags, in the order the manifest records them. */
@@ -23,10 +25,24 @@ const CAPABILITY_FLAGS = [
23
25
  ["queue", "enableQueue"],
24
26
  ];
25
27
  /**
26
- * The capability ids a scaffolded project was created with.
28
+ * The capability ids a scaffolded project was created with: the `enable*`
29
+ * flags in manifest order, then the selected capabilities that have no flag
30
+ * (`events`, `security`) in the order they were chosen.
31
+ *
32
+ * Those two used to reach the manifest but not package.json, so
33
+ * `--capabilities events,cqrs` recorded `["cqrs","events"]` in one and
34
+ * `["cqrs"]` in the other. A frontend-only project has no backend app to
35
+ * carry a capability and records none.
27
36
  */
28
37
  export function resolveProjectCapabilities(options) {
29
- return CAPABILITY_FLAGS.filter(([, flag]) => options[flag] === true).map(([capability]) => capability);
38
+ if (options.projectType === "frontend")
39
+ return [];
40
+ const capabilities = CAPABILITY_FLAGS.filter(([, flag]) => options[flag] === true).map(([capability]) => capability);
41
+ for (const capability of options.capabilities ?? []) {
42
+ if (!capabilities.includes(capability))
43
+ capabilities.push(capability);
44
+ }
45
+ return capabilities;
30
46
  }
31
47
  /**
32
48
  * The `@zudojs/*` packages that back `capabilities`, using the same mapping