feat(exempt): add new options to exempt the milestones (#279)
* feat(exempt): add new options to exempt the milestones closes #270 * test(milestones): add coverage * test(issue): add coverage * chore(rebase): fix all errors due to the rebase also made some changes regarding the change I made with the lint scripts and prettier. I did not saw that some scripts were already here and I created to more to keep the old ones as well * test(milestone): add coverage * chore(index): update index * fix(checks): remove checks over optional number options the code was actually handling the case where the values are NaN so it's fine
This commit is contained in:
parent
1b9f13b607
commit
f71123a6f7
21
README.md
21
README.md
|
@ -43,6 +43,9 @@ $ npm test
|
||||||
| `close-pr-label` | Label to apply on the closing pr. | Optional |
|
| `close-pr-label` | Label to apply on the closing pr. | Optional |
|
||||||
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
||||||
| `exempt-pr-labels` | Labels on the pr exempted from being marked as stale. | Optional |
|
| `exempt-pr-labels` | Labels on the pr exempted from being marked as stale. | Optional |
|
||||||
|
| `exempt-milestones` | Milestones on an issue or a pr exempted from being marked as stale. | Optional |
|
||||||
|
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||||
|
| `exempt-pr-milestones` | Milestones on the pr exempted from being marked as stale (override `exempt-milestones`). | Optional |
|
||||||
| `only-labels` | Only labels checked for stale issue/pr. | Optional |
|
| `only-labels` | Only labels checked for stale issue/pr. | Optional |
|
||||||
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional |
|
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related). _Defaults to **30**_ | Optional |
|
||||||
| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. _Defaults to **true**_ | Optional |
|
| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. _Defaults to **true**_ | Optional |
|
||||||
|
@ -181,6 +184,24 @@ jobs:
|
||||||
start-date: '2020-18-04T00:00:00Z' // ISO 8601 or RFC 2822
|
start-date: '2020-18-04T00:00:00Z' // ISO 8601 or RFC 2822
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Avoid stale for specific milestones:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
exempt-issue-milestones: 'future,alpha,beta'
|
||||||
|
exempt-pr-milestones: 'bugfix,improvement'
|
||||||
|
```
|
||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|
||||||
To see debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository. You can run this action in debug only mode (no actions will be taken on your issues) by passing `debug-only` `true` as an argument to the action.
|
To see debug output from this action, you must set the secret `ACTIONS_STEP_DEBUG` to `true` in your repository. You can run this action in debug only mode (no actions will be taken on your issues) by passing `debug-only` `true` as an argument to the action.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
20
action.yml
20
action.yml
|
@ -23,20 +23,20 @@ inputs:
|
||||||
required: false
|
required: false
|
||||||
default: '60'
|
default: '60'
|
||||||
days-before-issue-stale:
|
days-before-issue-stale:
|
||||||
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding the issues only.'
|
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding only the issues.'
|
||||||
required: false
|
required: false
|
||||||
days-before-pr-stale:
|
days-before-pr-stale:
|
||||||
description: 'The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding the pull requests only.'
|
description: 'The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding only the pull requests.'
|
||||||
required: false
|
required: false
|
||||||
days-before-close:
|
days-before-close:
|
||||||
description: 'The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests.'
|
description: 'The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests.'
|
||||||
required: false
|
required: false
|
||||||
default: '7'
|
default: '7'
|
||||||
days-before-issue-close:
|
days-before-issue-close:
|
||||||
description: 'The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding the issues only.'
|
description: 'The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding only the issues.'
|
||||||
required: false
|
required: false
|
||||||
days-before-pr-close:
|
days-before-pr-close:
|
||||||
description: 'The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding the pull requests only.'
|
description: 'The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding only the pull requests.'
|
||||||
required: false
|
required: false
|
||||||
stale-issue-label:
|
stale-issue-label:
|
||||||
description: 'The label to apply when an issue is stale.'
|
description: 'The label to apply when an issue is stale.'
|
||||||
|
@ -60,6 +60,18 @@ inputs:
|
||||||
description: 'The labels that mean a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
description: 'The labels that mean a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
||||||
default: ''
|
default: ''
|
||||||
required: false
|
required: false
|
||||||
|
exempt-milestones:
|
||||||
|
description: 'The milestones that mean an issue or a pr is exempt from being marked stale. Separate multiple milestones with commas (eg. "milestone1,milestone2")'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-issue-milestones:
|
||||||
|
description: 'The milestones that mean an issue is exempt from being marked stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the issue.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
exempt-pr-milestones:
|
||||||
|
description: 'The milestones that mean a pull request is exempt from being marked stale. Separate multiple milestones with commas (eg. "milestone1,milestone2"). Override "exempt-milestones" option regarding only the pull requests.'
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
only-labels:
|
only-labels:
|
||||||
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
|
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
|
||||||
default: ''
|
default: ''
|
||||||
|
|
|
@ -19,29 +19,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.IssueProcessor = void 0;
|
exports.IssueProcessor = void 0;
|
||||||
const github_1 = __nccwpck_require__(5438);
|
const github_1 = __nccwpck_require__(5438);
|
||||||
|
const issue_1 = __nccwpck_require__(4783);
|
||||||
|
const issue_logger_1 = __nccwpck_require__(2984);
|
||||||
|
const logger_1 = __nccwpck_require__(6212);
|
||||||
|
const milestones_1 = __nccwpck_require__(4601);
|
||||||
const get_humanized_date_1 = __nccwpck_require__(965);
|
const get_humanized_date_1 = __nccwpck_require__(965);
|
||||||
const is_date_more_recent_than_1 = __nccwpck_require__(1473);
|
const is_date_more_recent_than_1 = __nccwpck_require__(1473);
|
||||||
const is_valid_date_1 = __nccwpck_require__(891);
|
const is_valid_date_1 = __nccwpck_require__(891);
|
||||||
const get_issue_type_1 = __nccwpck_require__(5153);
|
const get_issue_type_1 = __nccwpck_require__(5153);
|
||||||
const issue_logger_1 = __nccwpck_require__(1699);
|
|
||||||
const logger_1 = __nccwpck_require__(8236);
|
|
||||||
const is_labeled_1 = __nccwpck_require__(6792);
|
const is_labeled_1 = __nccwpck_require__(6792);
|
||||||
const is_pull_request_1 = __nccwpck_require__(5400);
|
const is_pull_request_1 = __nccwpck_require__(5400);
|
||||||
const labels_to_list_1 = __nccwpck_require__(9107);
|
|
||||||
const should_mark_when_stale_1 = __nccwpck_require__(2461);
|
const should_mark_when_stale_1 = __nccwpck_require__(2461);
|
||||||
const logger = new logger_1.Logger();
|
const words_to_list_1 = __nccwpck_require__(1883);
|
||||||
/***
|
/***
|
||||||
* Handle processing of issues for staleness/closure.
|
* Handle processing of issues for staleness/closure.
|
||||||
*/
|
*/
|
||||||
class IssueProcessor {
|
class IssueProcessor {
|
||||||
constructor(options, getActor, getIssues, listIssueComments, getLabelCreationDate) {
|
constructor(options, getActor, getIssues, listIssueComments, getLabelCreationDate) {
|
||||||
|
this._logger = new logger_1.Logger();
|
||||||
|
this._operationsLeft = 0;
|
||||||
this.staleIssues = [];
|
this.staleIssues = [];
|
||||||
this.closedIssues = [];
|
this.closedIssues = [];
|
||||||
this.deletedBranchIssues = [];
|
this.deletedBranchIssues = [];
|
||||||
this.removedLabelIssues = [];
|
this.removedLabelIssues = [];
|
||||||
this.operationsLeft = 0;
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.operationsLeft = options.operationsPerRun;
|
this._operationsLeft = options.operationsPerRun;
|
||||||
this.client = github_1.getOctokit(options.repoToken);
|
this.client = github_1.getOctokit(options.repoToken);
|
||||||
if (getActor) {
|
if (getActor) {
|
||||||
this._getActor = getActor;
|
this._getActor = getActor;
|
||||||
|
@ -56,7 +58,7 @@ class IssueProcessor {
|
||||||
this._getLabelCreationDate = getLabelCreationDate;
|
this._getLabelCreationDate = getLabelCreationDate;
|
||||||
}
|
}
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
logger.warning('Executing in debug mode. Debug output will be written but no issues will be processed.');
|
this._logger.warning('Executing in debug mode. Debug output will be written but no issues will be processed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static _updatedSince(timestamp, num_days) {
|
static _updatedSince(timestamp, num_days) {
|
||||||
|
@ -68,39 +70,37 @@ class IssueProcessor {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// get the next batch of issues
|
// get the next batch of issues
|
||||||
const issues = yield this._getIssues(page);
|
const issues = yield this._getIssues(page);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
const actor = yield this._getActor();
|
const actor = yield this._getActor();
|
||||||
if (issues.length <= 0) {
|
if (issues.length <= 0) {
|
||||||
logger.info('---');
|
this._logger.info('---');
|
||||||
logger.info('No more issues found to process. Exiting.');
|
this._logger.info('No more issues found to process. Exiting.');
|
||||||
return this.operationsLeft;
|
return this._operationsLeft;
|
||||||
}
|
}
|
||||||
for (const issue of issues.values()) {
|
for (const issue of issues.values()) {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
const isPr = is_pull_request_1.isPullRequest(issue);
|
issueLogger.info(`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${issue.isPullRequest})`);
|
||||||
issueLogger.info(`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`);
|
|
||||||
// calculate string based messages for this issue
|
// calculate string based messages for this issue
|
||||||
const staleMessage = isPr
|
const staleMessage = issue.isPullRequest
|
||||||
? this.options.stalePrMessage
|
? this.options.stalePrMessage
|
||||||
: this.options.staleIssueMessage;
|
: this.options.staleIssueMessage;
|
||||||
const closeMessage = isPr
|
const closeMessage = issue.isPullRequest
|
||||||
? this.options.closePrMessage
|
? this.options.closePrMessage
|
||||||
: this.options.closeIssueMessage;
|
: this.options.closeIssueMessage;
|
||||||
const staleLabel = isPr
|
const staleLabel = issue.isPullRequest
|
||||||
? this.options.stalePrLabel
|
? this.options.stalePrLabel
|
||||||
: this.options.staleIssueLabel;
|
: this.options.staleIssueLabel;
|
||||||
const closeLabel = isPr
|
const closeLabel = issue.isPullRequest
|
||||||
? this.options.closePrLabel
|
? this.options.closePrLabel
|
||||||
: this.options.closeIssueLabel;
|
: this.options.closeIssueLabel;
|
||||||
const exemptLabels = labels_to_list_1.labelsToList(isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels);
|
const skipMessage = issue.isPullRequest
|
||||||
const skipMessage = isPr
|
|
||||||
? this.options.skipStalePrMessage
|
? this.options.skipStalePrMessage
|
||||||
: this.options.skipStaleIssueMessage;
|
: this.options.skipStaleIssueMessage;
|
||||||
const issueType = get_issue_type_1.getIssueType(isPr);
|
const issueType = get_issue_type_1.getIssueType(issue.isPullRequest);
|
||||||
const daysBeforeStale = isPr
|
const daysBeforeStale = issue.isPullRequest
|
||||||
? this._getDaysBeforePrStale()
|
? this._getDaysBeforePrStale()
|
||||||
: this._getDaysBeforeIssueStale();
|
: this._getDaysBeforeIssueStale();
|
||||||
if (isPr) {
|
if (issue.isPullRequest) {
|
||||||
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
|
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -134,38 +134,44 @@ class IssueProcessor {
|
||||||
continue; // don't process issues which were created before the start date
|
continue; // don't process issues which were created before the start date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Does this issue have a stale label?
|
if (issue.isStale) {
|
||||||
let isStale = is_labeled_1.isLabeled(issue, staleLabel);
|
|
||||||
if (isStale) {
|
|
||||||
issueLogger.info(`This issue has a stale label`);
|
issueLogger.info(`This issue has a stale label`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
issueLogger.info(`This issue hasn't a stale label`);
|
issueLogger.info(`This issue hasn't a stale label`);
|
||||||
}
|
}
|
||||||
|
const exemptLabels = words_to_list_1.wordsToList(issue.isPullRequest
|
||||||
|
? this.options.exemptPrLabels
|
||||||
|
: this.options.exemptIssueLabels);
|
||||||
if (exemptLabels.some((exemptLabel) => is_labeled_1.isLabeled(issue, exemptLabel))) {
|
if (exemptLabels.some((exemptLabel) => is_labeled_1.isLabeled(issue, exemptLabel))) {
|
||||||
if (isStale) {
|
if (issue.isStale) {
|
||||||
issueLogger.info(`An exempt label was added after the stale label.`);
|
issueLogger.info(`An exempt label was added after the stale label.`);
|
||||||
yield this._removeStaleLabel(issue, staleLabel);
|
yield this._removeStaleLabel(issue, staleLabel);
|
||||||
}
|
}
|
||||||
issueLogger.info(`Skipping ${issueType} because it has an exempt label`);
|
issueLogger.info(`Skipping ${issueType} because it has an exempt label`);
|
||||||
continue; // don't process exempt issues
|
continue; // don't process exempt issues
|
||||||
}
|
}
|
||||||
|
const milestones = new milestones_1.Milestones(this.options, issue);
|
||||||
|
if (milestones.shouldExemptMilestones()) {
|
||||||
|
issueLogger.info(`Skipping ${issueType} because it has an exempt milestone`);
|
||||||
|
continue; // don't process exempt milestones
|
||||||
|
}
|
||||||
// should this issue be marked stale?
|
// should this issue be marked stale?
|
||||||
const shouldBeStale = !IssueProcessor._updatedSince(issue.updated_at, this.options.daysBeforeStale);
|
const shouldBeStale = !IssueProcessor._updatedSince(issue.updated_at, this.options.daysBeforeStale);
|
||||||
// determine if this issue needs to be marked stale first
|
// determine if this issue needs to be marked stale first
|
||||||
if (!isStale && shouldBeStale && shouldMarkAsStale) {
|
if (!issue.isStale && shouldBeStale && shouldMarkAsStale) {
|
||||||
issueLogger.info(`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`);
|
issueLogger.info(`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`);
|
||||||
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
yield this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
isStale = true; // this issue is now considered stale
|
issue.isStale = true; // this issue is now considered stale
|
||||||
}
|
}
|
||||||
// process the issue if it was marked stale
|
// process the issue if it was marked stale
|
||||||
if (isStale) {
|
if (issue.isStale) {
|
||||||
issueLogger.info(`Found a stale ${issueType}`);
|
issueLogger.info(`Found a stale ${issueType}`);
|
||||||
yield this._processStaleIssue(issue, issueType, staleLabel, actor, closeMessage, closeLabel);
|
yield this._processStaleIssue(issue, issueType, staleLabel, actor, closeMessage, closeLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.operationsLeft <= 0) {
|
if (this._operationsLeft <= 0) {
|
||||||
logger.warning('Reached max number of operations to process. Exiting.');
|
this._logger.warning('Reached max number of operations to process. Exiting.');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// do the next batch
|
// do the next batch
|
||||||
|
@ -244,7 +250,7 @@ class IssueProcessor {
|
||||||
return comments.data;
|
return comments.data;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
logger.error(`List issue comments error: ${error.message}`);
|
this._logger.error(`List issue comments error: ${error.message}`);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -262,7 +268,7 @@ class IssueProcessor {
|
||||||
return actor.data.login;
|
return actor.data.login;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// grab issues from github in baches of 100
|
// grab issues from github in batches of 100
|
||||||
_getIssues(page) {
|
_getIssues(page) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// generate type for response
|
// generate type for response
|
||||||
|
@ -277,10 +283,10 @@ class IssueProcessor {
|
||||||
direction: this.options.ascending ? 'asc' : 'desc',
|
direction: this.options.ascending ? 'asc' : 'desc',
|
||||||
page
|
page
|
||||||
});
|
});
|
||||||
return issueResult.data;
|
return issueResult.data.map((issue) => new issue_1.Issue(this.options, issue));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
logger.error(`Get issues for repo error: ${error.message}`);
|
this._logger.error(`Get issues for repo error: ${error.message}`);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -291,7 +297,7 @@ class IssueProcessor {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
issueLogger.info(`Marking issue #${issue.number} as stale`);
|
issueLogger.info(`Marking issue #${issue.number} as stale`);
|
||||||
this.staleIssues.push(issue);
|
this.staleIssues.push(issue);
|
||||||
this.operationsLeft -= 2;
|
this._operationsLeft -= 2;
|
||||||
// if the issue is being marked stale, the updated date should be changed to right now
|
// if the issue is being marked stale, the updated date should be changed to right now
|
||||||
// so that close calculations work correctly
|
// so that close calculations work correctly
|
||||||
const newUpdatedAtDate = new Date();
|
const newUpdatedAtDate = new Date();
|
||||||
|
@ -331,7 +337,7 @@ class IssueProcessor {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
issueLogger.info(`Closing issue #${issue.number} for being stale`);
|
issueLogger.info(`Closing issue #${issue.number} for being stale`);
|
||||||
this.closedIssues.push(issue);
|
this.closedIssues.push(issue);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -377,7 +383,7 @@ class IssueProcessor {
|
||||||
_getPullRequest(issue) {
|
_getPullRequest(issue) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
try {
|
try {
|
||||||
const pullRequest = yield this.client.pulls.get({
|
const pullRequest = yield this.client.pulls.get({
|
||||||
owner: github_1.context.repo.owner,
|
owner: github_1.context.repo.owner,
|
||||||
|
@ -406,7 +412,7 @@ class IssueProcessor {
|
||||||
}
|
}
|
||||||
const branch = pullRequest.head.ref;
|
const branch = pullRequest.head.ref;
|
||||||
issueLogger.info(`Deleting branch ${branch} from closed issue #${issue.number}`);
|
issueLogger.info(`Deleting branch ${branch} from closed issue #${issue.number}`);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
try {
|
try {
|
||||||
yield this.client.git.deleteRef({
|
yield this.client.git.deleteRef({
|
||||||
owner: github_1.context.repo.owner,
|
owner: github_1.context.repo.owner,
|
||||||
|
@ -425,7 +431,7 @@ class IssueProcessor {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
issueLogger.info(`Removing label "${label}" from issue #${issue.number}`);
|
issueLogger.info(`Removing label "${label}" from issue #${issue.number}`);
|
||||||
this.removedLabelIssues.push(issue);
|
this.removedLabelIssues.push(issue);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
// @todo remove the debug only to be able to test the code below
|
// @todo remove the debug only to be able to test the code below
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
return;
|
return;
|
||||||
|
@ -449,7 +455,7 @@ class IssueProcessor {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
const issueLogger = new issue_logger_1.IssueLogger(issue);
|
||||||
issueLogger.info(`Checking for label on issue #${issue.number}`);
|
issueLogger.info(`Checking for label on issue #${issue.number}`);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
const options = this.client.issues.listEvents.endpoint.merge({
|
const options = this.client.issues.listEvents.endpoint.merge({
|
||||||
owner: github_1.context.repo.owner,
|
owner: github_1.context.repo.owner,
|
||||||
repo: github_1.context.repo.repo,
|
repo: github_1.context.repo.repo,
|
||||||
|
@ -499,7 +505,43 @@ exports.IssueProcessor = IssueProcessor;
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 1699:
|
/***/ 4783:
|
||||||
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.Issue = void 0;
|
||||||
|
const is_labeled_1 = __nccwpck_require__(6792);
|
||||||
|
const is_pull_request_1 = __nccwpck_require__(5400);
|
||||||
|
class Issue {
|
||||||
|
constructor(options, issue) {
|
||||||
|
this._options = options;
|
||||||
|
this.title = issue.title;
|
||||||
|
this.number = issue.number;
|
||||||
|
this.created_at = issue.created_at;
|
||||||
|
this.updated_at = issue.updated_at;
|
||||||
|
this.labels = issue.labels;
|
||||||
|
this.pull_request = issue.pull_request;
|
||||||
|
this.state = issue.state;
|
||||||
|
this.locked = issue.locked;
|
||||||
|
this.milestone = issue.milestone;
|
||||||
|
this.isPullRequest = is_pull_request_1.isPullRequest(this);
|
||||||
|
this.staleLabel = this._getStaleLabel();
|
||||||
|
this.isStale = is_labeled_1.isLabeled(this, this.staleLabel);
|
||||||
|
}
|
||||||
|
_getStaleLabel() {
|
||||||
|
return this.isPullRequest
|
||||||
|
? this._options.stalePrLabel
|
||||||
|
: this._options.staleIssueLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Issue = Issue;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 2984:
|
||||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -551,7 +593,7 @@ exports.IssueLogger = IssueLogger;
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 8236:
|
/***/ 6212:
|
||||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -592,6 +634,58 @@ class Logger {
|
||||||
exports.Logger = Logger;
|
exports.Logger = Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 4601:
|
||||||
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.Milestones = void 0;
|
||||||
|
const lodash_deburr_1 = __importDefault(__nccwpck_require__(1601));
|
||||||
|
const words_to_list_1 = __nccwpck_require__(1883);
|
||||||
|
class Milestones {
|
||||||
|
constructor(options, issue) {
|
||||||
|
this._options = options;
|
||||||
|
this._issue = issue;
|
||||||
|
}
|
||||||
|
static _cleanMilestone(label) {
|
||||||
|
return lodash_deburr_1.default(label.toLowerCase());
|
||||||
|
}
|
||||||
|
shouldExemptMilestones() {
|
||||||
|
const exemptMilestones = this._getExemptMilestones();
|
||||||
|
return exemptMilestones.some((exemptMilestone) => this._hasMilestone(exemptMilestone));
|
||||||
|
}
|
||||||
|
_getExemptMilestones() {
|
||||||
|
return words_to_list_1.wordsToList(this._issue.isPullRequest
|
||||||
|
? this._getExemptPullRequestMilestones()
|
||||||
|
: this._getExemptIssueMilestones());
|
||||||
|
}
|
||||||
|
_getExemptIssueMilestones() {
|
||||||
|
return this._options.exemptIssueMilestones !== ''
|
||||||
|
? this._options.exemptIssueMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
_getExemptPullRequestMilestones() {
|
||||||
|
return this._options.exemptPrMilestones !== ''
|
||||||
|
? this._options.exemptPrMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
_hasMilestone(milestone) {
|
||||||
|
if (!this._issue.milestone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (Milestones._cleanMilestone(milestone) ===
|
||||||
|
Milestones._cleanMilestone(this._issue.milestone.title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Milestones = Milestones;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 9639:
|
/***/ 9639:
|
||||||
|
@ -707,12 +801,12 @@ exports.isLabeled = void 0;
|
||||||
const lodash_deburr_1 = __importDefault(__nccwpck_require__(1601));
|
const lodash_deburr_1 = __importDefault(__nccwpck_require__(1601));
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Check if the label is listed as a label of the issue
|
* Check if the given label is listed as a label of the given issue
|
||||||
*
|
*
|
||||||
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
||||||
* @param {Readonly<string>} label The label to check the presence with
|
* @param {Readonly<string>} label The label to check the presence with
|
||||||
*
|
*
|
||||||
* @return {boolean} Return true when the given label is also in the issue labels
|
* @return {boolean} Return true when the given label is also in the given issue labels
|
||||||
*/
|
*/
|
||||||
function isLabeled(issue, label) {
|
function isLabeled(issue, label) {
|
||||||
return !!issue.labels.find((issueLabel) => {
|
return !!issue.labels.find((issueLabel) => {
|
||||||
|
@ -740,40 +834,6 @@ function isPullRequest(issue) {
|
||||||
exports.isPullRequest = isPullRequest;
|
exports.isPullRequest = isPullRequest;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ 9107:
|
|
||||||
/***/ ((__unused_webpack_module, exports) => {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
||||||
exports.labelsToList = void 0;
|
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
* Transform a string of comma separated labels
|
|
||||||
* to an array of labels
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* labelsToList('label') => ['label']
|
|
||||||
* labelsToList('label,label') => ['label', 'label']
|
|
||||||
* labelsToList('kebab-label') => ['kebab-label']
|
|
||||||
* labelsToList('kebab%20label') => ['kebab%20label']
|
|
||||||
* labelsToList('label with words') => ['label with words']
|
|
||||||
*
|
|
||||||
* @param {Readonly<string>} labels A string of comma separated labels
|
|
||||||
*
|
|
||||||
* @return {string[]} A list of labels
|
|
||||||
*/
|
|
||||||
function labelsToList(labels) {
|
|
||||||
if (!labels.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return labels.split(',').map(l => l.trim());
|
|
||||||
}
|
|
||||||
exports.labelsToList = labelsToList;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 2461:
|
/***/ 2461:
|
||||||
|
@ -789,6 +849,40 @@ function shouldMarkWhenStale(daysBeforeStale) {
|
||||||
exports.shouldMarkWhenStale = shouldMarkWhenStale;
|
exports.shouldMarkWhenStale = shouldMarkWhenStale;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 1883:
|
||||||
|
/***/ ((__unused_webpack_module, exports) => {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.wordsToList = void 0;
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Transform a string of comma separated words
|
||||||
|
* to an array of words
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* wordsToList('label') => ['label']
|
||||||
|
* wordsToList('label,label') => ['label', 'label']
|
||||||
|
* wordsToList('kebab-label') => ['kebab-label']
|
||||||
|
* wordsToList('kebab%20label') => ['kebab%20label']
|
||||||
|
* wordsToList('label with words') => ['label with words']
|
||||||
|
*
|
||||||
|
* @param {Readonly<string>} words A string of comma separated words
|
||||||
|
*
|
||||||
|
* @return {string[]} A list of words
|
||||||
|
*/
|
||||||
|
function wordsToList(words) {
|
||||||
|
if (!words.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return words.split(',').map((word) => word.trim());
|
||||||
|
}
|
||||||
|
exports.wordsToList = wordsToList;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 3109:
|
/***/ 3109:
|
||||||
|
@ -870,15 +964,14 @@ function getAndValidateArgs() {
|
||||||
deleteBranch: core.getInput('delete-branch') === 'true',
|
deleteBranch: core.getInput('delete-branch') === 'true',
|
||||||
startDate: core.getInput('start-date') !== ''
|
startDate: core.getInput('start-date') !== ''
|
||||||
? core.getInput('start-date')
|
? core.getInput('start-date')
|
||||||
: undefined
|
: undefined,
|
||||||
|
exemptMilestones: core.getInput('exempt-milestones'),
|
||||||
|
exemptIssueMilestones: core.getInput('exempt-issue-milestones'),
|
||||||
|
exemptPrMilestones: core.getInput('exempt-pr-milestones')
|
||||||
};
|
};
|
||||||
for (const numberInput of [
|
for (const numberInput of [
|
||||||
'days-before-stale',
|
'days-before-stale',
|
||||||
'days-before-issue-stale',
|
|
||||||
'days-before-pr-stale',
|
|
||||||
'days-before-close',
|
'days-before-close',
|
||||||
'days-before-issue-close',
|
|
||||||
'days-before-pr-close',
|
|
||||||
'operations-per-run'
|
'operations-per-run'
|
||||||
]) {
|
]) {
|
||||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||||
|
|
10
package.json
10
package.json
|
@ -6,10 +6,12 @@
|
||||||
"main": "lib/main.js",
|
"main": "lib/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"format": "prettier --write **/*.ts",
|
"format": "prettier --write --ignore-unknown **/*.{md,json,yml,ts}",
|
||||||
"format-check": "prettier --check **/*.ts",
|
"format-check": "prettier --check --ignore-unknown **/*.{md,json,yml,ts}",
|
||||||
"lint": "prettier --check --ignore-unknown **/*.{md,json,yml,ts} && eslint src/**/*.ts",
|
"lint": "eslint src/**/*.ts",
|
||||||
"lint:fix": "prettier --write --ignore-unknown **/*.{md,json,yml,ts} && eslint src/**/*.ts --fix",
|
"lint:fix": "eslint src/**/*.ts --fix",
|
||||||
|
"lint:all": "npm run format-check && npm run lint",
|
||||||
|
"lint:all:fix": "npm run format && npm run lint:fix",
|
||||||
"pack": "ncc build",
|
"pack": "ncc build",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
||||||
|
|
|
@ -1,30 +1,21 @@
|
||||||
import {context, getOctokit} from '@actions/github';
|
import {context, getOctokit} from '@actions/github';
|
||||||
import {GitHub} from '@actions/github/lib/utils';
|
import {GitHub} from '@actions/github/lib/utils';
|
||||||
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
||||||
|
import {Issue} from './classes/issue';
|
||||||
|
import {IssueLogger} from './classes/loggers/issue-logger';
|
||||||
|
import {Logger} from './classes/loggers/logger';
|
||||||
|
import {Milestones} from './classes/milestones';
|
||||||
import {IssueType} from './enums/issue-type';
|
import {IssueType} from './enums/issue-type';
|
||||||
import {getHumanizedDate} from './functions/dates/get-humanized-date';
|
import {getHumanizedDate} from './functions/dates/get-humanized-date';
|
||||||
import {isDateMoreRecentThan} from './functions/dates/is-date-more-recent-than';
|
import {isDateMoreRecentThan} from './functions/dates/is-date-more-recent-than';
|
||||||
import {isValidDate} from './functions/dates/is-valid-date';
|
import {isValidDate} from './functions/dates/is-valid-date';
|
||||||
import {getIssueType} from './functions/get-issue-type';
|
import {getIssueType} from './functions/get-issue-type';
|
||||||
import {IssueLogger} from './classes/issue-logger';
|
|
||||||
import {Logger} from './classes/logger';
|
|
||||||
import {isLabeled} from './functions/is-labeled';
|
import {isLabeled} from './functions/is-labeled';
|
||||||
import {isPullRequest} from './functions/is-pull-request';
|
import {isPullRequest} from './functions/is-pull-request';
|
||||||
import {labelsToList} from './functions/labels-to-list';
|
|
||||||
import {shouldMarkWhenStale} from './functions/should-mark-when-stale';
|
import {shouldMarkWhenStale} from './functions/should-mark-when-stale';
|
||||||
import {IsoDateString} from './types/iso-date-string';
|
|
||||||
import {IsoOrRfcDateString} from './types/iso-or-rfc-date-string';
|
import {IsoOrRfcDateString} from './types/iso-or-rfc-date-string';
|
||||||
|
import {wordsToList} from './functions/words-to-list';
|
||||||
export interface Issue {
|
import {IIssue} from './interfaces/issue';
|
||||||
title: string;
|
|
||||||
number: number;
|
|
||||||
created_at: IsoDateString;
|
|
||||||
updated_at: IsoDateString;
|
|
||||||
labels: Label[];
|
|
||||||
pull_request: any;
|
|
||||||
state: string;
|
|
||||||
locked: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PullRequest {
|
export interface PullRequest {
|
||||||
number: number;
|
number: number;
|
||||||
|
@ -79,10 +70,11 @@ export interface IssueProcessorOptions {
|
||||||
skipStalePrMessage: boolean;
|
skipStalePrMessage: boolean;
|
||||||
deleteBranch: boolean;
|
deleteBranch: boolean;
|
||||||
startDate: IsoOrRfcDateString | undefined; // Should be ISO 8601 or RFC 2822
|
startDate: IsoOrRfcDateString | undefined; // Should be ISO 8601 or RFC 2822
|
||||||
|
exemptMilestones: string;
|
||||||
|
exemptIssueMilestones: string;
|
||||||
|
exemptPrMilestones: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger: Logger = new Logger();
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Handle processing of issues for staleness/closure.
|
* Handle processing of issues for staleness/closure.
|
||||||
*/
|
*/
|
||||||
|
@ -95,13 +87,14 @@ export class IssueProcessor {
|
||||||
return millisSinceLastUpdated <= daysInMillis;
|
return millisSinceLastUpdated <= daysInMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly _logger: Logger = new Logger();
|
||||||
|
private _operationsLeft = 0;
|
||||||
readonly client: InstanceType<typeof GitHub>;
|
readonly client: InstanceType<typeof GitHub>;
|
||||||
readonly options: IssueProcessorOptions;
|
readonly options: IssueProcessorOptions;
|
||||||
readonly staleIssues: Issue[] = [];
|
readonly staleIssues: Issue[] = [];
|
||||||
readonly closedIssues: Issue[] = [];
|
readonly closedIssues: Issue[] = [];
|
||||||
readonly deletedBranchIssues: Issue[] = [];
|
readonly deletedBranchIssues: Issue[] = [];
|
||||||
readonly removedLabelIssues: Issue[] = [];
|
readonly removedLabelIssues: Issue[] = [];
|
||||||
private operationsLeft = 0;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: IssueProcessorOptions,
|
options: IssueProcessorOptions,
|
||||||
|
@ -117,7 +110,7 @@ export class IssueProcessor {
|
||||||
) => Promise<string | undefined>
|
) => Promise<string | undefined>
|
||||||
) {
|
) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.operationsLeft = options.operationsPerRun;
|
this._operationsLeft = options.operationsPerRun;
|
||||||
this.client = getOctokit(options.repoToken);
|
this.client = getOctokit(options.repoToken);
|
||||||
|
|
||||||
if (getActor) {
|
if (getActor) {
|
||||||
|
@ -137,7 +130,7 @@ export class IssueProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
logger.warning(
|
this._logger.warning(
|
||||||
'Executing in debug mode. Debug output will be written but no issues will be processed.'
|
'Executing in debug mode. Debug output will be written but no issues will be processed.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -146,49 +139,45 @@ export class IssueProcessor {
|
||||||
async processIssues(page = 1): Promise<number> {
|
async processIssues(page = 1): Promise<number> {
|
||||||
// get the next batch of issues
|
// get the next batch of issues
|
||||||
const issues: Issue[] = await this._getIssues(page);
|
const issues: Issue[] = await this._getIssues(page);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
const actor: string = await this._getActor();
|
const actor: string = await this._getActor();
|
||||||
|
|
||||||
if (issues.length <= 0) {
|
if (issues.length <= 0) {
|
||||||
logger.info('---');
|
this._logger.info('---');
|
||||||
logger.info('No more issues found to process. Exiting.');
|
this._logger.info('No more issues found to process. Exiting.');
|
||||||
return this.operationsLeft;
|
return this._operationsLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const issue of issues.values()) {
|
for (const issue of issues.values()) {
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
const isPr = isPullRequest(issue);
|
|
||||||
|
|
||||||
issueLogger.info(
|
issueLogger.info(
|
||||||
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
|
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${issue.isPullRequest})`
|
||||||
);
|
);
|
||||||
|
|
||||||
// calculate string based messages for this issue
|
// calculate string based messages for this issue
|
||||||
const staleMessage: string = isPr
|
const staleMessage: string = issue.isPullRequest
|
||||||
? this.options.stalePrMessage
|
? this.options.stalePrMessage
|
||||||
: this.options.staleIssueMessage;
|
: this.options.staleIssueMessage;
|
||||||
const closeMessage: string = isPr
|
const closeMessage: string = issue.isPullRequest
|
||||||
? this.options.closePrMessage
|
? this.options.closePrMessage
|
||||||
: this.options.closeIssueMessage;
|
: this.options.closeIssueMessage;
|
||||||
const staleLabel: string = isPr
|
const staleLabel: string = issue.isPullRequest
|
||||||
? this.options.stalePrLabel
|
? this.options.stalePrLabel
|
||||||
: this.options.staleIssueLabel;
|
: this.options.staleIssueLabel;
|
||||||
const closeLabel: string = isPr
|
const closeLabel: string = issue.isPullRequest
|
||||||
? this.options.closePrLabel
|
? this.options.closePrLabel
|
||||||
: this.options.closeIssueLabel;
|
: this.options.closeIssueLabel;
|
||||||
const exemptLabels: string[] = labelsToList(
|
const skipMessage = issue.isPullRequest
|
||||||
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
|
|
||||||
);
|
|
||||||
const skipMessage = isPr
|
|
||||||
? this.options.skipStalePrMessage
|
? this.options.skipStalePrMessage
|
||||||
: this.options.skipStaleIssueMessage;
|
: this.options.skipStaleIssueMessage;
|
||||||
const issueType: IssueType = getIssueType(isPr);
|
const issueType: IssueType = getIssueType(issue.isPullRequest);
|
||||||
const daysBeforeStale: number = isPr
|
const daysBeforeStale: number = issue.isPullRequest
|
||||||
? this._getDaysBeforePrStale()
|
? this._getDaysBeforePrStale()
|
||||||
: this._getDaysBeforeIssueStale();
|
: this._getDaysBeforeIssueStale();
|
||||||
|
|
||||||
if (isPr) {
|
if (issue.isPullRequest) {
|
||||||
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
|
issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
|
||||||
} else {
|
} else {
|
||||||
issueLogger.info(`Days before issue stale: ${daysBeforeStale}`);
|
issueLogger.info(`Days before issue stale: ${daysBeforeStale}`);
|
||||||
|
@ -244,21 +233,24 @@ export class IssueProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does this issue have a stale label?
|
if (issue.isStale) {
|
||||||
let isStale: boolean = isLabeled(issue, staleLabel);
|
|
||||||
|
|
||||||
if (isStale) {
|
|
||||||
issueLogger.info(`This issue has a stale label`);
|
issueLogger.info(`This issue has a stale label`);
|
||||||
} else {
|
} else {
|
||||||
issueLogger.info(`This issue hasn't a stale label`);
|
issueLogger.info(`This issue hasn't a stale label`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exemptLabels: string[] = wordsToList(
|
||||||
|
issue.isPullRequest
|
||||||
|
? this.options.exemptPrLabels
|
||||||
|
: this.options.exemptIssueLabels
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
|
exemptLabels.some((exemptLabel: Readonly<string>): boolean =>
|
||||||
isLabeled(issue, exemptLabel)
|
isLabeled(issue, exemptLabel)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (isStale) {
|
if (issue.isStale) {
|
||||||
issueLogger.info(`An exempt label was added after the stale label.`);
|
issueLogger.info(`An exempt label was added after the stale label.`);
|
||||||
await this._removeStaleLabel(issue, staleLabel);
|
await this._removeStaleLabel(issue, staleLabel);
|
||||||
}
|
}
|
||||||
|
@ -269,6 +261,15 @@ export class IssueProcessor {
|
||||||
continue; // don't process exempt issues
|
continue; // don't process exempt issues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const milestones: Milestones = new Milestones(this.options, issue);
|
||||||
|
|
||||||
|
if (milestones.shouldExemptMilestones()) {
|
||||||
|
issueLogger.info(
|
||||||
|
`Skipping ${issueType} because it has an exempt milestone`
|
||||||
|
);
|
||||||
|
continue; // don't process exempt milestones
|
||||||
|
}
|
||||||
|
|
||||||
// should this issue be marked stale?
|
// should this issue be marked stale?
|
||||||
const shouldBeStale = !IssueProcessor._updatedSince(
|
const shouldBeStale = !IssueProcessor._updatedSince(
|
||||||
issue.updated_at,
|
issue.updated_at,
|
||||||
|
@ -276,16 +277,16 @@ export class IssueProcessor {
|
||||||
);
|
);
|
||||||
|
|
||||||
// determine if this issue needs to be marked stale first
|
// determine if this issue needs to be marked stale first
|
||||||
if (!isStale && shouldBeStale && shouldMarkAsStale) {
|
if (!issue.isStale && shouldBeStale && shouldMarkAsStale) {
|
||||||
issueLogger.info(
|
issueLogger.info(
|
||||||
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
||||||
);
|
);
|
||||||
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
isStale = true; // this issue is now considered stale
|
issue.isStale = true; // this issue is now considered stale
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the issue if it was marked stale
|
// process the issue if it was marked stale
|
||||||
if (isStale) {
|
if (issue.isStale) {
|
||||||
issueLogger.info(`Found a stale ${issueType}`);
|
issueLogger.info(`Found a stale ${issueType}`);
|
||||||
await this._processStaleIssue(
|
await this._processStaleIssue(
|
||||||
issue,
|
issue,
|
||||||
|
@ -298,8 +299,10 @@ export class IssueProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.operationsLeft <= 0) {
|
if (this._operationsLeft <= 0) {
|
||||||
logger.warning('Reached max number of operations to process. Exiting.');
|
this._logger.warning(
|
||||||
|
'Reached max number of operations to process. Exiting.'
|
||||||
|
);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +430,7 @@ export class IssueProcessor {
|
||||||
});
|
});
|
||||||
return comments.data;
|
return comments.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`List issue comments error: ${error.message}`);
|
this._logger.error(`List issue comments error: ${error.message}`);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,7 +447,7 @@ export class IssueProcessor {
|
||||||
return actor.data.login;
|
return actor.data.login;
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab issues from github in baches of 100
|
// grab issues from github in batches of 100
|
||||||
private async _getIssues(page: number): Promise<Issue[]> {
|
private async _getIssues(page: number): Promise<Issue[]> {
|
||||||
// generate type for response
|
// generate type for response
|
||||||
const endpoint = this.client.issues.listForRepo;
|
const endpoint = this.client.issues.listForRepo;
|
||||||
|
@ -462,9 +465,12 @@ export class IssueProcessor {
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return issueResult.data;
|
|
||||||
|
return issueResult.data.map(
|
||||||
|
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Get issues for repo error: ${error.message}`);
|
this._logger.error(`Get issues for repo error: ${error.message}`);
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -482,7 +488,7 @@ export class IssueProcessor {
|
||||||
|
|
||||||
this.staleIssues.push(issue);
|
this.staleIssues.push(issue);
|
||||||
|
|
||||||
this.operationsLeft -= 2;
|
this._operationsLeft -= 2;
|
||||||
|
|
||||||
// if the issue is being marked stale, the updated date should be changed to right now
|
// if the issue is being marked stale, the updated date should be changed to right now
|
||||||
// so that close calculations work correctly
|
// so that close calculations work correctly
|
||||||
|
@ -530,7 +536,7 @@ export class IssueProcessor {
|
||||||
|
|
||||||
this.closedIssues.push(issue);
|
this.closedIssues.push(issue);
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
return;
|
return;
|
||||||
|
@ -578,7 +584,7 @@ export class IssueProcessor {
|
||||||
issue: Issue
|
issue: Issue
|
||||||
): Promise<PullRequest | undefined> {
|
): Promise<PullRequest | undefined> {
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pullRequest = await this.client.pulls.get({
|
const pullRequest = await this.client.pulls.get({
|
||||||
|
@ -621,7 +627,7 @@ export class IssueProcessor {
|
||||||
`Deleting branch ${branch} from closed issue #${issue.number}`
|
`Deleting branch ${branch} from closed issue #${issue.number}`
|
||||||
);
|
);
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.git.deleteRef({
|
await this.client.git.deleteRef({
|
||||||
|
@ -644,7 +650,7 @@ export class IssueProcessor {
|
||||||
|
|
||||||
this.removedLabelIssues.push(issue);
|
this.removedLabelIssues.push(issue);
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
// @todo remove the debug only to be able to test the code below
|
// @todo remove the debug only to be able to test the code below
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
|
@ -673,7 +679,7 @@ export class IssueProcessor {
|
||||||
|
|
||||||
issueLogger.info(`Checking for label on issue #${issue.number}`);
|
issueLogger.info(`Checking for label on issue #${issue.number}`);
|
||||||
|
|
||||||
this.operationsLeft -= 1;
|
this._operationsLeft -= 1;
|
||||||
|
|
||||||
const options = this.client.issues.listEvents.endpoint.merge({
|
const options = this.client.issues.listEvents.endpoint.merge({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -722,7 +728,7 @@ export class IssueProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _removeStaleLabel(
|
private async _removeStaleLabel(
|
||||||
issue: Readonly<Issue>,
|
issue: Issue,
|
||||||
staleLabel: Readonly<string>
|
staleLabel: Readonly<string>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const issueLogger: IssueLogger = new IssueLogger(issue);
|
const issueLogger: IssueLogger = new IssueLogger(issue);
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IMilestone} from '../interfaces/milestone';
|
||||||
|
import {IssueProcessorOptions, Label} from '../IssueProcessor';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
|
||||||
|
describe('Issue', (): void => {
|
||||||
|
let issue: Issue;
|
||||||
|
let optionsInterface: IssueProcessorOptions;
|
||||||
|
let issueInterface: IIssue;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface = {
|
||||||
|
ascending: false,
|
||||||
|
closeIssueLabel: '',
|
||||||
|
closeIssueMessage: '',
|
||||||
|
closePrLabel: '',
|
||||||
|
closePrMessage: '',
|
||||||
|
daysBeforeClose: 0,
|
||||||
|
daysBeforeIssueClose: 0,
|
||||||
|
daysBeforeIssueStale: 0,
|
||||||
|
daysBeforePrClose: 0,
|
||||||
|
daysBeforePrStale: 0,
|
||||||
|
daysBeforeStale: 0,
|
||||||
|
debugOnly: false,
|
||||||
|
deleteBranch: false,
|
||||||
|
exemptIssueLabels: '',
|
||||||
|
exemptIssueMilestones: '',
|
||||||
|
exemptMilestones: '',
|
||||||
|
exemptPrLabels: '',
|
||||||
|
exemptPrMilestones: '',
|
||||||
|
onlyLabels: '',
|
||||||
|
operationsPerRun: 0,
|
||||||
|
removeStaleWhenUpdated: false,
|
||||||
|
repoToken: '',
|
||||||
|
skipStaleIssueMessage: false,
|
||||||
|
skipStalePrMessage: false,
|
||||||
|
staleIssueMessage: '',
|
||||||
|
stalePrMessage: '',
|
||||||
|
startDate: undefined,
|
||||||
|
stalePrLabel: 'dummy-stale-pr-label',
|
||||||
|
staleIssueLabel: 'dummy-stale-issue-label'
|
||||||
|
};
|
||||||
|
issueInterface = {
|
||||||
|
title: 'dummy-title',
|
||||||
|
number: 8,
|
||||||
|
created_at: 'dummy-created-at',
|
||||||
|
updated_at: 'dummy-updated-at',
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
name: 'dummy-name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pull_request: {},
|
||||||
|
state: 'dummy-state',
|
||||||
|
locked: false,
|
||||||
|
milestone: {
|
||||||
|
title: 'dummy-milestone'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor()', (): void => {
|
||||||
|
it('should set the title with the given issue title', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.title).toStrictEqual('dummy-title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the number with the given issue number', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.number).toStrictEqual(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the created_at with the given issue created_at', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.created_at).toStrictEqual('dummy-created-at');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the updated_at with the given issue updated_at', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.updated_at).toStrictEqual('dummy-updated-at');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the labels with the given issue labels', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.labels).toStrictEqual([
|
||||||
|
{
|
||||||
|
name: 'dummy-name'
|
||||||
|
} as Label
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the pull_request with the given issue pull_request', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.pull_request).toStrictEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the state with the given issue state', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.state).toStrictEqual('dummy-state');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the locked with the given issue locked', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.locked).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the milestone with the given issue milestone', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.milestone).toStrictEqual({
|
||||||
|
title: 'dummy-milestone'
|
||||||
|
} as IMilestone);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue pull_request is not set', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isPullRequest to false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isPullRequest).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue pull_request is set', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isPullRequest to true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isPullRequest).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is not a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the staleLabel with the given option staleIssueLabel', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.staleLabel).toStrictEqual('dummy-stale-issue-label');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the staleLabel with the given option stalePrLabel', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.staleLabel).toStrictEqual('dummy-stale-pr-label');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not contains the stale label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issueInterface.labels = [];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isStale to false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isStale).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue contains the stale label', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
issueInterface.labels = [
|
||||||
|
{
|
||||||
|
name: 'dummy-stale-issue-label'
|
||||||
|
} as Label
|
||||||
|
];
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the isStale to true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
expect(issue.isStale).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {isLabeled} from '../functions/is-labeled';
|
||||||
|
import {isPullRequest} from '../functions/is-pull-request';
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IMilestone} from '../interfaces/milestone';
|
||||||
|
import {IssueProcessorOptions, Label} from '../IssueProcessor';
|
||||||
|
import {IsoDateString} from '../types/iso-date-string';
|
||||||
|
|
||||||
|
export class Issue implements IIssue {
|
||||||
|
private readonly _options: IssueProcessorOptions;
|
||||||
|
readonly title: string;
|
||||||
|
readonly number: number;
|
||||||
|
created_at: IsoDateString;
|
||||||
|
updated_at: IsoDateString;
|
||||||
|
readonly labels: Label[];
|
||||||
|
readonly pull_request: Object | null | undefined;
|
||||||
|
readonly state: string;
|
||||||
|
readonly locked: boolean;
|
||||||
|
readonly milestone: IMilestone | undefined;
|
||||||
|
readonly isPullRequest: boolean;
|
||||||
|
readonly staleLabel: string;
|
||||||
|
isStale: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: Readonly<IssueProcessorOptions>,
|
||||||
|
issue: Readonly<IIssue>
|
||||||
|
) {
|
||||||
|
this._options = options;
|
||||||
|
this.title = issue.title;
|
||||||
|
this.number = issue.number;
|
||||||
|
this.created_at = issue.created_at;
|
||||||
|
this.updated_at = issue.updated_at;
|
||||||
|
this.labels = issue.labels;
|
||||||
|
this.pull_request = issue.pull_request;
|
||||||
|
this.state = issue.state;
|
||||||
|
this.locked = issue.locked;
|
||||||
|
this.milestone = issue.milestone;
|
||||||
|
|
||||||
|
this.isPullRequest = isPullRequest(this);
|
||||||
|
this.staleLabel = this._getStaleLabel();
|
||||||
|
this.isStale = isLabeled(this, this.staleLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getStaleLabel(): string {
|
||||||
|
return this.isPullRequest
|
||||||
|
? this._options.stalePrLabel
|
||||||
|
: this._options.staleIssueLabel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {Issue} from '../IssueProcessor';
|
import {Issue} from '../issue';
|
||||||
import {IssueLogger} from './issue-logger';
|
import {IssueLogger} from './issue-logger';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import {Issue} from '../IssueProcessor';
|
import {Issue} from '../issue';
|
||||||
import {Logger} from './logger';
|
import {Logger} from './logger';
|
||||||
|
|
||||||
export class IssueLogger implements Logger {
|
export class IssueLogger implements Logger {
|
||||||
private readonly _issue: Issue;
|
private readonly _issue: Issue;
|
||||||
|
|
||||||
constructor(issue: Readonly<Issue>) {
|
constructor(issue: Issue) {
|
||||||
this._issue = issue;
|
this._issue = issue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,596 @@
|
||||||
|
import {IIssue} from '../interfaces/issue';
|
||||||
|
import {IssueProcessorOptions} from '../IssueProcessor';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
import {Milestones} from './milestones';
|
||||||
|
|
||||||
|
describe('Milestones', (): void => {
|
||||||
|
let milestones: Milestones;
|
||||||
|
let optionsInterface: IssueProcessorOptions;
|
||||||
|
let issue: Issue;
|
||||||
|
let issueInterface: IIssue;
|
||||||
|
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface = {
|
||||||
|
ascending: false,
|
||||||
|
closeIssueLabel: '',
|
||||||
|
closeIssueMessage: '',
|
||||||
|
closePrLabel: '',
|
||||||
|
closePrMessage: '',
|
||||||
|
daysBeforeClose: 0,
|
||||||
|
daysBeforeIssueClose: 0,
|
||||||
|
daysBeforeIssueStale: 0,
|
||||||
|
daysBeforePrClose: 0,
|
||||||
|
daysBeforePrStale: 0,
|
||||||
|
daysBeforeStale: 0,
|
||||||
|
debugOnly: false,
|
||||||
|
deleteBranch: false,
|
||||||
|
exemptIssueLabels: '',
|
||||||
|
exemptPrLabels: '',
|
||||||
|
onlyLabels: '',
|
||||||
|
operationsPerRun: 0,
|
||||||
|
removeStaleWhenUpdated: false,
|
||||||
|
repoToken: '',
|
||||||
|
skipStaleIssueMessage: false,
|
||||||
|
skipStalePrMessage: false,
|
||||||
|
staleIssueLabel: '',
|
||||||
|
staleIssueMessage: '',
|
||||||
|
stalePrLabel: '',
|
||||||
|
stalePrMessage: '',
|
||||||
|
startDate: undefined,
|
||||||
|
exemptIssueMilestones: '',
|
||||||
|
exemptPrMilestones: '',
|
||||||
|
exemptMilestones: ''
|
||||||
|
};
|
||||||
|
issueInterface = {
|
||||||
|
created_at: '',
|
||||||
|
locked: false,
|
||||||
|
milestone: undefined,
|
||||||
|
number: 0,
|
||||||
|
pull_request: undefined,
|
||||||
|
state: '',
|
||||||
|
title: '',
|
||||||
|
updated_at: '',
|
||||||
|
labels: []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shouldExemptMilestones()', (): void => {
|
||||||
|
describe('when the given issue is not a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones =
|
||||||
|
'dummy-exempt-issue-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-issue-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt an issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptIssueMilestones =
|
||||||
|
'dummy-exempt-issue-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt issue milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-issue-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue is a pull request', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.pull_request = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-pr-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptMilestones = 'dummy-exempt-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given options are configured to exempt a pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does not have a milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-pr-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
issueInterface.milestone = {
|
||||||
|
title: 'dummy-exempt-milestone'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
issue = new Issue(optionsInterface, issueInterface);
|
||||||
|
milestones = new Milestones(optionsInterface, issue);
|
||||||
|
|
||||||
|
const result = milestones.shouldExemptMilestones();
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import deburr from 'lodash.deburr';
|
||||||
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
|
import {IssueProcessorOptions} from '../IssueProcessor';
|
||||||
|
import {Issue} from './issue';
|
||||||
|
|
||||||
|
type CleanMilestone = string;
|
||||||
|
|
||||||
|
export class Milestones {
|
||||||
|
private static _cleanMilestone(label: Readonly<string>): CleanMilestone {
|
||||||
|
return deburr(label.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _options: IssueProcessorOptions;
|
||||||
|
private readonly _issue: Issue;
|
||||||
|
|
||||||
|
constructor(options: Readonly<IssueProcessorOptions>, issue: Issue) {
|
||||||
|
this._options = options;
|
||||||
|
this._issue = issue;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExemptMilestones(): boolean {
|
||||||
|
const exemptMilestones: string[] = this._getExemptMilestones();
|
||||||
|
|
||||||
|
return exemptMilestones.some((exemptMilestone: Readonly<string>): boolean =>
|
||||||
|
this._hasMilestone(exemptMilestone)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptMilestones(): string[] {
|
||||||
|
return wordsToList(
|
||||||
|
this._issue.isPullRequest
|
||||||
|
? this._getExemptPullRequestMilestones()
|
||||||
|
: this._getExemptIssueMilestones()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptIssueMilestones(): string {
|
||||||
|
return this._options.exemptIssueMilestones !== ''
|
||||||
|
? this._options.exemptIssueMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExemptPullRequestMilestones(): string {
|
||||||
|
return this._options.exemptPrMilestones !== ''
|
||||||
|
? this._options.exemptPrMilestones
|
||||||
|
: this._options.exemptMilestones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hasMilestone(milestone: Readonly<string>): boolean {
|
||||||
|
if (!this._issue.milestone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
Milestones._cleanMilestone(milestone) ===
|
||||||
|
Milestones._cleanMilestone(this._issue.milestone.title)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {Issue} from '../IssueProcessor';
|
import {Issue} from '../classes/issue';
|
||||||
import {isLabeled} from './is-labeled';
|
import {isLabeled} from './is-labeled';
|
||||||
|
|
||||||
describe('isLabeled()', (): void => {
|
describe('isLabeled()', (): void => {
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import deburr from 'lodash.deburr';
|
import deburr from 'lodash.deburr';
|
||||||
import {Issue, Label} from '../IssueProcessor';
|
import {Issue} from '../classes/issue';
|
||||||
|
import {Label} from '../IssueProcessor';
|
||||||
import {CleanLabel} from '../types/clean-label';
|
import {CleanLabel} from '../types/clean-label';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Check if the label is listed as a label of the issue
|
* Check if the given label is listed as a label of the given issue
|
||||||
*
|
*
|
||||||
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
|
||||||
* @param {Readonly<string>} label The label to check the presence with
|
* @param {Readonly<string>} label The label to check the presence with
|
||||||
*
|
*
|
||||||
* @return {boolean} Return true when the given label is also in the issue labels
|
* @return {boolean} Return true when the given label is also in the given issue labels
|
||||||
*/
|
*/
|
||||||
export function isLabeled(
|
export function isLabeled(
|
||||||
issue: Readonly<Issue>,
|
issue: Readonly<Issue>,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Issue} from '../IssueProcessor';
|
import {Issue} from '../classes/issue';
|
||||||
import {isPullRequest} from './is-pull-request';
|
import {isPullRequest} from './is-pull-request';
|
||||||
|
|
||||||
describe('isPullRequest()', (): void => {
|
describe('isPullRequest()', (): void => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Issue} from '../IssueProcessor';
|
import {Issue} from '../classes/issue';
|
||||||
|
|
||||||
export function isPullRequest(issue: Readonly<Issue>): boolean {
|
export function isPullRequest(issue: Readonly<Issue>): boolean {
|
||||||
return !!issue.pull_request;
|
return !!issue.pull_request;
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
import {labelsToList} from './labels-to-list';
|
|
||||||
|
|
||||||
describe('labelsToList()', (): void => {
|
|
||||||
let labels: string;
|
|
||||||
|
|
||||||
describe('when the given labels is empty', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an empty list of labels', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is a simple label', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'label';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of one label', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(['label']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is a label with extra spaces before and after', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = ' label ';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of one label and remove all spaces before and after', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(['label']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is a kebab case label', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'kebab-case-label';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of one label', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(['kebab-case-label']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is two kebab case labels separated with a comma', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'kebab-case-label-1,kebab-case-label-2';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of two labels', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual([
|
|
||||||
'kebab-case-label-1',
|
|
||||||
'kebab-case-label-2'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is a multiple word label', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'label like a sentence';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of one label', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(['label like a sentence']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is two multiple word labels separated with a comma', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'label like a sentence, another label like a sentence';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of two labels', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual([
|
|
||||||
'label like a sentence',
|
|
||||||
'another label like a sentence'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is a multiple word label with %20 spaces', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels = 'label%20like%20a%20sentence';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of one label', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(['label%20like%20a%20sentence']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given labels is two multiple word labels with %20 spaces separated with a comma', (): void => {
|
|
||||||
beforeEach((): void => {
|
|
||||||
labels =
|
|
||||||
'label%20like%20a%20sentence,another%20label%20like%20a%20sentence';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a list of two labels', (): void => {
|
|
||||||
expect.assertions(1);
|
|
||||||
|
|
||||||
const result = labelsToList(labels);
|
|
||||||
|
|
||||||
expect(result).toStrictEqual([
|
|
||||||
'label%20like%20a%20sentence',
|
|
||||||
'another%20label%20like%20a%20sentence'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* @description
|
|
||||||
* Transform a string of comma separated labels
|
|
||||||
* to an array of labels
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* labelsToList('label') => ['label']
|
|
||||||
* labelsToList('label,label') => ['label', 'label']
|
|
||||||
* labelsToList('kebab-label') => ['kebab-label']
|
|
||||||
* labelsToList('kebab%20label') => ['kebab%20label']
|
|
||||||
* labelsToList('label with words') => ['label with words']
|
|
||||||
*
|
|
||||||
* @param {Readonly<string>} labels A string of comma separated labels
|
|
||||||
*
|
|
||||||
* @return {string[]} A list of labels
|
|
||||||
*/
|
|
||||||
export function labelsToList(labels: Readonly<string>): string[] {
|
|
||||||
if (!labels.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels.split(',').map(l => l.trim());
|
|
||||||
}
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import {wordsToList} from './words-to-list';
|
||||||
|
|
||||||
|
describe('wordsToList()', (): void => {
|
||||||
|
let words: string;
|
||||||
|
|
||||||
|
describe('when the given words is empty', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty list of words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a simple word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a word with extra spaces before and after', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = ' word ';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word and remove all spaces before and after', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a kebab case word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'kebab-case-word';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['kebab-case-word']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two kebab case words separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'kebab-case-word-1,kebab-case-word-2';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['kebab-case-word-1', 'kebab-case-word-2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a multiple word word', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word like a sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word like a sentence']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two multiple word words separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word like a sentence, another word like a sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([
|
||||||
|
'word like a sentence',
|
||||||
|
'another word like a sentence'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is a multiple word word with %20 spaces', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word%20like%20a%20sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of one word', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual(['word%20like%20a%20sentence']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the given words is two multiple word words with %20 spaces separated with a comma', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
words = 'word%20like%20a%20sentence,another%20word%20like%20a%20sentence';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of two words', (): void => {
|
||||||
|
expect.assertions(1);
|
||||||
|
|
||||||
|
const result = wordsToList(words);
|
||||||
|
|
||||||
|
expect(result).toStrictEqual([
|
||||||
|
'word%20like%20a%20sentence',
|
||||||
|
'another%20word%20like%20a%20sentence'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Transform a string of comma separated words
|
||||||
|
* to an array of words
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* wordsToList('label') => ['label']
|
||||||
|
* wordsToList('label,label') => ['label', 'label']
|
||||||
|
* wordsToList('kebab-label') => ['kebab-label']
|
||||||
|
* wordsToList('kebab%20label') => ['kebab%20label']
|
||||||
|
* wordsToList('label with words') => ['label with words']
|
||||||
|
*
|
||||||
|
* @param {Readonly<string>} words A string of comma separated words
|
||||||
|
*
|
||||||
|
* @return {string[]} A list of words
|
||||||
|
*/
|
||||||
|
export function wordsToList(words: Readonly<string>): string[] {
|
||||||
|
if (!words.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return words.split(',').map((word: Readonly<string>): string => word.trim());
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {Label} from '../IssueProcessor';
|
||||||
|
import {IsoDateString} from '../types/iso-date-string';
|
||||||
|
import {IMilestone} from './milestone';
|
||||||
|
|
||||||
|
export interface IIssue {
|
||||||
|
title: string;
|
||||||
|
number: number;
|
||||||
|
created_at: IsoDateString;
|
||||||
|
updated_at: IsoDateString;
|
||||||
|
labels: Label[];
|
||||||
|
pull_request: Object | null | undefined;
|
||||||
|
state: string;
|
||||||
|
locked: boolean;
|
||||||
|
milestone: IMilestone | undefined;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface IMilestone {
|
||||||
|
title: string;
|
||||||
|
}
|
|
@ -52,16 +52,15 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
||||||
startDate:
|
startDate:
|
||||||
core.getInput('start-date') !== ''
|
core.getInput('start-date') !== ''
|
||||||
? core.getInput('start-date')
|
? core.getInput('start-date')
|
||||||
: undefined
|
: undefined,
|
||||||
|
exemptMilestones: core.getInput('exempt-milestones'),
|
||||||
|
exemptIssueMilestones: core.getInput('exempt-issue-milestones'),
|
||||||
|
exemptPrMilestones: core.getInput('exempt-pr-milestones')
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const numberInput of [
|
for (const numberInput of [
|
||||||
'days-before-stale',
|
'days-before-stale',
|
||||||
'days-before-issue-stale',
|
|
||||||
'days-before-pr-stale',
|
|
||||||
'days-before-close',
|
'days-before-close',
|
||||||
'days-before-issue-close',
|
|
||||||
'days-before-pr-close',
|
|
||||||
'operations-per-run'
|
'operations-per-run'
|
||||||
]) {
|
]) {
|
||||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||||
|
|
Loading…
Reference in New Issue