zudojs-cli 1.2.1 → 2.0.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 (109) hide show
  1. package/README.md +43 -1
  2. package/dist/src/adapters/frontend/flutter.adapter.d.ts +9 -0
  3. package/dist/src/adapters/frontend/flutter.adapter.js +16 -1
  4. package/dist/src/bin/zudojs.js +16 -5
  5. package/dist/src/cliApplication/cliApplication.builtins.d.ts +4 -0
  6. package/dist/src/cliApplication/cliApplication.builtins.js +28 -9
  7. package/dist/src/cliApplication/cliApplication.core.d.ts +8 -0
  8. package/dist/src/cliApplication/cliApplication.core.js +45 -5
  9. package/dist/src/cliApplication/cliApplication.help.d.ts +17 -0
  10. package/dist/src/cliApplication/cliApplication.help.js +94 -0
  11. package/dist/src/cliApplication/cliApplication.logger.js +4 -4
  12. package/dist/src/cliCommand/cliCommand.validator.js +22 -0
  13. package/dist/src/cliConstant/cliConstant.value.d.ts +18 -12
  14. package/dist/src/cliConstant/cliConstant.value.js +25 -19
  15. package/dist/src/cliError/cliError.argument.js +2 -2
  16. package/dist/src/cliError/cliError.base.js +28 -5
  17. package/dist/src/cliError/cliError.command.js +5 -2
  18. package/dist/src/cliError/cliError.execution.js +3 -3
  19. package/dist/src/cliParser/cliParser.core.js +11 -5
  20. package/dist/src/cliParser/cliParser.helper.d.ts +11 -0
  21. package/dist/src/cliParser/cliParser.helper.js +19 -0
  22. package/dist/src/cliParser/cliParser.longOption.js +4 -4
  23. package/dist/src/cliParser/cliParser.shortOption.js +7 -4
  24. package/dist/src/cliVersion/cliVersion.update.js +5 -3
  25. package/dist/src/commands/add.command.js +17 -2
  26. package/dist/src/commands/commandError.helper.d.ts +15 -0
  27. package/dist/src/commands/commandError.helper.js +28 -0
  28. package/dist/src/commands/create.command.js +284 -46
  29. package/dist/src/commands/doctor.command.d.ts +8 -0
  30. package/dist/src/commands/doctor.command.js +38 -5
  31. package/dist/src/commands/generate.command.js +71 -32
  32. package/dist/src/constants/index.d.ts +40 -68
  33. package/dist/src/constants/index.js +73 -23
  34. package/dist/src/generators/frontend/frontendPipeline.js +16 -4
  35. package/dist/src/generators/integration/integrationGenerator.core.js +10 -6
  36. package/dist/src/generators/project/project.generator.js +5 -1
  37. package/dist/src/index.d.ts +4 -4
  38. package/dist/src/index.js +3 -3
  39. package/dist/src/manifest/index.d.ts +1 -0
  40. package/dist/src/manifest/index.js +1 -0
  41. package/dist/src/manifest/manifestFile.helper.d.ts +42 -0
  42. package/dist/src/manifest/manifestFile.helper.js +141 -0
  43. package/dist/src/manifest/manifestManager.core.d.ts +16 -0
  44. package/dist/src/manifest/manifestManager.core.js +65 -35
  45. package/dist/src/prompts/backend/api-style.prompt.js +2 -5
  46. package/dist/src/prompts/backend/backend-architecture.prompt.js +5 -22
  47. package/dist/src/prompts/backend/database.prompt.js +5 -23
  48. package/dist/src/prompts/backend/services.prompt.js +12 -10
  49. package/dist/src/prompts/cancel.prompt.d.ts +18 -0
  50. package/dist/src/prompts/cancel.prompt.js +26 -0
  51. package/dist/src/prompts/capabilities/capabilities.prompt.js +2 -5
  52. package/dist/src/prompts/frontend/framework.prompt.js +2 -5
  53. package/dist/src/prompts/frontend/frontend-architecture.prompt.js +2 -5
  54. package/dist/src/prompts/index.d.ts +1 -0
  55. package/dist/src/prompts/index.js +1 -0
  56. package/dist/src/prompts/project/confirmation.prompt.js +2 -5
  57. package/dist/src/prompts/project/project-name.prompt.js +6 -7
  58. package/dist/src/prompts/project/project-type.prompt.js +2 -5
  59. package/dist/src/prompts/workspace/package-manager.prompt.js +5 -12
  60. package/dist/src/registries/adapter/frontendAdapterRegistry.core.d.ts +6 -0
  61. package/dist/src/registries/adapter/frontendAdapterRegistry.core.js +12 -0
  62. package/dist/src/registries/adapter/packageManagerRegistry.core.d.ts +5 -0
  63. package/dist/src/registries/adapter/packageManagerRegistry.core.js +11 -0
  64. package/dist/src/registries/dependency/dependencyRegistry.core.d.ts +9 -0
  65. package/dist/src/registries/dependency/dependencyRegistry.core.js +16 -0
  66. package/dist/src/registries/generator/generatorRegistry.core.d.ts +8 -0
  67. package/dist/src/registries/generator/generatorRegistry.core.js +14 -0
  68. package/dist/src/resolvers/capability/capabilityResolver.core.d.ts +1 -1
  69. package/dist/src/resolvers/capability/capabilityResolver.core.js +0 -2
  70. package/dist/src/resolvers/layout/projectLayout.core.js +1 -6
  71. package/dist/src/resolvers/project.resolver.d.ts +9 -3
  72. package/dist/src/resolvers/project.resolver.js +11 -7
  73. package/dist/src/rollback/index.d.ts +1 -1
  74. package/dist/src/rollback/index.js +1 -1
  75. package/dist/src/rollback/rollbackManager.core.d.ts +19 -1
  76. package/dist/src/rollback/rollbackManager.core.js +21 -2
  77. package/dist/src/runners/package-manager/index.d.ts +1 -1
  78. package/dist/src/runners/package-manager/index.js +1 -1
  79. package/dist/src/runners/package-manager/packageManagerRunner.core.d.ts +13 -1
  80. package/dist/src/runners/package-manager/packageManagerRunner.core.js +19 -1
  81. package/dist/src/runners/process/processRunner.core.d.ts +11 -0
  82. package/dist/src/runners/process/processRunner.core.js +41 -6
  83. package/dist/src/runners/task/index.d.ts +1 -1
  84. package/dist/src/runners/task/taskRunner.core.d.ts +22 -1
  85. package/dist/src/runners/task/taskRunner.core.js +35 -6
  86. package/dist/src/scaffolders/framework.scaffolder.js +3 -0
  87. package/dist/src/scaffolders/scaffolder.helper.d.ts +10 -0
  88. package/dist/src/scaffolders/scaffolder.helper.js +25 -2
  89. package/dist/src/templates/microservice/index.d.ts +1 -1
  90. package/dist/src/templates/microservice/index.js +1 -1
  91. package/dist/src/templates/microservice/microservice.template.d.ts +11 -4
  92. package/dist/src/templates/microservice/microservice.template.js +40 -19
  93. package/dist/src/templates/modular-monolith/modularMonolith.template.js +27 -8
  94. package/dist/src/templates/monolith/monolith.template.js +11 -17
  95. package/dist/src/templates/shared/capability.template.d.ts +25 -0
  96. package/dist/src/templates/shared/capability.template.js +46 -0
  97. package/dist/src/templates/shared/index.d.ts +1 -0
  98. package/dist/src/templates/shared/index.js +1 -0
  99. package/dist/src/utils/utils.exec.d.ts +22 -0
  100. package/dist/src/utils/utils.exec.js +51 -4
  101. package/dist/src/utils/utils.fileSystem.js +46 -2
  102. package/dist/src/utils/utils.name.d.ts +9 -0
  103. package/dist/src/utils/utils.name.js +29 -3
  104. package/dist/src/validators/environment/environmentValidator.core.d.ts +11 -1
  105. package/dist/src/validators/environment/environmentValidator.core.js +40 -73
  106. package/dist/src/validators/project/index.d.ts +1 -1
  107. package/dist/src/validators/project/projectValidator.core.d.ts +11 -1
  108. package/dist/src/validators/project/projectValidator.core.js +20 -4
  109. package/package.json +1 -1
