Files
OmniRoute/tests/unit/onnxruntime-single-copy.test.ts
Dave Cox dcfbc24625 fix(deps): pin onnxruntime-node to the exact version @huggingface/transformers requires (#10543)
`@huggingface/transformers` 4.2.0 hard-pins `onnxruntime-node` to "1.24.3".
The production-group bump in #10403 raised the root range from "~1.24.3" to
"~1.27.0", so npm stopped deduping and nested a second copy under
`node_modules/@huggingface/transformers/node_modules/onnxruntime-node`.

Both copies ship a native `libonnxruntime.so.1` under the SAME SONAME, so
glibc binds whichever is dlopen()ed first and the other addon dies. The
Dockerfile post-build verification imports `@huggingface/transformers` and
`onnxruntime-node` in one process, so `docker build` has failed on every
commit since #10403:

  Error: .../transformers/node_modules/onnxruntime-node/bin/napi-v6/linux/x64/libonnxruntime.so.1:
  version `VERS_1.27.0' not found (required by .../onnxruntime-node/bin/napi-v6/linux/x64/onnxruntime_binding.node)

Restore the root range to "~1.24.3" so a single hoisted copy is resolved
again. Copying the nested native binaries into the standalone bundle is NOT
a workaround: it makes both `.so` files present, which is precisely what
triggers the SONAME clash above (verified against a real image build).

Regression guard: tests/unit/onnxruntime-single-copy.test.ts asserts the
lockfile resolves exactly one onnxruntime-node and that it matches the
version transformers pins. Confirmed failing on the pre-fix lockfile
(two copies, 1.27.0 vs 1.24.3) and passing after.

Validated with a full `docker build --target runner-base`: the post-build
verification step now passes (#19 DONE 156.9s) and the image boots healthy
(/api/monitoring/health 200, migrations 134-148 applied).
2026-08-17 07:59:52 -03:00

73 lines
3.3 KiB
TypeScript

/**
* Regression guard — the dependency tree must resolve exactly ONE
* `onnxruntime-node` (and one `onnxruntime-common`).
*
* `@huggingface/transformers` pins `onnxruntime-node` to an EXACT version
* (4.2.0 → "1.24.3"). Whenever the root range in package.json drifts off that
* pin, npm nests a second copy under
* `node_modules/@huggingface/transformers/node_modules/onnxruntime-node`.
*
* Two copies cannot coexist in one Node process: both ship a native
* `libonnxruntime.so.1` under the SAME SONAME, so glibc's loader binds
* whichever was dlopen()ed first and the other addon dies with
*
* Error: .../libonnxruntime.so.1: version `VERS_1.27.0' not found
* (required by .../onnxruntime_binding.node)
*
* That is exactly what a production-group dependabot bump did on 2026-08-16
* (root `onnxruntime-node` "~1.24.3" → "~1.27.0"): it broke the Docker image
* build at the Dockerfile's post-build standalone verification step, which
* imports `@huggingface/transformers` and `onnxruntime-node` in one process.
*
* Keep the root range compatible with whatever `@huggingface/transformers`
* pins — do not "fix" a future recurrence by copying the nested native
* binaries into the bundle; the SONAME clash makes that impossible.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
const lockfile = JSON.parse(readFileSync(join(repoRoot, "package-lock.json"), "utf8")) as {
packages: Record<string, { version?: string; dependencies?: Record<string, string> }>;
};
function copiesOf(pkg: string): string[] {
return Object.keys(lockfile.packages).filter(
(key) => key === `node_modules/${pkg}` || key.endsWith(`/node_modules/${pkg}`)
);
}
// Scoped to `onnxruntime-node` on purpose. `onnxruntime-common` is types/interfaces
// only and `onnxruntime-web` is WASM — neither dlopen()s anything, so their nested
// duplicates (onnxruntime-web carries its own onnxruntime-common) are harmless.
// `onnxruntime-node` is the sole package shipping the native libonnxruntime.so.1.
test("package-lock.json resolves exactly one copy of onnxruntime-node", () => {
assert.deepEqual(
copiesOf("onnxruntime-node"),
["node_modules/onnxruntime-node"],
"onnxruntime-node must resolve to a single hoisted copy — a nested duplicate ships a " +
"second libonnxruntime.so.1 under the same SONAME and breaks the standalone/Docker build"
);
});
test("root onnxruntime-node matches the exact version @huggingface/transformers pins", () => {
const transformers = lockfile.packages["node_modules/@huggingface/transformers"];
assert.ok(transformers, "@huggingface/transformers must be present in the lockfile");
const pinned = transformers.dependencies?.["onnxruntime-node"];
assert.ok(pinned, "@huggingface/transformers must declare an onnxruntime-node dependency");
const resolved = lockfile.packages["node_modules/onnxruntime-node"]?.version;
assert.equal(
resolved,
pinned,
`the hoisted onnxruntime-node (${resolved}) must equal the version ` +
`@huggingface/transformers pins (${pinned}); otherwise npm nests a second, ` +
`ABI-incompatible native copy`
);
});