zudojs-cli 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +12 -10
  67. package/dist/src/scripts/postinstall.cjs +0 -77
  68. package/src/scripts/postinstall.cjs +0 -77
@@ -0,0 +1,27 @@
1
+ /**
2
+ * zudojs-cli — pnpm settings for generated projects
3
+ *
4
+ * pnpm 10 stopped running the install scripts of dependencies unless the
5
+ * project lists them, and pnpm 11 turned the resulting notice into a failing
6
+ * `ERR_PNPM_IGNORED_BUILDS`. Every generated project depends on `tsx`
7
+ * (backend) or Vite (frontend), both of which pull in esbuild and its
8
+ * postinstall, so a fresh `pnpm install` exited with code 1 and
9
+ * `pnpm run dev` — which re-verifies the install first — never started.
10
+ *
11
+ * pnpm 10 reads `onlyBuiltDependencies`; pnpm 11 reads `allowBuilds`. Both
12
+ * are written so either version installs cleanly, and both live in
13
+ * `pnpm-workspace.yaml`, the file pnpm itself creates for this purpose.
14
+ *
15
+ * @module templates/shared/pnpm
16
+ */
17
+ /** Dependencies whose install scripts a generated project allows. */
18
+ export declare const PNPM_ALLOWED_BUILD_SCRIPTS: readonly ["esbuild", "@swc/core", "sharp", "@parcel/watcher", "@tailwindcss/oxide", "unrs-resolver"];
19
+ /**
20
+ * Renders `pnpm-workspace.yaml`.
21
+ *
22
+ * `packages` is omitted for a single-package project: pnpm then treats the
23
+ * directory as the sole workspace member, which is what it already does
24
+ * when it writes the file itself.
25
+ */
26
+ export declare function renderPnpmWorkspaceFile(packages?: readonly string[]): string;
27
+ //# sourceMappingURL=pnpm.template.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * zudojs-cli — pnpm settings for generated projects
3
+ *
4
+ * pnpm 10 stopped running the install scripts of dependencies unless the
5
+ * project lists them, and pnpm 11 turned the resulting notice into a failing
6
+ * `ERR_PNPM_IGNORED_BUILDS`. Every generated project depends on `tsx`
7
+ * (backend) or Vite (frontend), both of which pull in esbuild and its
8
+ * postinstall, so a fresh `pnpm install` exited with code 1 and
9
+ * `pnpm run dev` — which re-verifies the install first — never started.
10
+ *
11
+ * pnpm 10 reads `onlyBuiltDependencies`; pnpm 11 reads `allowBuilds`. Both
12
+ * are written so either version installs cleanly, and both live in
13
+ * `pnpm-workspace.yaml`, the file pnpm itself creates for this purpose.
14
+ *
15
+ * @module templates/shared/pnpm
16
+ */
17
+ /** Dependencies whose install scripts a generated project allows. */
18
+ export const PNPM_ALLOWED_BUILD_SCRIPTS = [
19
+ "esbuild",
20
+ "@swc/core",
21
+ "sharp",
22
+ "@parcel/watcher",
23
+ "@tailwindcss/oxide",
24
+ "unrs-resolver",
25
+ ];
26
+ /**
27
+ * Renders `pnpm-workspace.yaml`.
28
+ *
29
+ * `packages` is omitted for a single-package project: pnpm then treats the
30
+ * directory as the sole workspace member, which is what it already does
31
+ * when it writes the file itself.
32
+ */
33
+ export function renderPnpmWorkspaceFile(packages = []) {
34
+ const lines = [];
35
+ if (packages.length > 0) {
36
+ lines.push("packages:");
37
+ for (const glob of packages) {
38
+ lines.push(` - ${JSON.stringify(glob)}`);
39
+ }
40
+ lines.push("");
41
+ }
42
+ lines.push("# Dependencies allowed to run install scripts (pnpm 10).");
43
+ lines.push("onlyBuiltDependencies:");
44
+ for (const name of PNPM_ALLOWED_BUILD_SCRIPTS) {
45
+ // Quoted: a bare leading "@" is a reserved YAML indicator.
46
+ lines.push(` - ${JSON.stringify(name)}`);
47
+ }
48
+ lines.push("");
49
+ lines.push("# The same list in the form pnpm 11 reads.");
50
+ lines.push("allowBuilds:");
51
+ for (const name of PNPM_ALLOWED_BUILD_SCRIPTS) {
52
+ lines.push(` ${JSON.stringify(name)}: true`);
53
+ }
54
+ lines.push("");
55
+ return lines.join("\n");
56
+ }
57
+ //# sourceMappingURL=pnpm.template.js.map
@@ -1,2 +1,11 @@
1
+ /**
2
+ * Returns the recorded architecture of the project at `cwd`, or `null` when
3
+ * the directory is not a Zudojs project.
4
+ *
5
+ * Used to read a `zudojs.config.json` that no template has ever written, so
6
+ * it returned `null` for every project. It now reads the same sources as
7
+ * every other command: the manifest, the legacy `zudojs.config.ts`, then
8
+ * the `zudojs` block in `package.json`.
9
+ */
1
10
  export declare function detectArchitecture(cwd: string): Promise<string | null>;