@@ -19,25 +19,13 @@ import { generateRoute } from "../generators/route/route.generator.js";
19
19
  import { generateModel } from "../generators/model/model.generator.js";
20
20
  import { generateDto } from "../generators/dto/dto.generator.js";
21
21
  import { generateValidator } from "../generators/validator/validator.generator.js";
22
- import { CLIGenerationError, CLIValidationError } from "../errors/index.js";
22
+ import { CLIGenerationError, CLINotInProjectError, CLIValidationError, } from "../errors/index.js";
23
23
  import { assertGeneratableName, assertSafePathSegment, } from "../utils/utils.name.js";
24
+ import { SCHEMATIC_NAMES } from "../constants/index.js";
24
25
  import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
26
+ import { findProjectRoot } from "../resolvers/project.resolver.js";
27
+ import { describeError } from "./commandError.helper.js";
25
28
  import { captureWrites, findWriteConflicts } from "../utils/utils.writeGuard.js";
26
- const VALID_SCHEMATICS = [
27
- "service",
28
- "module",
29
- "command",
30
- "query",
31
- "controller",
32
- "repository",
33
- "middleware",
34
- "event",
35
- "job",
36
- "route",
37
- "model",
38
- "dto",
39
- "validator",
40
- ];
41
29
  /**
42
30
  * Resolves the directory a schematic is generated into.
43
31
  *
@@ -86,6 +74,13 @@ function getArchitectureRoot(architecture, schematic, serviceName) {
86
74
  if (schematic === "module") {
87
75
  return "src/modules";
88
76
  }
77
+ if (schematic === "service") {
78
+ // The monolith template scaffolds `src/services/app.service.ts` and a
79
+ // `src/services/index.ts` barrel. Generating into `src/<name>/`
80
+ // instead left two conventions in one project, and the generated
81
+ // service was never exported from the barrel the template owns.
82
+ return "src/services";
83
+ }
89
84
  return "src";
90
85
  }
91
86
  }
@@ -116,8 +111,8 @@ export async function runGenerateCommand(context) {
116
111
  const dryRun = context.values["dry-run"] === true;
117
112
  const force = context.values.force === true;
118
113
  if (!schematic ||
119
- !VALID_SCHEMATICS.includes(schematic)) {
120
- throw new CLIValidationError(`Schematic name is required. Available: ${VALID_SCHEMATICS.join(", ")}`);
114
+ !SCHEMATIC_NAMES.includes(schematic)) {
115
+ throw new CLIValidationError(`Schematic name is required. Available: ${SCHEMATIC_NAMES.join(", ")}`);
121
116
  }
122
117
  if (!name) {
123
118
  throw new CLIValidationError("Resource name is required.");
@@ -133,20 +128,55 @@ export async function runGenerateCommand(context) {
133
128
  if (moduleName !== undefined) {
134
129
  assertSafePathSegment(moduleName, "--module");
135
130
  }
136
- const cwd = context.cwd;
137
- const layout = resolveProjectLayout(cwd);
131
+ // `generate` used to resolve the layout from the current directory only:
132
+ // from any subdirectory of a project it found nothing, warned, fell back
133
+ // to "src" and wrote a second tree (proj/src/src/foo/…) while exiting 0.
134
+ // It now walks up like `build` does, and refuses outside a project like
135
+ // `dev`, `build` and `add` do instead of scattering files into the cwd.
136
+ const projectRoot = findProjectRoot(context.cwd);
137
+ if (projectRoot === null) {
138
+ throw new CLINotInProjectError();
139
+ }
140
+ const cwd = projectRoot;
141
+ const layout = resolveProjectLayout(projectRoot);
138
142
  const architecture = layout?.architecture ?? null;
139
143
  if (layout) {
140
144
  context.logger.info(`Detected architecture: ${layout.architecture}`);
141
145
  }
142
- else {
143
- context.logger.warn("No Zudojs project detected (no .zudojs/manifest.json, zudojs.config.ts " +
144
- "or `zudojs` field in package.json). Run `zudojs create` first.");
146
+ if (projectRoot !== context.cwd) {
147
+ context.logger.info(`Project root: ${projectRoot}`);
145
148
  }
149
+ /**
150
+ * A microservice's service is a whole workspace app — `package.json`,
151
+ * `tsconfig.json`, `Dockerfile`, `src/app.ts`, `src/server.ts` and a port —
152
+ * which this schematic does not produce.
153
+ *
154
+ * It used to warn and continue, writing a bare service class into
155
+ * `apps/services/<name>/`. That directory matches the workspace glob but has
156
+ * no manifest, so pnpm skipped it, `pnpm -r run build` never compiled it,
157
+ * and `zudojs add --service <name>` reported it as an unknown service. The
158
+ * files were unreachable and the command still exited 0. Refusing with the
159
+ * two commands that do work is more useful than dead code.
160
+ */
146
161
  if (architecture === "microservice" && schematic === "service") {
147
- context.logger.warn('In microservice architecture, prefer "zudojs generate module" — services are top-level apps.');
162
+ throw new CLIValidationError(`A service in a microservice project is a workspace app, which "generate service" does not scaffold.\n` +
163
+ ` - Add it at creation time: zudojs create <project> --architecture microservice --services ${name}\n` +
164
+ ` - Or add a module to an existing app: zudojs generate module ${name} --service <existing-service>`);
148
165
  }
