zudojs-cli 1.0.0 → 1.1.0

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 (68) hide show
  1. package/README.md +16 -6
  2. package/dist/src/adapters/frontend/frontendAdapter.type.d.ts +6 -0
  3. package/dist/src/bin/zudojs.js +8 -32
  4. package/dist/src/cliApplication/cliApplication.core.js +2 -2
  5. package/dist/src/cliApplication/cliApplication.logger.d.ts +28 -0
  6. package/dist/src/cliApplication/cliApplication.logger.js +68 -0
  7. package/dist/src/cliApplication/index.d.ts +1 -3
  8. package/dist/src/cliApplication/index.js +1 -3
  9. package/dist/src/cliVersion/cliVersion.update.d.ts +38 -0
  10. package/dist/src/cliVersion/cliVersion.update.js +63 -0
  11. package/dist/src/cliVersion/index.d.ts +2 -1
  12. package/dist/src/cliVersion/index.js +2 -1
  13. package/dist/src/commands/add.command.d.ts +29 -0
  14. package/dist/src/commands/add.command.js +94 -109
  15. package/dist/src/commands/build.command.d.ts +8 -0
  16. package/dist/src/commands/build.command.js +12 -18
  17. package/dist/src/commands/create.command.js +8 -5
  18. package/dist/src/commands/dev.command.d.ts +30 -2
  19. package/dist/src/commands/dev.command.js +104 -183
  20. package/dist/src/commands/doctor.command.d.ts +18 -0
  21. package/dist/src/commands/doctor.command.js +141 -113
  22. package/dist/src/commands/generate.command.d.ts +2 -1
  23. package/dist/src/commands/generate.command.js +23 -42
  24. package/dist/src/commands/info.command.js +61 -30
  25. package/dist/src/constants/index.d.ts +18 -2
  26. package/dist/src/constants/index.js +31 -2
  27. package/dist/src/generators/frontend/frontendGenerator.core.d.ts +2 -0
  28. package/dist/src/generators/frontend/frontendGenerator.core.js +11 -0
  29. package/dist/src/generators/frontend/frontendPipeline.js +45 -2
  30. package/dist/src/generators/fullstack/fullstackComposer.core.d.ts +2 -0
  31. package/dist/src/generators/fullstack/fullstackComposer.core.js +8 -22
  32. package/dist/src/index.d.ts +3 -2
  33. package/dist/src/index.js +3 -2
  34. package/dist/src/installers/dependency.installer.d.ts +9 -0
  35. package/dist/src/installers/dependency.installer.js +21 -0
  36. package/dist/src/installers/index.d.ts +1 -3
  37. package/dist/src/installers/index.js +1 -3
  38. package/dist/src/resolvers/architecture.resolver.d.ts +11 -1
  39. package/dist/src/resolvers/architecture.resolver.js +12 -13
  40. package/dist/src/resolvers/configuration/configurationResolver.core.d.ts +10 -1
  41. package/dist/src/resolvers/configuration/configurationResolver.core.js +68 -29
  42. package/dist/src/resolvers/index.d.ts +1 -0
  43. package/dist/src/resolvers/index.js +1 -0
  44. package/dist/src/resolvers/layout/index.d.ts +5 -0
  45. package/dist/src/resolvers/layout/index.js +5 -0
  46. package/dist/src/resolvers/layout/projectLayout.core.d.ts +60 -0
  47. package/dist/src/resolvers/layout/projectLayout.core.js +236 -0
  48. package/dist/src/resolvers/project.resolver.d.ts +5 -0
  49. package/dist/src/resolvers/project.resolver.js +10 -6
  50. package/dist/src/templates/microservice/microservice.template.d.ts +0 -1
  51. package/dist/src/templates/microservice/microservice.template.js +2 -10
  52. package/dist/src/templates/modular-monolith/modularMonolith.template.d.ts +0 -1
  53. package/dist/src/templates/modular-monolith/modularMonolith.template.js +4 -7
  54. package/dist/src/templates/monolith/monolith.template.d.ts +0 -1
  55. package/dist/src/templates/monolith/monolith.template.js +4 -7
  56. package/dist/src/templates/shared/appRuntime.template.d.ts +0 -11
  57. package/dist/src/templates/shared/appRuntime.template.js +0 -20
  58. package/dist/src/templates/shared/index.d.ts +1 -0
  59. package/dist/src/templates/shared/index.js +1 -0
  60. package/dist/src/templates/shared/pnpm.template.d.ts +27 -0
  61. package/dist/src/templates/shared/pnpm.template.js +57 -0
  62. package/dist/src/utils/utils.detect.d.ts +9 -0
  63. package/dist/src/utils/utils.detect.js +11 -11
  64. package/dist/src/utils/utils.exec.d.ts +17 -1
  65. package/dist/src/utils/utils.exec.js +65 -4
  66. package/package.json +8 -6
  67. package/dist/src/scripts/postinstall.cjs +0 -77
  68. package/src/scripts/postinstall.cjs +0 -77
