/**
* 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
}