1+ name : Auto Label PR from Linked Issue
2+
3+ on :
4+ pull_request :
5+ types : [opened, edited, synchronize, reopened]
6+
7+ permissions :
8+ pull-requests : write
9+ issues : read
10+
11+ jobs :
12+ label-pr :
13+ runs-on : ubuntu-latest
14+
15+ steps :
16+ - name : Checkout code
17+ uses : actions/checkout@v4
18+
19+ - name : Extract Issue Numbers from PR Body
20+ id : extract-issues
21+ uses : actions/github-script@v7
22+ with :
23+ result-encoding : string
24+ script : |
25+ const prBody = context.payload.pull_request.body || '';
26+ const prTitle = context.payload.pull_request.title || '';
27+
28+ // Regex patterns to find linked issues
29+ // Matches: #123, fixes #123, closes #123, resolves #123, etc.
30+ const patterns = [
31+ /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi,
32+ /#(\d+)/g
33+ ];
34+
35+ const issueNumbers = new Set();
36+
37+ // Search in PR body and title
38+ const textToSearch = prBody + ' ' + prTitle;
39+
40+ patterns.forEach(pattern => {
41+ const matches = [...textToSearch.matchAll(pattern)];
42+ matches.forEach(match => {
43+ issueNumbers.add(match[1]);
44+ });
45+ });
46+
47+ const issues = Array.from(issueNumbers);
48+ console.log('Found linked issues:', issues);
49+
50+ // Return as JSON string
51+ return JSON.stringify(issues);
52+
53+ - name : Get Labels from Linked Issues
54+ id : get-labels
55+ uses : actions/github-script@v7
56+ with :
57+ result-encoding : string
58+ script : |
59+ const issueNumbers = JSON.parse('${{ steps.extract-issues.outputs.result }}');
60+
61+ // Labels to exclude from being applied to PRs
62+ const excludedLabels = ['recode', 'hacktoberfest-accepted'];
63+
64+ if (!issueNumbers || issueNumbers.length === 0) {
65+ console.log('No linked issues found');
66+ return JSON.stringify([]);
67+ }
68+
69+ const allLabels = new Set();
70+
71+ for (const issueNumber of issueNumbers) {
72+ try {
73+ const issue = await github.rest.issues.get({
74+ owner: context.repo.owner,
75+ repo: context.repo.repo,
76+ issue_number: parseInt(issueNumber)
77+ });
78+
79+ console.log(`Issue #${issueNumber} labels:`, issue.data.labels.map(l => l.name));
80+
81+ issue.data.labels.forEach(label => {
82+ // Only add label if it's not in the excluded list
83+ if (!excludedLabels.includes(label.name.toLowerCase())) {
84+ allLabels.add(label.name);
85+ } else {
86+ console.log(`Excluding label: ${label.name}`);
87+ }
88+ });
89+ } catch (error) {
90+ console.log(`Could not fetch issue #${issueNumber}:`, error.message);
91+ }
92+ }
93+
94+ const labels = Array.from(allLabels);
95+ console.log('All labels to apply:', labels);
96+
97+ return JSON.stringify(labels);
98+
99+ - name : Apply Labels to PR
100+ uses : actions/github-script@v7
101+ with :
102+ script : |
103+ const labels = ${{ steps.get-labels.outputs.result }};
104+
105+ if (!labels || labels.length === 0) {
106+ console.log('No labels to apply');
107+ return;
108+ }
109+
110+ try {
111+ await github.rest.issues.addLabels({
112+ owner: context.repo.owner,
113+ repo: context.repo.repo,
114+ issue_number: context.payload.pull_request.number,
115+ labels: labels
116+ });
117+
118+ console.log(`Successfully applied ${labels.length} labels to PR #${context.payload.pull_request.number}`);
119+
120+ // Add a comment to the PR
121+ await github.rest.issues.createComment({
122+ owner: context.repo.owner,
123+ repo: context.repo.repo,
124+ issue_number: context.payload.pull_request.number,
125+ body: `🏷️ Labels automatically applied from linked issue(s): ${labels.map(l => `\`${l}\``).join(', ')}`
126+ });
127+ } catch (error) {
128+ console.error('Error applying labels:', error.message);
129+ core.setFailed(`Failed to apply labels: ${error.message}`);
130+ }
0 commit comments