tower / files / tools / build-pages.sh
build-pages.sh
342ab851 2026-09-28 Pavel Safronov | 1 | #!/bin/sh |
| 2 | # Build the GitLab Pages site: the default branch at the root, and every other branch | |
| 3 | # at /<branch>/, so any branch can be looked at in a browser without a dev server. | |
| 4 | # | |
| 5 | # GitLab's own way to do this (parallel deployments) is Premium-only, and this project | |
| 6 | # is on the Free plan. So the default branch's pipeline builds every branch into one | |
| 7 | # site, and a push to any other branch triggers a default-branch pipeline to rebuild it | |
| 8 | # (.gitlab-ci.yml). Assets are relative (vite.config.ts), so a build works in a folder. | |
| 9 | # | |
| 10 | # Every folder also gets a branches.json listing what was built, with links relative to | |
| 11 | # that folder, which is what the app's branch picker reads (src/ui/app.ts). | |
| 12 | # | |
| 13 | # sh tools/build-pages.sh [out] run from the repository root; out defaults to public | |
| 14 | set -eu | |
| 15 | ||
| 16 | DEFAULT="${CI_DEFAULT_BRANCH:-main}" | |
| 17 | OUT="${1:-public}" | |
| 18 | WORK="$(mktemp -d)" | |
| 19 | trap 'rm -rf "$WORK"' EXIT | |
| 20 | ||
| 21 | # The default branch, as checked out. | |
| 22 | npm ci | |
| 23 | npm run build | |
| 24 | rm -rf "$OUT" | |
| 25 | mv dist "$OUT" | |
| 26 | # What was built, as branch:folder pairs; the default branch's folder is the root. | |
| 27 | # Neither a git branch name nor a folder name can hold a colon or a space. | |
| 28 | built="$DEFAULT:" | |
| 29 | ||
| 30 | # Every other branch GitLab has, each into its own folder. CI clones shallowly and | |
| 31 | # fetches only the branch it is building, so fetch the rest first. | |
| 32 | git fetch --quiet --prune origin '+refs/heads/*:refs/remotes/origin/*' | |
| 33 | for branch in $(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin); do | |
| 34 | case "$branch" in "$DEFAULT"|HEAD) continue ;; esac | |
| 35 | # A folder name a URL can carry: fix/worker-loading becomes fix-worker-loading. | |
| 36 | name="$(printf '%s' "$branch" | tr 'A-Z' 'a-z' | tr -c 'a-z0-9.\n-' '-')" | |
| 37 | if [ -e "$OUT/$name" ]; then | |
| 38 | echo "skipping $branch: /$name/ is already part of the site" | |
| 39 | continue | |
| 40 | fi | |
| 41 | echo "building $branch into /$name/" | |
| 42 | git worktree add --quiet --detach "$WORK/$name" "origin/$branch" | |
| 43 | # A branch that does not build is left out rather than failing the whole site. | |
| 44 | if (cd "$WORK/$name" && npm ci && npm run build); then | |
| 45 | mv "$WORK/$name/dist" "$OUT/$name" | |
| 46 | built="$built $branch:$name" | |
| 47 | else | |
| 48 | echo "WARNING: $branch did not build; it has no preview this time" | |
| 49 | fi | |
| 50 | git worktree remove --force "$WORK/$name" | |
| 51 | done | |
| 52 | ||
| 53 | # branches.json in every folder: each branch, a link to it from here, and which is this. | |
| 54 | write_branches() { | |
| 55 | here="$1" | |
| 56 | up="" | |
| 57 | [ -n "$here" ] && up="../" | |
| 58 | sep="" | |
| 59 | { | |
| 60 | printf '[' | |
| 61 | for pair in $built; do | |
| 62 | b="${pair%%:*}" | |
| 63 | f="${pair#*:}" | |
| 64 | href="$up${f:+$f/}" | |
| 65 | [ -n "$href" ] || href="./" | |
| 66 | cur=false | |
| 67 | [ "$f" = "$here" ] && cur=true | |
| 68 | printf '%s\n {"branch": "%s", "href": "%s", "current": %s}' "$sep" "$b" "$href" "$cur" | |
| 69 | sep="," | |
| 70 | done | |
| 71 | printf '\n]\n' | |
| 72 | } > "$OUT/${here:+$here/}branches.json" | |
| 73 | } | |
| 74 | for pair in $built; do | |
| 75 | write_branches "${pair#*:}" | |
| 76 | done |