Skip to content

Commit 4548d13

Browse files
committed
fix(cli): bypass bootstrap when command is dev or build
1 parent 8636af3 commit 4548d13

10 files changed

Lines changed: 269 additions & 286 deletions

File tree

packages/cli/src/bin/boot.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {register} from "node:module";
2+
import {join} from "node:path";
3+
import {pathToFileURL} from "node:url";
4+
5+
const EXT = process.env.CLI_MODE === "ts" ? "ts" : "js";
6+
7+
register(pathToFileURL(join(import.meta.dirname, `../loaders/alias.hook.${EXT}`)), {
8+
parentURL: import.meta.dirname,
9+
data: {
10+
"@tsed/core": import.meta.resolve("@tsed/core"),
11+
"@tsed/di": import.meta.resolve("@tsed/di"),
12+
"@tsed/schema": import.meta.resolve("@tsed/schema"),
13+
"@tsed/cli-core": import.meta.resolve("@tsed/cli-core"),
14+
"@tsed/cli": import.meta.resolve("@tsed/cli")
15+
},
16+
transferList: []
17+
});
18+
19+
const {tools, commands, resources, CliCore, PKG, TEMPLATE_DIR, ArchitectureConvention, ProjectConvention} = await import("../index.js");
20+
21+
CliCore.bootstrap({
22+
name: "tsed",
23+
pkg: PKG as any,
24+
templateDir: TEMPLATE_DIR,
25+
plugins: true,
26+
updateNotifier: true,
27+
checkPrecondition: true,
28+
commands,
29+
tools,
30+
resources,
31+
defaultProjectPreferences() {
32+
return {
33+
convention: ProjectConvention.DEFAULT,
34+
architecture: ArchitectureConvention.DEFAULT
35+
};
36+
},
37+
project: {
38+
reinstallAfterRun: true
39+
},
40+
logger: {
41+
level: "info"
42+
}
43+
}).catch((error) => {
44+
console.error(error);
45+
process.exit(-1);
46+
});

packages/cli/src/bin/tsed-build.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {spawn} from "node:child_process";
2+
import process from "node:process";
3+
import {fileURLToPath} from "node:url";
4+
5+
function runNode(cmd: string, args: string[]) {
6+
return new Promise<void>((resolve, reject) => {
7+
const child = spawn(cmd, args, {
8+
env: process.env,
9+
stdio: "inherit"
10+
});
11+
12+
child.on("error", reject);
13+
child.on("exit", (code) => {
14+
if (code === 0) {
15+
resolve();
16+
return;
17+
}
18+
19+
reject(new Error(`vite build exited with code ${code}`));
20+
});
21+
});
22+
}
23+
24+
export async function build(rawArgs: string[] = process.argv.slice(2)) {
25+
const viteBin = fileURLToPath(import.meta.resolve("vite/bin/vite.js"));
26+
await runNode(process.execPath, [viteBin, "build", ...rawArgs]);
27+
}

