Skip to content

feat(cli): --dry-run, --base-url, --timeout, and the load report in the artifact #72

feat(cli): --dry-run, --base-url, --timeout, and the load report in the artifact

feat(cli): --dry-run, --base-url, --timeout, and the load report in the artifact #72

Workflow file for this run

name: Site
# The site at tmhsdigital.github.io/plumbline carries a JavaScript
# reimplementation of the calibration floor. If it disagrees with the Python,
# the site misreports the tool, which is worse than having no site. This
# workflow holds the two together, checks the assembled site as a visitor would
# meet it, and only then deploys. Pull requests run both checks and never
# deploy; a push to main or a manual run deploys only if both pass. A stale
# site is the better failure.
#
# It is deliberately separate from ci.yml and release.yml: it shares no jobs,
# no concurrency group, and no permissions with either.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
# A newer push to a pull request makes its older run pointless. On main, a run
# is never cancelled part way, so a deploy is never cut off.
concurrency:
group: site-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
parity:
name: the site's floor agrees with the Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.13"
enable-cache: true
- name: Sync dependencies
run: uv sync --locked
# Without this, a change to the Python floor would leave the JavaScript
# agreeing with an old answer and the check below passing.
- name: The committed fixture is what the Python produces today
run: uv run python scripts/floor_golden.py --check
# Regenerates the worked example from the mock adapter, by the command
# docs/example-report.md records, and refuses if any line of the report
# differs from what that command prints today. Renders the repository's
# docs from this commit, and refuses on a broken link or anchor, or raw
# HTML outside the allowlist.
- name: Assemble the site
run: uv run python scripts/build_site.py --out _site
# The runner's own Node, with no setup action and no package install.
# The check uses the standard library only. Given the built example, it
# also requires the page to derive the report's ECE line exactly.
- name: floor.js reproduces the fixture and the example report
run: |
node --version
node scripts/check_floor_parity.mjs _site/example-run.json
# search.js against cases that say what a reader should find first, and
# every search-index.json entry against the page and id it names. Also
# the files the page scripts fetch, which the link check cannot see.
- name: Search ranks as it should, and every index entry resolves
run: node scripts/check_search.mjs _site
# The exact bytes the deploy job publishes. On a pull request this is
# also a downloadable preview of the site.
- name: Keep the assembled site
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: _site
docs:
name: the assembled site's links, anchors, and meta tags resolve
needs: parity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# The renderer's refusals (raw HTML off the allowlist, broken links and
# anchors, remote images, a modified vendored file) each have a case.
- name: The doc renderer refuses what it should
run: node scripts/render_docs.mjs --self-test
- name: Fetch the assembled site
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: github-pages
path: artifact
# Checked as published, not as built: the same archive the deploy job
# serves, unpacked.
- name: Every link, anchor, and meta tag resolves, and nothing loads from another origin
run: |
mkdir site-as-published
tar -xf artifact/artifact.tar -C site-as-published
node scripts/check_site_links.mjs site-as-published
# The runner's own Chrome, driven over the DevTools protocol by the
# runner's own Node; nothing is installed. It fails on a broken flow (the
# calculator, a shared link, search) and on any console error or CSP
# violation, on every page.
- name: The pages work in a real browser
run: node scripts/smoke_site.mjs site-as-published
deploy:
name: deploy to GitHub Pages
needs: [parity, docs]
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
# One deploy at a time; a queued one waits rather than cancelling.
concurrency:
group: pages
cancel-in-progress: false
steps:
- name: Publish the checked site
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
live:
name: the live site resolves as the checked one did
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# What Pages actually serves, including a real 404 for a missing path,
# which only a request to the live site can show. The CDN can take a
# moment to serve the new build, so a failure is retried once.
- name: Every link, anchor, meta tag, and policy resolves on the live site
run: |
node scripts/check_site_links.mjs https://tmhsdigital.github.io/plumbline/ || {
sleep 60
node scripts/check_site_links.mjs https://tmhsdigital.github.io/plumbline/
}