OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-bridge / .pear-git / ci.yaml

103 lines · yaml ref:
# pear-git CI — auto-build + publish on `git push pear master`.
#
# Canonical bare-app distribution (path A): install the root dependency closure, then
# `pgh publish .` content-addresses the app into the ReleaseRegistry so users can
# `pgh app install atproto-bridge`. NOT the tarball/`artifacts:` path — that only serves a
# blob over hyper-deploy and isn't installable via `pgh app install` (see hypernote /
# hyper-wallet ci.yaml for the same pattern).
#
# The root package.json declares the union of the store/pds deps plus the extracted pure
# packages (atproto-repo, atproto-identity) as file: siblings, so one install hoists the
# whole closure into ./node_modules; its postinstall runs scripts/bare-deps.mjs to add
# @noble's "bare" ./crypto condition. The store/pds source dirs ship as-is and resolve
# their deps upward from the single root node_modules.
#
# NO server-boot smoke here: CI steps are synchronous with a hard timeout, and a step
# that boots a long-running server orphans the child when the timeout fires and aborts
# the build. The boot smoke lives in `npm run smoke` (local verification) only.

steps:
  - name: install deps (root closure)
    run: npm install --production
    timeout: 420

  # hyper-svc is a file:../hyper-svc sibling dep — the CI git-archives ONLY atproto-bridge
  # into /tmp, so npm leaves a broken symlink and the published tree lacks hyper-svc (the
  # daemon then crashes importing it in control.js). It is self-contained (own node_modules),
  # so vendor a dereferenced copy into the closure. Same shape as hypernote's copy-deps.
  - name: verify vendored siblings are in hosted git history
    run: |
      # A vendored sibling is copied from the WORKING TREE, so whatever is checked out on the build
      # machine is what ships to the fleet — reviewed or not. On 2026-07-28 that meant atproto-room
      # shipped 27 commits (incl. 0.2.14 projection-drift and 0.2.16 _isCreator) that were only on a
      # feature branch, and hyper-svc shipped 2 unpushed commits. Nothing surfaced it.
      #
      # This makes git AUTHORITATIVE for what ships: refuse to build unless every sibling's HEAD is
      # reachable from some hosted branch. Reachability, NOT equality with the default branch —
      # atproto-room legitimately ships from statebase-fork-heal, and a default-branch check would
      # block a repo that is perfectly fine.
      #
      # Untracked files WARN rather than block: cp -rL ships them too, but they are usually probe
      # scripts, and blocking on them would make the build unusable. Escape hatch for an emergency:
      # PGV3_ALLOW_UNPUSHED_SIBLINGS=1.
      #
      # The list is DERIVED from package.json's file: deps — never hardcoded. It used to be
      # spelled out here AND in the vendor step AND in a third hyper-svc-only step, and nothing
      # kept the three in sync. Adding favela-community to package.json (0.9.36) without touching
      # the other two broke `pgh publish` for every build after it, silently, for ~10 days.
      SIBS=$(node -e 'const d=require("./package.json");const a={...d.dependencies,...d.devDependencies};console.log(Object.entries(a).filter(([,v])=>String(v).startsWith("file:")).map(([k])=>k).join(" "))')
      echo "siblings (derived from package.json): $SIBS"
      [ -n "$SIBS" ] || { echo "no file: siblings found — package.json unreadable?"; exit 1; }
      rc=0
      for pkg in $SIBS; do
        SRC=/storage/projects/code/hypercore-kb/hyper-fleeky/$pkg
        [ -d "$SRC" ] || SRC="$HOME/hyper-fleeky/$pkg"
        [ -d "$SRC/.git" ] || { echo "  $pkg: not a git checkout — skipped"; continue; }
        GD="$HOME/git-repos/$pkg.git"
        if [ ! -d "$GD" ]; then echo "  $pkg: NOT HOSTED ($GD) — refuse"; rc=1; continue; fi
        LOCAL=$(git -C "$SRC" rev-parse HEAD 2>/dev/null)
        SHORT=$(echo "$LOCAL" | cut -c1-7)
        if [ -n "$(git -C "$SRC" status --porcelain --untracked-files=no 2>/dev/null | head -1)" ]; then
          echo "  $pkg: MODIFIED tracked files — refuse (commit or stash)"; rc=1; continue
        fi
        REFS=$(git --git-dir="$GD" for-each-ref --contains "$LOCAL" --format='%(refname:short)' refs/heads 2>/dev/null | tr '\n' ' ')
        if [ -z "$REFS" ]; then echo "  $pkg: $SHORT NOT in hosted history — refuse (push it)"; rc=1; continue; fi
        U=$(git -C "$SRC" ls-files --others --exclude-standard 2>/dev/null | wc -l)
        if [ "$U" -gt 0 ]; then echo "  $pkg: $SHORT ok (on: $REFS) — WARN: $U untracked file(s) also vendored"
        else echo "  $pkg: $SHORT ok (on: $REFS)"; fi
      done
      if [ "$rc" -ne 0 ]; then
        if [ -n "$PGV3_ALLOW_UNPUSHED_SIBLINGS" ]; then echo "PGV3_ALLOW_UNPUSHED_SIBLINGS set — continuing"; rc=0
        else echo "REFUSING TO BUILD: a vendored sibling is not in hosted git history. Push it, or set PGV3_ALLOW_UNPUSHED_SIBLINGS=1."; fi
      fi
      exit $rc
    timeout: 60

  # WHY THIS STEP EXISTS: the CI git-archives ONLY atproto-bridge into /tmp, so every `file:../`
  # sibling is left as a BROKEN SYMLINK and the published tree lacks it — the daemon then
  # crash-loops MODULE_NOT_FOUND on import. `cp -rL` dereferences a real copy into the closure.
  #
  # ONE list, derived from package.json, matching the guard step above. Previously this was three
  # hardcoded lists (guard / hyper-svc / the rest) that drifted apart; see the note in the guard.
  # NB: the name is QUOTED — an unquoted "file:" here is a second colon and YAML reads it as a
  # nested mapping ("mapping values are not allowed here"). Same trap #26 hit.
  - name: "vendor file: siblings into the closure"
    run: |
      SIBS=$(node -e 'const d=require("./package.json");const a={...d.dependencies,...d.devDependencies};console.log(Object.entries(a).filter(([,v])=>String(v).startsWith("file:")).map(([k])=>k).join(" "))')
      [ -n "$SIBS" ] || { echo "no file: siblings found — refusing to publish a tree with unresolved deps"; exit 1; }
      rc=0
      for pkg in $SIBS; do
        SRC=/storage/projects/code/hypercore-kb/hyper-fleeky/$pkg
        [ -d "$SRC" ] || SRC="$HOME/hyper-fleeky/$pkg"
        if [ ! -d "$SRC" ]; then echo "  $pkg: NOT FOUND on this build machine — refuse"; rc=1; continue; fi
        rm -rf "node_modules/$pkg" && cp -rL "$SRC" "node_modules/$pkg" && echo "  vendored $pkg from $SRC"
      done
      # Fail LOUD rather than let `pgh publish` discover the gap: publish's own refusal is correct
      # but it reports one sibling at a time and reads like a publish bug, not a vendoring bug.
      exit $rc
    timeout: 180

  - name: pgh publish (content-addressed → installable app)
    run: pgh publish . --accept-conflict "automated CI publish; dep version-splits inherited from per-app lockfiles across the pgh ecosystem"
    timeout: 300