1+ name : ' Auto Label PR'
2+
3+ on :
4+ pull_request :
5+ types : [ opened, synchronize, reopened ]
6+
7+ jobs :
8+ label-pr :
9+ runs-on : ubuntu-latest
10+ steps :
11+ - uses : actions/checkout@v4
12+ with :
13+ fetch-depth : 0
14+
15+ - name : Auto Label PR Based on Commits
16+ uses : actions/github-script@v7
17+ with :
18+ github-token : ${{ secrets.GITHUB_TOKEN }}
19+ script : |
20+ const { issue: { number: issue_number }, repo: { owner, repo } } = context;
21+
22+ // Get commits in the PR
23+ const { data: commits } = await github.rest.pulls.listCommits({
24+ owner,
25+ repo,
26+ pull_number: issue_number
27+ });
28+
29+ // Define valid types and their corresponding labels
30+ const typeToLabel = {
31+ 'feat': 'type: new feature',
32+ 'fix': 'type: bug',
33+ 'docs': 'type: doc',
34+ 'test': 'type: test',
35+ 'chore': 'type: chore',
36+ 'enhance': 'type: enhancement',
37+ 'amend': 'type: amend',
38+ 'style': 'type: chore',
39+ 'refactor': 'type: enhancement',
40+ 'perf': 'type: enhancement',
41+ 'build': 'type: chore'
42+ };
43+
44+ const validTypes = Object.keys(typeToLabel);
45+ const pattern = new RegExp(`^(${validTypes.join('|')})(\(.+\))?!?: .+`);
46+
47+ let isValid = true;
48+ let invalidCommits = [];
49+
50+ // Check each commit
51+ for (const commit of commits) {
52+ const message = commit.commit.message.split('\n')[0]; // Get first line
53+ if (!pattern.test(message)) {
54+ isValid = false;
55+ invalidCommits.push(message);
56+ }
57+ }
58+
59+ // Get the first valid commit to determine the type
60+ const firstValidCommit = commits.find(commit => {
61+ const message = commit.commit.message.split('\n')[0];
62+ return pattern.test(message);
63+ });
64+
65+ // Get existing type labels
66+ const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
67+ owner,
68+ repo,
69+ issue_number
70+ });
71+
72+ if (isValid && firstValidCommit) {
73+ // Extract type from first valid commit
74+ const type = firstValidCommit.commit.message.match(pattern)[1];
75+ const labelToAdd = typeToLabel[type];
76+
77+ // Check if the label already exists
78+ const labelExists = currentLabels.some(label => label.name === labelToAdd);
79+
80+ if (!labelExists) {
81+ // Remove other type labels if they exist
82+ for (const label of currentLabels) {
83+ if (label.name.startsWith('type:') && label.name !== labelToAdd) {
84+ await github.rest.issues.removeLabel({
85+ owner,
86+ repo,
87+ issue_number,
88+ name: label.name
89+ });
90+ }
91+ }
92+
93+ // Add the new label
94+ await github.rest.issues.addLabels({
95+ owner,
96+ repo,
97+ issue_number,
98+ labels: [labelToAdd]
99+ });
100+ }
101+ } else {
102+ // Create error comment
103+ const errorMessage = `
104+ ❌ Some commit messages don't follow the conventional format.
105+
106+ Invalid commits:
107+ ${invalidCommits.map(msg => `- \`${msg}\``).join('\n')}
108+
109+ Please update your commits to follow the format:
110+ \`\`\`
111+ type: description
112+
113+ Valid types:
114+ - feat: New feature (type: new feature)
115+ - fix: Bug fix (type: bug)
116+ - docs: Documentation changes (type: doc)
117+ - test: Adding/updating tests (type: test)
118+ - chore: Maintenance tasks (type: chore)
119+ - enhance: Enhancement to existing features (type: enhancement)
120+ - amend: Small amendments (type: amend)
121+ - style: Code formatting (type: chore)
122+ - refactor: Code restructuring (type: enhancement)
123+ - perf: Performance improvements (type: enhancement)
124+ - build: Build system changes (type: chore)
125+ \`\`\`
126+
127+ You can update your commit messages using:
128+ \`\`\`bash
129+ git rebase -i HEAD~n # where n is the number of commits to edit
130+ # Change 'pick' to 'reword' for commits you want to edit
131+ \`\`\`
132+ `;
133+
134+ await github.rest.issues.createComment({
135+ owner,
136+ repo,
137+ issue_number,
138+ body: errorMessage
139+ });
140+ }
0 commit comments