OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / lib / walk-strategy.js

82 lines · javascript ref:
/**
 * walk-strategy — per-runtime tree-walking rules for pgh publish.
 *
 * Each runtime defines:
 *   rootSkip:  directories to EXCLUDE from the app's root tree walk.
 *              The deps in these dirs are walked separately as their
 *              own content-addressed trees.
 *   depDir:    where to look for top-level deps inside the app dir.
 *   depSkip:   what to exclude when walking an individual dep.
 *
 * This keeps the content-addressed store language-agnostic: each tree
 * is just bytes. The runtime-specific logic lives in how we slice up
 * the source tree at publish time and wire it back together at install.
 */

// Always-skipped entries across runtimes:
//   - Platform cruft (.DS_Store, Thumbs.db)
//   - pgh's own metadata files (lockfile is publish output, not content)
//   - VCS directories (.git, .pear-git) — big history, no runtime value;
//     npm's --install-links copies full source including .git when
//     resolving file: deps, which can bloat tree hashes with MB of refs
//   - Publisher-only metadata (.gitignore, .npmignore, package-lock.json) —
//     these are PUBLISHER-side artifacts, never executed. Including them in
//     published trees creates non-deterministic builds across installs: npm
//     ships/strips them inconsistently (registry tarball vs git vs cache,
//     --install-links copying file: deps) — leading to BUILD_SKEW on
//     otherwise byte-identical dep source code. Proven 2026-06-06: the only
//     diff between pgh's and pgh-sidecar's is-property@1.0.2 trees was a
//     `.npmignore`; is-property.js + package.json were byte-identical.
//     NOTE: kept DELIBERATELY narrow — README/LICENSE and all code stay in
//     the hash so a real source difference still surfaces as BUILD_SKEW
//     (the conflict-detail classifier reports those rather than the hash
//     silently swallowing them).
const BASE_SKIP = [
  '.DS_Store', 'Thumbs.db',
  '.pgh-lock.json',
  '.git', '.pear-git',
  '.gitignore', '.npmignore', 'package-lock.json'
]

// Bare apps (https://github.com/holepunchto/bare) consume the same npm
// node_modules/ layout as Node — the only differences are which runtime
// executes (bare vs node) and which built-in modules are available
// (bare-* vs node:*). For pgh's tree-walk purposes (slicing source from
// deps, hashing, dedup) they're identical, so 'bare' is an alias to the
// 'node' strategy. Apps with `pgh.runtime: "bare"` walk the same way.
const NODE_STRATEGY = {
  rootSkip: new Set([...BASE_SKIP, 'node_modules']),
  depDir: 'node_modules',
  depSkip: new Set([...BASE_SKIP, 'node_modules']),
  scopedPrefix: '@' // npm-style @scope/name subdirs
}

export const STRATEGIES = {
  node: NODE_STRATEGY,
  bare: NODE_STRATEGY,
  godot: {
    rootSkip: new Set([...BASE_SKIP, 'addons', '.godot']),
    depDir: 'addons',
    depSkip: new Set([...BASE_SKIP, '.godot']),
    scopedPrefix: null
  }
}

/**
 * Detect an app's runtime from its manifest / filesystem layout.
 * Explicit `pkg.pgh.runtime` wins; fallback heuristics cover common cases.
 */
export function detectRuntime (pkg, appDir, fs, path) {
  if (pkg && pkg.pgh && pkg.pgh.runtime) return pkg.pgh.runtime
  // Godot projects carry a project.godot at root
  if (fs.existsSync(path.join(appDir, 'project.godot'))) return 'godot'
  // Default: Node
  return 'node'
}

export function getStrategy (runtime) {
  const s = STRATEGIES[runtime]
  if (!s) throw new Error('unknown runtime: ' + runtime + ' — known: ' + Object.keys(STRATEGIES).join(', '))
  return s
}