Skip to content

Commit aa3cf88

Browse files
committed
add PR support
1 parent 79cd238 commit aa3cf88

3 files changed

Lines changed: 76 additions & 65 deletions

File tree

action.yml

Lines changed: 10 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,64 +14,20 @@ inputs:
1414
dry-run:
1515
description: 'Set to true to not perform repository changes'
1616
default: false
17-
expiration-label-map:
18-
description: 'A multiline input mapping labels with actions and destination labels, applied after an expiration time. See README.md'
17+
issue-expiration-label-map:
18+
description: 'A multiline input mapping labels with actions and destination labels for issues, applied after an expiration time. See README.md'
1919
default: |-
2020
''
21-
update-remove-labels:
21+
issue-update-remove-labels:
2222
description: 'A comma-seperated list of labels that should be removed on issue update. See README.md'
2323
default: ''
24-
25-
26-
#name: "'Stale Issue Cleanup' Action for GitHub Actions"
27-
#description: 'Close issues and pull requests with no recent activity'
28-
#branding:
29-
# icon: 'cloud'
30-
# color: 'orange'
31-
#inputs:
32-
# repo-token:
33-
# description: 'Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
34-
# required: true
35-
# issue-types:
36-
# description: 'Issue types to process ("issues", "pull_requests", or "issues,pull_requests")'
37-
# default: 'issues,pull_requests'
38-
# stale-issue-message:
39-
# description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
40-
# stale-pr-message:
41-
# description: 'The message to post on the pr when tagging it. If none provided, will not mark pull requests stale.'
42-
# days-before-stale:
43-
# description: 'The number of days old an issue can be before marking it stale.'
44-
# default: 60
45-
# days-before-close:
46-
# description: 'The number of days to wait to close an issue or pull request after it being marked stale.'
47-
# default: 7
48-
# stale-issue-label:
49-
# description: 'The label to apply when an issue is stale.'
50-
# default: 'Stale'
51-
# exempt-issue-labels:
52-
# description: 'The labels to apply when an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
53-
# stale-pr-label:
54-
# description: 'The label to apply when a pull request is stale.'
55-
# default: 'Stale'
56-
# exempt-pr-labels:
57-
# description: 'The labels to apply when a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
58-
# ancient-issue-message:
59-
# description: 'The message to post when an issue is very old.'
60-
# ancient-pr-message:
61-
# description: 'The message to post when a pr is very old.'
62-
# days-before-ancient:
63-
# description: 'The number of days old an issue can be before marking it ancient.'
64-
# default: 360
65-
# response-requested-label:
66-
# description: 'The label that gets applied when a response is requested.'
67-
# closed-for-staleness-label:
68-
# description: 'The label that gets applied when an issue is closed for staleness.'
69-
# minimum-upvotes-to-exempt:
70-
# description: 'The minimum number of "upvotes" that an issue needs to have before not marking as ancient.'
71-
# loglevel:
72-
# description: 'Set to DEBUG to enable debug logging'
73-
# dry-run:
74-
# description: 'Set to true to not perform repository changes'
24+
pr-expiration-label-map:
25+
description: 'A multiline input mapping labels with actions and destination labels for PRs, applied after an expiration time. See README.md'
26+
default: |-
27+
''
28+
pr-update-remove-labels:
29+
description: 'A comma-seperated list of labels that should be removed on PR update. See README.md'
30+
default: ''
7531

7632
runs:
7733
using: 'node16'

src/github.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,28 @@ export async function getIssues(labels: string[], token: string) {
1818
});
1919
}
2020

