|
| 1 | +name: Label PR based on title |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_run: |
| 5 | + workflows: ["Record PR number"] |
| 6 | + types: |
| 7 | + - completed |
| 8 | + |
| 9 | +jobs: |
| 10 | + upload: |
| 11 | + runs-on: ubuntu-latest |
| 12 | + # Guardrails to only ever run if PR recording workflow was indeed |
| 13 | + # run in a PR event and ran successfully |
| 14 | + if: > |
| 15 | + ${{ github.event.workflow_run.event == 'pull_request' && |
| 16 | + github.event.workflow_run.conclusion == 'success' }} |
| 17 | + steps: |
| 18 | + - name: 'Download artifact' |
| 19 | + uses: actions/github-script@v6 |
| 20 | + # For security, we only download artifacts tied to the successful PR recording workflow |
| 21 | + with: |
| 22 | + script: | |
| 23 | + const fs = require('fs'); |
| 24 | +
|
| 25 | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ |
| 26 | + owner: context.repo.owner, |
| 27 | + repo: context.repo.repo, |
| 28 | + run_id: ${{github.event.workflow_run.id }}, |
| 29 | + }); |
| 30 | +
|
| 31 | + const matchArtifact = artifacts.data.artifacts.filter(artifact => artifact.name == "pr")[0]; |
| 32 | +
|
| 33 | + const artifact = await github.rest.actions.downloadArtifact({ |
| 34 | + owner: context.repo.owner, |
| 35 | + repo: context.repo.repo, |
| 36 | + artifact_id: matchArtifact.id, |
| 37 | + archive_format: 'zip', |
| 38 | + }); |
| 39 | +
|
| 40 | + fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(artifact.data)); |
| 41 | + # NodeJS standard library doesn't provide ZIP capabilities; use system `unzip` command instead |
| 42 | + - run: unzip pr.zip |
| 43 | + |
| 44 | + - name: 'Label PR based on title' |
| 45 | + uses: actions/github-script@v6 |
| 46 | + with: |
| 47 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 48 | + # This safely runs in our base repo, not on fork |
| 49 | + # thus allowing us to provide a write access token to label based on PR title |
| 50 | + # and label PR based on semantic title accordingly |
| 51 | + script: | |
| 52 | + const fs = require('fs'); |
| 53 | + const pr_number = Number(fs.readFileSync('./number')); |
| 54 | + const pr_title = fs.readFileSync('./title', 'utf-8').trim(); |
| 55 | +
|
| 56 | + const FEAT_REGEX = /feat(\((.+)\))?(\:.+)/ |
| 57 | + const BUG_REGEX = /(fix|bug)(\((.+)\))?(\:.+)/ |
| 58 | + const DOCS_REGEX = /(docs|doc)(\((.+)\))?(\:.+)/ |
| 59 | + const CHORE_REGEX = /(chore)(\((.+)\))?(\:.+)/ |
| 60 | + const DEPRECATED_REGEX = /(deprecated)(\((.+)\))?(\:.+)/ |
| 61 | + const REFACTOR_REGEX = /(refactor)(\((.+)\))?(\:.+)/ |
| 62 | +
|
| 63 | + const labels = { |
| 64 | + "feature": FEAT_REGEX, |
| 65 | + "bug": BUG_REGEX, |
| 66 | + "documentation": DOCS_REGEX, |
| 67 | + "internal": CHORE_REGEX, |
| 68 | + "enhancement": REFACTOR_REGEX, |
| 69 | + "deprecated": DEPRECATED_REGEX, |
| 70 | + } |
| 71 | +
|
| 72 | + for (const label in labels) { |
| 73 | + const matcher = new RegExp(labels[label]) |
| 74 | + const isMatch = matcher.exec(pr_title) |
| 75 | + if (isMatch != null) { |
| 76 | + console.info(`Auto-labeling PR ${pr_number} with ${label}`) |
| 77 | +
|
| 78 | + await github.rest.issues.addLabels({ |
| 79 | + issue_number: pr_number, |
| 80 | + owner: context.repo.owner, |
| 81 | + repo: context.repo.repo, |
| 82 | + labels: [label] |
| 83 | + }) |
| 84 | +
|
| 85 | + break |
| 86 | + } |
| 87 | + } |
0 commit comments