fix(build): register onnxruntime-node's native bin/ as a standalone asset (#9687)

Docker/standalone builds of the LLMLingua SLM compression tier failed at
runtime with "Error: libonnxruntime.so.1: cannot open shared object file:
No such file or directory" (open-sse/services/compression/engines/llmlingua's
worker, via @huggingface/transformers -> onnxruntime-node).

onnxruntime-node's dist/binding.js is a normal JS file Next.js's standalone
trace bundles correctly, but binding.js dlopen()s a platform-specific native
library shipped under bin/napi-v3/<platform>/<arch>/libonnxruntime.so.1 — a
dynamic native load static file tracing can't see (same blind-spot class as
the separate colocateLlmlinguaOptionals stub bug, just for a .so instead of
a JS import, via NATIVE_ASSET_ENTRIES instead). That directory was simply
never registered, unlike better-sqlite3's native binary, which already goes
through the exact same mechanism correctly.

Fix: add an entry for onnxruntime-node/bin, mirroring the existing
better-sqlite3 entry. Confirmed against a real Docker build of the
Dockerfile's own post-build verification step: this was the very next
failure once the separate llmlingua-2 stub bug was fixed and the build
progressed far enough to reach it.

Covered by tests/unit/assemble-standalone-onnxruntime-native-asset.test.ts
(fails against the pre-fix code on both assertions, passes after).

(cherry picked from commit 8c98a59f26)
This commit is contained in:
Markus Hartung
2026-08-07 09:06:07 +02:00
committed by diegosouzapw
parent 4d816a4d8a
commit 4d6f2d28fe
2 changed files with 79 additions and 14 deletions

View File

@@ -48,10 +48,7 @@
import fs from "node:fs/promises";
import fsSync from "node:fs";
import path from "node:path";
import {
colocateLlmlinguaOptionals,
SEED_PACKAGES,
} from "./colocateOptionals.mjs";
import { colocateLlmlinguaOptionals, SEED_PACKAGES } from "./colocateOptionals.mjs";
/**
* Check whether a path exists (async).
@@ -78,7 +75,7 @@ async function exists(targetPath) {
* (relative to projectRoot) and destination (relative to outDir) can be joined
* for either path/platform. @type {{label:string, src:string[], dest:string[]}[]}
*/
const NATIVE_ASSET_ENTRIES = [
export const NATIVE_ASSET_ENTRIES = [
{
label: "wreq-js native runtime",
src: ["node_modules", "wreq-js", "rust"],
@@ -90,13 +87,17 @@ const NATIVE_ASSET_ENTRIES = [
dest: ["node_modules", "better-sqlite3", "build"],
},
{
// #8847: Bun (and npx -g global installs) resolve better-sqlite3's native
// binary from prebuilds/ instead of build/Release/, so the compiled build/
// copy alone leaves a hollow package that falls back to sql.js (OOM under
// Bun). Ship the prebuilds alongside the compiled binary.
label: "better-sqlite3 prebuilds (Bun / global installs)",
src: ["node_modules", "better-sqlite3", "prebuilds"],
dest: ["node_modules", "better-sqlite3", "prebuilds"],
// onnxruntime-node's dist/binding.js dlopen()s a platform-specific
// libonnxruntime.so.1 shipped under bin/napi-v3/<platform>/<arch>/ — a
// *dynamic* native load Next.js's standalone file trace can't see (same
// blind spot class as the LLMLingua closure below, just for a .so instead
// of a JS import). Without this the standalone bundle boots with
// "Error: libonnxruntime.so.1: cannot open shared object file: No such
// file or directory" the first time transformers/llmlingua actually try
// to run ONNX inference.
label: "onnxruntime-node native binaries (libonnxruntime .so + .node addon)",
src: ["node_modules", "onnxruntime-node", "bin"],
dest: ["node_modules", "onnxruntime-node", "bin"],
},
{
// TPROXY IP_TRANSPARENT addon (Fase 3 / Epic A). Built by build-tproxy-native
@@ -759,8 +760,7 @@ export function assembleStandalone({
rootDir: projectRoot,
targetNodeModulesDir: path.join(resolvedOutDir, "node_modules"),
seeds: [...SEED_PACKAGES, "@huggingface/transformers"],
log: (message) =>
console.log(`[assembleStandalone] ${message.trim()}`),
log: (message) => console.log(`[assembleStandalone] ${message.trim()}`),
});
}