2
11
  //# sourceMappingURL=utils.detect.d.ts.map
@@ -1,14 +1,14 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
1
+ import { resolveProjectLayout } from "../resolvers/layout/projectLayout.core.js";
2
+ /**
3
+ * Returns the recorded architecture of the project at `cwd`, or `null` when
4
+ * the directory is not a Zudojs project.
5
+ *
6
+ * Used to read a `zudojs.config.json` that no template has ever written, so
7
+ * it returned `null` for every project. It now reads the same sources as
8
+ * every other command: the manifest, the legacy `zudojs.config.ts`, then
9
+ * the `zudojs` block in `package.json`.
10
+ */
3
11
  export async function detectArchitecture(cwd) {
4
- try {
5
- const configPath = join(cwd, "zudojs.config.json");
6
- const content = await readFile(configPath, "utf-8");
7
- const config = JSON.parse(content);
8
- return config.architecture ?? null;
9
- }
10
- catch {
11
- return null;
12
- }
12
+ return resolveProjectLayout(cwd)?.architecture ?? null;
13
13
  }
14
14
  //# sourceMappingURL=utils.detect.js.map
@@ -5,7 +5,21 @@ export interface ExecResult {
5
5
  export interface ExecOptions {
6
6
  readonly env?: NodeJS.ProcessEnv;
7
7
  readonly timeout?: number;
8
+ /** Aborting the signal terminates the child process. */
9
+ readonly signal?: AbortSignal;
8
10
  }
11
+ /**
12
+ * Quotes one argument for cmd.exe. Arguments are validated before they reach
13
+ * here (project and service names are `[A-Za-z0-9_-]`), so this is defence
14
+ * in depth rather than the only barrier.
15
+ */
16
+ export declare function quoteForWindowsShell(argument: string): string;
17
+ /** The spawn arguments and shell flag a command needs on this platform. */
18
+ export declare function resolveSpawnTarget(file: string, args: readonly string[], platform?: NodeJS.Platform): {
19
+ readonly file: string;
20
+ readonly args: string[];
21
+ readonly shell: boolean;
22
+ };
9
23
  /**
10
24
  * Runs a short-lived command and buffers its output.
11
25
  *
@@ -18,7 +32,9 @@ export declare function execCommand(file: string, args: readonly string[], cwd:
18
32
  * Runs a command with inherited stdio and no timeout.
19
33
  *
20
34
  * Use for long-running processes (dev servers) and for dependency installs
21
- * so output streams to the user and slow commands are not killed.
35
+ * so output streams to the user and slow commands are not killed. Pass a
36
+ * `signal` to stop the process from the outside; an aborted run resolves
37
+ * rather than rejects, since stopping it was the caller's intent.
22
38
  */
23
39
  export declare function runStreaming(file: string, args: readonly string[], cwd: string, options?: ExecOptions): Promise<void>;
24
40
  //# sourceMappingURL=utils.exec.d.ts.map
@@ -1,6 +1,51 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  const execFileAsync = promisify(execFile);
4
+ /**
5
+ * Commands that are `.cmd` shims rather than executables on Windows.
6
+ *
7
+ * Node refuses to spawn a `.cmd`/`.bat` file without a shell (EINVAL since
8
+ * the fix for CVE-2024-27980), and spawning the bare name fails with ENOENT
9
+ * because only `pnpm.cmd` exists. Every `zudojs create` on Windows therefore
10
+ * failed at the install step, and `zudojs dev` never started anything.
11
+ */
12
+ const WINDOWS_SHIM_COMMANDS = new Set([
13
+ "npm",
14
+ "npx",
15
+ "pnpm",
16
+ "pnpx",
17
+ "yarn",
18
+ "bun",
19
+ "bunx",
20
+ "ng",
21
+ "flutter",
22
+ "tsx",
23
+ ]);
24
+ /** Characters that need quoting when a command line goes through cmd.exe. */
25
+ const CMD_UNSAFE = /[\s"&|<>^()%!]/;
26
+ /**
27
+ * Quotes one argument for cmd.exe. Arguments are validated before they reach
28
+ * here (project and service names are `[A-Za-z0-9_-]`), so this is defence
29
+ * in depth rather than the only barrier.
30
+ */
31
+ export function quoteForWindowsShell(argument) {
32
+ if (argument.length === 0)
33
+ return '""';
34
+ if (!CMD_UNSAFE.test(argument))
35
+ return argument;
36
+ return `"${argument.replace(/"/g, '\\"')}"`;
37
+ }
38
+ /** The spawn arguments and shell flag a command needs on this platform. */
39
+ export function resolveSpawnTarget(file, args, platform = process.platform) {
40
+ if (platform === "win32" && WINDOWS_SHIM_COMMANDS.has(file)) {
41
+ return {
42
+ file,
43
+ args: args.map(quoteForWindowsShell),
44
+ shell: true,
45
+ };
46
+ }
47
+ return { file, args: Array.from(args), shell: false };
48
+ }
4
49
  /**
5
50
  * Runs a short-lived command and buffers its output.
6
51
  *
@@ -9,10 +54,13 @@ const execFileAsync = promisify(execFile);
9
54
  * use {@link runStreaming} instead.
10
55
  */
11
56
  export async function execCommand(file, args, cwd, options = {}) {
12
- const { stdout, stderr } = await execFileAsync(file, args, {
57
+ const target = resolveSpawnTarget(file, args);
58
+ const { stdout, stderr } = await execFileAsync(target.file, target.args, {
13
59
  cwd,
14
60
  timeout: options.timeout ?? 120000,
61
+ shell: target.shell,
15
62
  ...(options.env ? { env: options.env } : {}),
63
+ ...(options.signal ? { signal: options.signal } : {}),
16
64
  });
17
65
  return { stdout, stderr };
18
66
  }
@@ -20,20 +68,33 @@ export async function execCommand(file, args, cwd, options = {}) {
20
68
  * Runs a command with inherited stdio and no timeout.
21
69
  *
22
70
  * Use for long-running processes (dev servers) and for dependency installs
23
- * so output streams to the user and slow commands are not killed.
71
+ * so output streams to the user and slow commands are not killed. Pass a
72
+ * `signal` to stop the process from the outside; an aborted run resolves
73
+ * rather than rejects, since stopping it was the caller's intent.
24
74
  */
25
75
  export function runStreaming(file, args, cwd, options = {}) {
76
+ const target = resolveSpawnTarget(file, args);
26
77
  return new Promise((resolve, reject) => {
27
- const child = spawn(file, Array.from(args), {
78
+ const child = spawn(target.file, target.args, {
28
79
  cwd,
29
80
  stdio: "inherit",
81
+ shell: target.shell,
30
82
  ...(options.env ? { env: options.env } : {}),
83
+ ...(options.signal ? { signal: options.signal } : {}),
31
84
  });
32
85
  child.on("error", (error) => {
86
+ if (error.name === "AbortError") {
87
+ resolve();
88
+ return;
89
+ }
90
+ if (error.code === "ENOENT") {
91
+ reject(new Error(`Command "${file}" was not found. Install it and make sure it is on your PATH.`));
92
+ return;
93
+ }
33
94
  reject(error);
34
95
  });
35
96
  child.on("exit", (code, signal) => {
36
- if (code === 0) {
97
+ if (code === 0 || options.signal?.aborted) {
37
98
  resolve();
38
99
  }
39
100
  else {
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "zudojs-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Command-line interface for scaffolding, generating, and managing Zudojs framework projects.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/src/index.js",
8
12
  "module": "./dist/src/index.js",
@@ -20,15 +24,14 @@
20
24
  "dist",
21
25
  "!dist/**/*.map",
22
26
  "!dist/**/*.tsbuildinfo",
23
- "!dist/.tsbuildinfo",
24
- "src/scripts/postinstall.cjs"
27
+ "!dist/.tsbuildinfo"
25
28
  ],
26
29
  "dependencies": {
27
30
  "@clack/prompts": "^1.7.0",
28
- "@zudojs/config": "1.0.0",
29
- "@zudojs/core": "1.0.0",
30
- "@zudojs/errors": "1.0.0",
31
- "@zudojs/logger": "1.0.0"
31
+ "@zudojs/config": "1.0.1",
32
+ "@zudojs/core": "1.1.0",
33
+ "@zudojs/errors": "1.0.1",
34
+ "@zudojs/logger": "1.1.0"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@types/node": "^26.4.1",
@@ -59,12 +62,11 @@
59
62
  "directory": "packages/cli"
60
63
  },
61
64
  "scripts": {
62
- "build": "tsc -p tsconfig.json && mkdir -p dist/src/scripts && cp src/scripts/postinstall.cjs dist/src/scripts/postinstall.cjs",
65
+ "build": "tsc -p tsconfig.json",
63
66
  "dev": "tsx src/bin/zudojs.ts",
64
67
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
65
68
  "clean": "rm -rf dist",
66
69
  "test": "vitest run",
67
- "test:watch": "vitest",
68
- "postinstall": "node src/scripts/postinstall.cjs"
70
+ "test:watch": "vitest"
69
71
  }
70
72
  }
@@ -1,77 +0,0 @@
1
- /**
2
- * @zudojs/cli — Postinstall Version Check
3
- *
4
- * Runs after `npm install -g` to warn users if a newer version is available.
5
- */
6
-
7
- const fs = require("node:fs");
8
- const path = require("node:path");
9
-
10
- const packageJsonPath = path.join(__dirname, "..", "..", "package.json");
11
-
12
- function getInstalledVersion() {
13
- try {
14
- const content = fs.readFileSync(packageJsonPath, "utf-8");
15
- const pkg = JSON.parse(content);
16
- return pkg.version || "unknown";
17
- } catch {
18
- return "unknown";
19
- }
20
- }
21
-
22
- async function checkLatestVersion(packageName) {
23
- try {
24
- const controller = new AbortController();
25
- const timeout = setTimeout(() => controller.abort(), 5000);
26
-
27
- const response = await fetch(
28
- `https://registry.npmjs.org/${packageName}/latest`,
29
- { signal: controller.signal },
30
- );
31
- clearTimeout(timeout);
32
-
33
- if (!response.ok) {
34
- return null;
35
- }
36
-
37
- const data = await response.json();
38
- return data.version || null;
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- /**
45
- * Whether the update check should be skipped.
46
- *
47
- * The check reaches out to registry.npmjs.org on every install. That is
48
- * unwanted in CI, in Docker builds and on air-gapped machines, and there was
49
- * previously no way to turn it off.
50
- */
51
- function shouldSkip() {
52
- if (process.env.ZUDOJS_NO_UPDATE_CHECK) return true;
53
- if (process.env.CI) return true;
54
- if (process.env.NODE_ENV === "test") return true;
55
- if (process.env.npm_config_offline === "true") return true;
56
- // Only a global install is the one the user upgrades with `npm i -g`.
57
- if (process.env.npm_config_global !== "true") return true;
58
- return false;
59
- }
60
-
61
- async function main() {
62
- if (shouldSkip()) {
63
- return;
64
- }
65
-
66
- const installed = getInstalledVersion();
67
- const latest = await checkLatestVersion("zudojs-cli");
68
-
69
- if (latest && latest !== installed) {
70
- console.log(
71
- `\n zudojs ${installed} is installed, but version ${latest} is available.`,
72
- );
73
- console.log(` Run: npm install -g zudojs-cli@latest\n`);
74
- }
75
- }
76
-
77
- main().catch(() => {});
@@ -1,77 +0,0 @@
1
- /**
2
- * @zudojs/cli — Postinstall Version Check
3
- *
4
- * Runs after `npm install -g` to warn users if a newer version is available.
5
- */
6
-
7
- const fs = require("node:fs");
8
- const path = require("node:path");
9
-
10
- const packageJsonPath = path.join(__dirname, "..", "..", "package.json");
11
-
12
- function getInstalledVersion() {
13
- try {
14
- const content = fs.readFileSync(packageJsonPath, "utf-8");
15
- const pkg = JSON.parse(content);
16
- return pkg.version || "unknown";
17
- } catch {
18
- return "unknown";
19
- }
20
- }
21
-
22
- async function checkLatestVersion(packageName) {
23
- try {
24
- const controller = new AbortController();
25
- const timeout = setTimeout(() => controller.abort(), 5000);
26
-
27
- const response = await fetch(
28
- `https://registry.npmjs.org/${packageName}/latest`,
29
- { signal: controller.signal },
30
- );
31
- clearTimeout(timeout);
32
-
33
- if (!response.ok) {
34
- return null;
35
- }
36
-
37
- const data = await response.json();
38
- return data.version || null;
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- /**
45
- * Whether the update check should be skipped.
46
- *
47
- * The check reaches out to registry.npmjs.org on every install. That is
48
- * unwanted in CI, in Docker builds and on air-gapped machines, and there was
49
- * previously no way to turn it off.
50
- */
51
- function shouldSkip() {
52
- if (process.env.ZUDOJS_NO_UPDATE_CHECK) return true;
53
- if (process.env.CI) return true;
54
- if (process.env.NODE_ENV === "test") return true;
55
- if (process.env.npm_config_offline === "true") return true;
56
- // Only a global install is the one the user upgrades with `npm i -g`.
57
- if (process.env.npm_config_global !== "true") return true;
58
- return false;
59
- }
60
-
61
- async function main() {
62
- if (shouldSkip()) {
63
- return;
64
- }
65
-
66
- const installed = getInstalledVersion();
67
- const latest = await checkLatestVersion("zudojs-cli");
68
-
69
- if (latest && latest !== installed) {
70
- console.log(
71
- `\n zudojs ${installed} is installed, but version ${latest} is available.`,
72
- );
73
- console.log(` Run: npm install -g zudojs-cli@latest\n`);
74
- }
75
- }
76
-
77
- main().catch(() => {});