OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / lib / local-fetcher.js

55 lines · javascript ref:
/**
 * localFetcher — build a {getTree, getContent} over one or more source
 * directories by walking them with the exact same walkTree the
 * publisher uses. Feeds installTree so the publisher can populate its
 * own `dependencies/` store with every tree it just walked — no
 * network round-trip involved.
 *
 * Critical invariant: the walkTree options here MUST match the options
 * used at lockfile-computation time, otherwise the hashes differ and
 * installTree throws "invalid tree" on cache misses.
 */

import fs from '#node:fs'
import path from '#node:path'

import { walkTree, KIND_FILE, KIND_SYMLINK } from 'hyper-overlay/tree'

/**
 * @param {Array<{ rootPath: string, skip: Set<string> }>} dirs
 * @returns {{ getTree: Function, getContent: Function }}
 */
export function localFetcher (dirs) {
  const trees = new Map() // treeHash → entries
  const content = new Map() // contentHash → Buffer

  for (const { rootPath, skip, matcher } of dirs) {
    const walked = walkTree(rootPath, { skip, matcher })
    for (const [rel, tree] of walked) {
      trees.set(tree.hash, tree.entries)
      for (const e of tree.entries) {
        const absEntry = path.join(rootPath, rel, e.name)
        if (e.kind === KIND_FILE) {
          try { content.set(e.childHash, fs.readFileSync(absEntry)) } catch {}
        } else if (e.kind === KIND_SYMLINK) {
          try { content.set(e.childHash, Buffer.from(fs.readlinkSync(absEntry), 'utf8')) } catch {}
        }
      }
    }
  }

  return {
    async getTree (hash) {
      const entries = trees.get(hash)
      if (!entries) throw new Error('localFetcher miss (tree): ' + hash.slice(0, 12))
      const size = entries.reduce((a, e) => a + (e.size || 0), 0)
      return { entries, size }
    },
    async getContent (hash) {
      const c = content.get(hash)
      if (!c) throw new Error('localFetcher miss (content): ' + hash.slice(0, 12))
      return c
    }
  }
}