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
104 changes: 104 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# GitHub Actions workflows

This directory drives three kinds of automation: the **release pipeline** (tag → build → Pages → GitHub Release → stamped spec branch), **continuous deploy from `main`** (rolling docs site and unstamped spec branch), and **PR gating** (spec tests, docs smoke-build, spellcheck).

## Workflows

- [release.yaml](release.yaml) — `push tag v*.*.*` or `workflow_dispatch`. Full release: build, version snapshot, Pages deploy, GitHub Release, stamped spec branch.
- [sync-release-notes.yaml](sync-release-notes.yaml) — `release: published|edited` or `workflow_dispatch`. Mirrors release notes into `docs-releases/` via PR.
- [publish-spec.yaml](publish-spec.yaml) — `workflow_dispatch` only. Manual recovery: re-push `assembled-spec` from existing release assets.
- [deploy.yaml](deploy.yaml) — `push: main` or dispatch. Rolling Pages deploy + pushes `assembled-spec-main` (unstamped).
- [test.yaml](test.yaml) — push/PR. `make build`, speccheck, test filling, lint.
- [test-deploy.yaml](test-deploy.yaml) — PR to `main`. Smoke-builds the site with and without a synthesized version snapshot.
- [spellcheck.yaml](spellcheck.yaml) — push/PR. `rojopolis/spellcheck-github-actions`.

## Release happy path

```mermaid
flowchart TD
tagPush["push tag v*.*.*"] --> releaseWf
dispatch["workflow_dispatch (tag input)"] --> releaseWf

subgraph releaseWf [release.yaml]
buildRelease[build-release]
deployPages[deploy-pages]
ghRelease[github-release]
publishSpec[publish-spec]
buildRelease --> deployPages
buildRelease --> ghRelease
ghRelease --> publishSpec
end

buildRelease -->|"docs-snapshot, openrpc.json, refs-openrpc.json"| artifacts[("Actions artifacts")]
deployPages --> pages[("GitHub Pages")]
ghRelease -->|"draft, upload, publish"| ghReleasePage[("GitHub Release")]
publishSpec -->|"force push"| assembledSpec[("assembled-spec branch")]

ghReleasePage -->|"release: published"| syncNotes[sync-release-notes.yaml]
syncNotes -->|"PR"| notesPR[("docs-releases/ PR")]
```

The `github-release` job deliberately uses a **draft → upload → publish** sequence. Before uploading assets it forces the release back to draft (`gh release edit … --draft=true`) so a re-run cannot publish prematurely. Only after `openrpc.json`, `refs-openrpc.json`, and `docs-snapshot-<tag>.tar.gz` are attached does it set `--draft=false`. That avoids firing `release: published` while assets are still missing, which would trigger [sync-release-notes.yaml](sync-release-notes.yaml) against an incomplete release.

## Branch and version lifecycle

Versioned API docs on Pages are assembled from past release snapshots plus the current build. [scripts/assemble-versions.js](../../scripts/assemble-versions.js) downloads `docs-snapshot-*.tar.gz` from published GitHub Releases (up to 10 versions) and writes `api_versions.json` plus `api_versioned_docs/version-X.Y.Z/`.

```mermaid
flowchart LR
mainPush["push to main"] --> deployWf[deploy.yaml]
deployWf --> pagesMain[("Pages: /next + latest landing")]
deployWf -->|"force push"| assembledMain[("assembled-spec-main (unstamped)")]

tagPush["push tag v*.*.*"] --> releaseWf2[release.yaml]
releaseWf2 --> assembledSpec2[("assembled-spec (stamped)")]
releaseWf2 --> ghRel[("GitHub Release + docs-snapshot-vX.Y.Z.tar.gz")]
ghRel -.->|"gh release download"| assembleScript["assemble-versions.js (MAX_VERSIONS=10)"]
assembleScript -->|"writes"| apiVersions[("api_versions.json + api_versioned_docs/version-X.Y.Z/")]
apiVersions -.-> deployWf
apiVersions -.-> releaseWf2
```

