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.
- package/README.md +16 -6
- package/dist/src/adapters/frontend/frontendAdapter.type.d.ts +6 -0
- package/dist/src/bin/zudojs.js +8 -32
- package/dist/src/cliApplication/cliApplication.core.js +2 -2
- package/dist/src/cliApplication/cliApplication.logger.d.ts +28 -0
- package/dist/src/cliApplication/cliApplication.logger.js +68 -0
- package/dist/src/cliApplication/index.d.ts +1 -3
- package/dist/src/cliApplication/index.js +1 -3
- package/dist/src/cliVersion/cliVersion.update.d.ts +38 -0
- package/dist/src/cliVersion/cliVersion.update.js +63 -0
- package/dist/src/cliVersion/index.d.ts +2 -1
- package/dist/src/cliVersion/index.js +2 -1
- package/dist/src/commands/add.command.d.ts +29 -0
- package/dist/src/commands/add.command.js +94 -109
- package/dist/src/commands/build.command.d.ts +8 -0
- package/dist/src/commands/build.command.js +12 -18
- package/dist/src/commands/create.command.js +8 -5
- package/dist/src/commands/dev.command.d.ts +30 -2
- package/dist/src/commands/dev.command.js +104 -183
- package/dist/src/commands/doctor.command.d.ts +18 -0
- package/dist/src/commands/doctor.command.js +141 -113
- package/dist/src/commands/generate.command.d.ts +2 -1
- package/dist/src/commands/generate.command.js +23 -42
- package/dist/src/commands/info.command.js +61 -30
- package/dist/src/constants/index.d.ts +18 -2
- package/dist/src/constants/index.js +31 -2
- package/dist/src/generators/frontend/frontendGenerator.core.d.ts +2 -0
- package/dist/src/generators/frontend/frontendGenerator.core.js +11 -0
- package/dist/src/generators/frontend/frontendPipeline.js +45 -2
- package/dist/src/generators/fullstack/fullstackComposer.core.d.ts +2 -0
- package/dist/src/generators/fullstack/fullstackComposer.core.js +8 -22
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +3 -2
- package/dist/src/installers/dependency.installer.d.ts +9 -0
- package/dist/src/installers/dependency.installer.js +21 -0
- package/dist/src/installers/index.d.ts +1 -3
- package/dist/src/installers/index.js +1 -3
- package/dist/src/resolvers/architecture.resolver.d.ts +11 -1
- package/dist/src/resolvers/architecture.resolver.js +12 -13
- package/dist/src/resolvers/configuration/configurationResolver.core.d.ts +10 -1
- package/dist/src/resolvers/configuration/configurationResolver.core.js +68 -29
- package/dist/src/resolvers/index.d.ts +1 -0
- package/dist/src/resolvers/index.js +1 -0
- package/dist/src/resolvers/layout/index.d.ts +5 -0
- package/dist/src/resolvers/layout/index.js +5 -0
- package/dist/src/resolvers/layout/projectLayout.core.d.ts +60 -0
- package/dist/src/resolvers/layout/projectLayout.core.js +236 -0
- package/dist/src/resolvers/project.resolver.d.ts +5 -0
- package/dist/src/resolvers/project.resolver.js +10 -6
- package/dist/src/templates/microservice/microservice.template.d.ts +0 -1
- package/dist/src/templates/microservice/microservice.template.js +2 -10
- package/dist/src/templates/modular-monolith/modularMonolith.template.d.ts +0 -1
- package/dist/src/templates/modular-monolith/modularMonolith.template.js +4 -7
- package/dist/src/templates/monolith/monolith.template.d.ts +0 -1
- package/dist/src/templates/monolith/monolith.template.js +4 -7
- package/dist/src/templates/shared/appRuntime.template.d.ts +0 -11
- package/dist/src/templates/shared/appRuntime.template.js +0 -20
- package/dist/src/templates/shared/index.d.ts +1 -0
- package/dist/src/templates/shared/index.js +1 -0
- package/dist/src/templates/shared/pnpm.template.d.ts +27 -0
- package/dist/src/templates/shared/pnpm.template.js +57 -0
- package/dist/src/utils/utils.detect.d.ts +9 -0
- package/dist/src/utils/utils.detect.js +11 -11
- package/dist/src/utils/utils.exec.d.ts +17 -1
- package/dist/src/utils/utils.exec.js +65 -4
- package/package.json +8 -6
- package/dist/src/scripts/postinstall.cjs +0 -77
- package/src/scripts/postinstall.cjs +0 -77
|
@@ -1,15 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* zudojs-cli — Dev Command
|
|
3
3
|
*
|
|
4
|
-
* The `zudojs dev` command.
|
|
5
|
-
*
|
|
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
|
-
import { existsSync
|
|
8
|
-
import { join } from "node:path";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { basename, join, relative } from "node:path";
|
|
9
18
|
import { runStreaming } from "../utils/utils.exec.js";
|
|
10
19
|
import { CLIValidationError, CLIGenerationError } from "../errors/index.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
20
|
+
import { getRunScriptCommand } from "../installers/dependency.installer.js";
|
|
21
|
+
import { resolveProjectLayout, } from "../resolvers/layout/projectLayout.core.js";
|
|
22
|
+
/**
|
|
23
|
+
* Computes the servers `zudojs dev` starts for a project layout.
|
|
24
|
+
*
|
|
25
|
+
* Exported so the selection can be tested without spawning anything.
|
|
26
|
+
*/
|
|
27
|
+
export function planDevServers(layout, options = {}) {
|
|
28
|
+
const servers = [];
|
|
29
|
+
const env = options.port !== undefined
|
|
30
|
+
? { ...process.env, PORT: String(options.port) }
|
|
31
|
+
: undefined;
|
|
32
|
+
if (!options.frontendOnly) {
|
|
33
|
+
for (const dir of layout.backendDirs) {
|
|
34
|
+
if (!existsSync(join(dir, "package.json")))
|
|
35
|
+
continue;
|
|
36
|
+
const [file, ...args] = getRunScriptCommand(layout.packageManager, "dev");
|
|
37
|
+
servers.push({
|
|
38
|
+
label: labelFor(layout.root, dir, "backend"),
|
|
39
|
+
cwd: dir,
|
|
40
|
+
file,
|
|
41
|
+
args,
|
|
42
|
+
...(env ? { env } : {}),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!options.backendOnly && layout.frontendDir !== undefined) {
|
|
47
|
+
const spec = frontendServer(layout, layout.frontendDir);
|
|
48
|
+
if (spec)
|
|
49
|
+
servers.push(spec);
|
|
50
|
+
}
|
|
51
|
+
return servers;
|
|
52
|
+
}
|
|
53
|
+
function labelFor(root, dir, fallback) {
|
|
54
|
+
const rel = relative(root, dir);
|
|
55
|
+
return rel === "" ? fallback : basename(dir);
|
|
56
|
+
}
|
|
57
|
+
function frontendServer(layout, dir) {
|
|
58
|
+
const framework = layout.frontendFramework ?? "react";
|
|
59
|
+
if (framework === "none")
|
|
60
|
+
return null;
|
|
61
|
+
if (framework === "flutter") {
|
|
62
|
+
if (!existsSync(join(dir, "pubspec.yaml")))
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
label: "web",
|
|
66
|
+
cwd: dir,
|
|
67
|
+
file: "flutter",
|
|
68
|
+
args: ["run", "--debug"],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (!existsSync(join(dir, "package.json")))
|
|
72
|
+
return null;
|
|
73
|
+
// Angular's package.json has `start: ng serve`; React Native's has `start`.
|
|
74
|
+
const script = framework === "angular" || framework === "react-native" ? "start" : "dev";
|
|
75
|
+
const [file, ...args] = getRunScriptCommand(layout.packageManager, script);
|
|
76
|
+
return {
|
|
77
|
+
label: labelFor(layout.root, dir, "frontend"),
|
|
78
|
+
cwd: dir,
|
|
79
|
+
file,
|
|
80
|
+
args,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
13
83
|
export async function runDevCommand(context) {
|
|
14
84
|
const frontendOnly = context.values["frontend-only"] === true;
|
|
15
85
|
const backendOnly = context.values["backend-only"] === true;
|
|
@@ -17,194 +87,45 @@ export async function runDevCommand(context) {
|
|
|
17
87
|
if (frontendOnly && backendOnly) {
|
|
18
88
|
throw new CLIValidationError("Cannot use --frontend-only and --backend-only together.");
|
|
19
89
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
throw new CLIValidationError("No Zudojs project found. Run `zudojs create` first.");
|
|
90
|
+
const layout = resolveProjectLayout(context.cwd);
|
|
91
|
+
if (!layout) {
|
|
92
|
+
throw new CLIValidationError("No Zudojs project found in this directory. Run `zudojs create` first.");
|
|
24
93
|
}
|
|
25
|
-
context.logger.info(`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
context.logger.info(`Backend architecture: ${config.backend.architecture}`);
|
|
94
|
+
context.logger.info(`Project type: ${layout.projectType}`);
|
|
95
|
+
if (layout.backendDirs.length > 0) {
|
|
96
|
+
context.logger.info(`Backend architecture: ${layout.architecture}`);
|
|
29
97
|
}
|
|
30
|
-
if (
|
|
31
|
-
context.logger.info(`Frontend: ${
|
|
98
|
+
if (layout.frontendFramework) {
|
|
99
|
+
context.logger.info(`Frontend: ${layout.frontendFramework}`);
|
|
32
100
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (SAFE_PATH_SEGMENT.test(service))
|
|
38
|
-
return true;
|
|
39
|
-
context.logger.warn(`Ignoring invalid service name in .zudojs/manifest.json: "${service}"`);
|
|
40
|
-
return false;
|
|
101
|
+
const servers = planDevServers(layout, {
|
|
102
|
+
frontendOnly,
|
|
103
|
+
backendOnly,
|
|
104
|
+
...(port !== undefined ? { port } : {}),
|
|
41
105
|
});
|
|
42
|
-
|
|
43
|
-
if (!frontendOnly &&
|
|
44
|
-
(config.type === "backend" || config.type === "fullstack")) {
|
|
45
|
-
if (config.backend?.architecture === "microservice") {
|
|
46
|
-
processes.push(...startMicroserviceDev(context.cwd, services));
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
processes.push(startBackendDev(context.cwd, config, port));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!backendOnly &&
|
|
53
|
-
(config.type === "frontend" || config.type === "fullstack")) {
|
|
54
|
-
if (config.frontend && config.frontend.framework !== "none") {
|
|
55
|
-
processes.push(startFrontendDev(context.cwd, config));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (processes.length === 0) {
|
|
106
|
+
if (servers.length === 0) {
|
|
59
107
|
context.logger.warn("No development servers to start.");
|
|
60
108
|
return;
|
|
61
109
|
}
|
|
62
|
-
|
|
110
|
+
for (const server of servers) {
|
|
111
|
+
context.logger.info(`Starting ${server.label}: ${server.file} ${server.args.join(" ")}`);
|
|
112
|
+
}
|
|
113
|
+
// One controller for every server: when any of them exits with an error
|
|
114
|
+
// the others are stopped too, instead of being left running detached
|
|
115
|
+
// after the command has already reported failure.
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
const runs = servers.map((server) => runStreaming(server.file, server.args, server.cwd, {
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
...(server.env ? { env: server.env } : {}),
|
|
120
|
+
}).catch((error) => {
|
|
121
|
+
controller.abort();
|
|
122
|
+
throw error;
|
|
123
|
+
}));
|
|
63
124
|
try {
|
|
64
|
-
await Promise.all(
|
|
125
|
+
await Promise.all(runs);
|
|
65
126
|
}
|
|
66
127
|
catch (error) {
|
|
67
128
|
throw new CLIGenerationError("Development server failed to start.", error);
|
|
68
129
|
}
|
|
69
130
|
}
|
|
70
|
-
function configFromManifest(manifest) {
|
|
71
|
-
if (!manifest)
|
|
72
|
-
return null;
|
|
73
|
-
return {
|
|
74
|
-
name: "zudojs-project",
|
|
75
|
-
type: manifest.projectType ?? "backend",
|
|
76
|
-
backend: manifest.backend
|
|
77
|
-
? { architecture: manifest.backend.architecture }
|
|
78
|
-
: { architecture: manifest.architecture },
|
|
79
|
-
frontend: manifest.frontend
|
|
80
|
-
? { framework: manifest.frontend.framework }
|
|
81
|
-
: undefined,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function readProjectConfig(cwd) {
|
|
85
|
-
const configPath = join(cwd, "zudojs.config.ts");
|
|
86
|
-
const configPathJs = join(cwd, "zudojs.config.js");
|
|
87
|
-
if (existsSync(configPath)) {
|
|
88
|
-
const content = readFileSync(configPath, "utf-8");
|
|
89
|
-
return parseConfigContent(content, cwd);
|
|
90
|
-
}
|
|
91
|
-
if (existsSync(configPathJs)) {
|
|
92
|
-
const content = readFileSync(configPathJs, "utf-8");
|
|
93
|
-
return parseConfigContent(content, cwd);
|
|
94
|
-
}
|
|
95
|
-
const pkgPath = join(cwd, "package.json");
|
|
96
|
-
if (existsSync(pkgPath)) {
|
|
97
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
98
|
-
if (pkg.zudojs) {
|
|
99
|
-
return {
|
|
100
|
-
name: pkg.name ?? "unknown",
|
|
101
|
-
type: pkg.zudojs.projectType ?? "backend",
|
|
102
|
-
backend: pkg.zudojs.architecture
|
|
103
|
-
? { architecture: pkg.zudojs.architecture }
|
|
104
|
-
: undefined,
|
|
105
|
-
frontend: pkg.zudojs.frontend
|
|
106
|
-
? { framework: pkg.zudojs.frontend }
|
|
107
|
-
: undefined,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
function parseConfigContent(content, cwd) {
|
|
114
|
-
const nameMatch = content.match(/name:\s*["']([^"']+)["']/);
|
|
115
|
-
const typeMatch = content.match(/projectType:\s*["'](\w+)["']/);
|
|
116
|
-
const architectureMatch = content.match(/architecture:\s*["'](\w+)["']/);
|
|
117
|
-
const frontendMatch = content.match(/frontend:\s*\{[\s\S]*?framework:\s*["']([^"']+)["']/);
|
|
118
|
-
const pkgPath = join(cwd, "package.json");
|
|
119
|
-
let name = "unknown";
|
|
120
|
-
if (existsSync(pkgPath)) {
|
|
121
|
-
try {
|
|
122
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
123
|
-
name = pkg.name ?? name;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// ignore
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
name: nameMatch?.[1] ?? name,
|
|
131
|
-
type: typeMatch?.[1] ?? "backend",
|
|
132
|
-
backend: architectureMatch?.[1]
|
|
133
|
-
? { architecture: architectureMatch[1] }
|
|
134
|
-
: undefined,
|
|
135
|
-
frontend: frontendMatch?.[1] ? { framework: frontendMatch[1] } : undefined,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
async function startBackendDev(cwd, config, port) {
|
|
139
|
-
// `--port=N` was appended to the tsx argv, where it became an argument of
|
|
140
|
-
// the watched script and was ignored. Generated servers read PORT from the
|
|
141
|
-
// environment, so that is where the option has to land.
|
|
142
|
-
const options = port !== undefined
|
|
143
|
-
? { env: { ...process.env, PORT: String(port) } }
|
|
144
|
-
: undefined;
|
|
145
|
-
const entry = config.backend?.architecture === "microservice"
|
|
146
|
-
? "apps/gateway/src"
|
|
147
|
-
: "src";
|
|
148
|
-
await runStreaming("tsx", ["watch", entry], cwd, options);
|
|
149
|
-
}
|
|
150
|
-
function startMicroserviceDev(cwd, services) {
|
|
151
|
-
const serviceDirs = services.map((service) => {
|
|
152
|
-
const nested = join(cwd, "apps", "services", service);
|
|
153
|
-
return existsSync(join(nested, "src"))
|
|
154
|
-
? nested
|
|
155
|
-
: join(cwd, "apps", service);
|
|
156
|
-
});
|
|
157
|
-
const promises = [];
|
|
158
|
-
const gatewayDir = join(cwd, "apps", "gateway");
|
|
159
|
-
if (existsSync(join(gatewayDir, "src"))) {
|
|
160
|
-
promises.push(runStreaming("tsx", ["watch", "src"], gatewayDir));
|
|
161
|
-
}
|
|
162
|
-
for (const dir of serviceDirs) {
|
|
163
|
-
if (existsSync(join(dir, "src"))) {
|
|
164
|
-
promises.push(runStreaming("tsx", ["watch", "src"], dir));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return promises;
|
|
168
|
-
}
|
|
169
|
-
async function startFrontendDev(cwd, config) {
|
|
170
|
-
const framework = config.frontend?.framework ?? "react";
|
|
171
|
-
const frontendDir = join(cwd, "apps", "web");
|
|
172
|
-
switch (framework) {
|
|
173
|
-
case "react":
|
|
174
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
175
|
-
break;
|
|
176
|
-
case "next":
|
|
177
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
178
|
-
break;
|
|
179
|
-
case "vue":
|
|
180
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
181
|
-
break;
|
|
182
|
-
case "nuxt":
|
|
183
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
184
|
-
break;
|
|
185
|
-
case "angular":
|
|
186
|
-
await runStreaming("ng", ["serve"], frontendDir);
|
|
187
|
-
break;
|
|
188
|
-
case "svelte":
|
|
189
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
190
|
-
break;
|
|
191
|
-
case "sveltekit":
|
|
192
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
193
|
-
break;
|
|
194
|
-
case "astro":
|
|
195
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
196
|
-
break;
|
|
197
|
-
case "vanilla":
|
|
198
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
199
|
-
break;
|
|
200
|
-
case "flutter":
|
|
201
|
-
await runStreaming("flutter", ["run", "--debug"], frontendDir);
|
|
202
|
-
break;
|
|
203
|
-
case "react-native":
|
|
204
|
-
await runStreaming("npx", ["react-native", "start"], frontendDir);
|
|
205
|
-
break;
|
|
206
|
-
default:
|
|
207
|
-
await runStreaming("npm", ["run", "dev"], frontendDir);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
131
|
//# sourceMappingURL=dev.command.js.map
|
|
@@ -4,5 +4,23 @@
|
|
|
4
4
|
* The `zudojs doctor` command for project diagnostics.
|
|
5
5
|
*/
|
|
6
6
|
import type { CLIContext } from "../cliType/cliType.type.js";
|
|
7
|
+
export interface DoctorCheck {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly passed: boolean;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a failed check is fatal. Advisory checks (a missing lock file in
|
|
13
|
+
* a project created with `--no-install`, for example) are reported as
|
|
14
|
+
* warnings and do not fail the command.
|
|
15
|
+
*/
|
|
16
|
+
readonly severity: "error" | "warning";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs every diagnostic for the project at `cwd`.
|
|
20
|
+
*
|
|
21
|
+
* Exported so the checks can be run against a scaffolded directory in tests
|
|
22
|
+
* without going through the logger.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runDoctorChecks(cwd: string): DoctorCheck[];
|
|
7
25
|
export declare function runDoctorCommand(context: CLIContext): Promise<void>;
|
|
8
26
|
//# sourceMappingURL=doctor.command.d.ts.map
|
|
@@ -4,22 +4,33 @@
|
|
|
4
4
|
* The `zudojs doctor` command for project diagnostics.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
7
|
+
import { join, relative } from "node:path";
|
|
8
8
|
import { CLIValidationError } from "../errors/index.js";
|
|
9
|
+
import { FEATURE_PACKAGES } from "../constants/index.js";
|
|
10
|
+
import { resolveProjectLayout, } from "../resolvers/layout/projectLayout.core.js";
|
|
11
|
+
/**
|
|
12
|
+
* Runs every diagnostic for the project at `cwd`.
|
|
13
|
+
*
|
|
14
|
+
* Exported so the checks can be run against a scaffolded directory in tests
|
|
15
|
+
* without going through the logger.
|
|
16
|
+
*/
|
|
17
|
+
export function runDoctorChecks(cwd) {
|
|
18
|
+
const layout = resolveProjectLayout(cwd);
|
|
19
|
+
const checks = [checkNodeVersion(), checkProject(layout)];
|
|
20
|
+
if (!layout) {
|
|
21
|
+
return checks;
|
|
22
|
+
}
|
|
23
|
+
checks.push(checkPackageManager(layout), checkInstalled(layout), checkTypeScriptConfig(layout), checkDependencies(layout), checkFeatures(layout));
|
|
24
|
+
return checks;
|
|
25
|
+
}
|
|
9
26
|
export async function runDoctorCommand(context) {
|
|
10
|
-
const checks =
|
|
27
|
+
const checks = runDoctorChecks(context.cwd);
|
|
11
28
|
const warnings = [];
|
|
12
29
|
const errors = [];
|
|
13
|
-
checks.push(checkNodeVersion());
|
|
14
|
-
checks.push(checkPackageManager(context.cwd));
|
|
15
|
-
checks.push(checkTypeScriptConfig(context.cwd));
|
|
16
|
-
checks.push(checkZudojsConfig(context.cwd));
|
|
17
|
-
checks.push(checkDependencies(context));
|
|
18
|
-
checks.push(checkArchitectureViolations(context.cwd));
|
|
19
30
|
context.logger.info("Zudojs Doctor - Project Diagnostics");
|
|
20
31
|
context.logger.info("");
|
|
21
32
|
for (const check of checks) {
|
|
22
|
-
const symbol = check.passed ? "✔" : "✖";
|
|
33
|
+
const symbol = check.passed ? "✔" : check.severity === "error" ? "✖" : "⚠";
|
|
23
34
|
context.logger.info(`${symbol} ${check.name}: ${check.message}`);
|
|
24
35
|
if (!check.passed) {
|
|
25
36
|
if (check.severity === "warning") {
|
|
@@ -61,153 +72,170 @@ function checkNodeVersion() {
|
|
|
61
72
|
severity: "error",
|
|
62
73
|
passed,
|
|
63
74
|
message: passed
|
|
64
|
-
? `Node.js ${version} (
|
|
65
|
-
: `Node.js ${version} (
|
|
75
|
+
? `Node.js ${version} (meets minimum v24)`
|
|
76
|
+
: `Node.js ${version} (requires >= v24)`,
|
|
66
77
|
};
|
|
67
78
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
function checkProject(layout) {
|
|
80
|
+
if (!layout) {
|
|
81
|
+
return {
|
|
82
|
+
name: "Zudojs project",
|
|
83
|
+
severity: "error",
|
|
84
|
+
passed: false,
|
|
85
|
+
message: "No Zudojs project found (no .zudojs/manifest.json, zudojs.config.ts or zudojs block in package.json)",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const source = layout.source === "manifest"
|
|
89
|
+
? ".zudojs/manifest.json"
|
|
90
|
+
: layout.source === "config"
|
|
91
|
+
? "zudojs.config.ts"
|
|
92
|
+
: "package.json#zudojs";
|
|
93
|
+
return {
|
|
94
|
+
name: "Zudojs project",
|
|
95
|
+
severity: "error",
|
|
96
|
+
passed: true,
|
|
97
|
+
message: `${layout.projectType} (${layout.architecture}) from ${source}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function checkPackageManager(layout) {
|
|
101
|
+
const lockFiles = {
|
|
102
|
+
pnpm: ["pnpm-lock.yaml"],
|
|
103
|
+
npm: ["package-lock.json"],
|
|
104
|
+
yarn: ["yarn.lock"],
|
|
105
|
+
bun: ["bun.lock", "bun.lockb"],
|
|
106
|
+
};
|
|
107
|
+
const expected = lockFiles[layout.packageManager] ?? [];
|
|
108
|
+
const passed = expected.some((file) => existsSync(join(layout.root, file)));
|
|
74
109
|
return {
|
|
75
110
|
name: "Package manager",
|
|
76
111
|
severity: "warning",
|
|
77
112
|
passed,
|
|
78
|
-
message: passed
|
|
113
|
+
message: passed
|
|
114
|
+
? `${layout.packageManager} (lock file present)`
|
|
115
|
+
: `${layout.packageManager} configured but no lock file found; run "${layout.packageManager} install"`,
|
|
79
116
|
};
|
|
80
117
|
}
|
|
81
|
-
function
|
|
82
|
-
const passed = existsSync(join(
|
|
83
|
-
existsSync(join(cwd, "tsconfig.base.json"));
|
|
118
|
+
function checkInstalled(layout) {
|
|
119
|
+
const passed = existsSync(join(layout.root, "node_modules"));
|
|
84
120
|
return {
|
|
85
|
-
name: "
|
|
86
|
-
severity: "
|
|
121
|
+
name: "Dependencies installed",
|
|
122
|
+
severity: "warning",
|
|
87
123
|
passed,
|
|
88
|
-
message: passed
|
|
124
|
+
message: passed
|
|
125
|
+
? "node_modules present"
|
|
126
|
+
: `node_modules missing; run "${layout.packageManager} install"`,
|
|
89
127
|
};
|
|
90
128
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
message = hasPkgConfig
|
|
99
|
-
? "Zudojs config in package.json"
|
|
100
|
-
: hasConfig
|
|
101
|
-
? "zudojs.config.ts found"
|
|
102
|
-
: "Zudojs config in package.json";
|
|
129
|
+
/** Directories that must each carry a tsconfig.json. */
|
|
130
|
+
function appDirs(layout) {
|
|
131
|
+
const dirs = [...layout.backendDirs];
|
|
132
|
+
if (layout.frontendDir !== undefined &&
|
|
133
|
+
layout.frontendFramework !== "flutter" &&
|
|
134
|
+
existsSync(join(layout.frontendDir, "package.json"))) {
|
|
135
|
+
dirs.push(layout.frontendDir);
|
|
103
136
|
}
|
|
137
|
+
return dirs;
|
|
138
|
+
}
|
|
139
|
+
function describe(layout, dir) {
|
|
140
|
+
const rel = relative(layout.root, dir);
|
|
141
|
+
return rel === "" ? "." : rel;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A workspace root has no tsconfig.json of its own — the apps do. This
|
|
145
|
+
* check used to look only at the root, so every fullstack and microservice
|
|
146
|
+
* project failed the doctor the moment it was created.
|
|
147
|
+
*/
|
|
148
|
+
function checkTypeScriptConfig(layout) {
|
|
149
|
+
const missing = appDirs(layout).filter((dir) => !existsSync(join(dir, "tsconfig.json")) &&
|
|
150
|
+
!existsSync(join(dir, "tsconfig.base.json")));
|
|
151
|
+
const passed = missing.length === 0;
|
|
104
152
|
return {
|
|
105
|
-
name: "
|
|
153
|
+
name: "TypeScript configuration",
|
|
106
154
|
severity: "error",
|
|
107
155
|
passed,
|
|
108
|
-
message
|
|
156
|
+
message: passed
|
|
157
|
+
? "tsconfig.json found in every app"
|
|
158
|
+
: `No tsconfig.json in: ${missing.map((d) => describe(layout, d)).join(", ")}`,
|
|
109
159
|
};
|
|
110
160
|
}
|
|
111
|
-
function
|
|
161
|
+
function readPackageJson(dir) {
|
|
112
162
|
try {
|
|
113
|
-
|
|
114
|
-
if (!existsSync(pkgPath))
|
|
115
|
-
return false;
|
|
116
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
117
|
-
return typeof pkg.zudojs === "object" && pkg.zudojs !== null;
|
|
163
|
+
return JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
118
164
|
}
|
|
119
165
|
catch {
|
|
120
|
-
return
|
|
166
|
+
return null;
|
|
121
167
|
}
|
|
122
168
|
}
|
|
123
|
-
function checkDependencies(
|
|
124
|
-
|
|
125
|
-
if (!existsSync(pkgPath)) {
|
|
126
|
-
return {
|
|
127
|
-
name: "Dependencies",
|
|
128
|
-
severity: "error",
|
|
129
|
-
passed: false,
|
|
130
|
-
message: "No package.json found",
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
135
|
-
const zudojsDeps = Object.keys(pkg.dependencies ?? {}).filter((d) => d.startsWith("@zudojs/"));
|
|
136
|
-
const passed = zudojsDeps.length > 0;
|
|
169
|
+
function checkDependencies(layout) {
|
|
170
|
+
if (layout.backendDirs.length === 0) {
|
|
137
171
|
return {
|
|
138
172
|
name: "Zudojs dependencies",
|
|
139
173
|
severity: "warning",
|
|
140
|
-
passed,
|
|
141
|
-
message:
|
|
142
|
-
? `${zudojsDeps.length} Zudojs packages installed`
|
|
143
|
-
: "No Zudojs packages found",
|
|
174
|
+
passed: true,
|
|
175
|
+
message: "Frontend-only project; no framework packages expected",
|
|
144
176
|
};
|
|
145
177
|
}
|
|
146
|
-
|
|
178
|
+
const found = new Set();
|
|
179
|
+
const unreadable = [];
|
|
180
|
+
for (const dir of layout.backendDirs) {
|
|
181
|
+
const pkg = readPackageJson(dir);
|
|
182
|
+
if (!pkg) {
|
|
183
|
+
unreadable.push(describe(layout, dir));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
for (const name of Object.keys(pkg.dependencies ?? {})) {
|
|
187
|
+
if (name.startsWith("@zudojs/"))
|
|
188
|
+
found.add(name);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (unreadable.length > 0) {
|
|
147
192
|
return {
|
|
148
|
-
name: "
|
|
193
|
+
name: "Zudojs dependencies",
|
|
149
194
|
severity: "error",
|
|
150
195
|
passed: false,
|
|
151
|
-
message:
|
|
196
|
+
message: `Failed to read package.json in: ${unreadable.join(", ")}`,
|
|
152
197
|
};
|
|
153
198
|
}
|
|
199
|
+
const passed = found.size > 0;
|
|
200
|
+
return {
|
|
201
|
+
name: "Zudojs dependencies",
|
|
202
|
+
severity: "warning",
|
|
203
|
+
passed,
|
|
204
|
+
message: passed
|
|
205
|
+
? `${found.size} Zudojs package(s) declared`
|
|
206
|
+
: "No @zudojs/* packages declared",
|
|
207
|
+
};
|
|
154
208
|
}
|
|
155
|
-
|
|
156
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Every feature recorded in a backend package.json must be backed by the
|
|
211
|
+
* package `zudojs add` installs for it.
|
|
212
|
+
*/
|
|
213
|
+
function checkFeatures(layout) {
|
|
157
214
|
const violations = [];
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (archMatch?.[1]) {
|
|
172
|
-
architecture = archMatch[1];
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (architecture === "modular-monolith" || architecture === "microservice") {
|
|
176
|
-
const servicesDir = join(srcDir, "services");
|
|
177
|
-
if (existsSync(servicesDir)) {
|
|
178
|
-
violations.push("src/services/ should be split into modules/ (modular-monolith) or apps/services/ (microservice)");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (architecture === "monolith") {
|
|
182
|
-
const modulesDir = join(srcDir, "modules");
|
|
183
|
-
if (existsSync(modulesDir)) {
|
|
184
|
-
violations.push("src/modules/ found in monolith architecture — consider modular-monolith or microservice architecture");
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
const pkgPath = join(cwd, "package.json");
|
|
188
|
-
if (existsSync(pkgPath)) {
|
|
189
|
-
try {
|
|
190
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
191
|
-
const features = pkg.zudojs?.features ?? [];
|
|
192
|
-
const deps = Object.keys(pkg.dependencies ?? {});
|
|
193
|
-
for (const feature of features) {
|
|
194
|
-
const pkgName = `@zudojs/${feature}`;
|
|
195
|
-
if (!deps.includes(pkgName)) {
|
|
196
|
-
violations.push(`Feature "${feature}" declared in package.json#zudojs.features but ${pkgName} not in dependencies`);
|
|
215
|
+
for (const dir of layout.backendDirs) {
|
|
216
|
+
const pkg = readPackageJson(dir);
|
|
217
|
+
if (!pkg)
|
|
218
|
+
continue;
|
|
219
|
+
const features = Array.isArray(pkg.zudojs?.features)
|
|
220
|
+
? pkg.zudojs.features.filter((f) => typeof f === "string")
|
|
221
|
+
: [];
|
|
222
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
223
|
+
for (const feature of features) {
|
|
224
|
+
const required = FEATURE_PACKAGES[feature] ?? [`@zudojs/${feature}`];
|
|
225
|
+
for (const name of required) {
|
|
226
|
+
if (!deps.includes(name)) {
|
|
227
|
+
violations.push(`${describe(layout, dir)}: feature "${feature}" declared but ${name} is not a dependency`);
|
|
197
228
|
}
|
|
198
229
|
}
|
|
199
230
|
}
|
|
200
|
-
catch {
|
|
201
|
-
// ignore parse errors
|
|
202
|
-
}
|
|
203
231
|
}
|
|
204
232
|
return {
|
|
205
|
-
name: "
|
|
233
|
+
name: "Features",
|
|
206
234
|
severity: "warning",
|
|
207
235
|
passed: violations.length === 0,
|
|
208
236
|
message: violations.length === 0
|
|
209
|
-
? "
|
|
210
|
-
:
|
|
237
|
+
? "Every declared feature has its package"
|
|
238
|
+
: violations.join("; "),
|
|
211
239
|
};
|
|
212
240
|
}
|
|
213
241
|
//# sourceMappingURL=doctor.command.js.map
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* zudojs-cli — Generate Command
|
|
3
3
|
*
|
|
4
4
|
* The `zudojs generate` (alias: `g`) command.
|
|
5
|
-
* Reads
|
|
5
|
+
* Reads the project manifest to determine the architecture and where the
|
|
6
|
+
* backend lives, then places the schematic accordingly.
|
|
6
7
|
*/
|
|
7
8
|
import type { CLIContext } from "../cliType/cliType.type.js";
|
|
8
9
|
export declare function runGenerateCommand(context: CLIContext): Promise<void>;
|