33# - If a PR is labeled stale, after 30 days inactivity close the PR.
44# - `high priority` and `no-stale` PRs are exempt.
55
6- name : Close stale pull requests
6+ name : Close stale issues and pull requests
77
88on :
99 schedule :
10- # Run daily at 00:30 UTC.
11- - cron : ' 30 0 * * * '
10+ # Run weekly at 00:30 UTC every Sunday .
11+ - cron : ' 30 0 * * 0 '
1212 workflow_dispatch :
1313
1414jobs :
@@ -17,13 +17,13 @@ jobs:
1717 runs-on : linux.large
1818 permissions :
1919 contents : read
20+ issues : write
2021 pull-requests : write
2122
2223 steps :
2324 - uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
2425 with :
2526 script : |
26- // Do some dumb retries on requests.
2727 const retries = 7;
2828 const baseBackoff = 100;
2929 const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
@@ -43,107 +43,112 @@ jobs:
4343 });
4444
4545 const MAX_API_REQUESTS = 100;
46-
47- // If a PRs not labeled stale, label them stale after no update for 60 days.
4846 const STALE_LABEL_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 60;
49- // For PRs already labeled stale, close after not update for 30 days.
5047 const STALE_CLOSE_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 30;
5148
5249 const STALE_MESSAGE =
53- "Looks like this PR hasn't been updated in a while so we're going to go ahead and mark this as `Stale`. <br>" +
50+ "Looks like this item hasn't been updated in a while so we're going to go ahead and mark this as `Stale`. <br>" +
5451 "Feel free to remove the `Stale` label if you feel this was a mistake. <br>" +
5552 "If you are unable to remove the `Stale` label please contact a maintainer in order to do so. <br>" +
56- "If you want the bot to never mark this PR stale again, add the `no-stale` label.<br>" +
57- "`Stale` pull requests will automatically be closed after 30 days of inactivity.<br>";
53+ "If you want the bot to never mark this item stale again, add the `no-stale` label.<br>" +
54+ "`Stale` items will automatically be closed after 30 days of inactivity.<br>";
5855
5956 let numAPIRequests = 0;
6057 let numProcessed = 0;
6158
62- async function processPull(pull ) {
63- core.info(`[${pull .number}] URL: ${pull .html_url}`);
59+ async function processItem(item, isPR ) {
60+ core.info(`[${item .number}] URL: ${item .html_url}`);
6461 numProcessed += 1;
65- const labels = pull .labels.map((label) => label.name);
62+ const labels = item .labels.map((label) => label.name);
6663
67- // Skip if certain labels are present.
6864 if (labels.includes("no-stale") || labels.includes("high priority")) {
69- core.info(`[${pull .number}] Skipping because PR has an exempting label.`);
65+ core.info(`[${item .number}] Skipping because item has an exempting label.`);
7066 return false;
7167 }
7268
73- // Check if the PR is stale, according to our configured thresholds.
7469 let staleThresholdMillis;
7570 if (labels.includes("Stale")) {
76- core.info(`[${pull .number}] PR is labeled stale, checking whether we should close it.`);
71+ core.info(`[${item .number}] Item is labeled stale, checking whether we should close it.`);
7772 staleThresholdMillis = STALE_CLOSE_THRESHOLD_MS;
7873 } else {
79- core.info(`[${pull .number}] Checking whether to label PR as stale.`);
74+ core.info(`[${item .number}] Checking whether to label item as stale.`);
8075 staleThresholdMillis = STALE_LABEL_THRESHOLD_MS;
8176 }
8277
8378 const millisSinceLastUpdated =
84- new Date().getTime() - new Date(pull .updated_at).getTime();
79+ new Date().getTime() - new Date(item .updated_at).getTime();
8580
8681 if (millisSinceLastUpdated < staleThresholdMillis) {
87- core.info(`[${pull .number}] Skipping because PR was updated recently`);
82+ core.info(`[${item .number}] Skipping because item was updated recently`);
8883 return false;
8984 }
9085
91- // At this point, we know we should do something.
92- // For PRs already labeled stale, close them.
9386 if (labels.includes("Stale")) {
94- core.info(`[${pull .number}] Closing PR .`);
87+ core.info(`[${item .number}] Closing item .`);
9588 numAPIRequests += 1;
96- await github.rest.issues.update({
89+ await github.rest.issues.update({
9790 owner: "pytorch",
9891 repo: "executorch",
99- issue_number: pull .number,
92+ issue_number: item .number,
10093 state: "closed",
101- });
94+ });
10295 } else {
103- // For PRs not labeled stale, label them stale.
104- core.info(`[${pull.number}] Labeling PR as stale.`);
105-
96+ core.info(`[${item.number}] Labeling item as stale.`);
10697 numAPIRequests += 1;
107- await github.rest.issues.createComment({
98+ await github.rest.issues.createComment({
10899 owner: "pytorch",
109100 repo: "executorch",
110- issue_number: pull .number,
101+ issue_number: item .number,
111102 body: STALE_MESSAGE,
112103 });
113104
114105 numAPIRequests += 1;
115- await github.rest.issues.addLabels({
106+ await github.rest.issues.addLabels({
116107 owner: "pytorch",
117108 repo: "executorch",
118- issue_number: pull .number,
109+ issue_number: item .number,
119110 labels: ["Stale"],
120111 });
121112 }
122113 }
123114
124- for await (const response of github.paginate.iterator(
125- github.rest.pulls.list,
126- {
127- owner: "pytorch",
128- repo: "executorch",
129- state: "open",
130- sort: "created",
131- direction: "asc",
132- per_page: 100,
133- }
134- )) {
135- numAPIRequests += 1;
136- const pulls = response.data;
137- // Awaiting in a loop is intentional here. We want to serialize execution so
138- // that log groups are printed correctl
139- for (const pull of pulls) {
140- if (numAPIRequests > MAX_API_REQUESTS) {
141- core.warning("Max API requests exceeded, exiting.");
142- process.exit(0);
115+ async function processType(listFn, isPR) {
116+ for await (const response of github.paginate.iterator(
117+ listFn,
118+ {
119+ owner: "pytorch",
120+ repo: "executorch",
121+ state: "open",
122+ sort: "created",
123+ direction: "asc",
124+ per_page: 100,
125+ }
126+ )) {
127+ numAPIRequests += 1;
128+ const items = response.data;
129+ for (const item of items) {
130+ if (numAPIRequests > MAX_API_REQUESTS) {
131+ core.warning("Max API requests exceeded, exiting.");
132+ process.exit(0);
133+ }
134+ await core.group(`Processing ${isPR ? "PR" : "Issue"} #${item.number}`, async () => {
135+ await processItem(item, isPR);
136+ });
143137 }
144- await core.group(`Processing PR #${pull.number}`, async () => {
145- await processPull(pull);
146- });
147138 }
148139 }
149- core.info(`Processed ${numProcessed} PRs total.`);
140+
141+ // Process PRs
142+ await processType(github.rest.pulls.list, true);
143+
144+ // Process Issues (exclude PRs)
145+ await processType(
146+ async (params) => {
147+ const resp = await github.rest.issues.listForRepo(params);
148+ resp.data = resp.data.filter((issue) => !issue.pull_request);
149+ return resp;
150+ },
151+ false
152+ );
153+
154+ core.info(`Processed ${numProcessed} items total.`);
0 commit comments