diff --git a/test/api/normalize-stderr.test.js b/test/api/normalize-stderr.test.js new file mode 100644 index 00000000000..ed0e32d147a --- /dev/null +++ b/test/api/normalize-stderr.test.js @@ -0,0 +1,33 @@ +"use strict"; + +const { normalizeStderr } = require("../utils/test-utils"); + +const NETWORK_IPV6 = " [webpack-dev-server] On Your Network (IPv6): "; + +describe("normalizeStderr", () => { + it("should replace a compressed IPv6 address, such as a unique local address", () => { + expect(normalizeStderr(`${NETWORK_IPV6}http://[fdb2:2c26:f4e4::1]:8080/`)).toBe( + `${NETWORK_IPV6}http://[x:x:x:x:x:x:x:x]:/`, + ); + }); + + it("should replace a full IPv6 address with its brackets", () => { + expect(normalizeStderr(`${NETWORK_IPV6}http://[2001:db8:0:0:0:0:0:1]:8080/`)).toBe( + `${NETWORK_IPV6}http://[x:x:x:x:x:x:x:x]:/`, + ); + }); + + it("should keep the IPv6 loopback", () => { + expect( + normalizeStderr( + " [webpack-dev-server] Loopback: http://localhost:8080/, http://[::1]:8080/", + ), + ).toBe(" [webpack-dev-server] Loopback: http://localhost:/, http://[::1]:/"); + }); + + it("should leave bracketed text that is not an IPv6 address", () => { + expect(normalizeStderr("[1:2] [dead:beef] [webpack-dev-server]")).toBe( + "[1:2] [dead:beef] [webpack-dev-server]", + ); + }); +}); diff --git a/test/utils/test-utils.js b/test/utils/test-utils.js index dd7d23e72f5..addd613a11b 100644 --- a/test/utils/test-utils.js +++ b/test/utils/test-utils.js @@ -2,6 +2,7 @@ const { exec } = require("node:child_process"); const fs = require("node:fs"); +const { isIPv6 } = require("node:net"); const path = require("node:path"); const { stripVTControlCharacters } = require("node:util"); const concat = require("concat-stream"); @@ -268,7 +269,12 @@ const normalizeStdout = (stdout) => { }; const IPV4 = /(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}/g; -const IPV6 = /([0-9a-f]){1,4}(:([0-9a-f]){1,4}){7}/gi; +// A bracketed IPv6 host in a URL, in full or compressed ("::") form, e.g. a unique local +// address such as `[fdb2:2c26:f4e4::1]`. The loopback `[::1]` and bracketed text that is not +// an IPv6 address stay as they are. +const IPV6 = /\[([0-9a-f:.]+)(%[^\]]+)?\]/gi; +const replaceIPv6 = (match, address) => + address !== "::1" && isIPv6(address) ? "[x:x:x:x:x:x:x:x]" : match; const normalizeStderr = (stderr) => { if (typeof stderr !== "string") { @@ -283,7 +289,7 @@ const normalizeStderr = (stderr) => { normalizedStderr = normalizeCwd(normalizedStderr); normalizedStderr = normalizedStderr.replaceAll(IPV4, "x.x.x.x"); - normalizedStderr = normalizedStderr.replaceAll(IPV6, "[x:x:x:x:x:x:x:x]"); + normalizedStderr = normalizedStderr.replaceAll(IPV6, replaceIPv6); normalizedStderr = normalizedStderr.replaceAll(/:[0-9]+\//g, ":/"); if (!/On Your Network \(IPv6\)/.test(stderr)) {