From 71c1339c72c7ac8f991e5438af40e466dfc43e96 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Mon, 18 May 2026 13:18:17 -0400 Subject: [PATCH 1/2] use npm list instead of custom traversal to get native module versions --- scripts/build-backend.ts | 2 +- scripts/lib/native-modules.ts | 84 +++++++++++------------------------ 2 files changed, 27 insertions(+), 59 deletions(-) diff --git a/scripts/build-backend.ts b/scripts/build-backend.ts index 5d358e4..e85d766 100755 --- a/scripts/build-backend.ts +++ b/scripts/build-backend.ts @@ -74,7 +74,7 @@ const { abi: NODE_ABI } = readNodeJsMobileVersions(); // 2. Enumerate every concrete (name, version) pair of native modules // in the dep tree. `npm ci` ran via the `prebackend:build` npm // hook, so node_modules is current. -const nativePairs = collectNativePairs(BACKEND_SRC_DIR, NATIVE_MODULES); +const nativePairs = await collectNativePairs(BACKEND_SRC_DIR, NATIVE_MODULES); // 3. Rollup bundles backend/index.js into both per-platform output // dirs in one pass. The `OUTPUT_DIR_*` env vars point rollup at diff --git a/scripts/lib/native-modules.ts b/scripts/lib/native-modules.ts index d5e999e..52447d1 100644 --- a/scripts/lib/native-modules.ts +++ b/scripts/lib/native-modules.ts @@ -1,5 +1,6 @@ -import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { $ } from "execa"; export type NativeModule = { name: string; usesNapi: boolean }; @@ -33,65 +34,32 @@ export type NativePair = { usesNapi: boolean; }; -/** - * Walk `/node_modules` for every installed `(name, - * version)` of `name` (top-level + every nested copy). Multi-version - * dep trees ship one artifact per `(name, version)` pair, not one per - * name (which would silently shadow the lower version with the higher - * one). - */ -function findNativeModuleVersions( - backendDir: string, - name: string, -): { version: string }[] { - const topLevelNodeModules = join(backendDir, "node_modules"); - const versions: { version: string }[] = []; - const seenDirs = new Set(); - const stack: string[] = [topLevelNodeModules]; - while (stack.length > 0) { - const dir = stack.pop()!; - const candidate = join(dir, name, "package.json"); - if (existsSync(candidate)) { - const packageDir = dirname(candidate); - if (!seenDirs.has(packageDir)) { - seenDirs.add(packageDir); - const { version } = JSON.parse(readFileSync(candidate, "utf-8")); - versions.push({ version }); - } - } - const entries = (() => { - try { - return readdirSync(dir, { withFileTypes: true }); - } catch { - return []; - } - })(); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - const nested = join(dir, entry.name, "node_modules"); - if (existsSync(nested)) stack.push(nested); - } - } - return versions; -} - -/** - * Resolve every `(name, version)` pair that needs a prebuild + - * platform packaging. Multiple disk locations can share the same - * version (e.g. four nested `sodium-native@5.1.0` copies); the - * addon-loader rewrite loads each one by its versioned key, so build- - * side dedup avoids racing parallel fetches into the same temp dir. - */ -export function collectNativePairs( +export async function collectNativePairs( backendDir: string, modules: readonly NativeModule[], -): NativePair[] { - const seen = new Map(); +): Promise { + const pairs: NativePair[] = []; + for (const { name, usesNapi } of modules) { - for (const { version } of findNativeModuleVersions(backendDir, name)) { - const key = `${name}__${version}`; - if (!seen.has(key)) seen.set(key, { name, version, usesNapi }); + const npmListResult = await $({ + cwd: backendDir, + lines: true, + })`npm list ${name} --parseable`; + + const versions = new Set(); + + for (const modulePath of npmListResult.stdout) { + const { version } = JSON.parse( + readFileSync(join(modulePath, "package.json"), "utf-8"), + ); + + versions.add(version); + } + + for (const version of versions.values()) { + pairs.push({ name, version, usesNapi }); } } - return [...seen.values()]; + + return pairs; } From 835f46a503c0fe7f0ed4fb1422a687ae9ed2a305 Mon Sep 17 00:00:00 2001 From: Gregor MacLennan Date: Tue, 19 May 2026 09:58:01 +0100 Subject: [PATCH 2/2] single npm ls, prod deps only --- scripts/lib/native-modules.ts | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/scripts/lib/native-modules.ts b/scripts/lib/native-modules.ts index 52447d1..09d6489 100644 --- a/scripts/lib/native-modules.ts +++ b/scripts/lib/native-modules.ts @@ -39,27 +39,27 @@ export async function collectNativePairs( modules: readonly NativeModule[], ): Promise { const pairs: NativePair[] = []; + const deps = new Set(); - for (const { name, usesNapi } of modules) { - const npmListResult = await $({ - cwd: backendDir, - lines: true, - })`npm list ${name} --parseable`; + const npmListResult = await $({ + cwd: backendDir, + lines: true, + })`npm list --all --parseable --long --production`; - const versions = new Set(); - - for (const modulePath of npmListResult.stdout) { - const { version } = JSON.parse( - readFileSync(join(modulePath, "package.json"), "utf-8"), - ); - - versions.add(version); - } - - for (const version of versions.values()) { - pairs.push({ name, version, usesNapi }); - } + for (const line of npmListResult.stdout) { + const moduleInfo = line.split(":").at(-1); + if (!moduleInfo) continue; + deps.add(moduleInfo); } + for (const moduleInfo of deps.values()) { + const version = moduleInfo.split("@").at(-1); + if (!version) continue; + const name = moduleInfo.slice(0, -version.length - 1); + const nativeModule = modules.find((m) => m.name === name); + if (!nativeModule) continue; + const { usesNapi } = nativeModule; + pairs.push({ name, version, usesNapi }); + } return pairs; }