@@ -3,141 +3,126 @@
3
3
  *
4
4
  * The `zudojs add` command for adding feature packages.
5
5
  */
6
- import { join } from "node:path";
6
+ import { join, relative } from "node:path";
7
7
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { runStreaming } from "../utils/utils.exec.js";
9
+ import { assertSafePathSegment } from "../utils/utils.name.js";
9
10
  import { CLIValidationError, CLIGenerationError } from "../errors/index.js";
10
11
  import { ManifestManager } from "../manifest/manifestManager.core.js";
11
- import { ZUDOJS_PACKAGES_VERSION } from "../constants/index.js";
12
- const FEATURE_PACKAGES = {
13
- database: ["@zudojs/database"],
14
- queue: ["@zudojs/queue"],
15
- messaging: ["@zudojs/messaging"],
16
- openapi: ["@zudojs/openapi"],
17
- observability: ["@zudojs/observability"],
18
- security: ["@zudojs/security"],
19
- cache: ["@zudojs/cache"],
20
- storage: ["@zudojs/storage"],
21
- scheduler: ["@zudojs/queue"],
22
- docs: ["@zudojs/docs"],
23
- };
12
+ import { getInstallCommand } from "../installers/dependency.installer.js";
13
+ import { FEATURE_PACKAGES, ZUDOJS_PACKAGES_VERSION, } from "../constants/index.js";
14
+ import { resolveProjectLayout, } from "../resolvers/layout/projectLayout.core.js";
15
+ /**
16
+ * Picks the `package.json` files a feature is added to.
17
+ *
18
+ * The workspace root used to be the only target. In a fullstack project
19
+ * that root holds no application code, so `@zudojs/database` landed where
20
+ * nothing could import it while `apps/api` stayed without it. Features now
21
+ * go to every backend app (`apps/api`, or the gateway and each service of a
22
+ * microservice project); `--service <name>` narrows a microservice project
23
+ * to one app.
24
+ */
25
+ export function selectAddTargets(layout, service) {
26
+ if (service !== undefined) {
27
+ if (layout.architecture !== "microservice") {
28
+ throw new CLIValidationError("--service only applies to microservice projects.");
29
+ }
30
+ const match = layout.backendDirs.find((dir) => dir.endsWith(`${join("apps", "services", service)}`));
31
+ const gateway = layout.backendDirs.find((dir) => dir.endsWith(join("apps", "gateway")));
32
+ const target = service === "gateway" ? gateway : match;
33
+ if (!target) {
34
+ throw new CLIValidationError(`Unknown service "${service}". Known: ${[
35
+ ...(gateway ? ["gateway"] : []),
36
+ ...layout.services,
37
+ ].join(", ")}`);
38
+ }
39
+ return [join(target, "package.json")];
40
+ }
41
+ return layout.backendDirs.map((dir) => join(dir, "package.json"));
42
+ }
43
+ /**
44
+ * The version written for a new `@zudojs/*` dependency.
45
+ *
46
+ * `workspace:*` was written whenever the project was a workspace. Every
47
+ * generated fullstack and microservice project is a workspace, and none of
48
+ * them contains the framework packages, so the next install failed with
49
+ * "not found in workspace". The protocol is now used only when the target
50
+ * already links a framework package that way — i.e. inside the Zudojs
51
+ * monorepo itself.
52
+ */
53
+ export function versionForNewDependency(pkg) {
54
+ const linksFramework = Object.entries({
55
+ ...pkg.dependencies,
56
+ ...pkg.devDependencies,
57
+ }).some(([name, version]) => name.startsWith("@zudojs/") && version.startsWith("workspace:"));
58
+ return linksFramework ? "workspace:*" : ZUDOJS_PACKAGES_VERSION;
59
+ }
24
60
  export async function runAddCommand(context) {
25
61
  const feature = context.values.feature;
62
+ const service = context.values.service;
63
+ const available = Object.keys(FEATURE_PACKAGES).join(", ");
26
64
  if (!feature) {
27
- throw new CLIValidationError("Feature name is required. Available: database, queue, messaging, openapi, observability, security");
65
+ throw new CLIValidationError(`Feature name is required. Available: ${available}`);
28
66
  }
29
67
  const packages = FEATURE_PACKAGES[feature];
30
68
  if (!packages) {
31
- throw new CLIValidationError(`Unknown feature: "${feature}". Available: ${Object.keys(FEATURE_PACKAGES).join(", ")}`);
69
+ throw new CLIValidationError(`Unknown feature: "${feature}". Available: ${available}`);
70
+ }
71
+ if (service !== undefined) {
72
+ assertSafePathSegment(service, "--service");
73
+ }
74
+ const layout = resolveProjectLayout(context.cwd);
75
+ if (!layout) {
76
+ throw new CLIValidationError("No Zudojs project found in this directory. Run `zudojs create` first.");
77
+ }
78
+ if (layout.projectType === "frontend") {
79
+ throw new CLIValidationError("Features are backend packages; this is a frontend-only project.");
80
+ }
81
+ const targets = selectAddTargets(layout, service);
82
+ if (targets.length === 0) {
83
+ throw new CLIValidationError("No backend app with a package.json was found in this project.");
32
84
  }
33
85
  context.logger.info(`Adding feature: ${feature}`);
34
86
  context.logger.info(`Packages: ${packages.join(", ")}`);
35
87
  try {
36
- const { manager, rootPkg, isWorkspace } = detectPackageManager(context.cwd);
37
- const pkgPath = join(context.cwd, rootPkg);
38
- const version = isWorkspace ? "workspace:*" : ZUDOJS_PACKAGES_VERSION;
39
- if (!existsSync(pkgPath)) {
40
- throw new CLIGenerationError(`Could not find package.json at ${pkgPath}`);
41
- }
42
- const pkgContent = JSON.parse(readFileSync(pkgPath, "utf-8"));
43
- pkgContent.dependencies ??= {};
44
- pkgContent.zudojs ??= {
45
- features: [],
46
- };
47
- const zudojsConfig = pkgContent.zudojs;
48
- const features = new Set(zudojsConfig.features);
49
- if (!features.has(feature)) {
88
+ for (const pkgPath of targets) {
89
+ if (!existsSync(pkgPath)) {
90
+ throw new CLIGenerationError(`Could not find ${pkgPath}`);
91
+ }
92
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
93
+ const version = versionForNewDependency(pkg);
94
+ pkg.dependencies ??= {};
95
+ pkg.zudojs ??= { features: [] };
96
+ const block = pkg.zudojs;
97
+ const features = new Set(Array.isArray(block.features)
98
+ ? block.features.filter((f) => typeof f === "string")
99
+ : []);
50
100
  features.add(feature);
51
- zudojsConfig.features = [...features];
52
- }
53
- for (const pkg of packages) {
54
- if (!(pkg in pkgContent.dependencies)) {
55
- pkgContent.dependencies[pkg] = version;
101
+ block.features = [...features];
102
+ for (const name of packages) {
103
+ if (!(name in pkg.dependencies)) {
104
+ pkg.dependencies[name] = version;
105
+ }
56
106
  }
107
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
108
+ context.logger.info(`Updated ${relative(context.cwd, pkgPath) || "package.json"}`);
57
109
  }
58
- writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2) + "\n");
59
- context.logger.info(`Updated ${rootPkg} with new dependencies.`);
60
- updateZudojsConfig(context.cwd, feature);
61
- const manifest = new ManifestManager(context.cwd);
62
- const current = await manifest.read();
63
- if (current) {
64
- await manifest.addCapability(feature);
65
- }
110
+ // The manifest records capabilities for the whole project.
111
+ await new ManifestManager(layout.root).addCapability(feature);
66
112
  if (context.values["skip-install"] !== true) {
67
- context.logger.info(`Installing dependencies with ${manager}...`);
68
- await runStreaming(manager, ["install"], context.cwd);
113
+ const [file, ...args] = getInstallCommand(layout.packageManager);
114
+ context.logger.info(`Installing dependencies with ${file}...`);
115
+ await runStreaming(file, args, layout.root);
69
116
  }
70
117
  context.logger.info(`Feature "${feature}" added successfully.`);
71
- context.logger.info(`Next steps:`);
72
- context.logger.info(` Configure in src/config/${feature}.config.ts`);
118
+ context.logger.info(`Import from ${packages.map((p) => `"${p}"`).join(", ")} in your modules.`);
73
119
  }
74
120
  catch (error) {
75
121
  if (error instanceof CLIValidationError) {
76
122
  throw error;
77
123
  }
78
124
  const message = error instanceof Error ? error.message : String(error);
79
- context.logger.error(`Failed to add feature: ${feature} - ${message}`);
80
- throw new CLIGenerationError(`Failed to add feature: ${feature}: ${message}`, error);
81
- }
82
- }
83
- function detectPackageManager(cwd) {
84
- // A project is a workspace only when a workspace definition exists:
85
- // pnpm-workspace.yaml or a "workspaces" field in package.json. Lock files
86
- // merely indicate the package manager.
87
- const hasPnpmWorkspace = existsSync(join(cwd, "pnpm-workspace.yaml"));
88
- let hasWorkspacesField = false;
89
- try {
90
- const pkgPath = join(cwd, "package.json");
91
- if (existsSync(pkgPath)) {
92
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
93
- hasWorkspacesField = pkg.workspaces !== undefined;
94
- }
95
- }
96
- catch {
97
- // ignore malformed package.json
98
- }
99
- const isWorkspace = hasPnpmWorkspace || hasWorkspacesField;
100
- const manager = hasPnpmWorkspace || existsSync(join(cwd, "pnpm-lock.yaml"))
101
- ? "pnpm"
102
- : existsSync(join(cwd, "yarn.lock"))
103
- ? "yarn"
104
- : existsSync(join(cwd, "bun.lock")) ||
105
- existsSync(join(cwd, "bun.lockb"))
106
- ? "bun"
107
- : "npm";
108
- return { manager, rootPkg: "package.json", isWorkspace };
109
- }
110
- function updateZudojsConfig(cwd, feature) {
111
- const configPath = join(cwd, "zudojs.config.ts");
112
- if (!existsSync(configPath))
113
- return;
114
- let content = readFileSync(configPath, "utf-8");
115
- const featureMap = {
116
- database: "database",
117
- queue: "queue",
118
- messaging: "events",
119
- openapi: "openapi",
120
- observability: "observability",
121
- security: "security",
122
- cache: "cache",
123
- storage: "storage",
124
- scheduler: "scheduler",
125
- docs: "docs",
126
- };
127
- const configKey = featureMap[feature];
128
- if (!configKey)
129
- return;
130
- const pattern = /(export\s+default\s+defineConfig\(\{[\s\S]*?)\n\}\);/;
131
- const match = content.match(pattern);
132
- if (!match)
133
- return;
134
- const featureBlock = `\n ${configKey}: {\n enabled: true,\n },`;
135
- if (content.includes(`${configKey}:`)) {
136
- content = content.replace(new RegExp(`${configKey}:\\s*\\{[^}]*\\}`), `${configKey}: {\n enabled: true,\n }`);
137
- }
138
- else {
139
- content = content.replace(pattern, `$1${featureBlock}\n});`);
125
+ throw new CLIGenerationError(`Failed to add feature "${feature}": ${message}`, error);
140
126
  }
141
- writeFileSync(configPath, content);
142
127
  }