149
- if (architecture === "modular-monolith" && schematic === "service") {
166
+ /**
167
+ * A modular monolith has modules, not services, so `generate service` means
168
+ * `generate module` there.
169
+ *
170
+ * This used to log the mapping and then run the service schematic anyway,
171
+ * which wrote four inert files into a new top-level directory that the
172
+ * runtime never loads. Rewriting the schematic here — rather than at the
173
+ * dispatch switch — keeps the base-path resolution, the overwrite guard and
174
+ * the result message all describing the same thing.
175
+ */
176
+ const effectiveSchematic = architecture === "modular-monolith" && schematic === "service"
177
+ ? "module"
178
+ : schematic;
179
+ if (effectiveSchematic !== schematic) {
150
180
  context.logger.info('Mapping "service" → "module" for modular-monolith architecture.');
151
181
  }
152
182
  const schematicOptions = {
@@ -156,7 +186,7 @@ export async function runGenerateCommand(context) {
156
186
  architecture: architecture ?? undefined,
157
187
  };
158
188
  if (!dryRun && !force) {
159
- const planned = await captureWrites(() => runSchematic(schematic, name, schematicOptions, cwd));
189
+ const planned = await captureWrites(() => runSchematic(effectiveSchematic, name, schematicOptions, cwd));
160
190
  const conflicts = findWriteConflicts(cwd, planned);
161
191
  if (conflicts.length > 0) {
162
192
  throw new CLIValidationError(`Refusing to overwrite existing files:\n${conflicts
@@ -164,7 +194,7 @@ export async function runGenerateCommand(context) {
164
194
  .join("\n")}\nRe-run with --force to overwrite them.`);
165
195
  }
166
196
  }
167
- const result = await runSchematic(schematic, name, {
197
+ const result = await runSchematic(effectiveSchematic, name, {
168
198
  ...schematicOptions,
169
199
  onModuleRegistered: (registration) => {
170
200
  if (registration.registered)
@@ -191,9 +221,15 @@ async function runSchematic(schematic, name, options, cwd) {
191
221
  const dryRun = options.dryRun;
192
222
  // In the microservice layout `--service` selected the owning app and is
193
223
  // already part of basePath, so it must not nest the schematic again.
194
- const cqrsService = options.architecture === "microservice"
195
- ? undefined
196
- : (options.service ?? "default");
224
+ //
225
+ // Without `--service` there is no CQRS group at all. This used to fall
226
+ // back to the literal `"default"`, which became a path segment no template
227
+ // ever creates: `zudojs generate command pay` wrote `src/default/commands/`,
228
+ // and with `--module identity` it wrote `src/modules/identity/default/`
229
+ // right next to the real, empty `src/modules/identity/commands/`. Nothing
230
+ // imported either. The generators already handle an absent group by
231
+ // writing to `<basePath>/commands/<name>`.
232
+ const cqrsService = options.architecture === "microservice" ? undefined : options.service;
197
233
  switch (schematic) {
198
234
  case "service":
199
235
  return await generateService({ name, basePath, dryRun }, cwd);
@@ -222,11 +258,14 @@ async function runSchematic(schematic, name, options, cwd) {
222
258
  case "validator":
223
259
  return await generateValidator({ name, basePath, dryRun }, cwd);
224
260
  default:
225
- throw new CLIValidationError(`Unknown schematic: "${schematic}". Available: ${VALID_SCHEMATICS.join(", ")}.`);
261
+ throw new CLIValidationError(`Unknown schematic: "${schematic}". Available: ${SCHEMATIC_NAMES.join(", ")}.`);
226
262
  }
227
263
  }
228
264
  catch (error) {
229
- throw new CLIGenerationError(`Failed to generate ${schematic}: ${name}`, error);
265
+ // The cause was attached but never rendered, so an ENOTDIR or EACCES
266
+ // surfaced as a bare "Failed to generate service: billing" with no way
267
+ // to find out more. `build` and `add` interpolate it; so does this.
268
+ throw new CLIGenerationError(`Failed to generate ${schematic} "${name}": ${describeError(error)}`, error);
230
269
  }
231
270
  }
232
271
  //# sourceMappingURL=generate.command.js.map
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Constants used by the CLI scaffolding system.
5
5
  */
6
+ import type { BackendArchitecture, DatabaseProvider, PackageManagerType } from "../types/projectConfiguration.type.js";
6
7
  export { CLI_VERSION } from "../cliConstant/cliVersion.js";
7
8
  /**
8
9
  * Version range every generated project depends on for `@zudojs/*` packages.
@@ -22,74 +23,45 @@ export declare const ZUDOJS_PACKAGES_VERSION: "^1.0.0";
22
23
  * project is backed by its package.
23
24
  */
24
25
  export declare const FEATURE_PACKAGES: Readonly<Record<string, readonly string[]>>;
25
- export declare const ARCHITECTURE_CHOICES: readonly [{
26
- readonly value: "monolith";
27
- readonly label: "Monolith";
28
- }, {
29
- readonly value: "modular-monolith";
30
- readonly label: "Modular Monolith";
31
- }, {
32
- readonly value: "microservice";
33
- readonly label: "Microservice";
34
- }];
35
- export declare const PACKAGE_MANAGER_CHOICES: readonly [{
36
- readonly value: "pnpm";
37
- readonly label: "pnpm";
38
- }, {
39
- readonly value: "npm";
40
- readonly label: "npm";
41
- }, {
42
- readonly value: "yarn";
43
- readonly label: "yarn";
44
- }];
45
- export declare const DATABASE_CHOICES: readonly [{
46
- readonly value: "postgresql";
47
- readonly label: "PostgreSQL";
48
- }, {
49
- readonly value: "mysql";
50
- readonly label: "MySQL";
51
- }, {
52
- readonly value: "sqlite";
53
- readonly label: "SQLite";
54
- }];
55
- export declare const FEATURE_CHOICES: readonly [{
56
- readonly value: "database";
57
- readonly label: "Database";
58
- }, {
59
- readonly value: "queue";
60
- readonly label: "Queue";
61
- }, {
62
- readonly value: "messaging";
63
- readonly label: "Messaging";
64
- }, {
65
- readonly value: "openapi";
66
- readonly label: "OpenAPI";
67
- }, {
68
- readonly value: "observability";
69
- readonly label: "Observability";
70
- }, {
71
- readonly value: "security";
72
- readonly label: "Security";
73
- }];
74
- export declare const SCHEMA_CHOICES: readonly [{
75
- readonly value: "service";
76
- readonly label: "Service (CQRS)";
77
- }, {
78
- readonly value: "module";
79
- readonly label: "Module";
80
- }, {
81
- readonly value: "command";
82
- readonly label: "Command";
83
- }, {
84
- readonly value: "query";
85
- readonly label: "Query";
86
- }, {
87
- readonly value: "controller";
88
- readonly label: "Controller";
89
- }, {
90
- readonly value: "repository";
91
- readonly label: "Repository";
92
- }];
26
+ /** A selectable answer offered by a prompt. */
27
+ export interface CLIChoiceOption<Value extends string = string> {
28
+ readonly value: Value;
29
+ readonly label: string;
30
+ readonly hint?: string;
31
+ }
32
+ /** Backend architectures offered by `zudojs create`. */
33
+ export declare const ARCHITECTURE_CHOICES: readonly CLIChoiceOption<BackendArchitecture>[];
34
+ /**
35
+ * The package managers the CLI can install with.
36
+ *
37
+ * One canonical list. It previously existed three times — as
38
+ * `VALID_PACKAGE_MANAGERS` in the create command, `PACKAGE_MANAGERS` in the
39
+ * layout resolver and `SUPPORTED_MANAGERS` in the package-manager runner —
40
+ * which is how `bun` came to be missing from the published choice list while
41
+ * the CLI supported it everywhere else.
42
+ */
43
+ export declare const PACKAGE_MANAGERS: readonly PackageManagerType[];
44
+ /** Package managers the CLI can install with, as prompt choices. */
45
+ export declare const PACKAGE_MANAGER_CHOICES: readonly CLIChoiceOption<PackageManagerType>[];
46
+ /** Database engines the CLI has adapters for. */
47
+ export declare const DATABASE_CHOICES: readonly CLIChoiceOption<DatabaseProvider>[];
48
+ /**
49
+ * Features `zudojs add` accepts.
50
+ *
51
+ * Derived from `FEATURE_PACKAGES` so the two cannot drift: the hand-written
52
+ * list named six of the ten installable features.
53
+ */
54
+ export declare const FEATURE_CHOICES: readonly CLIChoiceOption[];
55
+ /**
56
+ * Schematics `zudojs generate` accepts, in the order the help lists them.
57
+ *
58
+ * This is the canonical list; the hand-written one named six of thirteen.
59
+ */
60
+ export declare const SCHEMA_CHOICES: readonly CLIChoiceOption[];
61
+ /** Names only, for help text and validation messages. */
62
+ export declare const SCHEMATIC_NAMES: readonly string[];
63
+ /** Feature names only, for help text and validation messages. */
64
+ export declare const FEATURE_NAMES: readonly string[];
93
65
  export declare const DEFAULT_ARCHITECTURE: "monolith";
94
66
  export declare const DEFAULT_PACKAGE_MANAGER: "pnpm";
95
67
  export declare const DEFAULT_DATABASE: "postgresql";
@@ -35,37 +35,87 @@ export const FEATURE_PACKAGES = {
35
35
  scheduler: ["@zudojs/scheduler"],
36
36
  docs: ["@zudojs/docs"],
37
37
  };
38
- export const ARCHITECTURE_CHOICES = [
39
- { value: "monolith", label: "Monolith" },
40
- { value: "modular-monolith", label: "Modular Monolith" },
41
- { value: "microservice", label: "Microservice" },
42
- ];
43
- export const PACKAGE_MANAGER_CHOICES = [
38
+ /** Backend architectures offered by `zudojs create`. */
39
+ export const ARCHITECTURE_CHOICES = Object.freeze([
40
+ { value: "monolith", label: "Monolith", hint: "Single application" },
41
+ {
42
+ value: "modular-monolith",
43
+ label: "Modular Monolith",
44
+ hint: "Modular single application",
45
+ },
46
+ {
47
+ value: "microservice",
48
+ label: "Microservices",
49
+ hint: "Independent services",
50
+ },
51
+ ]);
52
+ /**
53
+ * The package managers the CLI can install with.
54
+ *
55
+ * One canonical list. It previously existed three times — as
56
+ * `VALID_PACKAGE_MANAGERS` in the create command, `PACKAGE_MANAGERS` in the
57
+ * layout resolver and `SUPPORTED_MANAGERS` in the package-manager runner —
58
+ * which is how `bun` came to be missing from the published choice list while
59
+ * the CLI supported it everywhere else.
60
+ */
61
+ export const PACKAGE_MANAGERS = Object.freeze([
62
+ "pnpm",
63
+ "npm",
64
+ "yarn",
65
+ "bun",
66
+ ]);
67
+ /** Package managers the CLI can install with, as prompt choices. */
68
+ export const PACKAGE_MANAGER_CHOICES = Object.freeze([
44
69
  { value: "pnpm", label: "pnpm" },
45
70
  { value: "npm", label: "npm" },
46
- { value: "yarn", label: "yarn" },
47
- ];
48
- export const DATABASE_CHOICES = [
49
- { value: "postgresql", label: "PostgreSQL" },
50
- { value: "mysql", label: "MySQL" },
51
- { value: "sqlite", label: "SQLite" },
52
- ];
53
- export const FEATURE_CHOICES = [
54
- { value: "database", label: "Database" },
55
- { value: "queue", label: "Queue" },
56
- { value: "messaging", label: "Messaging" },
57
- { value: "openapi", label: "OpenAPI" },
58
- { value: "observability", label: "Observability" },
59
- { value: "security", label: "Security" },
60
- ];
61
- export const SCHEMA_CHOICES = [
71
+ { value: "yarn", label: "Yarn" },
72
+ { value: "bun", label: "Bun" },
73
+ ]);
74
+ /** Database engines the CLI has adapters for. */
75
+ export const DATABASE_CHOICES = Object.freeze([
76
+ {
77
+ value: "postgresql",
78
+ label: "PostgreSQL",
79
+ hint: "Recommended for production",
80
+ },
81
+ {
82
+ value: "mysql",
83
+ label: "MySQL",
84
+ hint: "Widely used relational database",
85
+ },
86
+ { value: "sqlite", label: "SQLite", hint: "Lightweight, file-based" },
87
+ ]);
88
+ /**
89
+ * Features `zudojs add` accepts.
90
+ *
91
+ * Derived from `FEATURE_PACKAGES` so the two cannot drift: the hand-written
92
+ * list named six of the ten installable features.
93
+ */
94
+ export const FEATURE_CHOICES = Object.freeze(Object.keys(FEATURE_PACKAGES).map((value) => Object.freeze({ value, label: value })));
95
+ /**
96
+ * Schematics `zudojs generate` accepts, in the order the help lists them.
97
+ *
98
+ * This is the canonical list; the hand-written one named six of thirteen.
99
+ */
100
+ export const SCHEMA_CHOICES = Object.freeze([
62
101
  { value: "service", label: "Service (CQRS)" },
63
102
  { value: "module", label: "Module" },
64
103
  { value: "command", label: "Command" },
65
104
  { value: "query", label: "Query" },
66
105
  { value: "controller", label: "Controller" },
67
106
  { value: "repository", label: "Repository" },
68
- ];
107
+ { value: "middleware", label: "Middleware" },
108
+ { value: "event", label: "Event" },
109
+ { value: "job", label: "Job" },
110
+ { value: "route", label: "Route" },
111
+ { value: "model", label: "Model" },
112
+ { value: "dto", label: "DTO" },
113
+ { value: "validator", label: "Validator" },
114
+ ]);
115
+ /** Names only, for help text and validation messages. */
116
+ export const SCHEMATIC_NAMES = Object.freeze(SCHEMA_CHOICES.map((choice) => choice.value));
117
+ /** Feature names only, for help text and validation messages. */
118
+ export const FEATURE_NAMES = Object.freeze(FEATURE_CHOICES.map((choice) => choice.value));
69
119
  export const DEFAULT_ARCHITECTURE = "monolith";
70
120
  export const DEFAULT_PACKAGE_MANAGER = "pnpm";
71
121
  export const DEFAULT_DATABASE = "postgresql";
@@ -67,6 +67,13 @@ export async function runFrontendPipeline(adapter, context, packageManagerRegist
67
67
  }
68
68
  return { files, errors };
69
69
  }
70
+ // The resolver records a warning for every dependency it could not pin,
71
+ // which then goes into package.json as "latest". Nothing read that array,
72
+ // so an adapter adding a dependency with no registered range silently made
73
+ // the generated project unreproducible.
74
+ for (const warning of resolution.warnings) {
75
+ process.stderr.write(`warning: ${warning}\n`);
76
+ }
70
77
  // 5. Install BOTH runtime and dev dependencies. Only devDependencies used
71
78
  // to be installed, so every runtime package an adapter declared was
72
79
  // resolved and then dropped on the floor. With `--no-install` the
@@ -77,10 +84,15 @@ export async function runFrontendPipeline(adapter, context, packageManagerRegist
77
84
  files.push("dependencies");
78
85
  }
79
86
  else if (packageManager) {
80
- const deps = resolution.dependencies.map((d) => d.name);
81
- const devDeps = resolution.devDependencies.map((d) => d.name);
82
- assertSafePackageNames(deps);
83
- assertSafePackageNames(devDeps);
87
+ // Install the resolved RANGE, not just the name. Mapping to names alone
88
+ // let npm/pnpm resolve `latest`, so the pinning the resolver exists to
89
+ // provide survived only on the `--no-install` path and two runs a month
90
+ // apart produced different majors. The names are validated separately
91
+ // from the `name@range` specs actually passed to the package manager.
92
+ assertSafePackageNames(resolution.dependencies.map((d) => d.name));
93
+ assertSafePackageNames(resolution.devDependencies.map((d) => d.name));
94
+ const deps = resolution.dependencies.map((d) => `${d.name}@${d.version}`);
95
+ const devDeps = resolution.devDependencies.map((d) => `${d.name}@${d.version}`);
84
96
  if (deps.length > 0) {
85
97
  await packageManager.add(context.projectPath, deps);
86
98
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { existsSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { writeFileTree } from "../../utils/utils.fileSystem.js";
8
+ import { mergeBarrelExport, writeFileTree, } from "../../utils/utils.fileSystem.js";
9
9
  import { renderDatabaseEnv } from "../../adapters/databases/databaseAdapter.resolver.js";
10
10
  /**
11
11
  * Generates integration files between frontend and backend.
@@ -24,11 +24,15 @@ export class IntegrationGenerator {
24
24
  const apiPath = this.getApiClientPath(context);
25
25
  files[apiPath] = apiClient;
26
26
  }
27
- // CORS configuration, written next to the backend it configures. In a
28
- // fullstack workspace the backend lives in apps/api; writing it at the
29
- // workspace root left it where nothing could import it.
30
- files[`${this.getBackendPath(context)}config/cors.ts`] =
31
- this.generateCorsConfig(context);
27
+ // CORS configuration, written inside the backend's own program. The
28
+ // backend tsconfig sets `rootDir: "src"` and `include: ["src/**/*"]`, so
29
+ // a `config/` directory beside it is excluded from compilation and cannot
30
+ // be imported from `src/` without leaving rootDir. The templates' config
31
+ // directory is `src/configs/`, and its barrel is where the rest of the
32
+ // app looks for configuration.
33
+ const configDir = `${this.getBackendPath(context)}src/configs`;
34
+ files[`${configDir}/cors.ts`] = this.generateCorsConfig(context);
35
+ files[`${configDir}/index.ts`] = mergeBarrelExport(context.projectPath, `${configDir}/index.ts`, `export { corsConfig } from "./cors.js";`);
32
36
  // Development proxy configuration (belongs to the frontend app). The
33
37
  // frontend adapter has already written a vite.config.ts carrying the
34
38
  // framework plugin and path aliases; overwriting it with this minimal
@@ -31,7 +31,11 @@ export async function generateProject(options, basePath = ".") {
31
31
  await writeFileTree(projectPath, templateFiles);
32
32
  }
33
33
  catch (error) {
34
- throw new CLIGenerationError(`Failed to write project files:`, error);
34
+ // This message is what the user sees at the moment the CLI rolls back and
35
+ // deletes the directory it created, so it has to say why. It used to end
36
+ // in a colon with the cause never rendered.
37
+ const reason = error instanceof Error ? error.message : String(error);
38
+ throw new CLIGenerationError(`Failed to write project files into ${projectPath}: ${reason}`, error);
35
39
  }
36
40
  const filesCreated = Object.keys(templateFiles);
37
41
  // Note: git initialization and dependency installation are orchestrated by
@@ -49,10 +49,10 @@ export { FullstackComposer } from "./generators/fullstack/index.js";
49
49
  export { IntegrationGenerator } from "./generators/integration/index.js";
50
50
  export { InfrastructureGenerator, type InfrastructureOptions, } from "./generators/infrastructure/index.js";
51
51
  export { ProcessRunner, type ProcessOptions } from "./runners/process/index.js";
52
- export { PackageManagerRunner, type PackageManagerRunOptions, } from "./runners/package-manager/index.js";
53
- export { TaskRunner, type TaskDefinition, type TaskResult, } from "./runners/task/index.js";
54
- export { RollbackManager, type RollbackEntry } from "./rollback/index.js";
55
- export { ManifestManager, type ZudojsManifest } from "./manifest/index.js";
52
+ export { PackageManagerRunner, assertPackageManager, type PackageManagerRunOptions, } from "./runners/package-manager/index.js";
53
+ export { TaskRunner, type TaskDefinition, type TaskResult, type TaskRunOptions, } from "./runners/task/index.js";
54
+ export { RollbackManager, type RollbackEntry, type RollbackFailure, type RollbackResult, } from "./rollback/index.js";
55
+ export { ManifestManager, parseManifest, type ManifestReadResult, type ManifestReadStatus, type ZudojsManifest, } from "./manifest/index.js";
56
56
  export type { ProjectConfiguration, ProjectType, BackendArchitecture, FrontendFramework, FrontendArchitecture, DatabaseProvider, ApiStyle, PackageManagerType, } from "./types/projectConfiguration.type.js";
57
57
  export type { ScaffoldOptions, ArchitectureType, PackageManager, DatabaseEngine, ProjectTemplate, GenerateOptions, } from "./types/index.js";
58
58
  //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -58,10 +58,10 @@ export { IntegrationGenerator } from "./generators/integration/index.js";
58
58
  export { InfrastructureGenerator, } from "./generators/infrastructure/index.js";
59
59
  // Runners
60
60
  export { ProcessRunner } from "./runners/process/index.js";
61
- export { PackageManagerRunner, } from "./runners/package-manager/index.js";
61
+ export { PackageManagerRunner, assertPackageManager, } from "./runners/package-manager/index.js";
62
62
  export { TaskRunner, } from "./runners/task/index.js";
63
63
  // Rollback
64
- export { RollbackManager } from "./rollback/index.js";
64
+ export { RollbackManager, } from "./rollback/index.js";
65
65
  // Manifest
66
- export { ManifestManager } from "./manifest/index.js";
66
+ export { ManifestManager, parseManifest, } from "./manifest/index.js";
67
67
  //# sourceMappingURL=index.js.map
@@ -4,4 +4,5 @@
4
4
  * Manifest system barrel exports.
5
5
  */
6
6
  export { ManifestManager, type ZudojsManifest, } from "./manifestManager.core.js";
7
+ export { parseManifest, type ManifestReadResult, type ManifestReadStatus, } from "./manifestFile.helper.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,5 @@
4
4
  * Manifest system barrel exports.
5
5
  */
6
6
  export { ManifestManager, } from "./manifestManager.core.js";
7
+ export { parseManifest, } from "./manifestFile.helper.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * zudojs-cli — Manifest File Helpers
3
+ *
4
+ * Parsing, validation, atomic writes and locking for
5
+ * `.zudojs/manifest.json`. Kept apart from the manager so the manager stays
6
+ * a thin read/modify/write API over them.
7
+ */
8
+ import type { ZudojsManifest } from "./manifestManager.core.js";
9
+ /** How a manifest read turned out. */
10
+ export type ManifestReadStatus = "ok" | "missing" | "invalid";
11
+ /** The outcome of reading a manifest, with missing and corrupt kept apart. */
12
+ export interface ManifestReadResult {
13
+ readonly status: ManifestReadStatus;
14
+ readonly manifest: ZudojsManifest | null;
15
+ /** Why an `invalid` manifest could not be used. */
16
+ readonly reason?: string;
17
+ }
18
+ /**
19
+ * Parses manifest text, filling in the fields an older CLI (or a hand edit)
20
+ * may have left out.
21
+ *
22
+ * `capabilities` used to be read straight off an unvalidated cast, so a
23
+ * manifest without it threw a TypeError from inside `addCapability`.
24
+ */
25
+ export declare function parseManifest(content: string): ManifestReadResult;
26
+ /**
27
+ * Writes the manifest through a sibling temp file and `rename`.
28
+ *
29
+ * Writing in place truncated the real file first, so an interrupted or
30
+ * failing write left an empty or half-written manifest behind.
31
+ */
32
+ export declare function writeManifestFile(manifestPath: string, manifest: ZudojsManifest): Promise<void>;
33
+ /**
34
+ * Serializes a read-modify-write of one manifest.
35
+ *
36
+ * Concurrent `zudojs add` runs lost each other's updates: each read the
37
+ * manifest, added its own capability and wrote the whole file back. The
38
+ * queue orders calls inside one process and the lock file orders them
39
+ * across processes.
40
+ */
41
+ export declare function withManifestLock<T>(manifestPath: string, operation: () => Promise<T>): Promise<T>;
42
+ //# sourceMappingURL=manifestFile.helper.d.ts.map