Files
leaysgur a26f856402 refactor(oxfmt): rename external_formatter to external_services (#25429)
`ExternalFormatter` is merely a transport that calls the JS side via napi; it is not referred to as a formatter.
2026-08-09 15:07:40 +00:00

84 lines
3.5 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!;
// Node.js < 25.4.0 has 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+.
// See: https://github.com/nodejs/node/issues/55706
const [major, minor] = process.versions.node.split(".").map(Number);
if (major < 25 || (major === 25 && minor < 4)) {
setTimeout(() => process.exit(), 50);
}
})();