OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / scripts / patch-keet-bundles.cjs

117 lines · javascript ref:
// Patch @holepunchto/keet-core-hyperdb's abi*.bundle.js files to inline
// the file content their resolution maps reference but don't include.
//
// Why: keet-core-hyperdb 1.22.0 ships with abi*.bundle.js files where
// each bundle's resolution map points at relative paths like /package.json
// or /lib/contract/foo.js, but the bundle itself only includes some of
// those targets as bundle entries. When bare-union-bundle's protocol.read
// hits a missing key it returns null, and bare-module's
// _extensions['.json'] then crashes on JSON.parse(null.toString()).
//
// The files exist on disk in keet-core-hyperdb's package tree — they just
// weren't inlined into the bundles. This script adds them. Idempotent;
// safe to re-run; no-op if the bundles are already complete.
//
// Run from hyper-tps build root: `node scripts/patch-keet-bundles.js`
//
// Drop this script when @holepunchto publishes a fixed keet-core-hyperdb.

const fs = require('fs')
const path = require('path')

const KEET_ROOT = path.resolve(
  process.argv[2] || 'node_modules/@holepunchto/keet-core-hyperdb'
)

if (!fs.existsSync(path.join(KEET_ROOT, 'package.json'))) {
  console.error('patch-keet-bundles: not a keet-core-hyperdb root: ' + KEET_ROOT)
  process.exit(2)
}

// bare-bundle may be hoisted to top-level node_modules (typical npm flat
// install) or nested under keet-core-hyperdb's own node_modules. Try both.
function loadBareBundle () {
  const candidates = [
    path.join(KEET_ROOT, 'node_modules/bare-bundle/index.js'),
    path.resolve(KEET_ROOT, '../../bare-bundle/index.js'),
    path.resolve(KEET_ROOT, '../../../bare-bundle/index.js')
  ]
  for (const p of candidates) {
    if (fs.existsSync(p)) return require(p)
  }
  throw new Error('patch-keet-bundles: bare-bundle not found in any of:\n  ' + candidates.join('\n  '))
}
const Bundle = loadBareBundle()

const bundleDir = path.join(KEET_ROOT, 'bundle')
const abiFiles = fs.readdirSync(bundleDir)
  .filter(f => /^abi\d+\.bundle\.js$/.test(f))
  .sort()

let totalInlined = 0
let bundlesPatched = 0
const trulyMissing = []

for (const f of abiFiles) {
  const fullPath = path.join(bundleDir, f)
  // Bust any cached require of this exact file from prior iterations
  delete require.cache[fullPath]
  const b = require(fullPath)

  const refs = new Set()
  for (const map of Object.values(b.resolutions || {})) {
    for (const v of Object.values(map)) {
      if (typeof v === 'string' && v.startsWith('/')) refs.add(v)
    }
  }

  let inlinedHere = 0
  // For each newly inlined file we need to know its resolution map (its
  // own imports). The bundle's existing resolutions describe how OTHER
  // files refer to it, not what it itself imports. Without an explicit
  // entry, _resolutions[file] becomes undefined; UnionBundle.load then
  // hits `Object.entries(undefined)` mid-load and crashes.
  //
  // For JSON files: no imports, empty {} is correct.
  // For JS files: would need bare-module-traverse to compute properly;
  // here we use {} as a safe minimum — if the loaded module then does
  // `require(...)`, it'll fail at that require with a clearer error
  // than "Object.entries of undefined".
  for (const p of refs) {
    if (b.read(p) !== null) continue
    if (p.startsWith('/node_modules/')) continue // bundle://host paths — out of scope
    const diskPath = path.join(KEET_ROOT, p)
    if (!fs.existsSync(diskPath)) {
      trulyMissing.push(`${f}:${p}`)
      continue
    }
    // imports: {} marks the file with an explicit (empty) resolution
    // entry. Without this, b._resolutions[p] stays unset, and
    // UnionBundle.load's `Object.entries(b.resolutions[k])` crashes
    // on the undefined value.
    b.write(p, fs.readFileSync(diskPath), { imports: {} })
    inlinedHere++
  }

  if (inlinedHere === 0) continue

  const buf = b.toBuffer()
  const b64 = buf.toString('base64')
  const newSource =
    "const Bundle = require('bare-bundle')\n" +
    "module.exports = Bundle.from(\n  Buffer.from(\n    '" + b64 + "',\n    'base64'\n  )\n)\n"
  fs.writeFileSync(fullPath, newSource)

  console.log('  ' + f.padEnd(20) + ' inlined ' + inlinedHere + ' missing entries')
  totalInlined += inlinedHere
  bundlesPatched++
}

console.log('patch-keet-bundles: ' + bundlesPatched + '/' + abiFiles.length +
  ' bundles patched, ' + totalInlined + ' files inlined')
if (trulyMissing.length) {
  console.log('patch-keet-bundles: ' + trulyMissing.length +
    ' references skipped (file not on disk; older-ABI bundle leftovers):')
  trulyMissing.slice(0, 10).forEach(s => console.log('  ' + s))
}