143
128
  //# sourceMappingURL=add.command.js.map
@@ -4,5 +4,13 @@
4
4
  * The `zudojs build` command for building Zudojs projects.
5
5
  */
6
6
  import type { CLIContext } from "../cliType/cliType.type.js";
7
+ import type { PackageManager } from "../types/index.js";
7
8
  export declare function runBuildCommand(context: CLIContext): Promise<void>;
9
+ /**
10
+ * Arguments that run the `build` script.
11
+ *
12
+ * pnpm's `-r` is only meaningful in a workspace; a single-package project's
13
+ * root `build` script is the whole build.
14
+ */
15
+ export declare function getBuildArgs(packageManager: PackageManager, isWorkspace: boolean): string[];
8
16
  //# sourceMappingURL=build.command.d.ts.map
@@ -3,10 +3,9 @@
3
3
  *
4
4
  * The `zudojs build` command for building Zudojs projects.
5
5
  */
6
- import { existsSync } from "node:fs";
7
- import { join } from "node:path";
8
6
  import { runStreaming } from "../utils/utils.exec.js";
9
7
  import { findProjectRoot } from "../resolvers/project.resolver.js";
8
+ import { detectPackageManager, resolveProjectLayout, } from "../resolvers/layout/projectLayout.core.js";
10
9
  import { CLIGenerationError, CLINotInProjectError } from "../errors/index.js";
