From b8aa865a2a1a3be737b5594e6d47e73627b082d4 Mon Sep 17 00:00:00 2001 From: Maxim Raznatovski Date: Mon, 12 Oct 2026 06:10:57 +0200 Subject: [PATCH 1/3] chore: align release-drafter feature labels --- .github/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index c4d0f60..4453bf3 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -12,7 +12,7 @@ categories: - title: "🚀 Features" labels: - - "feature" + - "new-feature" - "enhancement" - title: "🧰 Maintenance" From aaa030a35a94a7f1fa3074113e55468ac5ba21b4 Mon Sep 17 00:00:00 2001 From: Maxim Raznatovski Date: Mon, 12 Oct 2026 06:43:54 +0200 Subject: [PATCH 2/3] chore: sort refactor, docs and ci PRs into release notes --- .github/release-drafter.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 4453bf3..4fbf616 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -17,14 +17,15 @@ categories: - title: "🧰 Maintenance" labels: + - "refactor" + - "documentation" - "ci" - "maintenance" - - title: "⬆️ Dependencies and CI" + - title: "⬆️ Dependencies" collapse-after: 1 labels: - "dependencies" - - "ci" template: | ## What’s Changed From d3c932ba1403fd8fa0e86ea84a60b5f24b9dcf75 Mon Sep 17 00:00:00 2001 From: Maxim Raznatovski Date: Mon, 12 Oct 2026 06:44:31 +0200 Subject: [PATCH 3/3] chore: label PRs from a PR template checkbox --- .github/PULL_REQUEST_TEMPLATE.md | 24 ++++++++ .github/workflows/pr-labels.yaml | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/pr-labels.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c7adfad --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +# What does this implement/fix? + + + +**Related issue (if applicable):** + +- + +## Types of changes + + + +- [ ] Bugfix (non-breaking change which fixes an issue): `bugfix` +- [ ] New feature (non-breaking change which adds functionality): `new-feature` +- [ ] Enhancement to an existing feature: `enhancement` +- [ ] Breaking change (changes CLI options, settings, hooks or the protocol in a way that is not backwards compatible): `breaking-change` +- [ ] Refactor (no behavior change): `refactor` +- [ ] Documentation only: `documentation` +- [ ] Maintenance / chore: `maintenance` +- [ ] CI / workflow change: `ci` +- [ ] Dependencies bump: `dependencies` diff --git a/.github/workflows/pr-labels.yaml b/.github/workflows/pr-labels.yaml new file mode 100644 index 0000000..fe31b79 --- /dev/null +++ b/.github/workflows/pr-labels.yaml @@ -0,0 +1,95 @@ +--- +name: PR Labels + +# Each PR must carry exactly one of the labels the release notes sort by. +# The label comes from the ticked "Types of changes" checkbox in the PR +# template, since external contributors can't label their own PRs. + +# yamllint disable-line rule:truthy +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - labeled + - unlabeled + - reopened + - ready_for_review + branches: + - main + +permissions: + pull-requests: write + +jobs: + pr_labels: + name: Verify + runs-on: ubuntu-latest + # Bots like Dependabot don't use the template and label their own PRs. + if: github.event.pull_request.user.type != 'Bot' + steps: + - name: 🏷 Apply label from PR description checkbox + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const known = [ + 'breaking-change', 'bugfix', 'new-feature', 'enhancement', + 'refactor', 'documentation', 'maintenance', 'ci', 'dependencies', + ]; + + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + const current = (context.payload.pull_request.labels || []) + .map(l => l.name); + + // Match a ticked bullet whose first backticked word is a label, + // e.g. - [x] CI / workflow change: `ci` + const body = context.payload.pull_request.body || ''; + const re = /^\s*-\s*\[x\][^`\n]*`([^`]+)`/gim; + const ticked = []; + for (const m of body.matchAll(re)) { + const name = m[1].trim(); + if (known.includes(name) && !ticked.includes(name)) { + ticked.push(name); + } + } + + if (ticked.length > 1) { + core.setFailed( + `Multiple "Types of changes" checkboxes are ticked ` + + `(${ticked.join(', ')}). Tick exactly one.` + ); + return; + } + + // A ticked box wins. Without one, accept a single known label + // already on the PR, for PRs opened without the template. + if (ticked.length === 0) { + const existing = current.filter(n => known.includes(n)); + if (existing.length === 1) { + core.info(`Skipping: PR already labelled '${existing[0]}'.`); + return; + } + core.setFailed( + 'No "Types of changes" checkbox is ticked in the PR ' + + 'description. Edit the description and tick exactly ' + + 'one box so this PR can be release-noted.' + ); + return; + } + + const desired = ticked[0]; + + for (const name of current) { + if (known.includes(name) && name !== desired) { + await github.rest.issues.removeLabel({ + owner, repo, issue_number, name, + }).catch(() => {}); + } + } + if (!current.includes(desired)) { + await github.rest.issues.addLabels({ + owner, repo, issue_number, labels: [desired], + }); + }