import { createRequire } from "module";
import path from "path";
import fs from "fs";
import os from "os";
const require = createRequire(import.meta.url);
let _sdk = null;
const LINUX_X64_GNU = "@miden-sdk/node-linux-x64-gnu";
const LINUX_X64_MUSL = "@miden-sdk/node-linux-x64-musl";
export function loadNativeModule(options) {
if (_sdk) return _sdk;
const attempts = [];
const explicit = options?.modulePath || process.env.MIDEN_MODULE_PATH;
if (explicit) {
try {
_sdk = require(explicit);
return _sdk;
} catch (err) {
throw new Error(
`Miden napi module at MIDEN_MODULE_PATH="${explicit}" failed to ` +
`load: ${firstLine(err)}`
);
}
}
const platformPackages = getPlatformPackages();
for (const platformPackage of platformPackages) {
try {
_sdk = require(platformPackage);
return _sdk;
} catch (err) {
attempts.push(`require("${platformPackage}") -> ${firstLine(err)}`);
}
}
if (platformPackages.length === 0) {
attempts.push(`no prebuilt package published for ${platformLabel()}`);
}
const archMap = { arm64: "aarch64", x64: "x86_64" };
const arch = archMap[os.arch()] || os.arch();
const platform =
os.platform() === "darwin" ? "apple-darwin" : "unknown-linux-gnu";
const target = `${arch}-${platform}`;
const ext = os.platform() === "darwin" ? "dylib" : "so";
const libName = `libmiden_client_web.${ext}`;
const packageRoot = path.resolve(import.meta.dirname, "..");
const prebuildCandidates = [
path.join(
packageRoot,
"prebuilds",
`${os.platform()}-${os.arch()}`,
"miden_client_web.node"
),
path.join(packageRoot, "prebuilds", "miden_client_web.node"),
];
for (const p of prebuildCandidates) {
if (fs.existsSync(p)) {
try {
_sdk = require(p);
return _sdk;
} catch (err) {
attempts.push(`require("${p}") -> ${firstLine(err)}`);
}
}
}
const repoRoot = findRepoRoot(packageRoot);
if (repoRoot) {
const targetCandidates = [
path.join(repoRoot, "target", target, "release", libName),
path.join(repoRoot, "target", "release", libName),
path.join(repoRoot, "target", target, "debug", libName),
path.join(repoRoot, "target", "debug", libName),
];
for (const p of targetCandidates) {
if (fs.existsSync(p)) {
const nodeFile = path.join(path.dirname(p), "miden_client_web.node");
if (
!fs.existsSync(nodeFile) ||
fs.statSync(p).mtimeMs > fs.statSync(nodeFile).mtimeMs
) {
fs.copyFileSync(p, nodeFile);
}
try {
_sdk = require(nodeFile);
return _sdk;
} catch (err) {
attempts.push(`require("${nodeFile}") -> ${firstLine(err)}`);
}
}
}
}
throw new Error(buildNotFoundMessage(platformPackages, attempts));
}
function getPlatformPackages() {
const key = `${os.platform()}-${os.arch()}`;
if (key === "linux-x64") return linuxX64Packages();
const platformMap = {
"darwin-arm64": "@miden-sdk/node-darwin-arm64",
"darwin-x64": "@miden-sdk/node-darwin-x64",
};
return platformMap[key] ? [platformMap[key]] : [];
}
function linuxX64Packages() {
let report;
try {
report = getReportWithoutNetwork();
} catch {
return [LINUX_X64_GNU, LINUX_X64_MUSL];
}
if (report?.header?.glibcVersionRuntime) return [LINUX_X64_GNU];
const objs = Array.isArray(report?.sharedObjects) ? report.sharedObjects : [];
if (objs.some((f) => /ld-musl-/.test(f))) return [LINUX_X64_MUSL];
if (objs.some((f) => /ld-linux|\/libc\.so/.test(f))) return [LINUX_X64_GNU];
return [LINUX_X64_GNU, LINUX_X64_MUSL];
}
function getReportWithoutNetwork() {
const previousExcludeNetwork = process.report.excludeNetwork;
process.report.excludeNetwork = true;
try {
return process.report.getReport();
} finally {
process.report.excludeNetwork = previousExcludeNetwork;
}
}
function platformLabel() {
return `${os.platform()}-${os.arch()}`;
}
function firstLine(err) {
const code = err && err.code ? `${err.code}: ` : "";
const message = (err && err.message ? err.message : String(err)).split(
"\n"
)[0];
return `${code}${message}`;
}
function buildNotFoundMessage(platformPackages, attempts) {
const lines = [
`Miden napi module not found for ${platformLabel()}, Node ${process.version}.`,
"",
"Resolution attempts:",
...attempts.map((a) => ` - ${a}`),
"",
];
if (platformPackages.length > 0) {
const names = platformPackages.map((p) => `"${p}"`).join(" or ");
lines.push(
`Expected the optional dependency ${names} to be installed ` +
`and loadable. Common causes:`,
" - The optional dependency was skipped at install time (npm's " +
"cross-platform lockfile bug, --omit=optional / --no-optional, or a " +
"pruned or partially-copied node_modules in a Docker build).",
" - The base image's libc or CPU does not match a published binary " +
"(e.g. an Alpine/musl or arm64 image).",
""
);
} else {
lines.push("No prebuilt binary is published for this platform.", "");
}
lines.push(
"Fixes:",
" - Reinstall with optional dependencies on the target platform, e.g. " +
"`npm install --include=optional` (or delete node_modules + " +
"package-lock.json and reinstall on Linux; pnpm avoids this npm bug).",
" - On Alpine/musl, ensure @miden-sdk/node-linux-x64-musl is installed, " +
"or switch to a glibc base image such as node:22-bookworm-slim.",
" - Installing with npm for Linux from another OS (`--os=linux " +
"--cpu=x64`)? Also pass `--libc=glibc`, or `--libc=musl` for Alpine: " +
"npm skips both Linux packages when it cannot detect the C library.",
" - Or set MIDEN_MODULE_PATH to a prebuilt .node file.",
" - Or build from source: `cargo build -p miden-client-web " +
"--no-default-features --features nodejs --release`."
);
return lines.join("\n");
}
function findRepoRoot(startDir) {
let dir = startDir;
for (let i = 0; i < 10; i++) {
if (
fs.existsSync(path.join(dir, "Cargo.toml")) &&
fs.existsSync(path.join(dir, "crates"))
) {
return dir;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}