6b4f2e6bd0
git reads an existing empty file as a truncated index and dies with "index file smaller than expected", so mktemp's own file cannot be used as GIT_INDEX_FILE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
4.0 KiB
Bash
Executable File
112 lines
4.0 KiB
Bash
Executable File
#!/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
|
|
|
|
# GIT_INDEX_FILE must name a path that does not exist yet: git reads an existing
|
|
# empty file as a truncated index and dies with "index file smaller than
|
|
# expected". mktemp -d gives a private directory to put that path in.
|
|
index_dir=$(mktemp -d)
|
|
index="$index_dir/index"
|
|
trap 'rm -rf "$index_dir"' 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"
|