11
10
  export async function runBuildCommand(context) {
12
11
  const projectRoot = findProjectRoot(context.cwd);
@@ -16,34 +15,29 @@ export async function runBuildCommand(context) {
16
15
  throw new CLINotInProjectError();
17
16
  }
18
17
  context.logger.info(`Building project at: ${projectRoot}`);
19
- const packageManager = detectPackageManager(projectRoot);
20
- const buildArgs = getBuildArgs(packageManager);
18
+ const layout = resolveProjectLayout(projectRoot);
19
+ const packageManager = layout?.packageManager ?? detectPackageManager(projectRoot);
20
+ const buildArgs = getBuildArgs(packageManager, layout?.isWorkspace ?? false);
21
21
  try {
22
22
  await runStreaming(packageManager, buildArgs, projectRoot);
23
23
  context.logger.info("Build completed successfully.");
24
24
  }
25
25
  catch (error) {
26
26
  const message = error instanceof Error ? error.message : String(error);
27
- context.logger.error(`Build failed: ${message}`);
28
27
  // A failed build must fail the command so CI does not report success.
29
28
  throw new CLIGenerationError(`Build failed: ${message}`, error);
30
29
  }
31
30
  }
32
- function detectPackageManager(cwd) {
33
- if (existsSync(join(cwd, "pnpm-lock.yaml")))
34
- return "pnpm";
35
- if (existsSync(join(cwd, "yarn.lock")))
36
- return "yarn";
37
- if (existsSync(join(cwd, "package-lock.json")))
38
- return "npm";
39
- if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb")))
40
- return "bun";
41
- return "npm";
42
- }
43
- function getBuildArgs(packageManager) {
31
+ /**
32
+ * Arguments that run the `build` script.
33
+ *
34
+ * pnpm's `-r` is only meaningful in a workspace; a single-package project's
35
+ * root `build` script is the whole build.
36
+ */
37
+ export function getBuildArgs(packageManager, isWorkspace) {
44
38
  switch (packageManager) {
45
39
  case "pnpm":
46
- return ["-r", "run", "build"];
40
+ return isWorkspace ? ["-r", "run", "build"] : ["run", "build"];
47
41
  case "yarn":
48
42
  // Plain `yarn run build` works on both Yarn classic and Berry without
49
43
  // requiring the workspace-tools plugin.
@@ -299,7 +299,6 @@ async function createProject(options, context) {
299
299
  try {
300
300
  spinner.start("Creating project structure");
301
301
  await mkdir(targetPath, { recursive: true });
302
- rollback.trackDirectory(targetPath);
303
302
  spinner.stop("Project structure created");
304
303
  if (options.projectType === "fullstack" &&
305
304
  options.frontend &&
@@ -318,8 +317,7 @@ async function createProject(options, context) {
318
317
  else {
319
318
  spinner.start("Generating backend project");
320
319
  const result = await generateProject(options, targetPath);
321
- context.logger.info(`Files created: ${result.filesCreated.length}`);
322
- spinner.stop("Backend project generated");
320
+ spinner.stop(`Backend project generated (${result.filesCreated.length} files)`);
323
321
  }
324
322
  await writeProjectManifest(options, targetPath);
325
323
  if (options.installDeps) {
@@ -338,8 +336,11 @@ async function createProject(options, context) {
338
336
  await runStreaming(installFile, ["install"], targetPath);
339
337
  p.log.success("Dependencies installed");
340
338
  }
341
- catch {
342
- p.log.warn("Dependency installation skipped");
339
+ catch (error) {
340
+ // The project is complete without its node_modules; say what
341
+ // failed and how to retry instead of a bare "skipped".
342
+ const reason = error instanceof Error ? error.message : String(error);
343
+ p.log.warn(`Dependency installation failed: ${reason}\nRun "${installFile} install" inside ${projectName} to retry.`);
343
344
  }
344
345
  }
345
346
  if (options.initGit) {
@@ -445,6 +446,7 @@ async function generateFullstackProject(options, projectPath, rollback) {
445
446
  features: options.services,
446
447
  },
447
448
  projectPath,
449
+ installDeps: options.installDeps,
448
450
  });
449
451
  if (!result.success) {
450
452
  throw new CLIGenerationError(`Fullstack generation failed:\n${result.errors.join("\n")}`);
@@ -514,6 +516,7 @@ async function generateFrontendProject(options, projectPath) {
514
516
  architecture: options.frontendArchitecture ?? "zudojs-standard",
515
517
  language: options.language ?? "typescript",
516
518
  packageManager: options.packageManager,
519
+ installDeps: options.installDeps,
517
520
  });
518
521
  if (!result.success) {
519
522
  throw new CLIGenerationError(`Frontend generation failed:\n${result.errors.join("\n")}`);
@@ -1,9 +1,37 @@
1
1
  /**
2
2
  * zudojs-cli — Dev Command
3
3
  *
4
- * The `zudojs dev` command.
5
- * Starts development servers based on project configuration.
4
+ * The `zudojs dev` command. Starts the development servers of the project
5
+ * in the current directory.
6
+ *
7
+ * Servers are started through the project's package manager (`pnpm run
8
+ * dev`, `npm run dev`, …) rather than by spawning `tsx` or `ng` directly:
9
+ * those binaries are devDependencies of the generated project and are not
10
+ * on the user's PATH, so the direct spawn failed with ENOENT for everyone
11
+ * who had not installed them globally. Running the script also means the
12
+ * project's own `dev` script is what runs — `tsx watch src` used to be
13
+ * hard-coded here, which watched `src/index.ts`, a file that only exports
14
+ * `createApp` and never starts the server.
6
15
  */
7
16
  import type { CLIContext } from "../cliType/cliType.type.js";
17
+ import { type ProjectLayout } from "../resolvers/layout/projectLayout.core.js";
18
+ /** One development server to start. */
19
+ export interface DevServerSpec {
20
+ readonly label: string;
21
+ readonly cwd: string;
22
+ readonly file: string;
23
+ readonly args: readonly string[];
24
+ readonly env?: NodeJS.ProcessEnv;
25
+ }
26
+ /**
27
+ * Computes the servers `zudojs dev` starts for a project layout.
28
+ *
29
+ * Exported so the selection can be tested without spawning anything.
30
+ */
31
+ export declare function planDevServers(layout: ProjectLayout, options?: {
32
+ readonly frontendOnly?: boolean;
33
+ readonly backendOnly?: boolean;
34
+ readonly port?: number;
35
+ }): DevServerSpec[];
8
36
  export declare function runDevCommand(context: CLIContext): Promise<void>;
9
37
  //# sourceMappingURL=dev.command.d.ts.map