Skip to content

build

build #222

Workflow file for this run

name: build
# This is the production build+deploy pipeline: it clones the archive, reads
# ~16k per-style metadata files, and runs the full vite-ssg build. That is far
# too heavy for a per-PR check (and can stall for hours when the archive is
# mid-build), so it does NOT run on pull_request — fast PR validation lives in
# pr-check.yml. This workflow only runs where a real deploy can happen: main,
# manual dispatch, the archive [rebuild] dispatch, and the daily backstop.
on:
push:
branches:
- main
paths-ignore:
- README.md
- CONTRIBUTING.md
- LICENSE
- .gitignore
workflow_dispatch:
repository_dispatch:
types: [rebuild]
# Backstop: the archive sync triggers a [rebuild] dispatch after it publishes,
# but if that dispatch is ever dropped (network failure, missing token) the
# site would otherwise serve stale data indefinitely. A daily rebuild picks up
# any archive change the dispatch missed. It re-pins the current archive SHA,
# so a no-op day just rebuilds identical output.
schedule:
- cron: '17 5 * * *'
# Least privilege by default. `pages: write` / `id-token: write` are granted
# only to the deploy job, so the build job cannot touch Pages even if the
# deploy gate below were misconfigured.
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
# `pages: read` is what actions/configure-pages needs to read the Pages
# config (it does not enable Pages here, and its outputs are unused —
# astro.config.mjs hardcodes `site`). Deliberately not `write`: a PR build
# must not be able to publish.
permissions:
contents: read
pages: read
steps:
- name: Checkout
uses: actions/checkout@v6
# Build-time metadata only: the two registry JSONs and the file manifest.
# These are never served to a browser, so they can come from the private
# archive — which matters because archive-public does not publish
# font-metadata.json yet. blob:none + sparse-checkout pulls ~5 MB of blobs
# instead of the repo's 6 GB, while still giving `git ls-tree` the full
# tree for the manifest.
#
# Skipped when the secret is unavailable (fork PRs); fetch-data.sh then
# falls back to archive-public and reports the missing artifact clearly.
- name: Checkout archive-private (metadata only)
id: meta
continue-on-error: true
uses: actions/checkout@v6
with:
repository: fontist/fontist-archive-private
token: ${{ secrets.FONTIST_CI_PAT_TOKEN }}
path: vendor/archive-private
filter: blob:none
sparse-checkout: |
fonts.json
metadata
sparse-checkout-cone-mode: false
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Setup Node
uses: actions/setup-node@v4
with:
# Matches pr-check.yml (and local dev): keep the build and the PR
# test run on the same Node so a PR can't pass on a version the
# deploy build doesn't use. Node 20 is also deprecated on runners.
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
# Both cached artifacts are keyed on the upstream commit that produced
# them, so a cache hit is only ever used when the source is unchanged.
# fetch-data.sh and fetch-essenfont.mjs both skip their work when the
# output is already present, so a hit turns into a genuine no-op.
- name: Resolve upstream commits
id: upstream
run: |
echo "formulas=$(git ls-remote https://github.com/fontist/formulas.git v5 | cut -f1)" >> "$GITHUB_OUTPUT"
echo "essenfont=$(git ls-remote https://github.com/essenfont/essenfont.github.io.git HEAD | cut -f1)" >> "$GITHUB_OUTPUT"
# Saves cloning formulas + `npm install` in docs/ + generate.js (~13s),
# and means a formulas outage no longer breaks the site build.
- name: Cache formulas data
uses: actions/cache@v4
with:
path: |
public/formulas-data.json
public/stats.json
key: formulas-data-${{ steps.upstream.outputs.formulas }}
# 209 per-block woff2 files (~97 MB), otherwise re-cloned every build.
- name: Cache Essenfont woff2
uses: actions/cache@v4
with:
path: public/fonts
key: essenfont-${{ steps.upstream.outputs.essenfont }}
- name: Build (fetch-data.sh clones the archive + formulas v5, then vite-ssg builds)
# Only point at the private checkout if it actually succeeded, so a
# fork PR falls back cleanly instead of pointing at an empty directory.
env:
ARCHIVE_META_PATH: ${{ steps.meta.outcome == 'success' && 'vendor/archive-private' || '' }}
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
if: github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
needs: build
# Pages write access lives here only — the build job above runs read-only.
permissions:
contents: read
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4