Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/_lint.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
on:
workflow_call:

# Declared here, not inherited: a reusable workflow's token is whatever the caller
# grants, and lint needs nothing but the checkout (plus the releases API, which is
# public read).
permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

Expand All @@ -20,14 +27,24 @@ jobs:
# Two errors are suppressed: actionlint's `job` context schema is stale and does not
# know `job.workflow_repository` / `job.workflow_sha`, both of which are real and
# documented, and which publish-gh-pages.yml depends on to version-match assemble.mjs.
#
# PINNED, and checked. This is the only binary the repo executes that it did not
# build, and resolving `releases/latest` at run time meant every lint run took
# whatever bytes that tag pointed at — while `astral-sh/setup-uv` two files over
# is pinned to a full version with a comment explaining why. Renovate bumps
# ACTIONLINT_VERSION (see renovate.json); it cannot compute the new digest, so
# ACTIONLINT_SHA256 must be updated in the SAME PR. Until it is, the checksum
# fails and the job goes red — which is the point: it fails closed, not open.
- name: Lint workflow files
env:
GH_TOKEN: ${{ github.token }}
ACTIONLINT_VERSION: 1.7.12
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
run: |
set -euo pipefail
ver=$(gh api repos/rhysd/actionlint/releases/latest --jq .tag_name | tr -d v)
gh release download "v$ver" --repo rhysd/actionlint \
-p "actionlint_${ver}_linux_amd64.tar.gz" -O "$RUNNER_TEMP/al.tgz"
gh release download "v$ACTIONLINT_VERSION" --repo rhysd/actionlint \
-p "actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" -O "$RUNNER_TEMP/al.tgz"
echo "$ACTIONLINT_SHA256 $RUNNER_TEMP/al.tgz" | sha256sum -c -
tar xzf "$RUNNER_TEMP/al.tgz" --no-same-owner -C "$RUNNER_TEMP" actionlint
"$RUNNER_TEMP/actionlint" -color \
-ignore 'property "workflow_(repository|sha)" is not defined'
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/_test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
on:
workflow_call:

# Declared here, not inherited — see _lint.yml. The tests are pure Node + Python.
permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Unit tests (widget logic + make-switcher parity)
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ name: CI
# PR's critical path and a Pages failure never shows as a red check against an author who
# cannot act on it. This workflow therefore needs no elevated permissions, and its `name:`
# below is load-bearing — publish.yml matches on it.
# The token this workflow's own jobs get, and the ceiling for the reusable workflows it
# calls: nothing here builds or tests with anything but read access. Declared at the top
# rather than left to the repo/org default, which may be write-all. The `release` job
# overrides it upward for the one job that needs to create a Release.
permissions:
contents: read

on:
pull_request:
push:
Expand Down
67 changes: 59 additions & 8 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ on:
required: false
default: docs/_build/html
type: string
base-path:
description: >-
URL path the SITE is served at, without the version segment. Empty (the
default) means /<repo>, which is where a project Pages site lives
(https://ORG.github.io/REPO/). Set it to "/" for a custom domain (CNAME) or an
ORG.github.io repo — both serve from the root, where a /<repo> prefix would
404 every root-absolute asset.
required: false
default: ""
type: string

# REUSABLE docs build — the UNPRIVILEGED half, shared across MyST projects. Builds
# the docs at the versioned BASE_URL and uploads this build's `docs` artifact
Expand All @@ -37,6 +47,10 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
# Generous: build-command is the consumer's, and a docs build with a Python env to
# solve can legitimately take a while. It only has to beat the 6-hour default, so a
# hung build fails within the hour instead of holding a runner all day.
timeout-minutes: 30
steps:
# fetch-depth: 0 brings the full history *and tags* — a build-command may
# derive a version from `git describe` (this repo's docs do, to pin the
Expand All @@ -62,34 +76,71 @@ jobs:

# Version name = the site sub-dir this build is served at, and the BASE_URL it
# must be built with: pr-<n> for PRs, otherwise the ref name (the default branch,
# or a tag without `/`). No sanitisation — every version name is URL/path-safe.
# or a tag without `/`).
#
# This is the OTHER half of the contract, and it is not passed anywhere: the
# gather independently re-derives the same name for each source it stages (a
# release's tag, a PR's number, the repo's default_branch) and unpacks the zip at
# site/<that name>. Both sides compute the same string from the same facts, so
# this rule and assemble.mjs's dest rule must change together.
- name: Compute version name
#
# The name is VALIDATED, not assumed clean. In THIS repo it is clean by
# construction (`tags: ['*']` never builds a `/`-tag), but this is a reusable
# workflow: a consumer triggering it on `push: branches: ['**']` would let anyone
# who can push a branch choose the string — and git accepts `$( )`, backticks and
# quotes in a ref name. Hence both halves here: the ref comes through `env:` so
# the shell never parses it as script text, and the character set is then checked
# so it stays a safe URL path segment and site dir.
- name: Compute version name and BASE_URL
id: ver
env:
EVENT: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REF_NAME: ${{ github.ref_name }}
BASE_PATH: ${{ inputs.base-path }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = pull_request ]; then
name="pr-${{ github.event.pull_request.number }}"
if [ "$EVENT" = pull_request ]; then
name="pr-$PR_NUMBER"
else
name="${{ github.ref_name }}"
name="$REF_NAME"
fi
case "$name" in
"" | "." | "..") ok=false ;;
*[!A-Za-z0-9._-]*) ok=false ;;
*) ok=true ;;
esac
if [ "$ok" != true ]; then
echo "::error::version name '$name' is not usable as a URL path segment \
— it must be non-empty and contain only A-Z a-z 0-9 . _ - (a '/' in a branch \
or tag name is the usual cause). Build this ref under a name that is, or \
exclude it from the workflow's triggers."
exit 1
fi
echo "version-name=$name" >> "$GITHUB_OUTPUT"

# BASE_URL must match the versioned sub-path the build is served at, or its
# root-absolute assets 404 — see the version-name contract above.
# BASE_URL must match the versioned sub-path the site is served at, or the
# build's root-absolute assets 404. The site half of it is base-path, which
# defaults to /<repo> — right for a project Pages site, wrong for a custom
# domain or an ORG.github.io repo, which serve at the root. Normalise to
# exactly one leading slash and no trailing one, so "/", "/repo" and "repo/"
# all compose the same way.
base="${BASE_PATH:-/$REPO_NAME}"
base="/${base#/}"
base="${base%/}"
echo "BASE_URL=$base/$name" >> "$GITHUB_ENV"
echo "version name: $name BASE_URL: $base/$name"

# BASE_URL arrives via GITHUB_ENV from the step above (it needs base-path's
# normalisation, which an `env:` expression cannot do).
# VERSION_NAME is the bare version token, exported for builds
# that need it directly — e.g. a Sphinx conf.py setting pydata-sphinx-theme's
# switcher `version_match` — without parsing it back out of BASE_URL.
# build-command comes through an env var (not interpolated into the script)
# and is run with `eval` so compound commands work.
- name: Build docs
env:
BASE_URL: /${{ github.event.repository.name }}/${{ steps.ver.outputs.version-name }}
VERSION_NAME: ${{ steps.ver.outputs.version-name }}
BUILD_COMMAND: ${{ inputs.build-command }}
run: |
Expand Down
Loading