landing: CDN-vendor build — full Instrument of Wisdom site (batch 2) #85
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Auto-release. Two ways in: | |
| # 1. Merge to master (push) -> "auto" bump from conventional commits since the last tag. | |
| # A chore/docs-only merge is a graceful skip (bump.mjs exits 3), so nothing publishes | |
| # unless a feat/fix/perf/breaking change actually landed. | |
| # 2. Actions -> "Bump version" -> Run workflow -> pick auto/patch/minor/major by hand. | |
| # | |
| # Either way scripts/bump.mjs bumps every version field (package.json, package-lock.json, | |
| # both plugin manifests, CITATION.cff, landing page) + rotates CHANGELOG, then this workflow | |
| # commits "chore(release): vX.Y.Z", tags vX.Y.Z, and pushes commit + tag. | |
| # | |
| # Pushing the v* tag is what normally triggers release.yml — but pushes made with the default | |
| # GITHUB_TOKEN deliberately do NOT trigger other workflows (GitHub's recursion guard). So this | |
| # workflow also dispatches release.yml on the new tag explicitly, which needs `actions: write`. | |
| # That same recursion guard is why the release commit this workflow pushes does NOT re-trigger | |
| # the push path below (no infinite loop); the actor/subject guard is belt-and-suspenders. | |
| name: Bump version | |
| on: | |
| push: | |
| branches: [master] | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: "Bump type (auto = conventional commits since last tag: BREAKING -> major, feat -> minor, else patch)" | |
| type: choice | |
| default: auto | |
| options: | |
| - auto | |
| - patch | |
| - minor | |
| - major | |
| permissions: | |
| contents: write # push the release commit + tag | |
| actions: write # dispatch release.yml on the new tag | |
| concurrency: | |
| group: bump | |
| cancel-in-progress: false | |
| jobs: | |
| # Never tag a tree that fails the FULL quality gate (not just tests) — P0-11. | |
| gate: | |
| if: >- | |
| github.actor != 'github-actions[bot]' && | |
| !startsWith(github.event.head_commit.message, 'chore(release):') | |
| uses: ./.github/workflows/reusable-quality-gate.yml | |
| bump: | |
| needs: gate | |
| runs-on: ubuntu-latest | |
| # Never react to our own release commit (belt-and-suspenders behind GitHub's recursion guard). | |
| if: >- | |
| github.actor != 'github-actions[bot]' && | |
| !startsWith(github.event.head_commit.message, 'chore(release):') | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # full history + tags so "auto" can read commits since the last tag | |
| - uses: actions/setup-node@v6.4.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - run: npm ci | |
| - name: Bump version fields + rotate CHANGELOG | |
| id: bump | |
| # On push, always "auto"; on manual dispatch, honor the chosen input. | |
| env: | |
| BUMP: ${{ github.event_name == 'workflow_dispatch' && inputs.bump || 'auto' }} | |
| run: | | |
| set +e | |
| VERSION="$(node scripts/bump.mjs "$BUMP")" | |
| code=$? | |
| set -e | |
| if [ "$code" -eq 0 ]; then | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "release=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Releasing v$VERSION" | |
| elif [ "$code" -eq 3 ]; then | |
| echo "release=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Nothing to release (no feat/fix/perf/breaking commits) — skipping." | |
| else | |
| echo "::error::bump.mjs failed (exit $code)" | |
| exit "$code" | |
| fi | |
| - name: Commit, tag, push | |
| if: steps.bump.outputs.release == 'true' | |
| env: | |
| VERSION: ${{ steps.bump.outputs.version }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Orphan-tag guard: a partial prior run (bump committed + tagged, then the push or a | |
| # later step died) can leave vX.Y.Z already present locally or on origin. `git tag` | |
| # would then die with a bare "fatal: tag 'vX.Y.Z' already exists", wedging EVERY | |
| # future auto-release with a cryptic error. Detect it first and fail with an operator | |
| # runbook instead. We do NOT auto-delete the tag — clobbering a release tag in CI is | |
| # too dangerous; a human must remove the orphan deliberately. | |
| if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null 2>&1 \ | |
| || git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null 2>&1; then | |
| echo "::error::Release wedged: tag v$VERSION already exists (orphaned partial run). An operator must delete it: git push origin :refs/tags/v$VERSION, then re-run." | |
| exit 1 | |
| fi | |
| git add -A | |
| git commit -m "chore(release): v$VERSION" | |
| git tag -a "v$VERSION" -m "v$VERSION" | |
| git push origin "HEAD:${{ github.ref_name }}" "v$VERSION" | |
| - name: Trigger the release workflow on the new tag | |
| if: steps.bump.outputs.release == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ steps.bump.outputs.version }} | |
| run: gh workflow run release.yml --ref "v$VERSION" |