Initial Commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user