21+
// Issue processing steps
22+
// Step 1: Skip closed/merged/locked
23+
// Step 2: If the issue is a PR, use the PR configuration, else use the issue configuration
24+
// Step 3: Iterate all labels in the issue. If labeled, iterate over the configured labels and see if the issue's labels
25+
// match the configured ones.
26+
// Step 4: If they do, take the action specified in the configuration line, and repeat for all configuration lines
27+
// Step 5: Do step 4 but for the updateRemoveLabels
2128
export async function processIssues(issues: Issue[], args: args) {
2229
issues.forEach(async issue => {
30+
// Skip closed and locked issues
31+
if (issue.state === 'closed' || issue.state === 'merged' || issue.locked) return;
32+
2333
const timeline = await getIssueLabelTimeline(issue.number, args.token);
34+
const expirationLabelMap = isPr(issue) ? args.prExpirationLabelMap : args.expirationLabelMap;
35+
const removeLabelMap = isPr(issue) ? args.prUpdateRemoveLabels : args.updateRemoveLabels;
2436
// Enumerate labels in issue and check if each matches our action list
2537
issue.labels.forEach(label => {
2638
const issueLabel = typeof label === 'string' ? label : label.name;
2739
if (issueLabel) {
28-
if (args.expirationLabelMap) {
40+
if (expirationLabelMap) {
2941
// These are labels that we apply if an issue hasn't been updated in a specified timeframe
30-
args.expirationLabelMap.forEach(async lam => {
42+
expirationLabelMap.forEach(async lam => {
3143
const sourceLabelList = lam.split(':')[0].split(',');
3244
const configuredAction = lam.split(':')[1];
3345
const configuredTime = parseInt(lam.split(':')[2]);
@@ -36,25 +48,25 @@ export async function processIssues(issues: Issue[], args: args) {
3648
// Issue contains label specified and configured time has elapsed
3749
switch (configuredAction) {
3850
case 'add':
39-
await addLabelToIssue(issue.number, lam.split(':')[3]);
51+
await addLabelToIssue(issue.number, lam.split(':')[3], args.token);
4052
break;
4153
case 'remove':
42-
await removeLabelFromIssue(issue.number, lam.split(':')[3]);
54+
await removeLabelFromIssue(issue.number, lam.split(':')[3], args.token);
4355
break;
4456
case 'close':
45-
await closeIssue(issue.number);
57+
await closeIssue(issue.number, args.token);
4658
break;
4759
default:
4860
core.error(`Unknown action ${configuredAction} for issue #${issue.number}, doing nothing`);
4961
}
5062
}
5163
});
5264
}
53-
if (args.updateRemoveLabels) {
65+
if (removeLabelMap) {
5466
// These are labels that need removed if an issue has been updated after they were applied
55-
args.updateRemoveLabels.forEach(async removeMe => {
67+
removeLabelMap.forEach(async removeMe => {
5668
if (Date.parse(issue.updated_at) > getIssueLabelDate(timeline, removeMe)) {
57-
removeLabelFromIssue(issue.number, removeMe);
69+
removeLabelFromIssue(issue.number, removeMe, args.token);
5870
}
5971
});
6072
}
@@ -74,6 +86,36 @@ async function getIssueLabelTimeline(issueNumber: number, token: string) {
7486
).filter(event => event.event === 'labeled');
7587
}
7688

89+
async function addLabelToIssue(issue: number, label: string, token: string) {
90+
const octokit = github.getOctokit(token);
91+
await octokit.rest.issues.addLabels({
92+
owner: github.context.repo.owner,
93+
repo: github.context.repo.repo,
94+
issue_number: issue,
95+
labels: [label],
96+
});
97+
}
98+
99+
async function removeLabelFromIssue(issue: number, label: string, token: string) {
100+
const octokit = github.getOctokit(token);
101+
await octokit.rest.issues.removeLabel({
102+
owner: github.context.repo.owner,
103+
repo: github.context.repo.repo,
104+
issue_number: issue,
105+
name: label,
106+
});
107+
}
108+
109+
async function closeIssue(issue: number, token: string) {
110+
const octokit = github.getOctokit(token);
111+
await octokit.rest.issues.update({
112+
owner: github.context.repo.owner,
113+
repo: github.context.repo.repo,
114+
issue_number: issue,
115+
state: 'closed',
116+
});
117+
}
118+
77119
function getIssueLabelDate(timeline: Timeline, label: string) {
78120
// Return when the label was last applied
79121
return timeline.reduce((p, c) => {
@@ -94,3 +136,7 @@ function issueDateCompare(issueDate: string, configuredDays: number) {
94136
d.setDate(d.getDate() + configuredDays);
95137
return d.valueOf() < Date.now();
96138
}
139+
140+
function isPr(issue: Issue) {
141+
return !!issue.pull_request;
142+
}

src/input.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export interface args {
66
token: string;
77
expirationLabelMap?: string[];
88
updateRemoveLabels?: string[];
9+
prExpirationLabelMap?: string[];
10+
prUpdateRemoveLabels?: string[];
911
}
1012

1113
export function getAndValidateInputs(): args {
@@ -20,16 +22,23 @@ export function getAndValidateInputs(): args {
2022
// Action map
2123
const labelValidationRegex = new RegExp(`^[A-Za-z0-9_.-,]+:(${labelActions.join('|')}):\\d+(:[A-Za-z0-9_.-,]+)?/i`);
2224
const expirationLabelMap = core
23-
.getMultilineInput('expiration-label-map', { required: false })
25+
.getMultilineInput('issue-expiration-label-map', { required: false })
2426
.filter(m => labelValidationRegex.test(m));
25-
core.debug(`Parsed label mapping: ${expirationLabelMap}`);
26-
const updateRemoveLabels = core.getInput('update-remove-labels', { required: false }).split(',');
27+
core.debug(`Parsed issue label mapping: ${expirationLabelMap}`);
28+
const prExpirationLabelMap = core
29+
.getMultilineInput('pr-expiration-label-map', { required: false })
30+
.filter(m => labelValidationRegex.test(m));
31+
core.debug(`Parsed PR label mapping: ${prExpirationLabelMap}`);
32+
const updateRemoveLabels = core.getInput('issue-update-remove-labels', { required: false }).split(',');
33+
const prUpdateRemoveLabels = core.getInput('pr-update-remove-labels', { required: false }).split(',');
2734

2835
return {
2936
dryrun: core.getBooleanInput('dry-run', { required: false }),
3037
minimumUpvotesToExempt: minUpvotes,
3138
token: core.getInput('repo-token'),
3239
expirationLabelMap,
3340
updateRemoveLabels,
41+
prExpirationLabelMap,
42+
prUpdateRemoveLabels,
3443
};
3544
}

0 commit comments

Comments
 (0)