Skip to content

Commit a70dff9

Browse files
fix: exit webpack serve when a dev server fails to listen
As a plugin, webpack-dev-server starts listening after the first build and only logs a failure, e.g. a port already in use, so the CLI kept watching without a dev server and exited with 0. The CLI now sets the exit code to 2 and closes the compiler, as it does for dev servers without plugin support. Claude-Session: https://claude.ai/code/session_013FhutRFjuQemBzekJMYcqL
1 parent f524913 commit a70dff9

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

‎packages/webpack-cli/src/webpack-cli.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,7 +2296,9 @@ class WebpackCLI {
22962296
// needed for `webpack-dev-server@5` typings, which lack the plugin constructor
22972297
const DevServerPlugin = DevServer as unknown as new (options: DevServerConfiguration) => {
22982298
apply(compiler: Compiler | MultiCompiler): void;
2299+
listen(): Promise<void>;
22992300
};
2301+
const plugins: InstanceType<typeof DevServerPlugin>[] = [];
23002302

23012303
const compiler = await this.createCompiler(
23022304
webpackCLIOptions,
@@ -2361,6 +2363,8 @@ class WebpackCLI {
23612363
setupExitSignals: false,
23622364
});
23632365

2366+
plugins.push(plugin);
2367+
23642368
if (isDevServerPerCompiler) {
23652369
configuration.plugins = [...(configuration.plugins || []), plugin];
23662370
} else {
@@ -2406,6 +2410,25 @@ class WebpackCLI {
24062410
return;
24072411
}
24082412

2413+
// TODO remove it when webpack-dev-server reports listen failures to the compiler
2414+
// A dev server only logs a failure to listen, e.g. a port already in use, and the
2415+
// CLI would keep watching without it, so exit like dev servers without plugin
2416+
// support do
2417+
for (const plugin of plugins) {
2418+
const { listen } = plugin;
2419+
2420+
plugin.listen = () =>
2421+
listen.call(plugin).catch((error) => {
2422+
process.exitCode = 2;
2423+
2424+
compiler.close(() => {
2425+
process.exit();
2426+
});
2427+
2428+
throw error;
2429+
});
2430+
}
2431+
24092432
// Closing the compiler stops dev servers, failures keep the exit code
24102433
this.#setupGracefulShutdown(compiler, true);
24112434

‎test/serve/error-handling/serve-error-handling.test.js‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/* eslint-disable jest/require-top-level-describe -- Version-gated describe */
44

55
const fs = require("node:fs");
6+
const net = require("node:net");
67
const os = require("node:os");
78
const path = require("node:path");
89
const [devServerVersion] = require("webpack-dev-server/package.json").version;
@@ -163,6 +164,33 @@ describeDevServer6("serve error handling", () => {
163164
expect(stdout).toBeFalsy();
164165
});
165166

167+
test("should log the error and exit when the port is already in use", async () => {
168+
const server = net.createServer();
169+
170+
await new Promise((resolve) => {
171+
server.listen(port, "127.0.0.1", resolve);
172+
});
173+
174+
try {
175+
const { exitCode, stderr } = await run(__dirname, [
176+
"serve",
177+
"--config",
178+
"recover.config.js",
179+
"--env",
180+
`entry=${path.resolve(__dirname, "src/index.js")}`,
181+
"--host",
182+
"127.0.0.1",
183+
"--port",
184+
port,
185+
]);
186+
187+
expect(exitCode).toBe(2);
188+
expect(stderr).toContain("EADDRINUSE");
189+
} finally {
190+
server.close();
191+
}
192+
});
193+
166194
test("should log the validation error and exit when a middleware option is invalid", async () => {
167195
const { exitCode, stderr, stdout } = await run(__dirname, [
168196
"serve",

0 commit comments

Comments
 (0)