From 881404acf16f0edecd3724643c3eae579887c8c6 Mon Sep 17 00:00:00 2001 From: Sami Odeh Date: Fri, 2 Oct 2026 16:57:19 +0300 Subject: [PATCH] ci: release from the branch name, deploy docs only after npm publish --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy-docs.yml | 8 ++-- .github/workflows/publish.yml | 53 ++++++++++++++++++++++- .github/workflows/release.yml | 72 ++++++++++++++++++------------- 4 files changed, 99 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d322d2..84a5fdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [main] push: - branches: [main] + branches: [main, 'release/**'] jobs: check: diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index a60d420..b19c608 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,9 +1,9 @@ -name: 📚 Deploy Docs to GitHub Pages +name: 📚 Deploy Docs (manual) + +# Redeploys the docs site from main without a release, for docs-only fixes. Run it from the +# Actions tab. A release deploys the docs itself, as the last job of the publish workflow. on: - push: - tags: - - 'v*' workflow_dispatch: permissions: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d2af691..307629c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,8 @@ -name: 📦 Publish to npm +name: 📦 Publish — npm, GitHub Release, docs + +# Runs on a vX.Y.Z tag (pushed by the release workflow). Three jobs, each needing the one before: +# the full gate, then npm publish + the GitHub Release, then the docs deploy. A failed publish +# stops the docs from deploying, so the site never describes a version that is not on npm. on: push: @@ -36,6 +40,12 @@ jobs: - name: Build docs run: npm run build:docs + - name: Tag matches package.json + run: | + TAG="${GITHUB_REF_NAME#v}" + PKG=$(node -p "require('./package.json').version") + if [ "$TAG" != "$PKG" ]; then echo "ERROR: tag v$TAG but package.json is $PKG" && exit 1; fi + publish: needs: ci runs-on: ubuntu-latest @@ -68,3 +78,44 @@ jobs: uses: softprops/action-gh-release@v2 with: generate_release_notes: true + + deploy-docs: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + concurrency: + group: 'pages' + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build docs + run: npm run build:docs + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './docs-dist' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d9641d..5c0b1c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,30 @@ -name: 🚀 Release — Tag & Update Version +name: 🚀 Release — Version & Tag -# Runs when a release/* branch is merged into main. -# Reads the version from package.json, updates index.html, -# commits the change, tags main, and pushes — triggering -# the publish and deploy-docs workflows. +# Merging a release/X.Y.Z branch into main releases X.Y.Z: the version comes from the branch +# name, so nothing is bumped by hand. The workflow sets package.json to that version, dates the +# CHANGELOG heading, commits to main and pushes the tag vX.Y.Z, which runs the publish workflow +# (gate, npm, GitHub Release, docs). It can also be run from the Actions tab with a version typed in. on: pull_request: types: [closed] branches: [main] + workflow_dispatch: + inputs: + version: + description: 'Version to release, e.g. 3.0.1' + required: true jobs: - tag: - if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') + release: + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')) runs-on: ubuntu-latest permissions: contents: write steps: - - name: Checkout + - name: Checkout main uses: actions/checkout@v4 with: ref: main @@ -29,40 +36,45 @@ jobs: with: node-version: 22 - - name: Read version from package.json + - name: Version from the branch name (or the input) id: version + env: + HEAD_REF: ${{ github.event.pull_request.head.ref }} + INPUT_VERSION: ${{ github.event.inputs.version }} run: | - VERSION=$(node -p "require('./package.json').version") - echo "Version: $VERSION" - if [ -z "$VERSION" ]; then echo "ERROR: version is empty" && exit 1; fi + if [ -n "$INPUT_VERSION" ]; then VERSION="$INPUT_VERSION"; else VERSION="${HEAD_REF#release/}"; fi + VERSION="${VERSION#v}" + if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then + echo "ERROR: '$VERSION' is not a version. Name the branch release/X.Y.Z." && exit 1 + fi + if git ls-remote --exit-code --tags origin "v$VERSION" >/dev/null 2>&1; then + echo "ERROR: tag v$VERSION already exists" && exit 1 + fi + echo "Releasing $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - name: Update version in index.html + - name: Set package.json version and date the changelog run: | VERSION="${{ steps.version.outputs.version }}" - sed -i "s/\"softwareVersion\": \"[^\"]*\"/\"softwareVersion\": \"$VERSION\"/" index.html + npm version "$VERSION" --no-git-tag-version --allow-same-version + # the heading "## 3.0.0 (unreleased …)" or "## Unreleased" becomes "## 3.0.0 (2026-10-02)" + TODAY=$(date -u +%F) VERSION="$VERSION" perl -i -pe \ + 's/^## (?=.*unreleased)(?:v?\Q$ENV{VERSION}\E|\[?Unreleased\]?).*$/## $ENV{VERSION} ($ENV{TODAY})/i' CHANGELOG.md - - name: Check for changes - id: diff - run: | - if git diff --quiet; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Commit version update - if: steps.diff.outputs.changed == 'true' + - name: Commit to main run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/${{ github.repository }} - git add index.html - git commit -m "chore: update version to ${{ steps.version.outputs.version }}" - git push + if git diff --quiet; then + echo "package.json and CHANGELOG.md already at ${{ steps.version.outputs.version }}" + else + git add package.json package-lock.json CHANGELOG.md + git commit -m "release: ${{ steps.version.outputs.version }}" + git push origin main + fi - - name: Create and push tag + - name: Tag run: | - git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/${{ github.repository }} git tag "v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}"