OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scripts / flatten-node-modules.cjs

150 lines · javascript ref:
#!/usr/bin/env node
/**
 * Flatten only TOP-LEVEL symlinks in node_modules.
 *
 * Why this specifically: pgh's content-addressed materializer makes
 * `node_modules/<pkg>` and `node_modules/@scope/<pkg>` symlinks into
 * `node_modules/.pgh/<hash>/`. Bundlebee (lazy bundle loader in
 * keet-core 4.x) looks up modules via require.cache, but Bare caches
 * by realpath — so the bundle's `/node_modules/<pkg>/index.js`
 * references never match.
 *
 * Fix: replace each top-level symlink with a real copy of its target
 * (resolved through the symlink). Nested `<pkg>/node_modules/<dep>`
 * symlinks are NOT flattened, but they are RETARGETED: pgh writes them
 * relative (`../../<hash>`), so a copy that moved out of .pgh/<hash>/
 * carries links that dangle at its new depth. They all did (1310/1310 on
 * stortron, 0.1217.0): every nested require fell through to the top
 * level, and keet-core 4.22 got the app's blind-peering 2.1.2 instead of
 * its own 2.9.3. Bare caches by realpath, so each nested link must point
 * at the ONE place its hash loads from: the flattened top-level copy if
 * there is one (a second path would be a second instance), else
 * .pgh/<hash>. The same retarget applies to links inside each .pgh/<hash>, so a
 * package that stays in .pgh shares the top-level instances too.
 *
 * Cycle handling: avoided entirely by only processing top-level
 * entries (one or two levels deep). No recursion into copied content.
 */

'use strict'

const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')

const root = process.argv[2] || 'node_modules'
if (!fs.existsSync(root)) {
  console.error('flatten-node-modules: not found:', root)
  process.exit(1)
}

const start = Date.now()
let replaced = 0
let failed = 0
let retargeted = 0
// realpath of a .pgh/<hash> dir -> the top-level copy that now loads it
const flattenedFrom = new Map()
// flattened copy -> the .pgh dir it was copied from (its links are relative to there)
const copiedFrom = new Map()

function flattenLink (linkPath) {
  let real
  try { real = fs.realpathSync(linkPath) }
  catch { failed++; return }
  let st
  try { st = fs.statSync(real) }
  catch { failed++; return }
  if (!st.isDirectory()) {
    // a symlink to a file — just copy the file content
    const data = fs.readFileSync(real)
    fs.unlinkSync(linkPath)
    fs.writeFileSync(linkPath, data)
    replaced++
    return
  }
  // Replace symlink with a real recursive copy. Use `cp -r` (no -L)
  // so nested symlinks inside the target stay as symlinks — they
  // can resolve via the still-existing .pgh/ store.
  fs.unlinkSync(linkPath)
  try {
    execSync('cp -r ' + JSON.stringify(real) + '/. ' + JSON.stringify(linkPath), { stdio: 'pipe' })
    flattenedFrom.set(real, linkPath)
    copiedFrom.set(linkPath, real)
    replaced++
  } catch (err) {
    console.error('  cp failed for', linkPath, '←', real, ':', err.message)
    failed++
  }
}

function scanLevel (dir) {
  let entries
  try { entries = fs.readdirSync(dir, { withFileTypes: true }) }
  catch { return }
  for (const entry of entries) {
    if (entry.name === '.pgh') continue
    const p = path.join(dir, entry.name)
    if (entry.isSymbolicLink()) {
      flattenLink(p)
    } else if (entry.isDirectory() && entry.name.startsWith('@')) {
      // scoped package dir — recurse one level for `@scope/<pkg>`
      scanLevel(p)
    }
  }
}

// Every symlink directly in `<pkgDir>/node_modules/` (and `@scope/`).
function nestedLinks (pkgDir) {
  const out = []
  const nm = path.join(pkgDir, 'node_modules')
  let entries
  try { entries = fs.readdirSync(nm, { withFileTypes: true }) } catch { return out }
  for (const e of entries) {
    const p = path.join(nm, e.name)
    if (e.isSymbolicLink()) out.push(p)
    else if (e.isDirectory() && e.name.startsWith('@')) {
      let subs
      try { subs = fs.readdirSync(p, { withFileTypes: true }) } catch { continue }
      for (const s of subs) if (s.isSymbolicLink()) out.push(path.join(p, s.name))
    }
  }
  return out
}

// Point `link` at where its target hash loads from. `origin` is the dir the
// link's relative text was written against (differs from `pkgDir` for a copy).
function retarget (link, pkgDir, origin) {
  const text = fs.readlinkSync(link)
  const written = path.join(origin, path.relative(pkgDir, link))
  let target = path.resolve(path.dirname(written), text)
  try { target = fs.realpathSync(target) } catch { failed++; return }
  const canonical = flattenedFrom.get(target) || target
  const rel = path.relative(path.dirname(link), canonical)
  if (rel === text) return
  fs.unlinkSync(link)
  fs.symlinkSync(rel, link)
  retargeted++
}

function retargetAll () {
  for (const [copy, real] of copiedFrom) {
    for (const link of nestedLinks(copy)) retarget(link, copy, real)
  }
  const pgh = path.join(root, '.pgh')
  let hashes = []
  try { hashes = fs.readdirSync(pgh) } catch {}
  for (const h of hashes) {
    const dir = path.join(pgh, h)
    for (const link of nestedLinks(dir)) retarget(link, dir, dir)
  }
}

console.log('flatten-node-modules: scanning ' + root + ' (top-level + scoped)')
scanLevel(root)
retargetAll()
console.log('  replaced:    ' + replaced + ' top-level symlinks')
console.log('  retargeted:  ' + retargeted + ' nested symlinks')
console.log('  failed:      ' + failed)
console.log('  elapsed:     ' + ((Date.now() - start) / 1000).toFixed(1) + 's')
console.log('  note:        .pgh/ kept in place for nested links to unflattened hashes')