fix(frontend): bind Windows dev server to loopback by default (#6034)

Co-authored-by: huyengiang101086-spec <270684046+huyengiang101086-spec@users.noreply.github.com>
This commit is contained in:
huyengiang101086-spec 2026-09-30 08:12:31 +08:00 committed by GitHub
parent ed09efdb7f
commit f119cacbc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 2 deletions

View File

@ -40,6 +40,8 @@ Unit tests live under `tests/unit/` and mirror the `src/` layout (e.g., `tests/u
Webpack is the default development bundler. Use `DEER_FLOW_DEV_BUNDLER=turbo` with `pnpm dev` to opt in to Turbopack when diagnosing a local Next.js bundler issue.
On Windows `pnpm dev` binds `127.0.0.1` by default because Hyper-V/winnat excluded port ranges can reject Next's default `0.0.0.0` bind with `EACCES` (#2870). Pass `pnpm dev -- --hostname 0.0.0.0` to listen on a LAN interface instead.
Rstest runs them as two projects (`rstest.config.ts`). `*.test.ts` / `*.test.tsx` run in a plain **node** environment — that is nearly the whole suite, and it is the default for anything that is pure logic. `*.dom.test.ts` / `*.dom.test.tsx` run in **happy-dom**, for tests that need a document: hooks driven through `renderHook` from `@testing-library/react`, and components. Keep the split — a DOM environment costs roughly 3x the runtime of the node suite, so tests that do not render should not opt into it. A hook whose behavior only exists under real React (effect ordering, cleanup on unmount, re-render on store change) belongs in a `.dom.test.*` file rather than a node test that mocks `react` itself.
E2E tests live under `tests/e2e/` and use Playwright with Chromium. They mock all backend APIs via `page.route()` network interception and test real page interactions (navigation, chat input, streaming responses). Config: `playwright.config.ts`. The real-backend auth contract in `tests/e2e-real-backend/auth-disabled-contract.spec.ts` and `backend/tests/test_auth_me_permissions.py` pin the complete route-permission list; update both when adding registered permissions (including `projects:read/write/delete`).

View File

@ -25,6 +25,16 @@ export function getDevBundler(_platform = process.platform, env = process.env) {
return "webpack";
}
/**
* @param {string[]} nextArgs
*/
function hasHostnameOverride(nextArgs) {
return nextArgs.some(
(arg) =>
arg === "-H" || arg === "--hostname" || arg.startsWith("--hostname="),
);
}
/**
* @param {string} platform
* @param {string[]} extraArgs
@ -36,7 +46,15 @@ export function getNextDevArgs(
env = process.env,
) {
const nextArgs = extraArgs[0] === "--" ? extraArgs.slice(1) : extraArgs;
return ["dev", `--${getDevBundler(platform, env)}`, ...nextArgs];
const args = ["dev", `--${getDevBundler(platform, env)}`, ...nextArgs];
// Windows reserves dynamic TCP port ranges (Hyper-V / WSL2 / winnat) that can
// make Next's default 0.0.0.0 bind fail with EACCES while a loopback bind on
// the same port succeeds. Bind loopback by default; an explicit
// --hostname/-H passthrough still opts into a wider interface.
if (platform === "win32" && !hasHostnameOverride(nextArgs)) {
args.push("--hostname", "127.0.0.1");
}
return args;
}
function startDevServer() {

View File

@ -24,6 +24,39 @@ describe("frontend dev launcher", () => {
"--webpack",
"--port",
"3302",
"--hostname",
"127.0.0.1",
]);
});
test("binds loopback by default on Windows only", () => {
expect(getNextDevArgs("win32")).toEqual([
"dev",
"--webpack",
"--hostname",
"127.0.0.1",
]);
expect(getNextDevArgs("linux")).toEqual(["dev", "--webpack"]);
expect(getNextDevArgs("darwin")).toEqual(["dev", "--webpack"]);
});
test("keeps an explicit Windows hostname passthrough", () => {
expect(getNextDevArgs("win32", ["--", "--hostname", "0.0.0.0"])).toEqual([
"dev",
"--webpack",
"--hostname",
"0.0.0.0",
]);
expect(getNextDevArgs("win32", ["--", "--hostname=0.0.0.0"])).toEqual([
"dev",
"--webpack",
"--hostname=0.0.0.0",
]);
expect(getNextDevArgs("win32", ["--", "-H", "0.0.0.0"])).toEqual([
"dev",
"--webpack",
"-H",
"0.0.0.0",
]);
});
@ -31,7 +64,12 @@ describe("frontend dev launcher", () => {
expect(getDevBundler("win32")).toBe("webpack");
expect(getDevBundler("linux")).toBe("webpack");
expect(getDevBundler("darwin")).toBe("webpack");
expect(getNextDevArgs("win32")).toEqual(["dev", "--webpack"]);
expect(getNextDevArgs("win32")).toEqual([
"dev",
"--webpack",
"--hostname",
"127.0.0.1",
]);
expect(getNextDevArgs("linux")).toEqual(["dev", "--webpack"]);
});
});