120 lines
5.3 KiB
YAML
120 lines
5.3 KiB
YAML
# build.yml — the SINGLE-BUILDER CI gate for the doxa-maps bundler.
|
|
#
|
|
# WHY THIS EXISTS (IDEA-FORGEJO-CICD-BUILD-LOCK-MULTIWRITER):
|
|
# Many agents edit + commit source to this repo in PARALLEL. That is safe. What is
|
|
# NOT safe is many agents each running `bun run build` into the shared, committed
|
|
# web-root (public/): concurrent builds clobber each other's output and race the
|
|
# `git add public/` that follows. internal/build-lock.sh makes that STRUCTURALLY
|
|
# impossible on ONE machine (an flock mutex); this workflow does the same thing at
|
|
# CLUSTER scope — it makes CI the ONE-AND-ONLY builder, and serializes it.
|
|
#
|
|
# THE CONTRACT for a swarm agent:
|
|
# edit files -> git commit -> git push (DONE. never run `bun run build`.)
|
|
# The push lands here. This job — and ONLY this job — runs the authoritative build
|
|
# and commits the regenerated public/ back. See the handoff doc:
|
|
# _qa-cycle/03-deployment-cycle/delegation/HANDOFF-forgejo-cicd-buildgate.md
|
|
#
|
|
# NOTE ON PATH: Forgejo reads .forgejo/workflows/ (and .gitea/workflows/), NOT
|
|
# .github/workflows/. The existing .github/workflows/pages.yml is a GitHub-Pages
|
|
# workflow and never runs here; this is its Forgejo graduation.
|
|
|
|
name: build-gate
|
|
|
|
# Trigger on every push to the source branches. workflow_dispatch lets the Driver
|
|
# run it by hand from the Forgejo UI.
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- doxa-maps-plugin-studio
|
|
# Source-only edits trigger a build. Skip pushes that touch NOTHING but the
|
|
# build output or CI itself — the build-output commit this job makes would
|
|
# otherwise re-trigger the job in a loop.
|
|
paths-ignore:
|
|
- 'public/**'
|
|
- '.forgejo/**'
|
|
workflow_dispatch:
|
|
|
|
# THE HEART OF THE GATE. All builds share ONE concurrency group, so Forgejo runs
|
|
# at most one at a time. A push that arrives while a build is running is QUEUED,
|
|
# not cancelled and not failed — cancel-in-progress is FALSE on purpose: we want
|
|
# every push's source to be built in order, never dropped. This is the flock
|
|
# nonblock/queue behaviour of build-lock.sh, expressed as a scheduler primitive.
|
|
concurrency:
|
|
group: doxa-maps-build
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build:
|
|
# Requires a registered Forgejo Actions runner labelled `ubuntu-latest`
|
|
# (docker or host). If none is registered the job stays queued — see the
|
|
# handoff doc for the ONE Driver action that registers it.
|
|
runs-on: ubuntu-latest
|
|
|
|
# The job needs to read the repo AND commit the regenerated public/ back, so
|
|
# it needs write. The runner's automatic token carries the repo's scope.
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout the pushed source
|
|
uses: actions/checkout@v4
|
|
with:
|
|
# Full history so the build-output commit is a normal fast-forward.
|
|
fetch-depth: 0
|
|
|
|
- name: Install bun
|
|
uses: https://github.com/oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Install dependencies (locked)
|
|
run: bun install --frozen-lockfile
|
|
|
|
# THE AUTHORITATIVE BUILD. build:unlocked is the real chain (clean -> token
|
|
# -> per-bundle vite -> tree -> drop/parse checks -> verify -> gallery). We
|
|
# call :unlocked, NOT `bun run build`, because CI IS the single builder now —
|
|
# the local flock mutex would only get in its own way here. The MAPBOX_TOKEN
|
|
# secret becomes public/api/maps/token.json (gitignored) so the credential
|
|
# reaches the built site without ever entering git history.
|
|
- name: Build (authoritative, single-writer)
|
|
env:
|
|
MAPBOX_TOKEN: ${{ secrets.MAPBOX_TOKEN }}
|
|
VITE_MAPBOX_TOKEN: ${{ secrets.MAPBOX_TOKEN }}
|
|
BUILD_LOCK_OWNER: forgejo-ci
|
|
run: bun run build:unlocked
|
|
|
|
# Guards graduated from pages.yml: never publish a broken or credential-
|
|
# leaking web-root. A failure here fails the build — nothing is committed.
|
|
- name: Assert the token endpoint exists
|
|
run: |
|
|
test -s public/api/maps/token.json || {
|
|
echo "::error::public/api/maps/token.json is missing/empty — set the MAPBOX_TOKEN secret, or every basemap renders blank."
|
|
exit 1
|
|
}
|
|
|
|
- name: Assert no local credential is being published
|
|
run: |
|
|
if [ -e public/token.local.js ]; then
|
|
echo "::error::public/token.local.js is present in the web-root and must not be committed."
|
|
exit 1
|
|
fi
|
|
|
|
# Commit the regenerated public/ back to the branch. This is the ONE writer
|
|
# of build output in the whole system. If the build produced no change,
|
|
# commit nothing (idempotent). The token endpoint is gitignored, so it is
|
|
# never staged. The [skip ci]-equivalent is handled by the paths-ignore
|
|
# above: a public/-only commit does not re-trigger this job.
|
|
- name: Publish the built web-root
|
|
run: |
|
|
git config user.name "forgejo-ci"
|
|
git config user.email "ci@doxa.life"
|
|
git add public/
|
|
if git diff --cached --quiet; then
|
|
echo "No build-output change — nothing to publish."
|
|
exit 0
|
|
fi
|
|
git commit -m "build: regenerate public/ from ${GITHUB_SHA:-source}"
|
|
# Push back to the branch that triggered us. The runner token authorizes
|
|
# this; contents:write is granted above.
|
|
git push origin "HEAD:${GITHUB_REF_NAME}"
|