diff --git a/lib/Server.js b/lib/Server.js index 9f374baef9..507f2c4714 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -385,9 +385,10 @@ class Server { /** * @param {Port} port + * @param {string} host * @returns {Promise} */ - static async getFreePort(port) { + static async getFreePort(port, host) { if (typeof port !== "undefined" && port !== null && port !== "auto") { return port; } @@ -406,7 +407,7 @@ class Server { ? parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) : 3; - return pRetry(() => getPort(basePort), { + return pRetry(() => getPort(basePort, host), { retries: defaultPortRetry, }); } @@ -3200,7 +3201,8 @@ class Server { /** @type {Host} */ (this.options.host) ); this.options.port = await Server.getFreePort( - /** @type {Port} */ (this.options.port) + /** @type {Port} */ (this.options.port), + this.options.host ); } diff --git a/lib/getPort.js b/lib/getPort.js index b0c5080744..cbba2151ff 100644 --- a/lib/getPort.js +++ b/lib/getPort.js @@ -88,15 +88,26 @@ const getAvailablePort = async (port, hosts) => { /** * @param {number} basePort + * @param {string=} host * @return {Promise} */ -async function getPorts(basePort) { +async function getPorts(basePort, host) { if (basePort < minPort || basePort > maxPort) { throw new Error(`Port number must lie between ${minPort} and ${maxPort}`); } let port = basePort; - const hosts = getLocalHosts(); + const localhosts = getLocalHosts(); + let hosts; + if (host && !localhosts.has(host)) { + hosts = new Set([host]); + } else { + /* If the host is equivalent to localhost + we need to check every equivalent host + else the port might falsely appear as available + on some operating systems */ + hosts = localhosts; + } /** @type {Set} */ const portUnavailableErrors = new Set(["EADDRINUSE", "EACCES"]); while (port <= maxPort) { diff --git a/test/server/get-port.test.js b/test/server/get-port.test.js index b480315f00..258cafb47c 100644 --- a/test/server/get-port.test.js +++ b/test/server/get-port.test.js @@ -16,6 +16,7 @@ it("should pick the next port if the preferred port is unavailable", async () => server.unref(); await util.promisify(server.listen.bind(server))(preferredPort); const port = await getPort(preferredPort); + server.close(); expect(port).toBe(preferredPort + 1); }); @@ -34,3 +35,21 @@ it("should reject too high port numbers", async () => { expect(e.message).toBeDefined(); } }); + +describe("when passing a host", () => { + it("should bind to the preferred port", async () => { + const preferredPort = 8080; + const port = await getPort(8080, "127.0.0.1"); + expect(port).toBe(preferredPort); + }); + + it("should pick the next port if the preferred port is unavailable", async () => { + const preferredPort = 8345; + const server = net.createServer(); + server.unref(); + await util.promisify(server.listen.bind(server))(preferredPort); + const port = await getPort(preferredPort, "0.0.0.0"); + server.close(); + expect(port).toBe(preferredPort + 1); + }); +}); diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index 4a546c6e60..82272d80c8 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -664,6 +664,7 @@ declare class Server { }; /** * @param {Port} port + * @param {string} host * @returns {Promise} */ https: { @@ -716,9 +717,9 @@ declare class Server { description: string; multiple: boolean; path: string; - type: string; + type: string /** @type {WebSocketURL} */; }[]; - /** @type {ClientConfiguration} */ description: string; + description: string; multiple: boolean; simpleType: string; }; @@ -730,7 +731,7 @@ declare class Server { path: string; }[]; description: string; - simpleType: string; + /** @type {ServerConfiguration} */ simpleType: string; multiple: boolean; }; "https-cert-reset": { @@ -881,7 +882,7 @@ declare class Server { configs: ( | { type: string; - /** @type {MultiCompiler} */ multiple: boolean; + multiple: boolean; description: string; path: string; } @@ -905,6 +906,10 @@ declare class Server { path: string; }[]; description: string; + /** + * @private + * @returns {Promise} + */ simpleType: string; multiple: boolean; }; @@ -952,6 +957,10 @@ declare class Server { simpleType: string; multiple: boolean; }; + /** + * @param {string | Static | undefined} [optionsForStatic] + * @returns {NormalizedStatic} + */ "open-target-reset": { configs: { type: string; @@ -1118,7 +1127,7 @@ declare class Server { "server-options-pfx-reset": { configs: { description: string; - multiple: boolean; + /** @type {ServerConfiguration} */ multiple: boolean; path: string; type: string; }[]; @@ -1131,7 +1140,7 @@ declare class Server { description: string; negatedDescription: string; multiple: boolean; - /** @type {ServerOptions} */ path: string; + path: string; type: string; }[]; description: string; @@ -1144,10 +1153,10 @@ declare class Server { multiple: boolean; path: string; type: string; - values: string[]; + /** @type {ServerOptions} */ values: string[]; }[]; description: string; - multiple: boolean; + /** @type {Array} */ multiple: boolean; simpleType: string; }; static: { @@ -1167,7 +1176,7 @@ declare class Server { } )[]; description: string; - simpleType: string; + /** @type {ServerOptions} */ simpleType: string; multiple: boolean; }; "static-directory": { @@ -1279,7 +1288,7 @@ declare class Server { } | { description: string; - multiple: boolean; + /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ multiple: boolean; path: string; type: string; } @@ -1292,6 +1301,7 @@ declare class Server { configs: ( | { description: string; + /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ multiple: boolean; path: string; type: string; @@ -1300,13 +1310,13 @@ declare class Server { | { description: string; multiple: boolean; - /** @type {ServerOptions} */ path: string; - type: string; + path: string; + /** @type {ServerOptions} */ type: string; } )[]; description: string; simpleType: string; - multiple: boolean; + /** @type {ServerOptions} */ multiple: boolean; }; }; readonly processArguments: ( @@ -2033,6 +2043,9 @@ declare class Server { instanceof?: undefined; } )[]; + /** + * @returns {string} + */ }; instanceof?: undefined; } @@ -2073,6 +2086,9 @@ declare class Server { exclude: boolean; }; }; + /** + * @type {string[]} + */ Headers: { anyOf: ( | { @@ -2111,7 +2127,7 @@ declare class Server { } | { type: string; - description: string; + /** @type {ClientConfiguration} */ description: string; link: string; cli?: undefined /** @typedef {import("express").Request} Request */; } @@ -2122,7 +2138,6 @@ declare class Server { Host: { description: string; link: string; - /** @type {ServerConfiguration} */ anyOf: ( | { enum: string[]; @@ -2152,7 +2167,7 @@ declare class Server { } )[]; description: string; - link: string; + /** @type {string} */ link: string; }; IPC: { anyOf: ( @@ -2261,12 +2276,6 @@ declare class Server { minLength: number; }; minItems: number; - /** - * prependEntry Method for webpack 4 - * @param {any} originalEntry - * @param {any} newAdditionalEntries - * @returns {any} - */ minLength?: undefined; } | { @@ -2317,6 +2326,7 @@ declare class Server { enum?: undefined; } | { + /** @type {any} */ type: string; minLength: number; minimum?: undefined; @@ -2372,7 +2382,7 @@ declare class Server { ServerEnum: { enum: string[]; cli: { - exclude: boolean; + exclude: boolean /** @type {MultiCompiler} */; }; }; ServerString: { @@ -2403,10 +2413,6 @@ declare class Server { passphrase: { type: string; description: string; - /** - * @private - * @returns {Promise} - */ }; requestCert: { type: string; @@ -2776,7 +2782,7 @@ declare class Server { | { type: string; minLength: number; - items?: undefined; + /** @type {ServerConfiguration} */ items?: undefined; } )[]; description: string; @@ -2798,8 +2804,8 @@ declare class Server { anyOf: { $ref: string; }[]; - description: string; - /** @type {Array} */ link: string; + /** @type {ServerOptions} */ description: string; + link: string; }; WebSocketServerType: { enum: string[]; @@ -2857,7 +2863,6 @@ declare class Server { bonjour: { $ref: string; }; - /** @type {any} */ client: { $ref: string; }; @@ -2959,9 +2964,10 @@ declare class Server { static getHostname(hostname: Host): Promise; /** * @param {Port} port + * @param {string} host * @returns {Promise} */ - static getFreePort(port: Port): Promise; + static getFreePort(port: Port, host: string): Promise; /** * @returns {string} */ diff --git a/types/lib/getPort.d.ts b/types/lib/getPort.d.ts index fbe67e962f..6b6fab4291 100644 --- a/types/lib/getPort.d.ts +++ b/types/lib/getPort.d.ts @@ -1,6 +1,10 @@ export = getPorts; /** * @param {number} basePort + * @param {string=} host * @return {Promise} */ -declare function getPorts(basePort: number): Promise; +declare function getPorts( + basePort: number, + host?: string | undefined +): Promise;