2019-08-04 09:34:59 +08:00
|
|
|
import * as core from '@actions/core';
|
2021-02-13 19:09:37 +08:00
|
|
|
import {IssuesProcessor} from './classes/issues-processor';
|
2021-05-03 21:20:07 +08:00
|
|
|
import {isValidDate} from './functions/dates/is-valid-date';
|
2021-02-13 19:09:37 +08:00
|
|
|
import {IIssuesProcessorOptions} from './interfaces/issues-processor-options';
|
2021-06-03 21:18:48 +08:00
|
|
|
import {Issue} from './classes/issue';
|
2023-06-24 05:13:39 +08:00
|
|
|
import {getStateInstance} from './services/state.service';
|
2019-08-04 09:34:59 +08:00
|
|
|
|
2021-02-16 19:18:48 +08:00
|
|
|
async function _run(): Promise<void> {
|
2019-08-04 09:34:59 +08:00
|
|
|
try {
|
2021-02-16 19:18:48 +08:00
|
|
|
const args = _getAndValidateArgs();
|
2019-08-04 09:34:59 +08:00
|
|
|
|
2023-06-24 05:13:39 +08:00
|
|
|
const state = getStateInstance(args);
|
2023-07-05 00:29:58 +08:00
|
|
|
await state.restore();
|
2023-06-22 19:20:34 +08:00
|
|
|
|
|
|
|
const issueProcessor: IssuesProcessor = new IssuesProcessor(args, state);
|
2023-06-13 19:21:22 +08:00
|
|
|
|
|
|
|
const rateLimitAtStart = await issueProcessor.getRateLimit();
|
|
|
|
if (rateLimitAtStart) {
|
|
|
|
core.debug(
|
|
|
|
`Github API rate status: limit=${rateLimitAtStart.limit}, used=${rateLimitAtStart.used}, remaining=${rateLimitAtStart.remaining}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-03 21:18:48 +08:00
|
|
|
await issueProcessor.processIssues();
|
|
|
|
|
2023-06-13 19:21:22 +08:00
|
|
|
const rateLimitAtEnd = await issueProcessor.getRateLimit();
|
|
|
|
|
|
|
|
if (rateLimitAtEnd) {
|
|
|
|
core.debug(
|
|
|
|
`Github API rate status: limit=${rateLimitAtEnd.limit}, used=${rateLimitAtEnd.used}, remaining=${rateLimitAtEnd.remaining}`
|
|
|
|
);
|
|
|
|
|
|
|
|
if (rateLimitAtStart)
|
|
|
|
core.info(
|
|
|
|
`Github API rate used: ${
|
|
|
|
rateLimitAtStart.remaining - rateLimitAtEnd.remaining
|
|
|
|
}`
|
|
|
|
);
|
|
|
|
|
|
|
|
core.info(
|
|
|
|
`Github API rate remaining: ${rateLimitAtEnd.remaining}; reset at: ${rateLimitAtEnd.reset}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-06-22 19:20:34 +08:00
|
|
|
await state.persist();
|
|
|
|
|
2021-06-03 21:18:48 +08:00
|
|
|
await processOutput(
|
2021-06-07 21:27:59 +08:00
|
|
|
issueProcessor.staleIssues,
|
|
|
|
issueProcessor.closedIssues
|
2021-06-03 21:18:48 +08:00
|
|
|
);
|
2019-08-07 04:25:08 +08:00
|
|
|
} catch (error) {
|
|
|
|
core.error(error);
|
|
|
|
core.setFailed(error.message);
|
|
|
|
}
|
|
|
|
}
|
2019-08-04 09:34:59 +08:00
|
|
|
|
2021-02-16 19:18:48 +08:00
|
|
|
function _getAndValidateArgs(): IIssuesProcessorOptions {
|
2021-02-13 19:09:37 +08:00
|
|
|
const args: IIssuesProcessorOptions = {
|
2021-01-15 20:07:08 +08:00
|
|
|
repoToken: core.getInput('repo-token'),
|
2019-08-07 04:25:08 +08:00
|
|
|
staleIssueMessage: core.getInput('stale-issue-message'),
|
2019-08-07 06:26:47 +08:00
|
|
|
stalePrMessage: core.getInput('stale-pr-message'),
|
2020-07-14 01:05:59 +08:00
|
|
|
closeIssueMessage: core.getInput('close-issue-message'),
|
|
|
|
closePrMessage: core.getInput('close-pr-message'),
|
2022-10-19 18:08:31 +08:00
|
|
|
daysBeforeStale: parseFloat(
|
2021-10-20 21:25:24 +08:00
|
|
|
core.getInput('days-before-stale', {required: true})
|
2021-10-09 03:06:12 +08:00
|
|
|
),
|
2022-10-19 18:08:31 +08:00
|
|
|
daysBeforeIssueStale: parseFloat(core.getInput('days-before-issue-stale')),
|
|
|
|
daysBeforePrStale: parseFloat(core.getInput('days-before-pr-stale')),
|
2021-10-20 21:25:24 +08:00
|
|
|
daysBeforeClose: parseInt(
|
|
|
|
core.getInput('days-before-close', {required: true})
|
2019-08-07 04:25:08 +08:00
|
|
|
),
|
2021-10-20 21:25:24 +08:00
|
|
|
daysBeforeIssueClose: parseInt(core.getInput('days-before-issue-close')),
|
|
|
|
daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')),
|
2019-08-07 04:25:08 +08:00
|
|
|
staleIssueLabel: core.getInput('stale-issue-label', {required: true}),
|
2020-09-09 03:32:42 +08:00
|
|
|
closeIssueLabel: core.getInput('close-issue-label'),
|
2020-04-16 09:33:09 +08:00
|
|
|
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
2019-08-07 04:25:08 +08:00
|
|
|
stalePrLabel: core.getInput('stale-pr-label', {required: true}),
|
2020-09-09 03:32:42 +08:00
|
|
|
closePrLabel: core.getInput('close-pr-label'),
|
2020-04-16 09:33:09 +08:00
|
|
|
exemptPrLabels: core.getInput('exempt-pr-labels'),
|
2021-10-20 21:25:24 +08:00
|
|
|
onlyLabels: core.getInput('only-labels'),
|
2021-03-01 08:08:33 +08:00
|
|
|
onlyIssueLabels: core.getInput('only-issue-labels'),
|
|
|
|
onlyPrLabels: core.getInput('only-pr-labels'),
|
2021-10-20 21:25:24 +08:00
|
|
|
anyOfLabels: core.getInput('any-of-labels'),
|
2021-04-29 04:33:42 +08:00
|
|
|
anyOfIssueLabels: core.getInput('any-of-issue-labels'),
|
|
|
|
anyOfPrLabels: core.getInput('any-of-pr-labels'),
|
2019-08-07 04:25:08 +08:00
|
|
|
operationsPerRun: parseInt(
|
|
|
|
core.getInput('operations-per-run', {required: true})
|
2020-04-17 01:57:59 +08:00
|
|
|
),
|
2021-10-20 21:25:24 +08:00
|
|
|
removeStaleWhenUpdated: !(
|
|
|
|
core.getInput('remove-stale-when-updated') === 'false'
|
2020-05-11 22:46:03 +08:00
|
|
|
),
|
2021-10-20 21:25:24 +08:00
|
|
|
removeIssueStaleWhenUpdated: _toOptionalBoolean(
|
|
|
|
'remove-issue-stale-when-updated'
|
|
|
|
),
|
|
|
|
removePrStaleWhenUpdated: _toOptionalBoolean(
|
|
|
|
'remove-pr-stale-when-updated'
|
2021-04-30 21:14:51 +08:00
|
|
|
),
|
2020-06-24 01:55:24 +08:00
|
|
|
debugOnly: core.getInput('debug-only') === 'true',
|
2020-07-24 20:08:48 +08:00
|
|
|
ascending: core.getInput('ascending') === 'true',
|
2021-01-18 09:22:36 +08:00
|
|
|
deleteBranch: core.getInput('delete-branch') === 'true',
|
|
|
|
startDate:
|
|
|
|
core.getInput('start-date') !== ''
|
|
|
|
? core.getInput('start-date')
|
2021-01-19 18:54:16 +08:00
|
|
|
: undefined,
|
2021-10-20 21:25:24 +08:00
|
|
|
exemptMilestones: core.getInput('exempt-milestones'),
|
2021-01-19 18:54:16 +08:00
|
|
|
exemptIssueMilestones: core.getInput('exempt-issue-milestones'),
|
2021-02-16 19:18:48 +08:00
|
|
|
exemptPrMilestones: core.getInput('exempt-pr-milestones'),
|
2021-10-20 21:25:24 +08:00
|
|
|
exemptAllMilestones: core.getInput('exempt-all-milestones') === 'true',
|
|
|
|
exemptAllIssueMilestones: _toOptionalBoolean('exempt-all-issue-milestones'),
|
|
|
|
exemptAllPrMilestones: _toOptionalBoolean('exempt-all-pr-milestones'),
|
|
|
|
exemptAssignees: core.getInput('exempt-assignees'),
|
2021-02-28 19:15:08 +08:00
|
|
|
exemptIssueAssignees: core.getInput('exempt-issue-assignees'),
|
|
|
|
exemptPrAssignees: core.getInput('exempt-pr-assignees'),
|
2021-10-20 21:25:24 +08:00
|
|
|
exemptAllAssignees: core.getInput('exempt-all-assignees') === 'true',
|
|
|
|
exemptAllIssueAssignees: _toOptionalBoolean('exempt-all-issue-assignees'),
|
|
|
|
exemptAllPrAssignees: _toOptionalBoolean('exempt-all-pr-assignees'),
|
2021-06-08 21:31:20 +08:00
|
|
|
enableStatistics: core.getInput('enable-statistics') === 'true',
|
2023-03-21 21:11:19 +08:00
|
|
|
labelsToRemoveWhenStale: core.getInput('labels-to-remove-when-stale'),
|
2021-06-08 21:31:20 +08:00
|
|
|
labelsToRemoveWhenUnstale: core.getInput('labels-to-remove-when-unstale'),
|
2021-09-17 21:54:38 +08:00
|
|
|
labelsToAddWhenUnstale: core.getInput('labels-to-add-when-unstale'),
|
2021-10-20 21:25:24 +08:00
|
|
|
ignoreUpdates: core.getInput('ignore-updates') === 'true',
|
|
|
|
ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'),
|
|
|
|
ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'),
|
2022-06-24 05:43:36 +08:00
|
|
|
exemptDraftPr: core.getInput('exempt-draft-pr') === 'true',
|
2022-09-12 19:20:57 +08:00
|
|
|
closeIssueReason: core.getInput('close-issue-reason'),
|
|
|
|
includeOnlyAssigned: core.getInput('include-only-assigned') === 'true'
|
2019-08-04 09:34:59 +08:00
|
|
|
};
|
|
|
|
|
2022-10-19 18:08:31 +08:00
|
|
|
for (const numberInput of ['days-before-stale']) {
|
|
|
|
if (isNaN(parseFloat(core.getInput(numberInput)))) {
|
|
|
|
const errorMessage = `Option "${numberInput}" did not parse to a valid float`;
|
|
|
|
core.setFailed(errorMessage);
|
|
|
|
throw new Error(errorMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const numberInput of ['days-before-close', 'operations-per-run']) {
|
2019-08-07 04:25:08 +08:00
|
|
|
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
2021-05-03 21:20:07 +08:00
|
|
|
const errorMessage = `Option "${numberInput}" did not parse to a valid integer`;
|
|
|
|
core.setFailed(errorMessage);
|
|
|
|
throw new Error(errorMessage);
|
2019-08-04 09:34:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-18 09:22:36 +08:00
|
|
|
for (const optionalDateInput of ['start-date']) {
|
|
|
|
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
|
|
|
|
if (core.getInput(optionalDateInput) !== '') {
|
|
|
|
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
|
2021-05-03 21:20:07 +08:00
|
|
|
const errorMessage = `Option "${optionalDateInput}" did not parse to a valid date`;
|
|
|
|
core.setFailed(errorMessage);
|
|
|
|
throw new Error(errorMessage);
|
2021-01-18 09:22:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-24 05:43:36 +08:00
|
|
|
|
|
|
|
const validCloseReasons = ['', 'completed', 'not_planned'];
|
|
|
|
if (!validCloseReasons.includes(args.closeIssueReason)) {
|
|
|
|
const errorMessage = `Unrecognized close-issue-reason "${
|
|
|
|
args.closeIssueReason
|
|
|
|
}", valid values are: ${validCloseReasons.filter(Boolean).join(', ')}`;
|
|
|
|
core.setFailed(errorMessage);
|
|
|
|
throw new Error(errorMessage);
|
|
|
|
}
|
2021-01-18 09:22:36 +08:00
|
|
|
|
2019-08-04 09:34:59 +08:00
|
|
|
return args;
|
|
|
|
}
|
|
|
|
|
2021-06-03 21:18:48 +08:00
|
|
|
async function processOutput(
|
|
|
|
staledIssues: Issue[],
|
|
|
|
closedIssues: Issue[]
|
|
|
|
): Promise<void> {
|
|
|
|
core.setOutput('staled-issues-prs', JSON.stringify(staledIssues));
|
|
|
|
core.setOutput('closed-issues-prs', JSON.stringify(closedIssues));
|
|
|
|
}
|
|
|
|
|
2021-10-20 21:25:24 +08:00
|
|
|
/**
|
|
|
|
* @description
|
|
|
|
* From an argument name, get the value as an optional boolean
|
|
|
|
* This is very useful for all the arguments that override others
|
|
|
|
* It will allow us to easily use the original one when the return value is `undefined`
|
|
|
|
* Which is different from `true` or `false` that consider the argument as set
|
|
|
|
*
|
|
|
|
* @param {Readonly<string>} argumentName The name of the argument to check
|
|
|
|
*
|
|
|
|
* @returns {boolean | undefined} The value matching the given argument name
|
|
|
|
*/
|
|
|
|
function _toOptionalBoolean(
|
|
|
|
argumentName: Readonly<string>
|
|
|
|
): boolean | undefined {
|
|
|
|
const argument: string = core.getInput(argumentName);
|
|
|
|
|
|
|
|
if (argument === 'true') {
|
|
|
|
return true;
|
|
|
|
} else if (argument === 'false') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-03-01 08:08:33 +08:00
|
|
|
void _run();
|