chore(release): version packages #456
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
| name: changeset-check | |
| # Gate on PRs targeting master: require a `.changeset/*.md` file unless | |
| # the PR only touches paths in the allowlist below (docs, repo config, | |
| # CI tooling, markdown). Pattern lifted from umara/u-front. | |
| # | |
| # Escape layers, in order: | |
| # 1. PR title matches the auto-generated Version PR → skip (defensive; | |
| # Version PRs are created via GITHUB_TOKEN so they don't cascade | |
| # workflow runs, but this guards against a manual push to the | |
| # Version PR branch). | |
| # 2. PR carries the `no-changeset` label → skip (manual override for | |
| # cases the allowlist doesn't anticipate). | |
| # 3. All changed files are in the path allowlist → skip. | |
| # 4. Otherwise: require at least one added .changeset/<name>.md file. | |
| on: | |
| pull_request: | |
| branches: [master] | |
| types: [opened, synchronize, reopened, labeled, unlabeled] | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| changeset-required: | |
| name: changeset present (or auto-exempt) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Short-circuit on auto-generated Version PR | |
| id: version_pr | |
| env: | |
| TITLE: ${{ github.event.pull_request.title }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${TITLE}" == "chore(release): version packages"* ]]; then | |
| echo "skip=true" >> "${GITHUB_OUTPUT}" | |
| echo "Auto-generated Version PR — gate skipped." | |
| else | |
| echo "skip=false" >> "${GITHUB_OUTPUT}" | |
| fi | |
| - name: Short-circuit on no-changeset label | |
| if: steps.version_pr.outputs.skip != 'true' | |
| id: label | |
| env: | |
| LABELS: ${{ toJson(github.event.pull_request.labels) }} | |
| run: | | |
| set -euo pipefail | |
| if echo "${LABELS}" | jq -e '.[] | select(.name == "no-changeset")' >/dev/null; then | |
| echo "skip=true" >> "${GITHUB_OUTPUT}" | |
| echo "PR carries 'no-changeset' label — release-notes gate satisfied." | |
| else | |
| echo "skip=false" >> "${GITHUB_OUTPUT}" | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate package and lockfile metadata | |
| run: node --test web/release-metadata.test.mjs | |
| # Presence alone is not enough: a Changeset can reference a package that | |
| # no longer exists after a rename. Validate the complete pending queue | |
| # with Changesets itself so an invalid entry fails on the PR instead of | |
| # breaking every release run after it reaches master. | |
| - name: Setup Node for Changesets validation | |
| if: steps.version_pr.outputs.skip != 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install Changesets dependencies | |
| if: steps.version_pr.outputs.skip != 'true' | |
| run: npm ci --prefer-offline --no-audit --no-fund | |
| - name: Validate pending Changesets | |
| if: steps.version_pr.outputs.skip != 'true' | |
| # pull_request workflows run on a detached merge ref. Compare against | |
| # the fetched remote base because no local `master` branch exists. | |
| run: npx changeset status --since="origin/${{ github.base_ref }}" | |
| - name: Verify changeset present (or PR is allowlisted) | |
| if: steps.version_pr.outputs.skip != 'true' && steps.label.outputs.skip != 'true' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --no-tags origin "${BASE_SHA}" "${HEAD_SHA}" | |
| CHANGED="$(git diff --name-only "${BASE_SHA}"..."${HEAD_SHA}")" | |
| if [ -z "${CHANGED}" ]; then | |
| echo "No file changes — passing." | |
| exit 0 | |
| fi | |
| # Paths in this allowlist do NOT require a changeset. Tweak | |
| # if a new "obviously doesn't ship to users" path is added. | |
| # Patterns are extended-grep regexes: | |
| # - any *.md / *.mdx / *.txt anywhere in the tree | |
| # - the docs/ tree | |
| # - .changeset/ (the changesets themselves + README/config) | |
| # - .github/ (workflows, CODEOWNERS, issue templates…) | |
| # - .vscode/ (editor settings) | |
| # - dotfiles at repo root | |
| REQUIRES_CHANGESET="$(echo "${CHANGED}" | grep -vE \ | |
| -e '(^|/)[^/]+\.(md|mdx|txt)$' \ | |
| -e '^docs/' \ | |
| -e '^\.changeset/' \ | |
| -e '^\.github/' \ | |
| -e '^\.vscode/' \ | |
| -e '^\.editorconfig$' \ | |
| -e '^\.gitignore$' \ | |
| -e '^\.gitattributes$' \ | |
| -e '^LICENSE$' \ | |
| || true)" | |
| if [ -z "${REQUIRES_CHANGESET}" ]; then | |
| echo "✓ PR changes only docs / repo config / CI tooling (allowlist) — changeset not required." | |
| echo "" | |
| echo "Files in this PR:" | |
| echo "${CHANGED}" | sed 's/^/ /' | |
| exit 0 | |
| fi | |
| # Accept any added or modified changeset file. Exclude | |
| # deletions (--diff-filter=d) so a PR that *deletes* a | |
| # changeset can't satisfy the rule. Exclude the README + | |
| # config files which are infrastructure, not real entries. | |
| CHANGESETS="$(git diff --name-only --diff-filter=d "${BASE_SHA}"..."${HEAD_SHA}" \ | |
| | grep -E '^\.changeset/[^/]+\.md$' \ | |
| | grep -vE '^\.changeset/(README)\.md$' \ | |
| || true)" | |
| if [ -n "${CHANGESETS}" ]; then | |
| echo "✓ Found changeset(s):" | |
| echo "${CHANGESETS}" | sed 's/^/ /' | |
| exit 0 | |
| fi | |
| echo "::error::No changeset found and this PR touches files outside the docs/config/CI allowlist." | |
| echo "" | |
| echo "Files that triggered the changeset requirement:" | |
| echo "${REQUIRES_CHANGESET}" | sed 's/^/ /' | |
| echo "" | |
| echo "Add one with:" | |
| echo " npx changeset" | |
| echo "" | |
| echo "Or hand-create '.changeset/<descriptive-name>.md' following an existing example." | |
| echo "" | |
| echo "If this PR genuinely needs no release entry, either:" | |
| echo " - apply the 'no-changeset' label to skip the gate, or" | |
| echo " - expand the allowlist in .github/workflows/changeset-check.yml in the same PR." | |
| exit 1 |