## Maintainer runbook

### Cut a release

Tag `vX.Y.Z` on `main` and push the tag. [release.yaml](release.yaml) runs automatically; no manual steps are required for Pages, the GitHub Release, or `assembled-spec`.

### Automated Release Notes PR

NOTE: The release will then trigger a release notes PR. Because we publish the draft to NEXT, the actual commit sha of the repo doesn't change on tag push. This then can sometimes
not invalidate the cache. The subsequent automated PR that follows, forces the version and the release notes to invalidate the github pages CDN, when merged. This will guarantee that
the latest release invalidates the github pages cache and deploys.

### Re-run a release

```bash
gh workflow run release.yaml -f tag=vX.Y.Z
```

The workflow is idempotent: it re-drafts, re-uploads, and re-publishes. During `assemble-versions.js`, the pending tag is not downloaded from GitHub—the local snapshot from `docusaurus docs:version:api` is used instead (see [assemble-versions.js lines 110–121](../../scripts/assemble-versions.js)).

### Recover just `assembled-spec`

Use [publish-spec.yaml](publish-spec.yaml) with the tag input. It downloads `openrpc.json` and `refs-openrpc.json` from the existing GitHub Release and force-pushes `assembled-spec`. If those assets are missing, re-run [release.yaml](release.yaml) instead—do not use this recovery workflow.

### Recover release notes PR

```bash
gh workflow run sync-release-notes.yaml -f tag=vX.Y.Z
```

Reopens or updates the `release-notes/<slug>` PR that mirrors the GitHub Release into `docs-releases/`.

### Version dropdown missing a release

Confirm `docs-snapshot-vX.Y.Z.tar.gz` exists on the GitHub Release for that tag. [assemble-versions.js](../../scripts/assemble-versions.js) silently skips releases without that asset.

## Contracts between jobs

- `build-release` uploads two artifacts: `openrpc-spec` (`openrpc.json` + `refs-openrpc.json`) and `docs-snapshot` (the `.tar.gz`). Both `github-release` and `publish-spec` download `openrpc-spec`; `github-release` also downloads `docs-snapshot`.
- `concurrency.group: "pages"` is shared with [deploy.yaml](deploy.yaml). [release.yaml](release.yaml) sets `cancel-in-progress: true` so a release pre-empts an in-flight main deploy.
- `assembled-spec` is **stamped** (version baked in via `npm run spec:set-version`); `assembled-spec-main` is **unstamped** (rolling head of `main`). Consumers pin to one or the other deliberately.
- [sync-release-notes.yaml](sync-release-notes.yaml) keys its PR branch off `steps.sync.outputs.slug`—repeated edits to the same release update the same PR rather than spawning new ones.
- `release: published` fires the sync-release-notes workflow; therefore `github-release` uploads assets **while drafted** and only flips to published once.
17 changes: 12 additions & 5 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ jobs:
- name: Install JS dependencies
run: npm ci

- name: Assemble versioned docs from releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/assemble-versions.js

- name: Build docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run build:docs

- name: Upload artifact
Expand All @@ -65,7 +72,7 @@ jobs:
id: deployment
uses: actions/deploy-pages@v4

deploy-spec:
deploy-spec-main:
runs-on: ubuntu-latest
needs: build
permissions:
Expand All @@ -80,10 +87,10 @@ jobs:
run: |
git config user.name "GitHub Actions Bot"
git config user.email "<>"
- name: Deploy assembled spec
- name: Deploy assembled spec (main, unstamped)
run: |
git checkout -b assembled-spec
git checkout -b assembled-spec-main
git add -f openrpc.json
git add -f refs-openrpc.json
git commit -m "assemble openrpc.json"
git push -fu origin assembled-spec
git commit -m "assemble openrpc.json from main@${GITHUB_SHA::7}"
git push -fu origin assembled-spec-main
37 changes: 37 additions & 0 deletions .github/workflows/publish-spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Publish stamped spec branch

on:
release:
types: [ published ]

permissions:
contents: write

