#!/usr/bin/env bash # Build the site and publish it to the `pages` branch. # # .agents/scripts/publish-pages.sh # publish # .agents/scripts/publish-pages.sh --dry-run # build and report, push nothing # .agents/scripts/publish-pages.sh --pending X # main is *about* to become X # # `--pending` exists for the pre-push hook. Git has no post-push hook, so the # hook necessarily runs before main lands on the remote and the usual "HEAD must # equal origin/main" check cannot hold yet. The caller asserts the SHA the push # will create, and the hook only asserts it after confirming the push is a # fast-forward. # # `pages` is what the Gitea Pages Server actually serves. Publishing overwrites # the live site. There is no staging environment between here and visitors. # # This never checks `pages` out. It writes a tree straight from `dist/` with # plumbing (`write-tree` + `commit-tree`), so your working tree is untouched and # a failure halfway through leaves nothing behind. The commit is parented on the # current `pages`, so the branch keeps its history and rollback is one push. set -euo pipefail cd "$(git rev-parse --show-toplevel)" dry_run=0 pending='' while [ $# -gt 0 ]; do case "$1" in --dry-run) dry_run=1 ;; --pending) shift pending="${1:-}" ;; *) echo "publish-pages: unknown argument '$1'" >&2 exit 2 ;; esac shift done fail() { echo "publish-pages: $1" >&2 exit 1 } # Publishing a build made from uncommitted work means the live site shows # something no commit describes, and nobody can reproduce it later. [ -z "$(git status --porcelain)" ] || fail 'working tree is dirty; commit or stash first' branch=$(git rev-parse --abbrev-ref HEAD) [ "$branch" = 'main' ] || fail "publishing from '$branch'; only main is publishable" git fetch --quiet origin pages head=$(git rev-parse HEAD) if [ -n "$pending" ]; then [ "$head" = "$(git rev-parse "$pending")" ] || fail "HEAD is $head but the pending push is $pending" else git fetch --quiet origin main [ "$head" = "$(git rev-parse origin/main)" ] || fail 'HEAD is not origin/main; push main first so the site matches a pushed commit' fi previous=$(git rev-parse origin/pages) echo "publish-pages: building $head" pnpm run build >/dev/null # A build can succeed and still emit a stub -- that is exactly how this site # would go down. Check the routes exist before overwriting anything live. for route in index full-guide/index summary/index models/index agents/index \ skills/index rules/index skills-review/index \ hands-on/starter/index hands-on/rules/index; do [ -s "dist/$route.html" ] || fail "dist/$route.html missing or empty; refusing to publish" done index=$(mktemp) trap 'rm -f "$index"' EXIT # `--force` because the repository .gitignore lists `dist`; here `dist` *is* the # work tree, so those rules would otherwise exclude everything we mean to ship. GIT_INDEX_FILE="$index" git --work-tree=dist add --all --force . tree=$(GIT_INDEX_FILE="$index" git write-tree) if [ "$tree" = "$(git rev-parse "$previous^{tree}")" ]; then echo "publish-pages: dist is identical to the published tree; nothing to do" exit 0 fi subject="chore: publish $(git rev-parse --short "$head")" commit=$(git commit-tree "$tree" -p "$previous" -m "$subject Built from main $head $(git log -1 --format=%s "$head")") if [ "$dry_run" -eq 1 ]; then echo "publish-pages: would push $commit to pages (previous $previous)" echo "publish-pages: dry run, nothing pushed" exit 0 fi echo "publish-pages: rollback point is $previous" echo " git push --force origin $previous:refs/heads/pages" # AF_PUBLISHING stops the pre-push hook recursing into this script. AF_PUBLISHING=1 git push --force origin "$commit:refs/heads/pages" echo "publish-pages: published $commit"