name: PR Conventional Labeler # Labels each PR with its conventional-commit type (feat, fix, docs, perf, # refactor, chore) derived from the PR title. These labels drive the changelog # categories in .github/release.yml when release-please generates notes. # chore is labeled but excluded from the changelog (see .github/release.yml), # preserving the previous "chore hidden" behavior. on: pull_request_target: types: [opened, edited, reopened, ready_for_review] permissions: contents: read pull-requests: write jobs: label: runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 with: script: | // Conventional-commit types we manage as labels. const managedLabels = ['feat', 'fix', 'docs', 'perf', 'refactor', 'chore']; const pr = context.payload.pull_request; const title = pr.title || ''; // Matches: feat:, fix(scope):, perf!:, refactor(api)!: ... const match = title.match(/^([a-zA-Z]+)(\([^)]*\))?!?:/); const type = match ? match[1].toLowerCase() : null; const target = managedLabels.includes(type) ? type : null; const { owner, repo } = context.repo; const issue_number = pr.number; const current = pr.labels.map(l => l.name); // Remove any managed label that no longer matches the title // (e.g. PR retitled from feat: to fix:). for (const label of current) { if (managedLabels.includes(label) && label !== target) { await github.rest.issues .removeLabel({ owner, repo, issue_number, name: label }) .catch(() => {}); } } // Apply the matching label if not already present. if (target && !current.includes(target)) { await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [target], }); }