mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
d35d89f78c
Oxfmt still schedules its 50 ms forced-exit workaround on every Node 24 release, although Node 24.13.1 backported the ThreadsafeFunction fix. Exempt fixed Node 24 releases while retaining the existing workaround for older Node 20/22/24 versions and Node 25 before 25.4.0. The backport is documented in [Node 24.13.1's release notes](https://nodejs.org/en/blog/release/v24.13.1) and [nodejs/node@1e13e84f16](https://github.com/nodejs/node/commit/1e13e84f162397f1adbe58f7a6f62974cfd0fa95). This narrows the workaround introduced in #18980; it preserves worker disposal and `process.exitCode` handling. Earlier downstream validation of this same condition on Linux/Node 24.19 included ten successful checks of 12,032 synthetic files with complete output. In eleven paired downstream Vite+ check rounds, removing the timer saved a median of about 42 ms for full checks and 44 ms for three-file checks. These are project-specific workflow timings, not a universal 50 ms guarantee. Validation on `805650e8f60e25abb8ae1038580ab799e7f8dcd9`, using the repository's frozen pnpm 12.3.2 dependency installation and Rust 1.98.1: - `pnpm --dir apps/oxfmt build-dev`: native and JavaScript build passed. - `env -u NO_COLOR -u FORCE_COLOR pnpm --dir apps/oxfmt test --maxWorkers=4`: **480 passed, 10 skipped**, using the pinned Vitest 5.0.0. This covers the complete Oxfmt JavaScript suite, including CLI, API, LSP and the ten new regression cases. The first run had two color snapshot mismatches caused by inherited `NO_COLOR`; clearing the ambient color variables resolved them without changing tests or snapshots. - `cargo test -p oxfmt --locked`: **84 passed**. - `cargo clippy -p oxfmt --all-targets --locked -- -D warnings`: passed. - Changed-file formatting and repository-configured type-aware Oxlint/type checks: passed with the pinned tools. Additional checks exercised the freshly built native CLI on **real Node 24.13.1 and 24.19.0**: five successful runs per runtime over 12,032 synthetic JS/Markdown files, including worker callbacks; no 50 ms timers were registered. Both runtimes preserved all 497,780 bytes of a large stdin-formatting result and the expected parse-error exit code 2. These are correctness/stress checks of the dev build, not performance benchmarks. The entire Oxc workspace's other packages and the cross-platform CI matrix were not run locally. The follow-up commit `1c98d210501e276c2d6e3009562b2ebe41217ab3` removes the ten mocked shutdown regression cases at the maintainer's request. Its only change is deleting that test file; the production CLI is byte-for-byte unchanged from the fully validated revision above. The final PR changes only `apps/oxfmt/src-js/cli.ts`. The suite counts above describe the earlier run, including the subsequently removed tests; no new full-suite run is claimed for the deletion-only follow-up. AI assistance: implementation and self-review performed with OpenAI Codex using GPT-6 Astra (`gpt-6-astra`), with reasoning effort set to `high`.
86 lines
3.6 KiB
TypeScript
86 lines
3.6 KiB
TypeScript
import { runCli } from "./bindings";
|
|
import {
|
|
initExternalServices,
|
|
disposeExternalServices,
|
|
formatFile,
|
|
formatEmbeddedCode,
|
|
formatEmbeddedDoc,
|
|
sortTailwindClasses,
|
|
} from "./cli/worker-proxy";
|
|
import { loadJsConfig, loadVitePlusConfig } from "./cli/js_config";
|
|
|
|
// napi-JS `oxfmt` CLI entry point
|
|
// See also `run_cli()` function in `./src/main_napi.rs`
|
|
|
|
void (async () => {
|
|
const args = process.argv.slice(2);
|
|
|
|
// Node.js sets non-TTY `stdio` to non-blocking mode,
|
|
// which causes `WouldBlock` errors in Rust.
|
|
// As a workaround, if used with pipe, set blocking mode before calling NAPI bindings.
|
|
// See: https://github.com/napi-rs/napi-rs/issues/1630
|
|
//
|
|
// stdout: Writing large formatted output via `--stdin-filepath` can overflow the pipe buffer.
|
|
// https://github.com/oxc-project/oxc/issues/17939 (observed on macOS)
|
|
// @ts-expect-error: `_handle` is an internal API
|
|
if (!process.stdout.isTTY) process.stdout._handle?.setBlocking?.(true);
|
|
// stdin: In LSP mode (`--lsp`), VSCode communicates via stdin/stdout pipes.
|
|
// Rust reads stdin expecting blocking I/O, but non-blocking mode returns `EAGAIN` (os error 11).
|
|
// https://github.com/oxc-project/oxc/issues/20285
|
|
// @ts-expect-error: `_handle` is an internal API
|
|
if (!process.stdin.isTTY) process.stdin._handle?.setBlocking?.(true);
|
|
|
|
// LSP uses stdout for communication, so write logs to stderr to avoid breaking the protocol.
|
|
// Since LSP is handled on the Rust side, we have to check the flag here. (`runCli()` starts the server and waits)
|
|
// Also, for Oxfmt, this only actually affects loading JS/TS config files.
|
|
// The call to Prettier is currently done via `child_process`, so it won't break LSP.
|
|
if (args.includes("--lsp")) process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
|
|
// Call the Rust CLI first, to parse args and determine mode
|
|
// NOTE: If the mode is formatter CLI, it will also perform formatting and return an exit code
|
|
const [mode, exitCode] = await runCli(
|
|
args,
|
|
process.env.VP_VERSION ? loadVitePlusConfig : loadJsConfig,
|
|
initExternalServices,
|
|
formatFile,
|
|
formatEmbeddedCode,
|
|
formatEmbeddedDoc,
|
|
sortTailwindClasses,
|
|
);
|
|
|
|
// Migration modes are handled by JS
|
|
if (mode === "init") {
|
|
await import("./cli/migration/init").then((m) => m.runInit());
|
|
return;
|
|
}
|
|
if (mode === "migrate:prettier") {
|
|
await import("./cli/migration/migrate-prettier").then((m) => m.runMigratePrettier());
|
|
return;
|
|
}
|
|
if (mode === "migrate:biome") {
|
|
await import("./cli/migration/migrate-biome").then((m) => m.runMigrateBiome());
|
|
return;
|
|
}
|
|
|
|
// Other modes are handled by Rust, just need to set `exitCode`
|
|
|
|
// Clean up worker pool to not V8 crashes on process exit
|
|
await disposeExternalServices();
|
|
|
|
// NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`.
|
|
// `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies.
|
|
// https://nodejs.org/api/process.html#processexitcode
|
|
process.exitCode = exitCode!;
|
|
|
|
// Older Node.js versions have a race condition with ThreadsafeFunction cleanup that causes
|
|
// crashes on large codebases. Add a small delay to allow pending NAPI operations
|
|
// to complete before exit. Fixed in Node.js 25.4.0+ and backported to 24.13.1.
|
|
// See: https://github.com/nodejs/node/issues/55706
|
|
// Backport: https://nodejs.org/en/blog/release/v24.13.1
|
|
const [major, minor, patch] = process.versions.node.split(".").map(Number);
|
|
const hasNode24Fix = major === 24 && (minor > 13 || (minor === 13 && patch >= 1));
|
|
if (!hasNode24Fix && (major < 25 || (major === 25 && minor < 4))) {
|
|
setTimeout(() => process.exit(), 50);
|
|
}
|
|
})();
|