OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / lib / conflict-policy.js

53 lines · javascript ref:
/**
 * conflict-policy — app categories (compartments) for publish conflicts.
 *
 * Publish conflicts are only meaningful BETWEEN apps that are supposed to
 * co-version. Apps in different categories are expected to diverge — e.g.
 * `experimental` apps deliberately carrying a new major during a separate
 * test, or `external` apps (keet-daemon's private deps, hyper-tps the
 * leading-edge canary) that we don't co-version with at all. Holding a
 * version on purpose is a pin, not a conflict.
 *
 * Category resolution for an app, in order:
 *   1. the app's own lockfile.category (self-declared via pgh.category)
 *   2. the override map in ~/.config/pgh/conflict-policy.json
 *   3. the policy default (default 'production')
 *
 * conflict-policy.json shape:
 *   {
 *     "default": "production",
 *     "overrides": { "keet-daemon": "external", "hyper-tps": "external",
 *                    "pgh-experimental": "experimental", ... }
 *   }
 *
 * detectConflicts compares only rivals in the SAME category as the
 * publishing app. With no policy file present, the override map is empty
 * and every app falls to the default category, so conflict behavior is
 * unchanged (everything compared) — categories are purely additive.
 */

import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'

export const DEFAULT_CATEGORY = 'production'
export const POLICY_PATH = path.join(homedir(), '.config', 'pgh', 'conflict-policy.json')

export function loadPolicy (policyPath = POLICY_PATH) {
  let raw = {}
  try { raw = JSON.parse(fs.readFileSync(policyPath, 'utf8')) } catch { raw = {} }
  const def = typeof raw.default === 'string' ? raw.default : DEFAULT_CATEGORY
  const overrides = (raw.overrides && typeof raw.overrides === 'object') ? raw.overrides : {}
  return {
    default: def,
    overrides,
    // Resolve an app's category: self-declared lockfile.category wins, then
    // the override map, then the default.
    categoryOf (appName, lockfile) {
      if (lockfile && typeof lockfile.category === 'string') return lockfile.category
      if (Object.prototype.hasOwnProperty.call(overrides, appName)) return overrides[appName]
      return def
    }
  }
}