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
6 changes: 3 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ the required-branch guard) lives in `assemble/assemble.mjs` and is unit-tested
git tag vX.Y.Z && git push origin vX.Y.Z
```

CI runs lint + tests + the docs build; `_release.yml` creates a GitHub Release with
`version-switcher.mjs` and the tag's `docs.zip` as assets; and the nested
`publish.yml` reconstructs + deploys the site including the new tag. The plugin URL
CI runs lint + tests + the docs build; `release.yml` creates the GitHub Release with
`version-switcher.mjs` + the tag's `docs.zip` attached (via `gh`); and the nested
publish (tag trampoline) reconstructs + deploys the site including the new tag. The plugin URL
and the `uses:` refs for the reusable workflows all resolve to the same tag, so one
tag versions both halves.
41 changes: 0 additions & 41 deletions .github/workflows/_release.yml

This file was deleted.

9 changes: 9 additions & 0 deletions .github/workflows/_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ jobs:
- uses: actions/checkout@v7
- name: Unit tests (widget logic + make-switcher parity)
run: npm test
# release.yml downloads every artifact in the run and attaches them, so name this
# explicitly (not the default "artifact") and fail loudly if the plugin is missing
# — it's a release asset.
- name: Upload version switcher artifact
uses: actions/upload-artifact@v7
with:
name: version-switcher
path: plugins/version-switcher.mjs
if-no-files-found: error
26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ name: CI
# forks), pushes to main, and tags — lint, test, build the docs, and on tags
# publish the release assets. It uploads each build's `docs` artifact.
#
# Publishing is nested here (the `publish` job → publish-dispatch.yml → publish.yml) so
# its status is visible on the PR / commit. A single `publish` job covers every event:
# publish-dispatch.yml branches internally into deploy (internal PR / main push), tag
# trampoline (a same-SHA tag deploy would be silently dropped by Pages unless dispatched),
# or a fork-PR warning (forks build with a read-only token and never deploy). See the
# Publishing is nested here (the `publish` job → publish-dispatch.yml shim → publish.yml)
# so its status is visible on the PR / commit. A single `publish` job covers every event;
# publish.yml branches internally into deploy (internal PR / main push), tag trampoline
# (a same-SHA tag deploy would be silently dropped by Pages unless dispatched), or a
# fork-PR warning (forks build with a read-only token and never deploy). See the
# architecture explanation in docs/.
on:
pull_request:
Expand All @@ -33,17 +33,17 @@ jobs:
release:
needs: [lint, test, docs]
if: github.ref_type == 'tag'
uses: ./.github/workflows/_release.yml
uses: ./.github/workflows/release.yml
permissions:
contents: write # create the GitHub Release + attach assets

# One publish job for every event — publish-dispatch.yml owns the branching (deploy
# inline / tag trampoline / fork-PR warn), so there's no separate tag or fork job and
# no ref/fork guards here. It runs for fork PRs too, but only to surface the opt-in
# hint: the wrapper's `deploy` job excludes forks and a fork PR's token is read-only,
# so untrusted code never reaches a write token. `actions: write` lets the wrapper's
# tag trampoline re-dispatch itself; `version-name` is the in-run build for the inline
# path (ignored by the trampoline/warn branches).
# One publish job for every event — publish.yml (reached via the publish-dispatch.yml
# shim) owns the branching (deploy inline / tag trampoline / fork-PR warn), so there's
# no separate tag or fork job and no ref/fork guards here. It runs for fork PRs too,
# but only to surface the opt-in hint: publish.yml's `deploy` job excludes forks and a
# fork PR's token is read-only, so untrusted code never reaches a write token.
# `actions: write` lets the trampoline re-dispatch the shim; `version-name` is the
# in-run build for the inline path (ignored by the trampoline/warn branches).
publish:
needs: [lint, test, docs]
if: github.repository == 'DiamondLightSource/myst-version-switcher-plugin'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
eval "$BUILD_COMMAND"

# Pack the site as docs.zip with a bare html/ root — the durable contract both
# publish.yml's gather and _release.yml's asset rely on. Stage into html/
# publish.yml's gather and release.yml's asset rely on. Stage into html/
# first so any html-dir name yields the same root.
- name: Pack docs.zip (bare html/ root)
env:
Expand Down
113 changes: 20 additions & 93 deletions .github/workflows/publish-dispatch.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
name: Publish (dispatch)

# The ONE wrapper around publish.yml (the assemble + deploy engine) — it owns ALL the
# publish decisions so a consumer's ci.yml needs a single `publish` job (no separate
# tag or fork-warning job) and docs.yml needs no fork-preview hint. publish.yml is a
# pure `workflow_call` engine; this file branches on the originating event into one of
# three jobs:
#
# deploy — internal PR / default-branch push (via ci.yml's workflow_call), or any
# workflow_dispatch (the trampoline's re-dispatch, a fork-PR preview, a
# manual re-deploy). Nests publish.yml; for the inline path it injects the
# in-run build via `version-name`, otherwise pure durable gather.
# trampoline — a TAG push (via workflow_call). An inline tag deploy would be silently
# DROPPED by Pages (it reuses the default branch's just-deployed SHA on a
# non-dispatch event — deploy-pages#383), so re-dispatch THIS workflow as
# `workflow_dispatch`; that event forces a re-serve. Waits for ci.yml's
# parallel release job to attach the tag's docs.zip first (the dispatched
# run gathers the tag from that durable asset).
# warn — a FORK PR. Fork builds run with a read-only token and must never deploy,
# so just surface the manual-opt-in hint on the PR. (The token is
# read-only here regardless — `deploy` is `if`-excluded for forks too, and
# the workflow file itself comes from the trusted base, not the fork.)
#
# This repo DOGFOODS the exact wrapper a consumer adds — theirs differs only in the
# `deploy` job's `uses:` target (DiamondLightSource/myst-version-switcher-plugin/
# .github/workflows/publish.yml@<tag>), the single place they pin the engine's <tag>.
# Thin shim — the one dispatchable file in this repo (and the one consumers copy). It
# forwards to publish.yml's engine, exposing it two ways:
# workflow_call — ci.yml's `publish` job, for every event (publish.yml branches into
# deploy / tag trampoline / fork warn).
# workflow_dispatch — the tag trampoline's re-dispatch, the fork-PR preview (`pr`), and
# manual re-deploys.
# A reusable workflow can't be `workflow_dispatch`'d cross-repo, so this shim must live
# in the consumer's repo; publish.yml's trampoline re-dispatches it by name
# (`dispatch-workflow`). This repo dogfoods the exact shim a consumer adds — theirs only
# differs in the `uses:` target (…/publish.yml@<tag>), the single place they pin <tag>.
on:
workflow_call:
inputs:
version-name:
description: Version name of an in-run build to inject (pr-<n> | <default-branch> | <tag>); "" → pure durable gather.
required: false
default: ""
type: string
guard-default-branch:
description: "'false' to skip the default-branch-present guard (e.g. mid gh-pages migration)."
required: false
default: "true"
type: string
pr:
description: External fork PR number to approve + preview.
description: Version name of the in-run build to inject (passed by ci.yml's inline publish).
required: false
default: ""
type: string
Expand All @@ -50,64 +26,15 @@ on:
default: ""

jobs:
# Internal inline (PR / default-branch push) or any workflow_dispatch → deploy.
deploy:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref_type != 'tag') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository)
permissions:
contents: read # checkout + read release assets
actions: read # gh run download (this run's + cross-run docs artifacts)
pages: write # deploy to Pages
id-token: write # deploy-pages OIDC
statuses: write # set the preview-approved status on a fork PR head SHA
publish:
uses: ./.github/workflows/publish.yml
with:
version-name: ${{ inputs.version-name }}
guard-default-branch: ${{ inputs.guard-default-branch || 'true' }}
pr: ${{ inputs.pr }}

# A tag can't deploy inline — re-dispatch this workflow so the deploy runs on a
# workflow_dispatch event (which forces Pages to re-serve a same-SHA deploy).
trampoline:
if: github.event_name == 'push' && github.ref_type == 'tag'
runs-on: ubuntu-latest
version-name: ${{ inputs.version-name }} # "" on dispatch → pure durable gather
pr: ${{ inputs.pr }} # set (dispatch only) → pin that fork head SHA
dispatch-workflow: publish-dispatch.yml # the file the tag trampoline re-dispatches
permissions:
contents: read # gh release view
actions: write # gh workflow run (re-dispatch self)
steps:
- name: Wait for the release, then re-deploy via workflow_dispatch
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
# ci.yml's release job attaches the tag's docs.zip in parallel; the dispatched
# run gathers the tag from that asset, so don't dispatch until it exists.
for _ in $(seq 1 36); do
gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1 && break
sleep 5
done
gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1 \
|| { echo "::error::release $TAG not found after 3 min — did the release job fail?"; exit 1; }
gh workflow run publish-dispatch.yml --repo "$REPO" --ref "$DEFAULT_BRANCH"

# Fork PR: never deploys (read-only token). Surface the manual-opt-in hint on the PR.
warn:
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Explain the fork-preview opt-in
run: |
echo "::warning title=Docs preview not published::This is a fork PR, so the \
versioned docs site is NOT auto-published (fork builds run with a read-only \
token). A maintainer can publish a preview by running the publish-dispatch.yml \
workflow for PR #${{ github.event.pull_request.number }}: \
https://github.com/${{ github.repository }}/actions/workflows/publish-dispatch.yml"
contents: read
actions: write # publish.yml's trampoline re-dispatches this shim via gh
pages: write
id-token: write
statuses: write
Loading
Loading