Files
suaveplan/packages/testkit/testing/testing-integration/src/internal/bun-napi-shim.ts
2026-05-27 23:47:53 +01:00

87 lines
3.1 KiB
TypeScript

/**
* bun-napi-shim — intercept native-module loads that would otherwise crash
* Bun's NAPI loader before testcontainers' fallback paths run.
*
* Background: `dockerode` (which testcontainers uses for the Docker socket)
* pulls `ssh2` and `cpu-features`. Both packages load `.node` binaries via
* a wrapped `require()` with a `try { require(...) } catch {}` fallback to
* pure-JS implementations. Under Bun, however, the underlying NAPI loader
* panics on `uv_version_string` (oven-sh/bun#18546) BEFORE the catch block
* runs, killing the runner.
*
* This shim intercepts `Module._resolveFilename` for the two known offending
* `.node` paths and throws a plain JS error instead. ssh2/cpu-features then
* catch it and fall back to pure-JS code paths. Safe no-op under Node.
*
* Loaded as a side-effect by `src/setup/global-setup.ts` so every consumer
* who wires `createIntegrationSetup`'s returned `globalSetup` path gets the
* shim automatically; no per-consumer wiring needed.
*
* @module
*/
import { createRequire } from "node:module";
interface NodeModule {
_resolveFilename: (request: string, ...rest: unknown[]) => string;
}
const BLOCKED_NATIVE_SUFFIXES = [
"/sshcrypto.node",
"/cpufeatures.node",
] as const;
let shimInstalled = false;
/**
* Install the Module._resolveFilename interceptor when running under Bun.
*
* Idempotent: subsequent calls are no-ops. Returns `true` if the shim was
* installed (either now or previously), `false` if not running under Bun.
*
* @param options.moduleRef - Optional override for `node:module`. Used by
* tests to inject a fake `Module` whose `_resolveFilename` can be inspected.
* @param options.processRef - Optional override for the global `process`.
* Used by tests to simulate Bun vs Node detection without actually running
* under Bun.
*/
export function installBunNapiShim(
options: {
moduleRef?: NodeModule;
processRef?: { versions?: { bun?: string } };
} = {}
): boolean {
const proc =
options.processRef ??
/* istanbul ignore next */ (typeof process !== "undefined"
? (process as unknown as { versions?: { bun?: string } })
: undefined);
if (proc?.versions?.bun === undefined) return false;
if (shimInstalled) return true;
const mod =
options.moduleRef ??
/* istanbul ignore next */ (createRequire(import.meta.url)(
"node:module"
) as NodeModule);
const original = mod._resolveFilename;
mod._resolveFilename = function patched(
this: unknown,
request: string,
...rest: unknown[]
): string {
if (BLOCKED_NATIVE_SUFFIXES.some((s) => request.endsWith(s))) {
throw new Error(
`[bun-napi-shim] blocked native module load (${request}); pure-JS fallback engaged`
);
}
return original.call(this, request, ...rest);
};
shimInstalled = true;
return true;
}
/** Test-only: reset the install flag so a second `installBunNapiShim` call patches again. */
export function _resetForTests(): void {
shimInstalled = false;
}