/**
* prune-nested — the bare-bootstrap build's "smart" nested-node_modules prune.
*
* Shared by pgh's and pgh-sidecar's build-bootstrap-bare.js (extracted so the
* logic lives in one place and can be unit-tested — the build scripts run
* top-level side effects on import, so they can't be imported directly).
*
* Goal: a bare tarball copies a package's FULL node_modules, which after npm
* hoisting contains many `node_modules/<X>/node_modules/<dep>` copies. Some
* are true hoist-duplicates (the same version also sits higher up and would
* resolve identically); deleting those shrinks the bundle. Others are
* load-bearing version-conflict resolutions that MUST stay.
*
* Correctness hinges on what a nested copy would resolve to AFTER deletion.
* Node/Bare resolve by walking node_modules ancestors NEAREST-first. So a
* nested `<dep>@V` is a true hoist-dup iff its NEAREST ancestor already
* provides `<dep>@V` — NOT merely "the bundle root has V". The earlier
* version compared against the root only, which deleted copies that an
* INTERMEDIATE node_modules would shadow with a DIFFERENT version. Concretely
* it deleted pgh/node_modules/hypercore/node_modules/compact-encoding@3.2.0
* when the bundle root also had ce@3.2.0 — but hypercore then fell through to
* pgh/node_modules/compact-encoding@2.19.2 (the intermediate), and
* hypercore@11.33.1 (needs ce ^3.0.0) booted against ce 2.x → DECODING_ERROR.
*
* The fix threads a per-level resolution map (`resolveHere`): the merge of a
* directory's node_modules ancestors overlaid with its own packages
* (nearest wins). A nested copy is deleted only when `resolveHere` for the
* OWNING package already maps that dep to the SAME version.
*/
import fs from '#node:fs' // #node:* resolves to bare-fs under bare, fs under node — test:bare imports this
import path from '#node:path'
/**
* Index a single node_modules dir → Map<name, version> for the packages that
* live DIRECTLY in it (handles `@scope/name`). Used both for the top-level
* index and to build each nested level's resolution map.
*/
export function indexTopLevel (nodeModulesDir) {
const index = new Map()
function readVersion (pkgDir, name) {
try {
const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'))
if (pkg && pkg.version) index.set(name, pkg.version)
} catch {}
}
let entries
try { entries = fs.readdirSync(nodeModulesDir) } catch { return index }
for (const name of entries) {
if (name.startsWith('.')) continue
const pkgDir = path.join(nodeModulesDir, name)
let stat
try { stat = fs.lstatSync(pkgDir) } catch { continue }
if (!stat.isDirectory()) continue
if (name.startsWith('@')) {
let subs
try { subs = fs.readdirSync(pkgDir) } catch { continue }
for (const sub of subs) {
const subDir = path.join(pkgDir, sub)
let subStat; try { subStat = fs.statSync(subDir) } catch { continue }
if (!subStat.isDirectory()) continue
readVersion(subDir, name + '/' + sub)
}
} else {
readVersion(pkgDir, name)
}
}
return index
}
/**
* Prune true hoist-duplicate nested copies under `topNodeModules`, in place.
* @returns {{ dupsRemoved: number, nestedKept: number }}
*/
export function pruneNestedSmart (topNodeModules) {
let dupsRemoved = 0
let nestedKept = 0
// resolveHere: Map<name, version> — what a package located directly in this
// node_modules resolves each dep to when it has no own nested copy (merge of
// ancestors + this level's own packages, nearest wins). This is exactly the
// version a nested copy would fall through to if deleted.
function visit (nmDir, resolveHere) {
let entries
try { entries = fs.readdirSync(nmDir) } catch { return }
for (const name of entries) {
if (name.startsWith('.')) continue
const pkgDir = path.join(nmDir, name)
let stat
try { stat = fs.lstatSync(pkgDir) } catch { continue }
if (stat.isSymbolicLink() || !stat.isDirectory()) continue
if (name.startsWith('@')) {
let subs
try { subs = fs.readdirSync(pkgDir) } catch { continue }
for (const sub of subs) processPackage(pkgDir, sub, resolveHere)
} else {
processPackage(nmDir, name, resolveHere)
}
}
}
function processPackage (parentDir, dirName, resolveHere) {
const pkgDir = path.join(parentDir, dirName)
const nested = path.join(pkgDir, 'node_modules')
if (!fs.existsSync(nested)) return
// Resolution map for THIS package's private deps (those living in
// `nested`): they see `nested` nearest, then resolveHere upward. Snapshot
// before any removal — a removed copy matches resolveHere, so the
// effective version is unchanged whether read from it or the ancestor.
const childResolve = new Map(resolveHere)
for (const [n, v] of indexTopLevel(nested)) childResolve.set(n, v)
// Keep/delete each copy in `nested` using the OWNING package's resolution
// (resolveHere) — what it falls through to if the copy is removed.
let inner
try { inner = fs.readdirSync(nested) } catch { return }
for (const innerName of inner) {
if (innerName.startsWith('.')) continue
const innerDir = path.join(nested, innerName)
let innerStat
try { innerStat = fs.lstatSync(innerDir) } catch { continue }
if (innerStat.isSymbolicLink() || !innerStat.isDirectory()) continue
if (innerName.startsWith('@')) {
let subs
try { subs = fs.readdirSync(innerDir) } catch { continue }
for (const sub of subs) maybeRemove(innerDir, sub, innerName + '/' + sub, resolveHere)
} else {
maybeRemove(nested, innerName, innerName, resolveHere)
}
}
// Recurse: kept private deps may have their OWN nested deps.
visit(nested, childResolve)
// Clean up empty node_modules dir.
try {
if (fs.existsSync(nested) && fs.readdirSync(nested).length === 0) {
fs.rmSync(nested, { recursive: true, force: true })
}
} catch {}
}
function maybeRemove (parentDir, dirName, fullName, resolveHere) {
const pkgPath = path.join(parentDir, dirName)
let version
try {
version = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf8')).version
} catch { return }
const resolvedVersion = resolveHere.get(fullName)
if (resolvedVersion && resolvedVersion === version) {
// Nearest ancestor already provides this EXACT version — removing the
// nested copy resolves to the same version. True hoist-dup.
fs.rmSync(pkgPath, { recursive: true, force: true })
dupsRemoved++
} else {
// Nearest ancestor has a DIFFERENT version (or none) — this nested copy
// is load-bearing (version-conflict resolution). Keep it.
nestedKept++
}
}
visit(topNodeModules, indexTopLevel(topNodeModules))
return { dupsRemoved, nestedKept }
}