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
@@ -2,10 +2,9 @@
2
2
  * zudojs-cli — Generate Command
3
3
  *
4
4
  * The `zudojs generate` (alias: `g`) command.
5
- * Reads zudojs.config.ts to determine project architecture.
5
+ * Reads the project manifest to determine the architecture and where the
6
+ * backend lives, then places the schematic accordingly.
6
7
  */
7
- import { existsSync, readFileSync } from "node:fs";
8
- import { join } from "node:path";
9
8
  import { generateService } from "../generators/service/service.generator.js";
10
9
  import { generateModule } from "../generators/module/module.generator.js";
11
10
  import { generateCommand } from "../generators/command/command.generator.js";
@@ -21,6 +20,7 @@ import { generateDto } from "../generators/dto/dto.generator.js";
21
20
  import { generateValidator } from "../generators/validator/validator.generator.js";
22
21
  import { CLIGenerationError, CLIValidationError } from "../errors/index.js";
23
22
  import { assertGeneratableName, assertSafePathSegment, } from "../utils/utils.name.js";
23
+ import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
24
24
  const VALID_SCHEMATICS = [
25
25
  "service",
26
26
  "module",
@@ -82,41 +82,20 @@ function getArchitectureRoot(architecture, schematic, serviceName) {
82
82
  return "src";
83
83
  }
84
84
  }