jobs:
publish-spec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download stamped spec from release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.release.tag_name }}
run: |
gh release download "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--pattern openrpc.json \
--pattern refs-openrpc.json \
--clobber
- name: setup git config
run: |
git config user.name "GitHub Actions Bot"
git config user.email "<>"
- name: Deploy assembled spec (stamped release)
env:
TAG: ${{ github.event.release.tag_name }}
run: |
git checkout -b assembled-spec
git add -f openrpc.json
git add -f refs-openrpc.json
git commit -m "release ${TAG}"
git push -fu origin assembled-spec
122 changes: 122 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Release

on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Existing tag to (re)release, e.g. v1.2.0"
required: true
type: string

permissions:
contents: write
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}

- uses: actions/setup-go@v6
with:
go-version: ^1.26
id: go

- name: Build the spec
run: make build

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm

- name: Install JS dependencies
run: npm ci

- name: Stamp spec version from tag
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: npm run spec:set-version -- --version "$TAG"

- name: Build docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run build:docs

- name: Snapshot versioned docs
env:
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
VER="${TAG#v}"
npx docusaurus docs:version:api "$VER"
tar -czf "docs-snapshot-${TAG}.tar.gz" \
"api_versioned_docs/version-${VER}" \
"api_versioned_sidebars/version-${VER}-sidebars.json"

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: build

- name: Upload openrpc spec
uses: actions/upload-artifact@v4
with:
name: openrpc-spec
path: |
openrpc.json
refs-openrpc.json

- name: Upload docs snapshot
uses: actions/upload-artifact@v4
with:
name: docs-snapshot
path: docs-snapshot-*.tar.gz

deploy-pages:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build-release
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

github-release:
runs-on: ubuntu-latest
needs: build-release
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: openrpc-spec

- uses: actions/download-artifact@v4
with:
name: docs-snapshot

- name: Create draft GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--draft \
--generate-notes \
openrpc.json refs-openrpc.json "docs-snapshot-${TAG}.tar.gz"
53 changes: 53 additions & 0 deletions .github/workflows/sync-release-notes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Sync release notes to repo

on:
release:
types: [published, edited]
workflow_dispatch:
inputs:
tag:
description: "Release tag to sync (e.g. v1.2.0)"
required: true
type: string

permissions:
contents: write
pull-requests: write

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main

- uses: actions/setup-node@v4
with:
node-version: "24"

- name: Sync release note
id: sync
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: node scripts/sync-release-note.js

- name: Open / update PR
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
base: main
branch: release-notes/${{ steps.sync.outputs.slug }}
commit-message: "docs: release notes for ${{ steps.sync.outputs.tag }}"
title: "docs: release notes for ${{ steps.sync.outputs.tag }} - update pages to the latest release"
body: |
Mirrors the GitHub Release for **${{ steps.sync.outputs.tag }}** into `docs-releases/`.

Source: ${{ steps.sync.outputs.html_url }}

After this PR is merged, edit the markdown directly via PR for any future fixes — no need to republish the GitHub Release.
The PR also forces a Pages cache invalidation, so the release is fully visible.
add-paths: |
docs-releases/**
delete-branch: true
13 changes: 12 additions & 1 deletion .github/workflows/test-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Test build website
- name: Test build website (no versions)
run: |
npm run build:spec
npm run build:docs

- name: Test versioned build (synthesized snapshot)
run: |
npm run build:spec
npm run docs:copy
npx docusaurus docs:version:api 0.0.0-test
npm run build:docusaurus
test -d build/next || (echo "build/next missing — current docs not under /next when versions present" && exit 1)
test -f build/index.html || (echo "build/index.html missing — latest version landing not built at root" && exit 1)
grep -q '0.0.0-test' build/index.html || (echo "version dropdown not wired into latest landing" && exit 1)
echo "versioned build OK: /next + / (latest=0.0.0-test) both present"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ schema.json
.docusaurus/
.DS_Store
.tool-versions
api_versioned_docs/
api_versioned_sidebars/
api_versions.json
docs-snapshot-*.tar.gz
Loading
Loading