tower / files / tools / build-pages.sh
build-pages.sh
| 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 |