85
- function readZudojsArchitecture(cwd) {
86
- for (const configFile of ["zudojs.config.ts", "zudojs.config.js"]) {
87
- const configPath = join(cwd, configFile);
88
- if (existsSync(configPath)) {
89
- const content = readFileSync(configPath, "utf-8");
90
- const match = content.match(/architecture:\s*["'](\w[\w-]*)["']/);
91
- if (match?.[1])
92
- return match[1];
93
- }
94
- }
95
- // Fall back to the machine-managed manifest written by `zudojs create`.
96
- const manifestPath = join(cwd, ".zudojs", "manifest.json");
97
- if (existsSync(manifestPath)) {
98
- try {
99
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
100
- if (manifest.architecture)
101
- return manifest.architecture;
102
- }
103
- catch {
104
- // ignore malformed manifest
105
- }
106
- }
107
- // Fall back to the zudojs field in package.json.
108
- const pkgPath = join(cwd, "package.json");
109
- if (existsSync(pkgPath)) {
110
- try {
111
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
112
- if (pkg.zudojs?.architecture)
113
- return pkg.zudojs.architecture;
114
- }
115
- catch {
116
- // ignore malformed package.json
117
- }
85
+ /**
86
+ * Prefix under which backend schematics are generated.
87
+ *
88
+ * A fullstack project keeps its backend in `apps/api`; generating into the
89
+ * workspace root's `src/` produced files no app compiled. Microservice apps
90
+ * are addressed inside getArchitectureRoot, relative to that prefix.
91
+ */
92
+ function backendPrefix(cwd) {
93
+ const layout = resolveProjectLayout(cwd);
94
+ const [first] = layout?.backendDirs ?? [];
95
+ if (layout?.projectType === "fullstack" && first !== undefined && first !== cwd) {
96
+ return "apps/api/";
118
97
  }
119
- return null;
98
+ return "";
120
99
  }
121
100
  export async function runGenerateCommand(context) {
122
101
  const schematic = context.values.schematic;
@@ -143,12 +122,13 @@ export async function runGenerateCommand(context) {
143
122
  assertSafePathSegment(moduleName, "--module");
144
123
  }
145
124
  const cwd = context.cwd;
146
- const architecture = readZudojsArchitecture(cwd);
147
- if (architecture) {
148
- context.logger.info(`Detected architecture: ${architecture}`);
125
+ const layout = resolveProjectLayout(cwd);
126
+ const architecture = layout?.architecture ?? null;
127
+ if (layout) {
128
+ context.logger.info(`Detected architecture: ${layout.architecture}`);
149
129
  }
150
130
  else {
151
- context.logger.warn("No Zudojs project detected (no zudojs.config.ts, .zudojs/manifest.json " +
131
+ context.logger.warn("No Zudojs project detected (no .zudojs/manifest.json, zudojs.config.ts " +
152
132
  "or `zudojs` field in package.json). Run `zudojs create` first.");
153
133
  }
154
134
  if (architecture === "microservice" && schematic === "service") {
@@ -175,7 +155,8 @@ export async function runGenerateCommand(context) {
175
155
  }
176
156
  async function runSchematic(schematic, name, options, cwd) {
177
157
  try {
178
- const basePath = getBasePath(options.architecture, schematic, options.module, options.service);
158
+ const basePath = backendPrefix(cwd) +
159
+ getBasePath(options.architecture, schematic, options.module, options.service);
179
160
  const dryRun = options.dryRun;
180
161
  // In the microservice layout `--service` selected the owning app and is
181
162
  // already part of basePath, so it must not nest the schematic again.
@@ -3,42 +3,73 @@
3
3
  *
4
4
  * The `zudojs info` command.
5
5
  */
6
- import { readFileSync, existsSync } from "node:fs";
7
- import { join } from "node:path";
6
+ import { readFileSync } from "node:fs";
7
+ import { join, relative } from "node:path";
8
+ import { CLI_VERSION } from "../constants/index.js";
9
+ import { checkForNewerVersion } from "../cliVersion/cliVersion.update.js";
10
+ import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
8
11
  export async function runInfoCommand(context) {
9
- const pkgPath = join(context.cwd, "package.json");
12
+ context.logger.info("Zudojs CLI");
13
+ context.logger.info(` Version: ${CLI_VERSION}`);
14
+ context.logger.info(` Node.js: ${process.version}`);
15
+ const update = await checkForNewerVersion(CLI_VERSION, { env: context.env });
16
+ if (update.latest) {
17
+ context.logger.info(` Update available: ${update.latest} (npm install -g zudojs-cli@latest)`);
18
+ }
19
+ context.logger.info("");
20
+ const layout = resolveProjectLayout(context.cwd);
21
+ if (!layout) {
22
+ context.logger.info("Not in a Zudojs project directory.");
23
+ context.logger.info("Run `zudojs create <project-name>` to create a new project.");
24
+ return;
25
+ }
10
26
  let projectName = "unknown";
27
+ let projectVersion = "0.0.0";
11
28
  try {
12
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
13
- projectName = pkg.name ?? "unknown";
14
- const zudojsDeps = Object.entries(pkg.dependencies ?? {})
15
- .filter(([name]) => name.startsWith("@zudojs/"))
16
- .sort(([a], [b]) => a.localeCompare(b));
17
- context.logger.info("Zudojs Project Info");
18
- context.logger.info("");
19
- context.logger.info(`Project: ${projectName}`);
20
- context.logger.info(`Version: ${pkg.version ?? "0.0.0"}`);
21
- const hasPnpm = existsSync(join(context.cwd, "pnpm-lock.yaml"));
22
- const hasYarn = existsSync(join(context.cwd, "yarn.lock"));
23
- const packageManager = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
24
- context.logger.info(`Package Manager: ${packageManager}`);
25
- context.logger.info("");
26
- context.logger.info("Zudojs Dependencies:");
27
- if (zudojsDeps.length === 0) {
28
- context.logger.info(" (none)");
29
- }
30
- else {
31
- for (const [name, version] of zudojsDeps) {
32
- context.logger.info(` ${name}: ${version}`);
29
+ const pkg = JSON.parse(readFileSync(join(layout.root, "package.json"), "utf-8"));
30
+ projectName = pkg.name ?? projectName;
31
+ projectVersion = pkg.version ?? projectVersion;
32
+ }
33
+ catch {
34
+ // A project without a root package.json still has a manifest.
35
+ }
36
+ context.logger.info("Project");
37
+ context.logger.info(` Name: ${projectName}`);
38
+ context.logger.info(` Version: ${projectVersion}`);
39
+ context.logger.info(` Type: ${layout.projectType}`);
40
+ if (layout.backendDirs.length > 0) {
41
+ context.logger.info(` Architecture: ${layout.architecture}`);
42
+ }
43
+ if (layout.frontendFramework) {
44
+ context.logger.info(` Frontend: ${layout.frontendFramework}`);
45
+ }
46
+ context.logger.info(` Package manager: ${layout.packageManager}`);
47
+ if (layout.services.length > 0) {
48
+ context.logger.info(` Services: ${layout.services.join(", ")}`);
49
+ }
50
+ context.logger.info("");
51
+ context.logger.info("Zudojs dependencies");
52
+ const deps = new Map();
53
+ for (const dir of layout.backendDirs) {
54
+ try {
55
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
56
+ for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
57
+ if (name.startsWith("@zudojs/")) {
58
+ const where = relative(layout.root, dir);
59
+ deps.set(where === "" ? name : `${name} (${where})`, version);
60
+ }
33
61
  }
34
62
  }
63
+ catch {
64
+ // Reported by `zudojs doctor`.
65
+ }
35
66
  }
36
- catch {
37
- context.logger.info("Zudojs CLI Info");
38
- context.logger.info("");
39
- context.logger.info("Not in a Zudojs project directory.");
40
- context.logger.info("");
41
- context.logger.info("Run `zudojs create <project-name>` to create a new project.");
67
+ if (deps.size === 0) {
68
+ context.logger.info(" (none)");
69
+ return;
70
+ }
71
+ for (const [name, version] of [...deps.entries()].sort(([a], [b]) => a.localeCompare(b))) {
72
+ context.logger.info(` ${name}: ${version}`);
42
73
  }
43
74
  }
44
75
  //# sourceMappingURL=info.command.js.map
@@ -4,8 +4,24 @@
4
4
  * Constants used by the CLI scaffolding system.
5
5
  */
6
6
  export { CLI_VERSION } from "../cliConstant/cliVersion.js";
7
- /** Published version of all @zudojs/* framework packages. */
8
- export declare const ZUDOJS_PACKAGES_VERSION: "1.0.0";
7
+ /**
8
+ * Version range every generated project depends on for `@zudojs/*` packages.
9
+ *
10
+ * This is a caret range, not an exact pin, on purpose. The framework
11
+ * packages are released independently, so at any moment their latest
12
+ * versions differ (`@zudojs/openapi` went to 1.1.0 while the rest stayed at
13
+ * 1.0.0). An exact `1.0.0` pin made `pnpm install` in every freshly created
14
+ * project abort with `ERR_PNPM_NO_MATCHING_VERSION` for the package that had
15
+ * moved on. A range resolves each package to its newest compatible release.
16
+ */
17
+ export declare const ZUDOJS_PACKAGES_VERSION: "^1.0.0";
18
+ /**
19
+ * Packages installed by `zudojs add <feature>`.
20
+ *
21
+ * Also used by `zudojs doctor` to verify that every feature recorded in a
22
+ * project is backed by its package.
23
+ */
24
+ export declare const FEATURE_PACKAGES: Readonly<Record<string, readonly string[]>>;
9
25
  export declare const ARCHITECTURE_CHOICES: readonly [{
10
26
  readonly value: "monolith";
11
27
  readonly label: "Monolith";
@@ -4,8 +4,37 @@
4
4
  * Constants used by the CLI scaffolding system.
5
5
  */
6
6
  export { CLI_VERSION } from "../cliConstant/cliVersion.js";
7
- /** Published version of all @zudojs/* framework packages. */
8
- export const ZUDOJS_PACKAGES_VERSION = "1.0.0";
7
+ /**
8
+ * Version range every generated project depends on for `@zudojs/*` packages.
9
+ *
10
+ * This is a caret range, not an exact pin, on purpose. The framework
11
+ * packages are released independently, so at any moment their latest
12
+ * versions differ (`@zudojs/openapi` went to 1.1.0 while the rest stayed at
13
+ * 1.0.0). An exact `1.0.0` pin made `pnpm install` in every freshly created
14
+ * project abort with `ERR_PNPM_NO_MATCHING_VERSION` for the package that had
15
+ * moved on. A range resolves each package to its newest compatible release.
16
+ */
17
+ export const ZUDOJS_PACKAGES_VERSION = "^1.0.0";
18
+ /**
19
+ * Packages installed by `zudojs add <feature>`.
20
+ *
21
+ * Also used by `zudojs doctor` to verify that every feature recorded in a
22
+ * project is backed by its package.
23
+ */
24
+ export const FEATURE_PACKAGES = {
25
+ database: ["@zudojs/database"],
26
+ queue: ["@zudojs/queue"],
27
+ messaging: ["@zudojs/messaging"],
28
+ openapi: ["@zudojs/openapi"],
29
+ observability: ["@zudojs/observability"],
30
+ security: ["@zudojs/security"],
31
+ cache: ["@zudojs/cache"],
32
+ storage: ["@zudojs/storage"],
33
+ // Used to install @zudojs/queue; the scheduler package exists and is the
34
+ // one the feature name promises.
35
+ scheduler: ["@zudojs/scheduler"],
36
+ docs: ["@zudojs/docs"],
37
+ };
9
38
  export const ARCHITECTURE_CHOICES = [
10
39
  { value: "monolith", label: "Monolith" },
11
40
  { value: "modular-monolith", label: "Modular Monolith" },
@@ -14,6 +14,8 @@ export interface FrontendGenerationOptions {
14
14
  readonly architecture?: "zudojs-standard" | "feature-based" | "minimal" | "framework-default";
15
15
  readonly language?: "typescript" | "javascript";
16
16
  readonly packageManager?: "pnpm" | "npm" | "yarn" | "bun";
17
+ /** `false` records dependencies in package.json instead of installing. */
18
+ readonly installDeps?: boolean;
17
19
  }
18
20
  /**
19
21
  * Frontend generation result.
@@ -8,6 +8,8 @@ import { PackageManagerRegistry } from "../../registries/adapter/packageManagerR
8
8
  import { DependencyResolver } from "../../resolvers/dependency/dependencyResolver.core.js";
9
9
  import { CLIGenerationError } from "../../errors/index.js";
10
10
  import { runFrontendPipeline } from "./frontendPipeline.js";
11
+ import { writeFileTree } from "../../utils/utils.fileSystem.js";
12
+ import { renderPnpmWorkspaceFile } from "../../templates/shared/pnpm.template.js";
11
13
  /**
12
14
  * Generates frontend projects using framework adapters.
13
15
  */
@@ -40,10 +42,19 @@ export class FrontendGenerator {
40
42
  linting: true,
41
43
  formatting: true,
42
44
  },
45
+ ...(options.installDeps === false ? { skipInstall: true } : {}),
43
46
  };
44
47
  const files = [];
45
48
  const errors = [];
46
49
  try {
50
+ // A frontend-only project is its own pnpm root. Vite depends on
51
+ // esbuild, whose install script pnpm 10+ refuses without this list,
52
+ // and the pipeline installs dependencies right after scaffolding.
53
+ if (context.packageManager === "pnpm") {
54
+ await writeFileTree(context.projectPath, {
55
+ "pnpm-workspace.yaml": renderPnpmWorkspaceFile(),
56
+ });
57
+ }
47
58
  const outcome = await runFrontendPipeline(adapter, context, this.packageManagerRegistry, this.dependencyResolver);
48
59
  files.push(...outcome.files);
49
60
  errors.push(...outcome.errors);
@@ -10,6 +10,9 @@
10
10
  *
11
11
  * @module generators/frontend/pipeline
12
12
  */
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { writeFileTree } from "../../utils/utils.fileSystem.js";
13
16
  /**
14
17
  * Rejects package names a package manager would read as a flag.
15
18
  *
@@ -65,9 +68,14 @@ export async function runFrontendPipeline(adapter, context, packageManagerRegist
65
68
  }
66
69
  // 5. Install BOTH runtime and dev dependencies. Only devDependencies used
67
70
  // to be installed, so every runtime package an adapter declared was
68
- // resolved and then dropped on the floor.
71
+ // resolved and then dropped on the floor. With `--no-install` the
72
+ // dependencies are written to package.json for a later install.
69
73
  const packageManager = packageManagerRegistry.get(context.packageManager);
70
- if (packageManager) {
74
+ if (context.skipInstall === true) {
75
+ await recordDependencies(context.projectPath, resolution.dependencies, resolution.devDependencies);
76
+ files.push("dependencies");
77
+ }
78
+ else if (packageManager) {
71
79
  const deps = resolution.dependencies.map((d) => d.name);
72
80
  const devDeps = resolution.devDependencies.map((d) => d.name);
73
81
  assertSafePackageNames(deps);
@@ -90,4 +98,39 @@ export async function runFrontendPipeline(adapter, context, packageManagerRegist
90
98
  }
91
99
  return { files, errors };
92
100
  }
101
+ /**
102
+ * Adds the resolved dependencies to the project's package.json without
103
+ * running a package manager. Existing entries win: the adapter's own
104
+ * template may already pin a version.
105
+ */
106
+ async function recordDependencies(projectPath, dependencies, devDependencies) {
107
+ const pkgPath = join(projectPath, "package.json");
108
+ let pkg = {};
109
+ if (existsSync(pkgPath)) {
110
+ try {
111
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
112
+ }
113
+ catch {
114
+ pkg = {};
115
+ }
116
+ }
117
+ const merge = (target, entries) => {
118
+ const merged = { ...(target ?? {}) };
119
+ for (const entry of entries) {
120
+ if (!(entry.name in merged)) {
121
+ merged[entry.name] = entry.version || "latest";
122
+ }
123
+ }
124
+ return merged;
125
+ };
126
+ if (dependencies.length > 0) {
127
+ pkg.dependencies = merge(pkg.dependencies, dependencies);
128
+ }
129
+ if (devDependencies.length > 0) {
130
+ pkg.devDependencies = merge(pkg.devDependencies, devDependencies);
131
+ }
132
+ await writeFileTree(projectPath, {
133
+ "package.json": JSON.stringify(pkg, null, 2) + "\n",
134
+ });
135
+ }
93
136
  //# sourceMappingURL=frontendPipeline.js.map
@@ -12,6 +12,8 @@ import type { ProjectConfiguration } from "../../types/projectConfiguration.type
12
12
  export interface FullstackGenerationContext {
13
13
  readonly project: ProjectConfiguration;
14
14
  readonly projectPath: string;
15
+ /** `false` records frontend dependencies instead of installing them. */
16
+ readonly installDeps?: boolean;
15
17
  }
16
18
  /**
17
19
  * Fullstack generation result.
@@ -12,6 +12,7 @@ import { PackageManagerRegistry } from "../../registries/adapter/packageManagerR
12
12
  import { DependencyResolver } from "../../resolvers/dependency/dependencyResolver.core.js";
13
13
  import { runFrontendPipeline } from "../frontend/frontendPipeline.js";
14
14
  import { writeFileTree } from "../../utils/utils.fileSystem.js";
15
+ import { renderPnpmWorkspaceFile } from "../../templates/shared/pnpm.template.js";
15
16
  /**
16
17
  * Composes fullstack projects by orchestrating multiple generators.
17
18
  */
@@ -33,7 +34,7 @@ export class FullstackComposer {
33
34
  try {
34
35
  // 1. Create workspace structure
35
36
  await this.createWorkspace(context);
36
- files.push("package.json", "zudojs.config.ts");
37
+ files.push("package.json");
37
38
  if (this.getPackageManager(context) === "pnpm") {
38
39
  files.push("pnpm-workspace.yaml");
39
40
  }
@@ -90,31 +91,15 @@ export class FullstackComposer {
90
91
  typescript: "^5.0.0",
91
92
  },
92
93
  };
93
- // Project and framework names reach this template from user input. They
94
- // are emitted as JSON string literals (valid JS) so a quote, backslash,
95
- // newline or `";` sequence cannot break out of the literal and inject
96
- // code into the generated zudojs.config.ts.
97
- const literal = (value) => JSON.stringify(value);
98
- const frontendBlock = context.project.frontend
99
- ? `
100
- frontend: {
101
- framework: ${literal(context.project.frontend.framework)},
102
- },`
103
- : "";
104
- const zudojsConfig = `export default {
105
- name: ${literal(context.project.name)},
106
- projectType: "fullstack",
107
- architecture: ${literal(context.project.backend?.architecture ?? "monolith")},${frontendBlock}
108
- };
109
- `;
94
+ // The project's type, architecture and frontend are recorded in
95
+ // .zudojs/manifest.json by the create command; no zudojs.config.ts is
96
+ // written. The name reaches package.json through JSON.stringify, so a
97
+ // quote or newline in it stays data.
110
98
  const files = {
111
99
  "package.json": JSON.stringify(rootPackageJson, null, 2) + "\n",
112
- "zudojs.config.ts": zudojsConfig,
113
100
  };
114
101
  if (packageManager === "pnpm") {
115
- files["pnpm-workspace.yaml"] = `packages:
116
- ${workspaceGlobs.map((g) => ` - "${g}"`).join("\n")}
117
- `;
102
+ files["pnpm-workspace.yaml"] = renderPnpmWorkspaceFile(workspaceGlobs);
118
103
  }
119
104
  await writeFileTree(context.projectPath, files);
120
105
  }
@@ -172,6 +157,7 @@ export type Timestamp = string;
172
157
  formatting: true,
173
158
  envValidation: true,
174
159
  },
160
+ ...(context.installDeps === false ? { skipInstall: true } : {}),
175
161
  };
176
162
  // The same pipeline the frontend-only path uses: availability check,
177
163
  // scaffold, structure, integration, dependency resolution + install, and
@@ -23,8 +23,8 @@ export { CLI_NAME, CLI_DEFAULTS, CLI_COMMANDS, CLI_ALIASES, CLI_OPTION_PREFIXES,
23
23
  export { CLIError, isCLIError, normalizeCLIError, getCLIExitCode, getCLIErrorCode, type CLIErrorOptions, CommandNotFoundError, DuplicateCommandError, InvalidCommandNameError, InvalidArgumentsError, MissingArgumentError, InvalidOptionError, InvalidOptionNameError, MissingOptionValueError, DuplicateOptionError, CLIExecutionError, CLIPermissionError, CLIInterruptedError, CLIConfigurationError, } from "./cliError/index.js";
24
24
  export { CLICommandRegistry, CLICommandBuilder, createCommand, command, executeCommand, validateCommand, isCLICommand, sortCommands, } from "./cliCommand/index.js";
25
25
  export { CLIParser, type CLIParserOptions, parseCLIArguments, parseOptionValue, parseBoolean, isOption, isLongOption, isShortOption, resolveCommand, normalizeCLIValue, } from "./cliParser/index.js";
26
- export { ZudojsCLI, createCLI, createCLIWriter, registerCLIInterruptHandler, } from "./cliApplication/index.js";
27
- export { getCLIVersion, formatCLIVersion, getVersionString, isValidVersion, compareVersions, parseVersion, isCompatibleVersion, type CLIVersionInfo, } from "./cliVersion/index.js";
26
+ export { ZudojsCLI, createCLI, createCLIWriter, createCLILogger, formatCLILogLine, registerCLIInterruptHandler, type CLILoggerOptions, } from "./cliApplication/index.js";
27
+ export { getCLIVersion, formatCLIVersion, getVersionString, isValidVersion, compareVersions, parseVersion, isCompatibleVersion, checkForNewerVersion, isUpdateCheckDisabled, type CLIVersionInfo, type UpdateCheckOptions, type UpdateCheckResult, } from "./cliVersion/index.js";
28
28
  export type { FrontendAdapter, FrontendGenerationContext, FrontendFeatures, DependencyRequirement, ValidationResult, } from "./adapters/frontend/index.js";
29
29
  export { ReactAdapter, NextAdapter, VueAdapter, NuxtAdapter, AngularAdapter, SvelteAdapter, SvelteKitAdapter, AstroAdapter, VanillaAdapter, FlutterAdapter, ReactNativeAdapter, } from "./adapters/frontend/index.js";
30
30
  export { PnpmAdapter, NpmAdapter, YarnAdapter, BunAdapter, } from "./adapters/package-managers/index.js";
@@ -34,6 +34,7 @@ export { detectArchitecture } from "./resolvers/index.js";
34
34
  export { resolveProjectPath, findProjectRoot, } from "./resolvers/project.resolver.js";
35
35
  export { CapabilityResolver, type CapabilityDependency, type CapabilityResolutionResult, } from "./resolvers/capability/index.js";
36
36
  export { ConfigurationResolver, type ResolvedConfiguration, } from "./resolvers/configuration/index.js";
37
+ export { resolveProjectLayout, detectPackageManager, type ProjectLayout, type ProjectLayoutType, type ProjectLayoutSource, } from "./resolvers/layout/index.js";
37
38
  export { EnvironmentValidator, type EnvironmentCheck, type EnvironmentValidationResult, } from "./validators/index.js";
38
39
  export { ProjectValidator, type ProjectCheck, type ProjectValidationResult, } from "./validators/index.js";
39
40
  export { CompatibilityValidator, type CompatibilityCheck, type CompatibilityResult, } from "./validators/compatibility/index.js";
package/dist/src/index.js CHANGED
@@ -27,9 +27,9 @@ export { CLICommandRegistry, CLICommandBuilder, createCommand, command, executeC
27
27
  // Parser
28
28
  export { CLIParser, parseCLIArguments, parseOptionValue, parseBoolean, isOption, isLongOption, isShortOption, resolveCommand, normalizeCLIValue, } from "./cliParser/index.js";
29
29
  // Application
30
- export { ZudojsCLI, createCLI, createCLIWriter, registerCLIInterruptHandler, } from "./cliApplication/index.js";
30
+ export { ZudojsCLI, createCLI, createCLIWriter, createCLILogger, formatCLILogLine, registerCLIInterruptHandler, } from "./cliApplication/index.js";
31
31
  // Version
32
- export { getCLIVersion, formatCLIVersion, getVersionString, isValidVersion, compareVersions, parseVersion, isCompatibleVersion, } from "./cliVersion/index.js";
32
+ export { getCLIVersion, formatCLIVersion, getVersionString, isValidVersion, compareVersions, parseVersion, isCompatibleVersion, checkForNewerVersion, isUpdateCheckDisabled, } from "./cliVersion/index.js";
33
33
  export { ReactAdapter, NextAdapter, VueAdapter, NuxtAdapter, AngularAdapter, SvelteAdapter, SvelteKitAdapter, AstroAdapter, VanillaAdapter, FlutterAdapter, ReactNativeAdapter, } from "./adapters/frontend/index.js";
34
34
  export { PnpmAdapter, NpmAdapter, YarnAdapter, BunAdapter, } from "./adapters/package-managers/index.js";
35
35
  export { PostgresAdapter, MySqlAdapter, SqliteAdapter, } from "./adapters/databases/index.js";
@@ -39,6 +39,7 @@ export { detectArchitecture } from "./resolvers/index.js";
39
39
  export { resolveProjectPath, findProjectRoot, } from "./resolvers/project.resolver.js";
40
40
  export { CapabilityResolver, } from "./resolvers/capability/index.js";
41
41
  export { ConfigurationResolver, } from "./resolvers/configuration/index.js";
42
+ export { resolveProjectLayout, detectPackageManager, } from "./resolvers/layout/index.js";
42
43
  // Validators
43
44
  export { EnvironmentValidator, } from "./validators/index.js";
44
45
  export { ProjectValidator, } from "./validators/index.js";
@@ -1,5 +1,14 @@
1
1
  import type { PackageManager } from "../types/index.js";
2
2
  export declare function getInstallCommand(packageManager: PackageManager): [string, ...string[]];
3
3
  export declare function getAddCommand(packageManager: PackageManager, pkg: string): [string, ...string[]];
4
+ /**
5
+ * The command that runs a `package.json` script with the given manager.
6
+ *
7
+ * Generated projects declare their dev/build tooling (`tsx`, `vite`, `ng`)
8
+ * as devDependencies, so the binaries live in `node_modules/.bin` and are
9
+ * not on the user's PATH. Running the script through the package manager is
10
+ * what puts them there.
11
+ */
12
+ export declare function getRunScriptCommand(packageManager: PackageManager, script: string): [string, ...string[]];
4
13
  export declare function installDependencies(packageManager: PackageManager, cwd: string): Promise<void>;
5
14
  //# sourceMappingURL=dependency.installer.d.ts.map
@@ -27,6 +27,27 @@ export function getAddCommand(packageManager, pkg) {
27
27
  return ["pnpm", "add", pkg];
28
28
  }
29
29
  }
30
+ /**
31
+ * The command that runs a `package.json` script with the given manager.
32
+ *
33
+ * Generated projects declare their dev/build tooling (`tsx`, `vite`, `ng`)
34
+ * as devDependencies, so the binaries live in `node_modules/.bin` and are
35
+ * not on the user's PATH. Running the script through the package manager is
36
+ * what puts them there.
37
+ */
38
+ export function getRunScriptCommand(packageManager, script) {
39
+ switch (packageManager) {
40
+ case "npm":
41
+ return ["npm", "run", script];
42
+ case "yarn":
43
+ return ["yarn", "run", script];
44
+ case "bun":
45
+ return ["bun", "run", script];
46
+ case "pnpm":
47
+ default:
48
+ return ["pnpm", "run", script];
49
+ }
50
+ }
30
51
  export async function installDependencies(packageManager, cwd) {
31
52
  const [file, ...args] = getInstallCommand(packageManager);
32
53
  await runStreaming(file, args, cwd);
@@ -1,7 +1,5 @@
1
1
  /**
2
2
  * zudojs-cli — Installers
3
- *
4
- * Dependency installation utilities.
5
3
  */
6
- export { installDependencies, getInstallCommand, } from "./dependency.installer.js";
4
+ export { getInstallCommand, getAddCommand, getRunScriptCommand, installDependencies, } from "./dependency.installer.js";
7
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,5 @@
1
1
  /**
2
2
  * zudojs-cli — Installers
3
- *
4
- * Dependency installation utilities.
5
3
  */
6
- export { installDependencies, getInstallCommand, } from "./dependency.installer.js";
4
+ export { getInstallCommand, getAddCommand, getRunScriptCommand, installDependencies, } from "./dependency.installer.js";
7
5
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,12 @@
1
- export declare function detectArchitecture(cwd: string): Promise<"monolith" | "modular-monolith" | "microservice" | null>;
1
+ declare const ARCHITECTURES: readonly ["monolith", "modular-monolith", "microservice"];
2
+ type Architecture = (typeof ARCHITECTURES)[number];
3
+ /**
4
+ * Detects the backend architecture of the project at `cwd`.
5
+ *
6
+ * Reads the manifest (then the legacy config, then package.json) through the
7
+ * shared layout resolver and falls back to directory heuristics for projects
8
+ * that were not created by the CLI.
9
+ */
10
+ export declare function detectArchitecture(cwd: string): Promise<Architecture | null>;
11
+ export {};
2
12
  //# sourceMappingURL=architecture.resolver.d.ts.map
@@ -1,19 +1,18 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { existsSync } from "node:fs";
3
2
  import { join } from "node:path";
3
+ import { resolveProjectLayout } from "./layout/projectLayout.core.js";
4
+ const ARCHITECTURES = ["monolith", "modular-monolith", "microservice"];
5
+ /**
6
+ * Detects the backend architecture of the project at `cwd`.
7
+ *
8
+ * Reads the manifest (then the legacy config, then package.json) through the
9
+ * shared layout resolver and falls back to directory heuristics for projects
10
+ * that were not created by the CLI.
11
+ */
4
12
  export async function detectArchitecture(cwd) {
5
- const configPath = join(cwd, "zudojs.config.ts");
6
- if (existsSync(configPath)) {
7
- try {
8
- const content = await readFile(configPath, "utf-8");
9
- const match = content.match(/architecture:\s*["'](monolith|modular-monolith|microservice)["']/);
10
- if (match) {
11
- return match[1];
12
- }
13
- }
14
- catch {
15
- // fall through
16
- }
13
+ const recorded = resolveProjectLayout(cwd)?.architecture;
14
+ if (recorded && ARCHITECTURES.includes(recorded)) {
15
+ return recorded;
17
16
  }
18
17
  if (existsSync(join(cwd, "pnpm-workspace.yaml"))) {
19
18
  const gateway = existsSync(join(cwd, "apps/gateway"));
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * zudojs-cli — Configuration Resolver
3
3
  *
4
- * Resolves and validates project configuration from multiple sources.
4
+ * Resolves project configuration from the sources `zudojs create` writes.
5
5
  */
6
6
  export interface ResolvedConfiguration {
7
7
  readonly projectName: string;
@@ -16,7 +16,16 @@ export interface ResolvedConfiguration {
16
16
  readonly features: readonly string[];
17
17
  }
18
18
  export declare class ConfigurationResolver {
19
+ /**
20
+ * Resolves the configuration of the project at `cwd`, or `null` when the
21
+ * directory is not a Zudojs project.
22
+ *
23
+ * Only `zudojs.config.ts` used to be consulted, so every project created
24
+ * since the manifest became the source of truth resolved to `null`.
25
+ */
19
26
  resolve(cwd: string): ResolvedConfiguration | null;
27
+ private readJson;
28
+ private readLegacyConfig;
20
29
  private extractValue;
21
30
  private extractArray;
22
31
  }