From 2fbaa7a2b858a32972a39212bc46af325250c50e Mon Sep 17 00:00:00 2001 From: Markus Waldheim <31657644+mwaldheim@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:10:57 +0200 Subject: [PATCH] chore: add automatic pull request labels --- .github/workflows/auto-label.yml | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/auto-label.yml diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml new file mode 100644 index 0000000..de021f5 --- /dev/null +++ b/.github/workflows/auto-label.yml @@ -0,0 +1,59 @@ +# This code has been fully generated by an AI. +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 The semrel Authors +name: Automatically label pull requests + +on: + pull_request_target: + types: [opened, reopened, synchronize, edited] + +permissions: + issues: write + pull-requests: read + +concurrency: + group: auto-label-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + label: + runs-on: ubuntu-latest + steps: + - name: Apply labels based on the title and changed files + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const pullRequest = context.payload.pull_request; + const titleMatch = pullRequest.title.match(/^\s*(fix|bugfix|bug|feat|feature|enhancement|docs?|ci|deps?)(?:\([^)]*\))?!?:/i); + const titleLabels = { + fix: "bug", bugfix: "bug", bug: "bug", + feat: "enhancement", feature: "enhancement", enhancement: "enhancement", + doc: "documentation", docs: "documentation", + ci: "github_actions", dep: "dependencies", deps: "dependencies", + }; + const labels = new Set(); + if (titleMatch) labels.add(titleLabels[titleMatch[1].toLowerCase()]); + + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pullRequest.number, + per_page: 100, + }); + const dependencyFile = /(^|\/)(go\.(mod|sum|work|work\.sum)|package\.json|package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.(toml|lock)|pyproject\.toml|poetry\.lock|uv\.lock|requirements[^/]*\.txt|Pipfile(\.lock)?|composer\.(json|lock)|Gemfile(\.lock)?|pom\.xml|build\.gradle(\.kts)?|gradle\.properties|packages\.lock\.json|dependabot\.yml|renovate\.json|\.semrel\.lock)$/i; + for (const file of files) { + if (/(^|\/)(docs?|documentation)(\/|$)/i.test(file.filename) || /\.mdx?$/i.test(file.filename)) labels.add("documentation"); + if (/^\.github\/workflows\//i.test(file.filename)) labels.add("github_actions"); + if (dependencyFile.test(file.filename)) labels.add("dependencies"); + } + + const existing = new Set(pullRequest.labels.map((label) => label.name)); + const missing = [...labels].filter((label) => label && !existing.has(label)); + if (missing.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pullRequest.number, + labels: missing, + }); + }