georgelichen bdd68469c1
fix(frontend): stabilize Windows development startup (#5036)
* Stabilize Windows frontend development startup

Next.js Turbopack can infer a user-level workspace root and hit an internal Rust task panic on Windows. Keep the frontend root explicit and select Webpack on Windows while preserving Turbopack elsewhere.

Constraint: The existing non-Windows development workflow should keep Turbopack.

Rejected: Disable Turbopack on every platform | unnecessary performance regression.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Revisit the Windows Webpack fallback after the upstream Turbopack panic is resolved.

Tested: Prettier, ESLint, TypeScript, 139 frontend test files with 1060 tests, and Windows pnpm dev smoke test on port 3010.

Not-tested: Production build and browser E2E suite.

Related: #4957

* Make the frontend bundler fallback diagnosable

Keep the Windows Webpack fallback while allowing local diagnosis to select either supported Next.js bundler. Resolve the Turbopack root through the Node 20-compatible URL API so the explicit workspace boundary remains effective across the declared Next.js engine range.

Constraint: Windows defaults to Webpack because of the observed Turbopack panic, while non-Windows defaults to Turbopack.
Rejected: Accept arbitrary bundler values | Next.js only supports the two explicit flags and invalid values should fail early.
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Revisit the default Windows fallback when the upstream TaskGuard issue is resolved; the override is intentionally retained for diagnosis.
Tested: Frontend format, ESLint, TypeScript, 139-file/1062-test Rstest suite, target launcher tests, and next.config.js absolute-root load check.
Not-tested: Production build and browser E2E suite.
2026-08-27 16:07:16 +08:00

72 lines
1.8 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
/**
* @param {string} platform
* @param {Record<string, string | undefined>} env
*/
export function getDevBundler(platform = process.platform, env = process.env) {
const override = env.DEER_FLOW_DEV_BUNDLER?.trim();
if (override) {
if (override !== "turbo" && override !== "webpack") {
throw new Error(
'DEER_FLOW_DEV_BUNDLER must be either "turbo" or "webpack"',
);
}
return override;
}
return platform === "win32" ? "webpack" : "turbo";
}
/**
* @param {string} platform
* @param {string[]} extraArgs
* @param {Record<string, string | undefined>} env
*/
export function getNextDevArgs(
platform = process.platform,
extraArgs = [],
env = process.env,
) {
const nextArgs = extraArgs[0] === "--" ? extraArgs.slice(1) : extraArgs;
return ["dev", `--${getDevBundler(platform, env)}`, ...nextArgs];
}
function startDevServer() {
const frontendDir = fileURLToPath(new URL("..", import.meta.url));
const nextBin = fileURLToPath(
new URL("../node_modules/next/dist/bin/next", import.meta.url),
);
const child = spawn(
process.execPath,
[nextBin, ...getNextDevArgs(process.platform, process.argv.slice(2))],
{
cwd: frontendDir,
env: process.env,
stdio: "inherit",
},
);
child.on("error", (error) => {
console.error(`Failed to start Next.js: ${error.message}`);
process.exitCode = 1;
});
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exitCode = code ?? 1;
});
}
if (
process.argv[1] &&
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
startDevServer();
}