packages/cli/src/bin/tsed-dev.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {spawn} from "node:child_process";
2+
import {existsSync} from "node:fs";
3+
import process from "node:process";
4+
import {fileURLToPath} from "node:url";
5+
6+
import {normalizePath} from "@tsed/cli-core";
7+
import {logger} from "@tsed/di";
8+
9+
const RUN_MODE = "TSED_VITE_RUN_MODE";
10+
11+
function parseWatchValue(args: string[]) {
12+
if (args.includes("--no-watch")) {
13+
return false;
14+
}
15+
16+
const watchIndex = args.findIndex((arg) => arg === "--watch" || arg.startsWith("--watch="));
17+
18+
if (watchIndex === -1) {
19+
return true;
20+
}
21+
22+
const watchArg = args[watchIndex];
23+
24+
if (watchArg === "--watch") {
25+
const nextValue = args[watchIndex + 1];
26+
27+
if (!nextValue || nextValue.startsWith("-")) {
28+
return true;
29+
}
30+
31+
return nextValue !== "false";
32+
}
33+
34+
if (watchArg === "--watch=false") {
35+
return false;
36+
}
37+
38+
if (watchArg === "--watch=true") {
39+
return true;
40+
}
41+
42+
return true;
43+
}
44+
45+
function assertViteProject() {
46+
const configFile = normalizePath("vite.config.ts");
47+
48+
if (!existsSync(configFile)) {
49+
throw new Error("tsed dev is only available for ViteRuntime projects. Missing vite.config.ts in the current directory.");
50+
}
51+
}
52+
53+
async function createViteDevServer() {
54+
// @ts-ignore
55+
const {createServer} = await import("vite");
56+
57+
return createServer({
58+
configFile: normalizePath("vite.config.ts"),
59+
server: {
60+
middlewareMode: true,
61+
hmr: false,
62+
ws: false
63+
}
64+
});
65+
}
66+
67+
async function runViteApp() {
68+
const vite = await createViteDevServer();
69+
70+
const shutdown = async () => {
71+
await vite.close();
72+
process.exit(0);
73+
};
74+
75+
process.on("SIGINT", shutdown);
76+
process.on("SIGTERM", shutdown);
77+
78+
await vite.ssrLoadModule("/src/index.ts");
79+
await new Promise(() => {});
80+
}
81+
82+
async function runViteController(rawArgs: string[]) {
83+
const runnerFile = fileURLToPath(import.meta.url);
84+
const watch = parseWatchValue(rawArgs);
85+
const vite = await createViteDevServer();
86+
let childProcess: ReturnType<typeof spawn> | undefined;
87+
let restarting = false;
88+
let queued = false;
89+
90+
const startChild = () => {
91+
childProcess = spawn(process.execPath, [runnerFile, ...rawArgs], {
92+
env: {
93+
...process.env,
94+
[RUN_MODE]: "app"
95+
},
96+
stdio: "inherit"
97+
});
98+
};
99+
100+
const stopChild = async () => {
101+
if (!childProcess || childProcess.killed) {
102+
return;
103+
}
104+
105+
await new Promise((resolve) => {
106+
childProcess!.once("exit", resolve);
107+
childProcess!.kill("SIGTERM");
108+
});
109+
};
110+
111+
const restartChild = async (reason: string, file = "") => {
112+
if (restarting) {
113+
queued = true;
114+
return;
115+
}
116+
117+
restarting = true;
118+
const suffix = file ? `: ${file}` : "";
119+
120+
logger().info(`[tsed-dev] restart (${reason})${suffix}`);
121+
122+
await stopChild();
123+
startChild();
124+
restarting = false;
125+
126+
if (queued) {
127+
queued = false;
128+
await restartChild("queued");
129+
}
130+
};
131+
132+
if (watch) {
133+
vite.watcher.on("all", async (event, file) => {
134+
if (!file || file.includes("node_modules") || file.includes(".git") || file.includes("/dist/")) {
135+
return;
136+
}
137+
138+
if (["add", "change", "unlink"].includes(event)) {
139+
await restartChild(event, file);
140+
}
141+
});
142+
}
143+
144+
vite.watcher.once("ready", () => {
145+
startChild();
146+
});
147+
148+
const shutdown = async () => {
149+
await stopChild();
150+
await vite.close();
151+
process.exit(0);
152+
};
153+
154+
process.on("SIGINT", shutdown);
155+
process.on("SIGTERM", shutdown);
156+
157+
await new Promise(() => {});
158+
}
159+
160+
export async function dev(rawArgs: string[] = process.argv.slice(2)) {
161+
if (process.env[RUN_MODE] === "app") {
162+
await runViteApp();
163+
return;
164+
}
165+
166+
await runViteController(rawArgs);
167+
}

packages/cli/src/bin/tsed.ts

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,30 @@
11
#!/usr/bin/env node
2-
import "./ts-mode.js";
3-
4-
import {register} from "node:module";
5-
import {join} from "node:path";
6-
import {pathToFileURL} from "node:url";
7-
8-
import type {PackageJson} from "@tsed/cli-core";
9-
102
const EXT = process.env.CLI_MODE === "ts" ? "ts" : "js";
3+
const [, , commandName, ...rawArgs] = process.argv;
114

12-
register(pathToFileURL(join(import.meta.dirname, `../loaders/alias.hook.${EXT}`)), {
13-
parentURL: import.meta.dirname,
14-
data: {
15-
"@tsed/core": import.meta.resolve("@tsed/core"),
16-
"@tsed/di": import.meta.resolve("@tsed/di"),
17-
"@tsed/schema": import.meta.resolve("@tsed/schema"),
18-
"@tsed/cli-core": import.meta.resolve("@tsed/cli-core"),
19-
"@tsed/cli": import.meta.resolve("@tsed/cli")
20-
},
21-
transferList: []
22-
});
23-
24-
const {tools, commands, resources, CliCore, PKG, TEMPLATE_DIR, ArchitectureConvention, ProjectConvention} = await import("../index.js");
25-
26-
CliCore.bootstrap({
27-
name: "tsed",
28-
pkg: PKG as PackageJson,
29-
templateDir: TEMPLATE_DIR,
30-
plugins: true,
31-
updateNotifier: true,
32-
checkPrecondition: true,
33-
commands,
34-
tools,
35-
resources,
36-
defaultProjectPreferences() {
37-
return {
38-
convention: ProjectConvention.DEFAULT,
39-
architecture: ArchitectureConvention.DEFAULT
40-
};
41-
},
42-
project: {
43-
reinstallAfterRun: true
44-
},
45-
logger: {
46-
level: "info"
47-
}
48-
}).catch((error) => {
49-
console.error(error);
50-
process.exit(-1);
51-
});
5+
switch (commandName) {
6+
case "dev":
7+
try {
8+
const {dev} = await import("./tsed-dev.js");
9+
await dev(rawArgs);
10+
process.exit(0);
11+
} catch (error) {
12+
console.error(error);
13+
process.exit(1);
14+
}
15+
break;
16+
case "build":
17+
try {
18+
const {build} = await import("./tsed-build.js");
19+
await build(rawArgs);
20+
process.exit(0);
21+
} catch (error) {
22+
console.error(error);
23+
process.exit(1);
24+
}
25+
break;
26+
default:
27+
await import("./ts-mode.js");
28+
await import(`./boot.${EXT}`);
29+
break;
30+
}

packages/cli/src/commands/build/BuildCmd.spec.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/cli/src/commands/build/BuildCmd.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)