Compare commits

..

21 Commits

Author SHA1 Message Date
Geoffrey Testelin
98ed4cb500 chore(logs): final overhaul of the logs (#433)
* feat(logs): add a new log when an issue consumed at least one operation (#386)

* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs(operations-per-run): improve the doc for this option

* feat(logs): add a new log when an issue consumed at least one operation

the log will be visible as the last row of the processing of the given issue
closes #348

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* Typo in how to perform check for specific labels (#357)

Not tests but feels like a typo.

Or if we keep the title it `exempt` feels more approriate:

          exempt-issue-labels: 'roadmap'
          exempt-pr-labels: 'roadmap'

* feat(any-of-labels): add 2 new options to customize for issues/PRs (#380)

* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(any-of-labels): add 2 new options to customize for issues/PRs

closes #371
change this option and only-labels to have tree-logs

* chore(index): update it

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(logs): enhance the logs for assignees and milestones (#382)

* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(logs): enhance the logs for assignees and milestones

closes #381

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* style(typo): fix typo plural issue

* style(naming): rename two methods

* chore(error): remove a potential useless throw of error

* style(naming): rename one method

* refactor(issue): change the way to count the operations

* refactor(operations): create a method to reduce code duplication

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
Co-authored-by: Romain Rigaux <romain.rigaux@gmail.com>

* chore(logs): final overhaul of the logs

display more values in cyan
display all the options the same way (with links pointing to the readme so that with anchors we could find them easily later
make them a little more humanized
add more logs especially for the stale conditions

* style(logs): fix typos

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
Co-authored-by: Romain Rigaux <romain.rigaux@gmail.com>
2021-05-12 10:00:14 -04:00
Luke Tomlinson
523075947f Remove unused @octokit/reset and generate dist (#440) 2021-05-12 09:33:22 -04:00
dependabot[bot]
5a36bdc457 build(deps-dev): bump @types/semver from 7.3.4 to 7.3.5 (#444)
Bumps [@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver) from 7.3.4 to 7.3.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 09:35:03 -04:00
dependabot[bot]
4a6aba205d build(deps): bump semver from 7.3.4 to 7.3.5 (#442)
Bumps [semver](https://github.com/npm/node-semver) from 7.3.4 to 7.3.5.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.4...v7.3.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 09:34:45 -04:00
dependabot[bot]
2a6d5d6990 build(deps-dev): bump typescript from 4.2.3 to 4.2.4 (#438)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.2.3 to 4.2.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.2.3...v4.2.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 09:17:03 -04:00
dependabot[bot]
ebd757c01b build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 (#437)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 09:16:30 -04:00
dependabot[bot]
1ebe9f7454 build(deps): bump lodash from 4.17.19 to 4.17.21 (#436)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 09:15:41 -04:00
dependabot[bot]
a61d498059 build(deps-dev): bump @types/node from 14.14.31 to 15.0.2 (#431)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.31 to 15.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:47:44 -04:00
HonkingGoose
0a21a5ec79 chore: create bug/feature/PR template, configure template chooser (#397)
* chore: create bug/feature/PR template, configure template chooser

* use american grammar

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* use checklist

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* use bold text for context as well

* remove links from template chooser

* remove comment redirecting to hackerone

* rename bug -> problem, rewrite content

* remove steps to reproduce

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* problem -> issue

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* provide your -> your

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* improve help message to link issues

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* rename files, improve content

* add bug label to other_issue_report as well

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>

* remove nonsensical comment

* use h2 for headings

* give predefined config template

Co-authored-by: Geoffrey Testelin <geoffrey.testelin@gmail.com>
2021-05-07 16:44:19 -04:00
dependabot[bot]
e7199f9c84 build(deps-dev): bump @typescript-eslint/parser from 4.16.1 to 4.22.1 (#430)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.16.1 to 4.22.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.22.1/packages/parser)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:40:29 -04:00
dependabot[bot]
e307118008 build(deps-dev): bump eslint-plugin-jest from 24.1.5 to 24.3.6 (#426)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 24.1.5 to 24.3.6.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v24.1.5...v24.3.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:39:30 -04:00
dependabot[bot]
b53e7be65a build(deps): bump y18n from 4.0.0 to 4.0.1 (#410)
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:38:39 -04:00
Shawn Smith
ff764c226b Typo fixes (#434) 2021-05-07 16:30:29 -04:00
Geoffrey Testelin
c11507e9b7 feat(logs): add a new log when an issue consumed at least one operation (#386)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs(operations-per-run): improve the doc for this option

* feat(logs): add a new log when an issue consumed at least one operation

the log will be visible as the last row of the processing of the given issue
closes #348

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* chore(readme): improve the operations per run

Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>

* Typo in how to perform check for specific labels (#357)

Not tests but feels like a typo.

Or if we keep the title it `exempt` feels more approriate:

          exempt-issue-labels: 'roadmap'
          exempt-pr-labels: 'roadmap'

* feat(any-of-labels): add 2 new options to customize for issues/PRs (#380)

* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(any-of-labels): add 2 new options to customize for issues/PRs

closes #371
change this option and only-labels to have tree-logs

* chore(index): update it

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(logs): enhance the logs for assignees and milestones (#382)

* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(logs): enhance the logs for assignees and milestones

closes #381

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* style(typo): fix typo plural issue

* style(naming): rename two methods

* chore(error): remove a potential useless throw of error

* style(naming): rename one method

* refactor(issue): change the way to count the operations

* refactor(operations): create a method to reduce code duplication

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
Co-authored-by: Romain Rigaux <romain.rigaux@gmail.com>
2021-05-03 09:20:07 -04:00
Geoffrey Testelin
440fb174b5 feat(remove-stale-when-updated): add 2 options for issues and prs (#383)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(remove-stale-when-updated): add 2 options for issues and prs

closes #377
also I closed the stale process once the stale label is removed since the following process is regarding the closing and it should simply not occur if no longer stale

* chore(logs): add more logs to understand the process

* chore(logs): highlights more logs and humanize a bit more

* chore(index): update it

* refactor(checks): simplify if complexity

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-30 09:14:51 -04:00
Geoffrey Testelin
043fbbdea3 feat(logs): enhance the logs for assignees and milestones (#382)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(logs): enhance the logs for assignees and milestones

closes #381

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-28 16:39:28 -04:00
Geoffrey Testelin
c70e174d4a feat(any-of-labels): add 2 new options to customize for issues/PRs (#380)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#18)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(any-of-labels): add 2 new options to customize for issues/PRs

closes #371
change this option and only-labels to have tree-logs

* chore(index): update it

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-28 16:33:42 -04:00
Romain Rigaux
704929ea5a Typo in how to perform check for specific labels (#357)
Not tests but feels like a typo.

Or if we keep the title it `exempt` feels more approriate:

          exempt-issue-labels: 'roadmap'
          exempt-pr-labels: 'roadmap'
2021-04-28 16:27:23 -04:00
Geoffrey Testelin
5e20aa8410 feat(statistics): split the stats between issues and PRs (#364)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#17)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test(refactor): use toHaveLength

* feat(statistics): split the processed issues and prs

* feat(statistics): split the new stale issues and prs

* feat(statistics): split the no longer stale issues and prs

* chore(deps): undo upgrade of dependencies

* feat(statistics): split closed issues and prs

* feat(statistics): use the word "items" when something concern both issues and prs

* feat(statistics): split more stats by issues and prs

* feat(statistics): split more stats by issues and prs (final)

* chore(index): update it

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-27 14:47:02 -04:00
Ross Brodbeck
10eec4583b Update codeql.yml 2021-04-23 15:34:48 -04:00
Geoffrey Testelin
b717aa9f47 chore(logs): enhance the logs (#358)
* docs(only-labels): enhance the docs and fix duplicate (#341)

* docs(only-labels): remove duplicated option and improve descriptions

a bad rebase happend

* docs(readme): use a multi-line array and remove the optional column

the option column was not helpful since each value is optional
the multi-line array will allow to have a better UI in small devices and basically in GitHub too due to the max-width

* style(readme): break line for the statistics

* docs(readme): add a better description for the ascending option

* docs(action): add missing punctuation

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#342)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.16.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.16.1/packages/eslint-plugin)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump @octokit/rest from 18.3.0 to 18.3.2 (#350)

Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.3.0 to 18.3.2.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.3.0...v18.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* test: add more coverage for the stale label behaviour (#352) (#15)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(logs): add logs for the milestones

* chore(errors): use actions error instead of throw errors

* chore(logs): enhance the logs and add some colors

tl;dr: blue for values, megenta for options, white for messages, yellow light for warnings, yellow for milestones and green for success
still a WIP but I wish to confirm this before continuing
@hross is it ok for you?

* chore(index): update the index

* chore(ci): use npm ci instead of npm i

* chore(logs): removed some useless logs

* refactor(issues): remove useless check

* chore(statistics): show the real count of fetched issues

* refactor(operations): use a class to handle the operations left

closes #361

* chore(logs): include the total number of issues in the log for a batch

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-08 05:56:52 -05:00
36 changed files with 7314 additions and 1042 deletions

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,13 @@
---
name: Other issue report
about: Report other issue
title: ''
labels: bug
assignees: ''
---
## Describe your issue
## Further context
<!-- If helpful please provide screenshots, logs, links to other related issues. -->

View File

@@ -0,0 +1,29 @@
---
name: Stale issue report
about: Report issues with using the stale action
title: ''
labels: bug
assignees: ''
---
<!-- Have you tried the [debugging](https://github.com/actions/stale#debugging) section of the readme? -->
## Describe your issue
## Your stale action configuration
<!-- This is an example config, please copy/paste your config into it. -->
```yml
jobs:
stale:
runs-on: ...
steps:
- uses: actions/stale@...
with:
...
```
## Further context
<!-- If helpful please provide screenshots, logs, links to other related issues. -->

8
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,8 @@
<!-- List the change(s) you're making with this PR. -->
## Changes
- [x] ...
## Context
<!-- Explain why you're making the change(s). -->
<!-- If you're closing an issue with this PR, [link them with a keyword](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword). -->

View File

@@ -2,6 +2,8 @@ name: 'Code scanning'
on:
push:
branches:
- main
pull_request:
schedule:
- cron: '0 19 * * 0'

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: |
npm install
npm ci
npm run all
test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest

125
README.md
View File

@@ -2,54 +2,85 @@
Warns and then closes issues and PRs that have had no activity for a specified amount of time.
### Arguments
## All options
### List of options
Every argument is optional.
| Input | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `repo-token` | PAT(Personal Access Token) for authorizing repository.<br>_Defaults to **${{ github.token }}**_. |
| `days-before-stale` | Idle number of days before marking an issue/PR as stale.<br>_Defaults to **60**_. |
| `days-before-issue-stale` | Idle number of days before marking an issue as stale.<br>_Override `days-before-stale`_. |
| `days-before-pr-stale` | Idle number of days before marking an PR as stale.<br>_Override `days-before-stale`_. |
| `days-before-close` | Idle number of days before closing an stale issue/PR.<br>_Defaults to **7**_. |
| `days-before-issue-close` | Idle number of days before closing an stale issue.<br>_Override `days-before-close`_. |
| `days-before-pr-close` | Idle number of days before closing an stale PR.<br>_Override `days-before-close`_. |
| `stale-issue-message` | Message to post on the stale issue. |
| `stale-pr-message` | Message to post on the stale PR. |
| `close-issue-message` | Message to post on the stale issue while closing it. |
| `close-pr-message` | Message to post on the stale PR while closing it. |
| `stale-issue-label` | Label to apply on the stale issue.<br>_Defaults to **Stale**_. |
| `close-issue-label` | Label to apply on closing issue (automatically removed if no longer closed nor locked). |
| `stale-pr-label` | Label to apply on the stale PR.<br>_Defaults to **Stale**_. |
| `close-pr-label` | Label to apply on the closing PR (automatically removed if no longer closed nor locked). |
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. |
| `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. |
| `only-labels` | Only issues and PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered"). |
| `only-issue-labels` | Only issues with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
| `only-pr-labels` | Only PRs with ALL these labels are checked. Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
| `any-of-labels` | Only issues and PRs with ANY of these labels are checked. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). |
| `operations-per-run` | Maximum number of operations per run (GitHub API CRUD related).<br>_Defaults to **30**_. |
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments.<br>_Defaults to **true**_. |
| `debug-only` | Dry-run on action.<br>_Defaults to **false**_. |
| `ascending` | Order to get issues/PR (true is ascending, false is descending).<br>_Defaults to **false**_. |
| `skip-stale-issue-message` | Skip adding stale message on stale issue.<br>_Defaults to **false**_. |
| `skip-stale-pr-message` | Skip adding stale message on stale PR.<br>_Defaults to **false**_. |
| `start-date` | The date used to skip the stale action on issue/PR created before it (ISO 8601 or RFC 2822). |
| `delete-branch` | Delete the git branch after closing a stale pull request.<br>_Defaults to **false**_. |
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. |
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale.<br>_Priority over `exempt-milestones` rules_. |
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. |
| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale.<br>_Priority over `exempt-assignees` rules_. |
| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
| `enable-statistics` | Display some statistics at the end of the logs regarding the stale workflow (only when the logs are enabled).<br>_Defaults to **true**_. |
| Input | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `repo-token` | PAT (Personal Access Token) for authorizing the repository.<br>_Defaults to **${{ github.token }}**_. |
| `days-before-stale` | Idle number of days before marking an issue/PR as stale.<br>_Defaults to **60**_. |
| `days-before-issue-stale` | Idle number of days before marking an issue as stale.<br>_Override `days-before-stale`_. |
| `days-before-pr-stale` | Idle number of days before marking a PR as stale.<br>_Override `days-before-stale`_. |
| `days-before-close` | Idle number of days before closing a stale issue/PR.<br>_Defaults to **7**_. |
| `days-before-issue-close` | Idle number of days before closing a stale issue.<br>_Override `days-before-close`_. |
| `days-before-pr-close` | Idle number of days before closing a stale PR.<br>_Override `days-before-close`_. |
| `stale-issue-message` | Message to post on the stale issue. |
| `stale-pr-message` | Message to post on the stale PR. |
| `close-issue-message` | Message to post on the stale issue while closing it. |
| `close-pr-message` | Message to post on the stale PR while closing it. |
| `stale-issue-label` | Label to apply on the stale issue.<br>_Defaults to **Stale**_. |
| `close-issue-label` | Label to apply on closing issue.<br>Automatically removed if no longer closed nor locked). |
| `stale-pr-label` | Label to apply on the stale PR.<br>_Defaults to **Stale**_. |
| `close-pr-label` | Label to apply on the closing PR.<br>Automatically removed if no longer closed nor locked). |
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. |
| `exempt-pr-labels` | Labels on the PR exempted from being marked as stale. |
| `only-labels` | Only issues and PRs with ALL these labels are checked.<br>Separate multiple labels with commas (eg. "question,answered"). |
| `only-issue-labels` | Only issues with ALL these labels are checked.<br>Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
| `only-pr-labels` | Only PRs with ALL these labels are checked.<br>Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
| `any-of-labels` | Only issues and PRs with ANY of these labels are checked.<br>Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). |
| `any-of-issue-labels` | Only issues with ANY of these labels are checked.<br>Separate multiple labels with commas (eg. "incomplete,waiting-feedback").<br>_Override `any-of-labels`_. |
| `any-of-pr-labels` | Only PRs with ANY of these labels are checked.<br>Separate multiple labels with commas (eg. "incomplete,waiting-feedback").<br>_Override `any-of-labels`_. |
| `operations-per-run` | Maximum number of operations per run.<br>GitHub API CRUD related.<br>_Defaults to **30**_. |
| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments.<br>_Defaults to **true**_. |
| `remove-issue-stale-when-updated` | Remove stale label from issue on updates or comments.<br>_Defaults to **true**_.<br>_Override `remove-stale-when-updated`_. |
| `remove-pr-stale-when-updated` | Remove stale label from PR on updates or comments.<br>_Defaults to **true**_.<br>_Override `remove-stale-when-updated`_. |
| `remove-issue-stale-when-updated` | Remove stale label from issue on updates or comments.<br>_Defaults to **true**_.<br>_Override `remove-stale-when-updated`_. |
| `remove-pr-stale-when-updated` | Remove stale label from PR on updates or comments.<br>_Defaults to **true**_.<br>_Override `remove-stale-when-updated`_. |
| `debug-only` | Dry-run on action.<br>_Defaults to **false**_. |
| `ascending` | Order to get issues/PR.<br>`true` is ascending, `false` is descending.<br>_Defaults to **false**_. |
| `skip-stale-issue-message` | Skip adding stale message on stale issue.<br>_Defaults to **false**_. |
| `skip-stale-pr-message` | Skip adding stale message on stale PR.<br>_Defaults to **false**_. |
| `start-date` | The date used to skip the stale action on issue/PR created before it.<br>ISO 8601 or RFC 2822. |
| `delete-branch` | Delete the git branch after closing a stale pull request.<br>_Defaults to **false**_. |
| `exempt-milestones` | Milestones on an issue or a PR exempted from being marked as stale. |
| `exempt-issue-milestones` | Milestones on an issue exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale.<br>_Override `exempt-milestones`_. |
| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale.<br>_Priority over `exempt-milestones` rules_. |
| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale.<br>_Override `exempt-all-milestones`_. |
| `exempt-assignees` | Assignees on an issue or a PR exempted from being marked as stale. |
| `exempt-issue-assignees` | Assignees on an issue exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale.<br>_Override `exempt-assignees`_. |
| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale.<br>_Priority over `exempt-assignees` rules_. |
| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale.<br>_Override `exempt-all-assignees`_. |
| `enable-statistics` | Display some statistics at the end of the logs regarding the stale workflow.<br>Only when the logs are enabled.<br>_Defaults to **true**_. |
### Detailed options
#### operations-per-run
_Context:_
This action performs some API calls to GitHub to fetch or close issues and pull requests, set or update labels, add comments, delete branches, etc.
These operations are made in a very short period of time - because the action is very fast to run - and can be numerous based on your project action configuration and the quantity of issues and pull requests within it.
GitHub has a [rate limit](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) and if reached will block all of these API calls for one hour (or API calls from other actions using the same user (a.k.a: the github-token from the [repo-token](#repo-token) option)).
This option helps you to stay within the GitHub rate limits, as you can use this option to limit the number of operations for a single run.
_Purpose:_
This option aims to limit the number of operations made with the GitHub API to avoid reaching the [rate limit](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting).
Based on your project, your GitHub business plan and the date of the cron job you set for this action, you can increase this limit to a higher number.
If you are not sure which is the right value for you or if the default value is good enough, you could enable the logs and look at the end of the stale action.
If you reached the limit, you will see a warning message in the logs, telling you that you should increase the number of operations.
If you choose not to increase the limit, you might end up with un-processed issues or pull requests after a stale action run.
When [debugging](#Debugging), you can set it to a much higher number like `1000` since there will be fewer operations made with the GitHub API.
Only the [actor](#repo-token) and the batch of issues (100 per batch) will consume the operations.
Default value: `30`
### Usage
@@ -215,7 +246,7 @@ jobs:
exempt-all-pr-milestones: true
```
Avoid stale for specific labels:
Check stale for specific labels:
```yaml
name: 'Close stale issues and PRs'
@@ -230,7 +261,7 @@ jobs:
- uses: actions/stale@v3
with:
any-of-labels: 'needs-more-info,needs-demo'
# You can opt for 'only-labels' instead if your usecase requires all labels
# You can opt for 'only-labels' instead if your use-case requires all labels
# to be present in the issue/PR
```

File diff suppressed because it is too large Load Diff

View File

@@ -22,9 +22,13 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
onlyIssueLabels: '',
onlyPrLabels: '',
anyOfLabels: '',
anyOfIssueLabels: '',
anyOfPrLabels: '',
operationsPerRun: 100,
debugOnly: true,
removeStaleWhenUpdated: false,
removeIssueStaleWhenUpdated: undefined,
removePrStaleWhenUpdated: undefined,
ascending: false,
skipStaleIssueMessage: false,
skipStalePrMessage: false,
@@ -42,5 +46,5 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
exemptAllAssignees: false,
exemptAllIssueAssignees: undefined,
exemptAllPrAssignees: undefined,
enableStatistics: false
enableStatistics: true
});

View File

@@ -25,8 +25,8 @@ test('processing an issue with no label will make it stale and close it, if it i
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing an issue with no label and a start date as ECMAScript epoch in seconds being before the issue creation date will not make it stale nor close it when it is old enough and days-before-close is set to 0', async () => {
@@ -305,9 +305,9 @@ test('processing an issue with no label will make it stale and close it, if it i
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.deletedBranchIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(1);
expect(processor.deletedBranchIssues).toHaveLength(0);
});
test('processing an issue with no label will make it stale and not close it, if it is old enough only if days-before-close is set to > 0 and days-before-issue-close is set to > 0', async () => {
@@ -330,8 +330,8 @@ test('processing an issue with no label will make it stale and not close it, if
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
@@ -353,8 +353,8 @@ test('processing an issue with no label will make it stale and not close it if d
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with no label will make it stale and not close it if days-before-close is set to -1 and days-before-issue-close is set to > 0', async () => {
@@ -377,8 +377,8 @@ test('processing an issue with no label will make it stale and not close it if d
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with no label will not make it stale if days-before-stale is set to -1', async () => {
@@ -401,8 +401,8 @@ test('processing an issue with no label will not make it stale if days-before-st
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with no label will not make it stale if days-before-stale and days-before-issue-stale are set to -1', async () => {
@@ -426,8 +426,8 @@ test('processing an issue with no label will not make it stale if days-before-st
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue with no label will make it stale but not close it', async () => {
@@ -454,8 +454,8 @@ test('processing an issue with no label will make it stale but not close it', as
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing a stale issue will close it', async () => {
@@ -485,8 +485,8 @@ test('processing a stale issue will close it', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale issue containing a space in the label will close it', async () => {
@@ -516,8 +516,8 @@ test('processing a stale issue containing a space in the label will close it', a
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale issue containing a slash in the label will close it', async () => {
@@ -547,8 +547,8 @@ test('processing a stale issue containing a slash in the label will close it', a
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale issue will close it when days-before-issue-stale override days-before-stale', async () => {
@@ -579,8 +579,8 @@ test('processing a stale issue will close it when days-before-issue-stale overri
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale PR will close it', async () => {
@@ -610,8 +610,8 @@ test('processing a stale PR will close it', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale PR will close it when days-before-pr-stale override days-before-stale', async () => {
@@ -642,8 +642,8 @@ test('processing a stale PR will close it when days-before-pr-stale override day
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale issue will close it even if configured not to mark as stale', async () => {
@@ -674,8 +674,8 @@ test('processing a stale issue will close it even if configured not to mark as s
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale issue will close it even if configured not to mark as stale when days-before-issue-stale override days-before-stale', async () => {
@@ -707,8 +707,8 @@ test('processing a stale issue will close it even if configured not to mark as s
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale PR will close it even if configured not to mark as stale', async () => {
@@ -739,8 +739,8 @@ test('processing a stale PR will close it even if configured not to mark as stal
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('processing a stale PR will close it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => {
@@ -772,8 +772,8 @@ test('processing a stale PR will close it even if configured not to mark as stal
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(1);
});
test('closed issues will not be marked stale', async () => {
@@ -799,8 +799,8 @@ test('closed issues will not be marked stale', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('stale closed issues will not be closed', async () => {
@@ -827,8 +827,8 @@ test('stale closed issues will not be closed', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('closed prs will not be marked stale', async () => {
@@ -855,8 +855,8 @@ test('closed prs will not be marked stale', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('stale closed prs will not be closed', async () => {
@@ -883,8 +883,8 @@ test('stale closed prs will not be closed', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('locked issues will not be marked stale', async () => {
@@ -910,8 +910,8 @@ test('locked issues will not be marked stale', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('stale locked issues will not be closed', async () => {
@@ -939,8 +939,8 @@ test('stale locked issues will not be closed', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('locked prs will not be marked stale', async () => {
@@ -966,8 +966,8 @@ test('locked prs will not be marked stale', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('stale locked prs will not be closed', async () => {
@@ -995,8 +995,8 @@ test('stale locked prs will not be closed', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('exempt issue labels will not be marked stale', async () => {
@@ -1055,8 +1055,8 @@ test('exempt issue labels will not be marked stale (multi issue label with space
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('exempt issue labels will not be marked stale (multi issue label)', async () => {
@@ -1084,9 +1084,9 @@ test('exempt issue labels will not be marked stale (multi issue label)', async (
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
});
test('exempt pr labels will not be marked stale', async () => {
@@ -1131,7 +1131,7 @@ test('exempt pr labels will not be marked stale', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(2); // PR should get processed even though it has an exempt **issue** label
expect(processor.staleIssues).toHaveLength(2); // PR should get processed even though it has an exempt **issue** label
});
test('exempt issue labels will not be marked stale and will remove the existing stale label', async () => {
@@ -1215,13 +1215,12 @@ test('stale issues should not be closed if days is set to -1', async () => {
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
});
test('stale label should be removed if a comment was added to a stale issue', async () => {
const opts = {...DefaultProcessorOptions};
opts.removeStaleWhenUpdated = true;
const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true};
const TestIssueList: Issue[] = [
generateIssue(
opts,
@@ -1251,14 +1250,13 @@ test('stale label should be removed if a comment was added to a stale issue', as
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(1);
});
test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => {
const opts = {...DefaultProcessorOptions};
opts.removeStaleWhenUpdated = true;
const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true};
github.context.actor = 'abot';
const TestIssueList: Issue[] = [
generateIssue(
@@ -1289,9 +1287,9 @@ test('stale label should not be removed if a comment was added by the bot (and t
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(1);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
});
test('stale label containing a space should be removed if a comment was added to a stale issue', async () => {
@@ -1322,9 +1320,9 @@ test('stale label containing a space should be removed if a comment was added to
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(1);
});
test('stale issues should not be closed until after the closed number of days', async () => {
@@ -1354,9 +1352,9 @@ test('stale issues should not be closed until after the closed number of days',
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(1);
});
test('stale issues should be closed if the closed nubmer of days (additive) is also passed', async () => {
@@ -1387,9 +1385,9 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(1);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
});
test('stale issues should not be closed until after the closed number of days (long)', async () => {
@@ -1419,9 +1417,9 @@ test('stale issues should not be closed until after the closed number of days (l
// process our fake issue list
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(1);
});
test('skips stale message on issues when skip-stale-issue-message is set', async () => {
@@ -1455,9 +1453,9 @@ test('skips stale message on issues when skip-stale-issue-message is set', async
await processor.processIssues(1);
// issue should be staled
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(1);
// comment should not be created
expect(markSpy).toHaveBeenCalledWith(
@@ -1500,9 +1498,9 @@ test('skips stale message on prs when skip-stale-pr-message is set', async () =>
await processor.processIssues(1);
// issue should be staled
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(1);
// comment should not be created
expect(markSpy).toHaveBeenCalledWith(
@@ -1543,9 +1541,9 @@ test('not providing state takes precedence over skipStaleIssueMessage', async ()
await processor.processIssues(1);
// issue should be staled
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
});
test('not providing stalePrMessage takes precedence over skipStalePrMessage', async () => {
@@ -1577,9 +1575,9 @@ test('not providing stalePrMessage takes precedence over skipStalePrMessage', as
await processor.processIssues(1);
// issue should be staled
expect(processor.closedIssues.length).toEqual(0);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(0);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
});
test('git branch is deleted when option is enabled', async () => {
@@ -1606,10 +1604,10 @@ test('git branch is deleted when option is enabled', async () => {
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.deletedBranchIssues.length).toEqual(1);
expect(processor.closedIssues).toHaveLength(1);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.deletedBranchIssues).toHaveLength(1);
});
test('git branch is not deleted when issue is not pull request', async () => {
@@ -1636,10 +1634,10 @@ test('git branch is not deleted when issue is not pull request', async () => {
await processor.processIssues(1);
expect(processor.closedIssues.length).toEqual(1);
expect(processor.removedLabelIssues.length).toEqual(0);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.deletedBranchIssues.length).toEqual(0);
expect(processor.closedIssues).toHaveLength(1);
expect(processor.removedLabelIssues).toHaveLength(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.deletedBranchIssues).toHaveLength(0);
});
test('an issue without a milestone will be marked as stale', async () => {
@@ -1938,8 +1936,8 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue opened since 2 days and with the option "daysBeforeIssueStale" at 2 will make it stale', async () => {
@@ -1965,8 +1963,8 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing an issue opened since 2 days and with the option "daysBeforeIssueStale" at 1 will make it stale', async () => {
@@ -1992,8 +1990,8 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing a pull request opened since 2 days and with the option "daysBeforePrStale" at 3 will not make it stale', async () => {
@@ -2026,8 +2024,8 @@ test('processing a pull request opened since 2 days and with the option "daysBef
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(0);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(0);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing a pull request opened since 2 days and with the option "daysBeforePrStale" at 2 will make it stale', async () => {
@@ -2060,8 +2058,8 @@ test('processing a pull request opened since 2 days and with the option "daysBef
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing a pull request opened since 2 days and with the option "daysBeforePrStale" at 1 will make it stale', async () => {
@@ -2094,8 +2092,8 @@ test('processing a pull request opened since 2 days and with the option "daysBef
// process our fake issue list
await processor.processIssues(1);
expect(processor.staleIssues.length).toEqual(1);
expect(processor.closedIssues.length).toEqual(0);
expect(processor.staleIssues).toHaveLength(1);
expect(processor.closedIssues).toHaveLength(0);
});
test('processing a previously closed issue with a close label will remove the close label', async () => {

View File

@@ -0,0 +1,567 @@
import {Issue} from '../src/classes/issue';
import {IIssue} from '../src/interfaces/issue';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {ILabel} from '../src/interfaces/label';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue';
let issuesProcessorBuilder: IssuesProcessorBuilder;
let issuesProcessor: IssuesProcessorMock;
/**
* @description
* Assuming there is a comment on the issue
*/
describe('remove-stale-when-updated option', (): void => {
beforeEach((): void => {
issuesProcessorBuilder = new IssuesProcessorBuilder();
});
describe('when the option "remove-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removeStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
});
describe('remove-issue-stale-when-updated option', (): void => {
beforeEach((): void => {
issuesProcessorBuilder = new IssuesProcessorBuilder();
});
describe('when the option "remove-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepStaleWhenUpdated();
});
describe('when the option "remove-issue-stale-when-updated" is unset', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.unsetIssueStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-issue-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepIssueStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-issue-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removeIssueStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
});
describe('when the option "remove-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removeStaleWhenUpdated();
});
describe('when the option "remove-issue-stale-when-updated" is unset', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.unsetIssueStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
describe('when the option "remove-issue-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepIssueStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
describe('when the option "remove-issue-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removeIssueStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
});
});
describe('remove-pr-stale-when-updated option', (): void => {
beforeEach((): void => {
issuesProcessorBuilder = new IssuesProcessorBuilder();
});
describe('when the option "remove-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepStaleWhenUpdated();
});
describe('when the option "remove-pr-stale-when-updated" is unset', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.unsetPrStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-pr-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepPrStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-pr-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removePrStaleWhenUpdated();
});
test('should not remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
});
describe('when the option "remove-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removeStaleWhenUpdated();
});
describe('when the option "remove-pr-stale-when-updated" is unset', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.unsetPrStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
describe('when the option "remove-pr-stale-when-updated" is disabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.keepPrStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should not remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(0);
});
});
describe('when the option "remove-pr-stale-when-updated" is enabled', (): void => {
beforeEach((): void => {
issuesProcessorBuilder.removePrStaleWhenUpdated();
});
test('should remove the stale label on the issue', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.staleIssues([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
test('should remove the stale label on the pull request', async (): Promise<void> => {
expect.assertions(1);
issuesProcessor = issuesProcessorBuilder.stalePrs([{}]).build();
await issuesProcessor.processIssues();
expect(issuesProcessor.removedLabelIssues).toHaveLength(1);
});
});
});
});
class IssuesProcessorBuilder {
private _options: IIssuesProcessorOptions = {
...DefaultProcessorOptions
};
private _issues: Issue[] = [];
keepStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removeStaleWhenUpdated = false;
return this;
}
removeStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removeStaleWhenUpdated = true;
return this;
}
unsetIssueStaleWhenUpdated(): IssuesProcessorBuilder {
delete this._options.removeIssueStaleWhenUpdated;
return this;
}
keepIssueStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removeIssueStaleWhenUpdated = false;
return this;
}
removeIssueStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removeIssueStaleWhenUpdated = true;
return this;
}
unsetPrStaleWhenUpdated(): IssuesProcessorBuilder {
delete this._options.removePrStaleWhenUpdated;
return this;
}
keepPrStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removePrStaleWhenUpdated = false;
return this;
}
removePrStaleWhenUpdated(): IssuesProcessorBuilder {
this._options.removePrStaleWhenUpdated = true;
return this;
}
issuesOrPrs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
this._issues = issues.map(
(issue: Readonly<Partial<IIssue>>, index: Readonly<number>): Issue =>
generateIssue(
this._options,
index,
issue.title ?? 'dummy-title',
issue.updated_at ?? new Date().toDateString(),
issue.created_at ?? new Date().toDateString(),
!!issue.pull_request,
issue.labels ? issue.labels.map(label => label.name) : []
)
);
return this;
}
issues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
this.issuesOrPrs(
issues.map(
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
return {
...issue,
pull_request: null
};
}
)
);
return this;
}
staleIssues(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
this.issues(
issues.map(
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
return {
...issue,
updated_at: '2020-01-01T17:00:00Z',
created_at: '2020-01-01T17:00:00Z',
labels: issue.labels?.map(
(label: Readonly<ILabel>): ILabel => {
return {
...label,
name: 'Stale'
};
}
) ?? [
{
name: 'Stale'
}
]
};
}
)
);
return this;
}
prs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
this.issuesOrPrs(
issues.map(
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
return {
...issue,
pull_request: {key: 'value'}
};
}
)
);
return this;
}
stalePrs(issues: Partial<IIssue>[]): IssuesProcessorBuilder {
this.prs(
issues.map(
(issue: Readonly<Partial<IIssue>>): Partial<IIssue> => {
return {
...issue,
updated_at: '2020-01-01T17:00:00Z',
created_at: '2020-01-01T17:00:00Z',
labels: issue.labels?.map(
(label: Readonly<ILabel>): ILabel => {
return {
...label,
name: 'Stale'
};
}
) ?? [
{
name: 'Stale'
}
]
};
}
)
);
return this;
}
build(): IssuesProcessorMock {
return new IssuesProcessorMock(
this._options,
async () => 'abot',
async p => (p === 1 ? this._issues : []),
async () => [
{
user: {
login: 'notme',
type: 'User'
}
}
],
async () => new Date().toDateString()
);
}
}

View File

@@ -92,6 +92,14 @@ inputs:
description: 'Only issues or pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels.'
default: ''
required: false
any-of-issue-labels:
description: 'Only issues with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the issues.'
default: ''
required: false
any-of-pr-labels:
description: 'Only pull requests with at least one of these labels are checked if stale. Defaults to `` (disabled) and can be a comma-separated list of labels. Override "any-of-labels" option regarding only the pull requests.'
default: ''
required: false
only-issue-labels:
description: 'Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels. Override "only-labels" option regarding only the issues.'
default: ''
@@ -105,7 +113,15 @@ inputs:
default: '30'
required: false
remove-stale-when-updated:
description: 'Remove stale labels from issues when they are updated or commented on.'
description: 'Remove stale labels from issues and pull requests when they are updated or commented on.'
default: 'true'
required: false
remove-issue-stale-when-updated:
description: 'Remove stale labels from issues when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the issues.'
default: 'true'
required: false
remove-pr-stale-when-updated:
description: 'Remove stale labels from pull requests when they are updated or commented on. Override "remove-stale-when-updated" option regarding only the pull requests.'
default: 'true'
required: false
debug-only:

3522
dist/index.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -7,5 +7,6 @@ module.exports = {
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
verbose: true,
setupFilesAfterEnv: [`./jest/test.ts`]
};

11
jest/test.ts Normal file
View File

@@ -0,0 +1,11 @@
import chalk from 'chalk';
// Disabled the colors to:
// - improve the performances
// - avoid to mock chalk
// - avoid to have failing tests when testing the logs due to the extra text the log message will contains
//
// Note:
// If you need to debug the log colours you can remove this line temporarily
// But some tests will fail
chalk.level = 0;

322
package-lock.json generated
View File

@@ -206,6 +206,19 @@
"@babel/helper-validator-identifier": "^7.9.0",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
}
}
},
"@babel/parser": {
@@ -1560,11 +1573,6 @@
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-5.3.0.tgz",
"integrity": "sha512-5q2qBz4iZ0xS/DEJ0ROusFbN4cVlbJE9GvOByen+mv7artuGXfVhONqcuRd7jYN2glTmCnzcZw+X6LrjRVqs0A=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.4.tgz",
@@ -1573,11 +1581,6 @@
"@octokit/types": "^5.0.0"
}
},
"@octokit/plugin-request-log": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz",
"integrity": "sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ=="
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.2.tgz",
@@ -1612,95 +1615,6 @@
"once": "^1.4.0"
}
},
"@octokit/rest": {
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.3.3.tgz",
"integrity": "sha512-OxElwBanZn1AShCaIrRTLM9PwhGE5/busMke/go30OWAQ+eJMD7Us/67mtapE77EYY4FM2tvb4Eg25rZaA/NPA==",
"requires": {
"@octokit/core": "^3.2.3",
"@octokit/plugin-paginate-rest": "^2.6.2",
"@octokit/plugin-request-log": "^1.0.2",
"@octokit/plugin-rest-endpoint-methods": "4.13.3"
},
"dependencies": {
"@octokit/auth-token": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.5.tgz",
"integrity": "sha512-+DCtPykGnvXKWWQI0E1XD+CCeWSBhB6kwItXqfFmNBlIlhczuDPbg+P6BtLnVBaRJDAjv+1mrUJuRsFSjktopg==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.4.12",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.1.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.0.tgz",
"integrity": "sha512-CJ6n7izLFXLvPZaWzCQDjU/RP+vHiZmWdOunaCS87v+2jxMsW9FB5ktfIxybRBxZjxuJGRnxk7xJecWTVxFUYQ==",
"requires": {
"@octokit/request": "^5.3.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/plugin-paginate-rest": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.11.0.tgz",
"integrity": "sha512-7L9xQank2G3r1dGqrVPo1z62V5utbykOUzlmNHPz87Pww/JpZQ9KyG5CHtUzgmB4n5iDRKYNK/86A8D98HP0yA==",
"requires": {
"@octokit/types": "^6.11.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "4.13.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.3.tgz",
"integrity": "sha512-nMGS2osFcWXRfHkDR0d+lB1zpMPTZJ0NjysPUfs7BT5/juNG/Q0+5UB6nC1f62jPzun154qekzwOb7Q5oahCXQ==",
"requires": {
"@octokit/types": "^6.12.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.4.14",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz",
"integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.0.0",
"@octokit/types": "^6.7.1",
"deprecation": "^2.0.0",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.1",
"once": "^1.4.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/types": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.12.0.tgz",
"integrity": "sha512-KwOf16soD7aDEEi/PgNeJlHzjZPfrmmNy+7WezSdrpnqZ7YImBJcNnX9+5RUHt1MnA4h8oISRHTqaZDGsX9DRQ==",
"requires": {
"@octokit/openapi-types": "^5.3.0"
}
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"@octokit/types": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.1.2.tgz",
@@ -1839,9 +1753,9 @@
}
},
"@types/node": {
"version": "14.14.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
"integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g=="
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA=="
},
"@types/normalize-package-data": {
"version": "2.4.0",
@@ -1856,9 +1770,9 @@
"dev": true
},
"@types/semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-iotVxtCCsPLRAvxMFFgxL8HD2l4mAZ2Oin7/VJ2ooWO0VOK4EGOGmZWZn1uCq7RofR3I/1IOSjCHlFT71eVK0Q==",
"dev": true
},
"@types/stack-utils": {
@@ -1962,55 +1876,55 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz",
"integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz",
"integrity": "sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.15.1",
"@typescript-eslint/types": "4.15.1",
"@typescript-eslint/typescript-estree": "4.15.1",
"@typescript-eslint/scope-manager": "4.22.0",
"@typescript-eslint/types": "4.22.0",
"@typescript-eslint/typescript-estree": "4.22.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
}
},
"@typescript-eslint/parser": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz",
"integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.22.1.tgz",
"integrity": "sha512-l+sUJFInWhuMxA6rtirzjooh8cM/AATAe3amvIkqKFeMzkn85V+eLzb1RyuXkHak4dLfYzOmF6DXPyflJvjQnw==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "4.16.1",
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/typescript-estree": "4.16.1",
"@typescript-eslint/scope-manager": "4.22.1",
"@typescript-eslint/types": "4.22.1",
"@typescript-eslint/typescript-estree": "4.22.1",
"debug": "^4.1.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz",
"integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.1.tgz",
"integrity": "sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1"
"@typescript-eslint/types": "4.22.1",
"@typescript-eslint/visitor-keys": "4.22.1"
}
},
"@typescript-eslint/types": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz",
"integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.1.tgz",
"integrity": "sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz",
"integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.1.tgz",
"integrity": "sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/visitor-keys": "4.16.1",
"@typescript-eslint/types": "4.22.1",
"@typescript-eslint/visitor-keys": "4.22.1",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
@@ -2019,47 +1933,47 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz",
"integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.1.tgz",
"integrity": "sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.16.1",
"@typescript-eslint/types": "4.22.1",
"eslint-visitor-keys": "^2.0.0"
}
},
"eslint-visitor-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
"dev": true
}
}
},
"@typescript-eslint/scope-manager": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz",
"integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz",
"integrity": "sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.15.1",
"@typescript-eslint/visitor-keys": "4.15.1"
"@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.22.0"
}
},
"@typescript-eslint/types": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz",
"integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.22.0.tgz",
"integrity": "sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz",
"integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz",
"integrity": "sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.15.1",
"@typescript-eslint/visitor-keys": "4.15.1",
"@typescript-eslint/types": "4.22.0",
"@typescript-eslint/visitor-keys": "4.22.0",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
@@ -2068,12 +1982,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz",
"integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==",
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz",
"integrity": "sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.15.1",
"@typescript-eslint/types": "4.22.0",
"eslint-visitor-keys": "^2.0.0"
},
"dependencies": {
@@ -2643,14 +2557,54 @@
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"char-regex": {
@@ -3367,12 +3321,6 @@
"type-check": "~0.4.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -3607,9 +3555,9 @@
}
},
"eslint-plugin-jest": {
"version": "24.1.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.5.tgz",
"integrity": "sha512-FIP3lwC8EzEG+rOs1y96cOJmMVpdFNreoDJv29B5vIupVssRi8zrSY3QadogT0K3h1Y8TMxJ6ZSAzYUmFCp2hg==",
"version": "24.3.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.6.tgz",
"integrity": "sha512-WOVH4TIaBLIeCX576rLcOgjNXqP+jNlCiEmRgFTfQtJ52DpwnIQKAVGlGPAN7CZ33bW6eNfHD6s8ZbEUTQubJg==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "^4.0.1"
@@ -4370,9 +4318,9 @@
}
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"html-encoding-sniffer": {
@@ -7843,9 +7791,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.deburr": {
@@ -8835,9 +8783,9 @@
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"requires": {
"lru-cache": "^6.0.0"
}
@@ -9422,12 +9370,6 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}
}
},
@@ -9652,9 +9594,9 @@
}
},
"typescript": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
"dev": true
},
"unbox-primitive": {
@@ -9971,9 +9913,9 @@
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true
},
"yallist": {

View File

@@ -15,7 +15,7 @@
"pack": "ncc build",
"test": "jest",
"test:only-errors": "jest --reporters jest-silent-reporter --silent",
"test:watch": "jest --watch --notify --expandf",
"test:watch": "jest --watch --notify --expand",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
},
"repository": {
@@ -32,27 +32,28 @@
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/github": "^4.0.0",
"@octokit/rest": "^18.3.3",
"lodash.deburr": "^4.1.0",
"semver": "^7.3.4"
"semver": "^7.3.5"
},
"devDependencies": {
"@types/jest": "^26.0.20",
"@types/lodash.deburr": "^4.1.6",
"@types/node": "^14.14.31",
"@types/semver": "^7.3.4",
"@types/node": "^15.0.2",
"@types/semver": "^7.3.5",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"@typescript-eslint/parser": "^4.22.1",
"@vercel/ncc": "^0.27.0",
"chalk": "^4.1.0",
"eslint": "^7.21.0",
"eslint-plugin-github": "^4.1.2",
"eslint-plugin-jest": "^24.1.5",
"eslint-plugin-jest": "^24.3.6",
"jest": "^26.6.3",
"jest-circus": "^26.6.3",
"jest-silent-reporter": "^0.4.0",
"js-yaml": "^4.0.0",
"prettier": "^2.2.1",
"terminal-link": "^2.1.1",
"ts-jest": "^26.5.3",
"typescript": "^4.2.3"
"typescript": "^4.2.4"
}
}

View File

@@ -2,8 +2,8 @@ import {DefaultProcessorOptions} from '../../__tests__/constants/default-process
import {generateIIssue} from '../../__tests__/functions/generate-iissue';
import {IIssue} from '../interfaces/issue';
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {Issue} from './issue';
import {Assignees} from './assignees';
import {Issue} from './issue';
describe('Assignees', (): void => {
let assignees: Assignees;
@@ -12,7 +12,10 @@ describe('Assignees', (): void => {
let issueInterface: IIssue;
beforeEach((): void => {
optionsInterface = {...DefaultProcessorOptions};
optionsInterface = {
...DefaultProcessorOptions,
exemptAllAssignees: false
};
issueInterface = generateIIssue();
});
@@ -303,21 +306,51 @@ describe('Assignees', (): void => {
});
});
});
});
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 an assignee', (): void => {
describe('when the given options are configured to exempt all assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAssignees = '';
optionsInterface.exemptAllAssignees = true;
});
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
describe('when the given issue does not have an assignee', (): void => {
beforeEach((): void => {
optionsInterface.exemptPrAssignees = '';
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given issue does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-assignee'
}
];
});
it('should return true', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(true);
});
});
describe('when the given options are not configured to exempt all issue assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllIssueAssignees = false;
});
describe('when the given issue does not have an assignee', (): void => {
@@ -337,6 +370,102 @@ describe('Assignees', (): void => {
});
describe('when the given issue does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-assignee'
}
];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
});
describe('when the given options are configured to exempt all issue assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllIssueAssignees = true;
});
describe('when the given issue does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given issue does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-issue-assignee'
}
];
});
it('should return true', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(true);
});
});
});
});
});
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 an assignee', (): void => {
beforeEach((): void => {
optionsInterface.exemptAssignees = '';
});
describe('when the given options are not configured to exempt a pull request with an assignee', (): void => {
beforeEach((): void => {
optionsInterface.exemptPrAssignees = '';
});
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given pull request does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -362,7 +491,7 @@ describe('Assignees', (): void => {
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
});
describe('when the given issue does not have an assignee', (): void => {
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
@@ -378,7 +507,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
describe('when the given pull request does have an assignee different than the exempt pull request assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -398,7 +527,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
describe('when the given pull request does have an assignee equaling the exempt pull request assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -430,7 +559,7 @@ describe('Assignees', (): void => {
optionsInterface.exemptPrAssignees = '';
});
describe('when the given issue does not have an assignee', (): void => {
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
@@ -446,7 +575,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
describe('when the given pull request does have an assignee different than the exempt assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -466,7 +595,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
describe('when the given pull request does have an assignee equaling the exempt assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -492,7 +621,7 @@ describe('Assignees', (): void => {
optionsInterface.exemptPrAssignees = 'dummy-exempt-pr-assignee';
});
describe('when the given issue does not have an assignee', (): void => {
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
@@ -508,7 +637,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee different than the exempt pull request assignee', (): void => {
describe('when the given pull request does have an assignee different than the exempt pull request assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -528,7 +657,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee equaling the exempt pull request assignee', (): void => {
describe('when the given pull request does have an assignee equaling the exempt pull request assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -548,7 +677,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee different than the exempt assignee', (): void => {
describe('when the given pull request does have an assignee different than the exempt assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -568,7 +697,7 @@ describe('Assignees', (): void => {
});
});
describe('when the given issue does have an assignee equaling the exempt assignee', (): void => {
describe('when the given pull request does have an assignee equaling the exempt assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
@@ -589,6 +718,132 @@ describe('Assignees', (): void => {
});
});
});
describe('when the given options are configured to exempt all assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllAssignees = true;
});
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given pull request does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-assignee'
}
];
});
it('should return true', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(true);
});
});
describe('when the given options are not configured to exempt all pull request assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllPrAssignees = false;
});
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given pull request does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-assignee'
}
];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
});
describe('when the given options are configured to exempt all pull request assignees', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllPrAssignees = true;
});
describe('when the given pull request does not have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [];
});
it('should return false', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(false);
});
});
describe('when the given pull request does have an assignee', (): void => {
beforeEach((): void => {
issueInterface.assignees = [
{
login: 'dummy-exempt-issue-assignee'
}
];
});
it('should return true', (): void => {
expect.assertions(1);
issue = new Issue(optionsInterface, issueInterface);
assignees = new Assignees(optionsInterface, issue);
const result = assignees.shouldExemptAssignees();
expect(result).toStrictEqual(true);
});
});
});
});
});
});
});

View File

@@ -1,4 +1,6 @@
import chalk from 'chalk';
import deburr from 'lodash.deburr';
import {Option} from '../enums/option';
import {wordsToList} from '../functions/words-to-list';
import {IAssignee} from '../interfaces/assignee';
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
@@ -32,7 +34,8 @@ export class Assignees {
if (this._shouldExemptAllAssignees()) {
this._issueLogger.info(
'Skipping $$type because it has an exempt assignee'
chalk.white('└──'),
'Skipping this $$type because it has an exempt assignee'
);
return true;
@@ -42,7 +45,8 @@ export class Assignees {
if (exemptAssignees.length === 0) {
this._issueLogger.info(
`No option was specified to skip the stale process for this $$type`
chalk.white('├──'),
`No assignee option was specified to skip the stale process for this $$type`
);
this._logSkip();
@@ -50,9 +54,10 @@ export class Assignees {
}
this._issueLogger.info(
`Found ${exemptAssignees.length} assignee${
chalk.white('├──'),
`Found ${chalk.cyan(exemptAssignees.length)} assignee${
exemptAssignees.length > 1 ? 's' : ''
} on this $$type`
} that can exempt stale on this $$type`
);
const hasExemptAssignee: boolean = exemptAssignees.some(
@@ -62,11 +67,13 @@ export class Assignees {
if (!hasExemptAssignee) {
this._issueLogger.info(
chalk.white('├──'),
'No assignee on this $$type can exempt the stale process'
);
this._logSkip();
} else {
this._issueLogger.info(
chalk.white('└──'),
'Skipping this $$type because it has an exempt assignee'
);
}
@@ -83,12 +90,18 @@ export class Assignees {
private _getExemptIssueAssignees(): string[] {
if (this._options.exemptIssueAssignees === '') {
this._issueLogger.info(
'The option "exemptIssueAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptIssueAssignees
)} is disabled. No specific assignee can skip the stale process for this $$type`
);
if (this._options.exemptAssignees === '') {
this._issueLogger.info(
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAssignees
)} is disabled. No specific assignee can skip the stale process for this $$type`
);
return [];
@@ -99,9 +112,10 @@ export class Assignees {
);
this._issueLogger.info(
`The option "exemptAssignees" is set. ${
exemptAssignees.length
} assignee${
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAssignees
)} is set. ${chalk.cyan(exemptAssignees.length)} assignee${
exemptAssignees.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
@@ -114,9 +128,10 @@ export class Assignees {
);
this._issueLogger.info(
`The option "exemptIssueAssignees" is set. ${
exemptAssignees.length
} assignee${
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptIssueAssignees
)} is set. ${chalk.cyan(exemptAssignees.length)} assignee${
exemptAssignees.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
@@ -127,12 +142,18 @@ export class Assignees {
private _getExemptPullRequestAssignees(): string[] {
if (this._options.exemptPrAssignees === '') {
this._issueLogger.info(
'The option "exemptPrAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptPrAssignees
)} is disabled. No specific assignee can skip the stale process for this $$type`
);
if (this._options.exemptAssignees === '') {
this._issueLogger.info(
'The option "exemptAssignees" is disabled. No specific assignee can skip the stale process for this $$type'
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAssignees
)} is disabled. No specific assignee can skip the stale process for this $$type`
);
return [];
@@ -143,9 +164,10 @@ export class Assignees {
);
this._issueLogger.info(
`The option "exemptAssignees" is set. ${
exemptAssignees.length
} assignee${
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAssignees
)} is set. ${chalk.cyan(exemptAssignees.length)} assignee${
exemptAssignees.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
@@ -158,9 +180,10 @@ export class Assignees {
);
this._issueLogger.info(
`The option "exemptPrAssignees" is set. ${
exemptAssignees.length
} assignee${
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptPrAssignees
)} is set. ${chalk.cyan(exemptAssignees.length)} assignee${
exemptAssignees.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
@@ -178,6 +201,7 @@ export class Assignees {
if (isSameAssignee) {
this._issueLogger.info(
chalk.white('├──'),
`@${issueAssignee.login} is assigned on this $$type and is an exempt assignee`
);
}
@@ -196,13 +220,17 @@ export class Assignees {
private _shouldExemptAllIssueAssignees(): boolean {
if (this._options.exemptAllIssueAssignees === true) {
this._issueLogger.info(
'The option "exemptAllIssueAssignees" is enabled. Any assignee on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllIssueAssignees
)} is enabled. Any assignee on this $$type will skip the stale process`
);
return true;
} else if (this._options.exemptAllIssueAssignees === false) {
this._issueLogger.info(
'The option "exemptAllIssueAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllIssueAssignees
)} is disabled. Only some specific assignees on this $$type will skip the stale process`
);
return false;
@@ -216,13 +244,17 @@ export class Assignees {
private _shouldExemptAllPullRequestAssignees(): boolean {
if (this._options.exemptAllPrAssignees === true) {
this._issueLogger.info(
'The option "exemptAllPrAssignees" is enabled. Any assignee on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllPrAssignees
)} is enabled. Any assignee on this $$type will skip the stale process`
);
return true;
} else if (this._options.exemptAllPrAssignees === false) {
this._issueLogger.info(
'The option "exemptAllPrAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllPrAssignees
)} is disabled. Only some specific assignees on this $$type will skip the stale process`
);
return false;
@@ -236,16 +268,20 @@ export class Assignees {
private _logExemptAllAssigneesOption(): void {
if (this._options.exemptAllAssignees) {
this._issueLogger.info(
'The option "exemptAllAssignees" is enabled. Any assignee on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllAssignees
)} is enabled. Any assignee on this $$type will skip the stale process`
);
} else {
this._issueLogger.info(
'The option "exemptAllAssignees" is disabled. Only some specific assignees on this $$type will skip the stale process'
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllAssignees
)} is disabled. Only some specific assignees on this $$type will skip the stale process`
);
}
}
private _logSkip(): void {
this._issueLogger.info('Skip the assignees checks');
this._issueLogger.info(chalk.white('└──'), 'Skip the assignees checks');
}
}

View File

@@ -31,8 +31,12 @@ describe('Issue', (): void => {
onlyIssueLabels: '',
onlyPrLabels: '',
anyOfLabels: '',
anyOfIssueLabels: '',
anyOfPrLabels: '',
operationsPerRun: 0,
removeStaleWhenUpdated: false,
removeIssueStaleWhenUpdated: undefined,
removePrStaleWhenUpdated: undefined,
repoToken: '',
skipStaleIssueMessage: false,
skipStalePrMessage: false,

View File

@@ -6,6 +6,7 @@ import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {ILabel} from '../interfaces/label';
import {IMilestone} from '../interfaces/milestone';
import {IsoDateString} from '../types/iso-date-string';
import {Operations} from './operations';
export class Issue implements IIssue {
private readonly _options: IIssuesProcessorOptions;
@@ -20,6 +21,19 @@ export class Issue implements IIssue {
readonly milestone: IMilestone | undefined;
readonly assignees: IAssignee[];
isStale: boolean;
operations = new Operations();
get isPullRequest(): boolean {
return isPullRequest(this);
}
get staleLabel(): string {
return this._getStaleLabel();
}
get hasAssignees(): boolean {
return this.assignees.length > 0;
}
constructor(
options: Readonly<IIssuesProcessorOptions>,
@@ -40,18 +54,6 @@ export class Issue implements IIssue {
this.isStale = isLabeled(this, this.staleLabel);
}
get isPullRequest(): boolean {
return isPullRequest(this);
}
get staleLabel(): string {
return this._getStaleLabel();
}
get hasAssignees(): boolean {
return this.assignees.length > 0;
}
private _getStaleLabel(): string {
return this.isPullRequest
? this._options.stalePrLabel

View File

@@ -1,12 +1,14 @@
import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github';
import {GitHub} from '@actions/github/lib/utils';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import { coerce } from 'semver';
import chalk from 'chalk';
import {Option} from '../enums/option';
import {getHumanizedDate} from '../functions/dates/get-humanized-date';
import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than';
import {isValidDate} from '../functions/dates/is-valid-date';
import {isBoolean} from '../functions/is-boolean';
import {isLabeled} from '../functions/is-labeled';
import {isPullRequest} from '../functions/is-pull-request';
import {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
import {wordsToList} from '../functions/words-to-list';
import {IComment} from '../interfaces/comment';
@@ -19,6 +21,7 @@ import {Issue} from './issue';
import {IssueLogger} from './loggers/issue-logger';
import {Logger} from './loggers/logger';
import {Milestones} from './milestones';
import {StaleOperations} from './stale-operations';
import {Statistics} from './statistics';
/***
@@ -33,9 +36,32 @@ export class IssuesProcessor {
return millisSinceLastUpdated <= daysInMillis;
}
private static _endIssueProcessing(issue: Issue): void {
const consumedOperationsCount: number = issue.operations.getConsumedOperationsCount();
if (consumedOperationsCount > 0) {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(
chalk.cyan(consumedOperationsCount),
`operation${
consumedOperationsCount > 1 ? 's' : ''
} consumed for this $$type`
);
}
}
private static _getStaleMessageUsedOptionName(
issue: Readonly<Issue>
): Option.StalePrMessage | Option.StaleIssueMessage {
return issue.isPullRequest
? Option.StalePrMessage
: Option.StaleIssueMessage;
}
private readonly _logger: Logger = new Logger();
private readonly _operations: StaleOperations;
private readonly _statistics: Statistics | undefined;
private _operationsLeft = 0;
readonly client: InstanceType<typeof GitHub>;
readonly options: IIssuesProcessorOptions;
readonly staleIssues: Issue[] = [];
@@ -45,38 +71,58 @@ export class IssuesProcessor {
constructor(options: IIssuesProcessorOptions) {
this.options = options;
this._operationsLeft = this.options.operationsPerRun;
this.client = getOctokit(this.options.repoToken);
this._operations = new StaleOperations(this.options);
this._logger.info(chalk.yellow('Starting the stale action process...'));
if (this.options.debugOnly) {
this._logger.warning(chalk.yellowBright('Executing in debug mode!'));
this._logger.warning(
'Executing in debug mode. Debug output will be written but no issues will be processed.'
chalk.yellowBright(
'The debug output will be written but no issues/PRs will be processed.'
)
);
}
if (this.options.enableStatistics) {
this._statistics = new Statistics(this.options);
this._statistics = new Statistics();
}
}
async processIssues(page = 1): Promise<number> {
async processIssues(page: Readonly<number> = 1): Promise<number> {
// get the next batch of issues
const issues: Issue[] = await this.getIssues(page);
const actor: string = await this.getActor();
if (issues.length <= 0) {
this._logger.info('---');
this._statistics?.setOperationsLeft(this._operationsLeft).logStats();
this._logger.info('No more issues found to process. Exiting.');
this._logger.info(
chalk.green('No more issues found to process. Exiting...')
);
this._statistics
?.setRemainingOperations(this._operations.getRemainingOperationsCount())
.logStats();
return this._operationsLeft;
return this._operations.getRemainingOperationsCount();
} else {
this._logger.info(
chalk.yellow(
`Processing the batch of issues ${chalk.cyan(
`#${page}`
)} containing ${chalk.cyan(issues.length)} issue${
issues.length > 1 ? 's' : ''
}...`
)
);
}
for (const issue of issues.values()) {
const issueLogger: IssueLogger = new IssueLogger(issue);
this._statistics?.incrementProcessedIssuesCount();
this._statistics?.incrementProcessedItemsCount(issue);
issueLogger.info(`Found this $$type last updated ${issue.updated_at}`);
issueLogger.info(
`Found this $$type last updated at: ${chalk.cyan(issue.updated_at)}`
);
// calculate string based messages for this issue
const staleMessage: string = issue.isPullRequest
@@ -101,7 +147,11 @@ export class IssuesProcessor {
if (onlyLabels.length > 0) {
issueLogger.info(
`The option "onlyLabels" was specified to only processed the issues and pull requests with all those labels (${onlyLabels.length})`
`The option ${issueLogger.createOptionLink(
Option.OnlyLabels
)} was specified to only process issues and pull requests with all those labels (${chalk.cyan(
onlyLabels.length
)})`
);
const hasAllWhitelistedLabels: boolean = onlyLabels.every(
@@ -112,37 +162,64 @@ export class IssuesProcessor {
if (!hasAllWhitelistedLabels) {
issueLogger.info(
chalk.white('└──'),
`Skipping this $$type because it doesn't have all the required labels`
);
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process issues without all of the required labels
} else {
issueLogger.info(
`All the required labels are present on this $$type. Continuing the process`
chalk.white('├──'),
`All the required labels are present on this $$type`
);
issueLogger.info(
chalk.white('└──'),
`Continuing the process for this $$type`
);
}
} else {
issueLogger.info(
`The option "onlyLabels" was not specified. Continuing the process for this $$type`
`The option ${issueLogger.createOptionLink(
Option.OnlyLabels
)} was not specified`
);
issueLogger.info(
chalk.white('└──'),
`Continuing the process for this $$type`
);
}
issueLogger.info(`Days before $$type stale: ${daysBeforeStale}`);
issueLogger.info(
`Days before $$type stale: ${chalk.cyan(daysBeforeStale)}`
);
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
if (!staleMessage && shouldMarkAsStale) {
issueLogger.info(`Skipping $$type due to empty stale message`);
issueLogger.info(
`Skipping this $$type because it should be marked as stale based on the option ${issueLogger.createOptionLink(
this._getDaysBeforeStaleUsedOptionName(issue)
)} (${chalk.cyan(
daysBeforeStale
)}) but the option ${issueLogger.createOptionLink(
IssuesProcessor._getStaleMessageUsedOptionName(issue)
)} is not set`
);
IssuesProcessor._endIssueProcessing(issue);
continue;
}
if (issue.state === 'closed') {
issueLogger.info(`Skipping $$type because it is closed`);
continue; // don't process closed issues
issueLogger.info(`Skipping this $$type because it is closed`);
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process closed issues
}
if (issue.locked) {
issueLogger.info(`Skipping $$type because it is locked`);
continue; // don't process locked issues
issueLogger.info(`Skipping this $$type because it is locked`);
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process locked issues
}
// Try to remove the close label when not close/locked issue or PR
@@ -153,31 +230,35 @@ export class IssuesProcessor {
const createdAt: Date = new Date(issue.created_at);
issueLogger.info(
`A start date was specified for the ${getHumanizedDate(startDate)} (${
this.options.startDate
})`
`A start date was specified for the ${getHumanizedDate(
startDate
)} (${chalk.cyan(this.options.startDate)})`
);
// Expecting that GitHub will always set a creation date on the issues and PRs
// But you never know!
if (!isValidDate(createdAt)) {
throw new Error(
`Invalid issue field: "created_at". Expected a valid date`
IssuesProcessor._endIssueProcessing(issue);
core.setFailed(
new Error(
`Invalid issue field: "created_at". Expected a valid date`
)
);
}
issueLogger.info(
`$$type created the ${getHumanizedDate(createdAt)} (${
`$$type created the ${getHumanizedDate(createdAt)} (${chalk.cyan(
issue.created_at
})`
)})`
);
if (!isDateMoreRecentThan(createdAt, startDate)) {
issueLogger.info(
`Skipping $$type because it was created before the specified start date`
`Skipping this $$type because it was created before the specified start date`
);
continue; // don't process issues which were created before the start date
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process issues which were created before the start date
}
}
@@ -203,60 +284,117 @@ export class IssuesProcessor {
await this._removeStaleLabel(issue, staleLabel);
}
issueLogger.info(`Skipping $$type because it has an exempt label`);
continue; // don't process exempt issues
issueLogger.info(`Skipping this $$type because it has an exempt label`);
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process exempt issues
}
const anyOfLabels: string[] = wordsToList(this.options.anyOfLabels);
if (
anyOfLabels.length &&
!anyOfLabels.some((label: Readonly<string>): boolean =>
isLabeled(issue, label)
)
) {
const anyOfLabels: string[] = wordsToList(this._getAnyOfLabels(issue));
if (anyOfLabels.length > 0) {
issueLogger.info(
`Skipping $$type because it does not have any of the required labels`
`The option ${issueLogger.createOptionLink(
Option.AnyOfLabels
)} was specified to only process the issues and pull requests with one of those labels (${chalk.cyan(
anyOfLabels.length
)})`
);
const hasOneOfWhitelistedLabels: boolean = anyOfLabels.some(
(label: Readonly<string>): boolean => {
return isLabeled(issue, label);
}
);
if (!hasOneOfWhitelistedLabels) {
issueLogger.info(
chalk.white('└──'),
`Skipping this $$type because it doesn't have one of the required labels`
);
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process issues without any of the required labels
} else {
issueLogger.info(
chalk.white('├──'),
`One of the required labels is present on this $$type`
);
issueLogger.info(
chalk.white('└──'),
`Continuing the process for this $$type`
);
}
} else {
issueLogger.info(
`The option ${issueLogger.createOptionLink(
Option.AnyOfLabels
)} was not specified`
);
issueLogger.info(
chalk.white('└──'),
`Continuing the process for this $$type`
);
continue; // don't process issues without any of the required labels
}
const milestones: Milestones = new Milestones(this.options, issue);
if (milestones.shouldExemptMilestones()) {
issueLogger.info(
`Skipping $$type because it has an exempted milestone`
);
continue; // don't process exempt milestones
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process exempt milestones
}
const assignees: Assignees = new Assignees(this.options, issue);
if (assignees.shouldExemptAssignees()) {
continue; // don't process exempt assignees
IssuesProcessor._endIssueProcessing(issue);
continue; // Don't process exempt assignees
}
// should this issue be marked stale?
// Should this issue be marked stale?
const shouldBeStale = !IssuesProcessor._updatedSince(
issue.updated_at,
daysBeforeStale
);
// determine if this issue needs to be marked stale first
if (!issue.isStale && shouldBeStale && shouldMarkAsStale) {
issueLogger.info(
`Marking $$type 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);
issue.isStale = true; // this issue is now considered stale
} else if (!issue.isStale) {
issueLogger.info(
`Not marking as stale: shouldBeStale=${shouldBeStale}, shouldMarkAsStale=${shouldMarkAsStale}`
);
// Determine if this issue needs to be marked stale first
if (!issue.isStale) {
issueLogger.info(`This $$type is not stale`);
const updatedAtDate: Date = new Date(issue.updated_at);
if (shouldBeStale) {
issueLogger.info(
`This $$type should be stale based on the last update date the ${getHumanizedDate(
updatedAtDate
)} (${chalk.cyan(issue.updated_at)})`
);
if (shouldMarkAsStale) {
issueLogger.info(
`This $$type should be marked as stale based on the option ${issueLogger.createOptionLink(
this._getDaysBeforeStaleUsedOptionName(issue)
)} (${chalk.cyan(daysBeforeStale)})`
);
await this._markStale(issue, staleMessage, staleLabel, skipMessage);
issue.isStale = true; // This issue is now considered stale
issueLogger.info(`This $$type is now stale`);
} else {
issueLogger.info(
`This $$type should not be marked as stale based on the option ${issueLogger.createOptionLink(
this._getDaysBeforeStaleUsedOptionName(issue)
)} (${chalk.cyan(daysBeforeStale)})`
);
}
} else {
issueLogger.info(
`This $$type should not be stale based on the last update date the ${getHumanizedDate(
updatedAtDate
)} (${chalk.cyan(issue.updated_at)})`
);
}
}
// process the issue if it was marked stale
// Process the issue if it was marked stale
if (issue.isStale) {
issueLogger.info(`Found a stale $$type`);
issueLogger.info(`This $$type is already stale`);
await this._processStaleIssue(
issue,
staleLabel,
@@ -265,28 +403,44 @@ export class IssuesProcessor {
closeLabel
);
}
IssuesProcessor._endIssueProcessing(issue);
}
if (this._operationsLeft <= 0) {
if (!this._operations.hasRemainingOperations()) {
this._logger.warning(
'Reached max number of operations to process. Exiting.'
chalk.yellowBright('No more operations left! Exiting...')
);
this._logger.warning(
chalk.yellowBright(
`If you think that not enough issues were processed you could try to increase the quantity related to the ${this._logger.createOptionLink(
Option.OperationsPerRun
)} option which is currently set to ${chalk.cyan(
this.options.operationsPerRun
)}`
)
);
return 0;
}
// do the next batch
this._logger.info(
chalk.green(`Batch ${chalk.cyan(`#${page}`)} processed.`)
);
// Do the next batch
return this.processIssues(page + 1);
}
// grab comments for an issue since a given date
// Grab comments for an issue since a given date
async listIssueComments(
issueNumber: number,
sinceDate: string
issueNumber: Readonly<number>,
sinceDate: Readonly<string>
): Promise<IComment[]> {
// find any comments since date on the given issue
// Find any comments since date on the given issue
try {
this._operationsLeft -= 1;
this._statistics?.incrementFetchedIssuesCommentsCount();
this._operations.consumeOperation();
this._statistics?.incrementFetchedItemsCommentsCount();
const comments = await this.client.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -305,11 +459,9 @@ export class IssuesProcessor {
let actor;
try {
this._operationsLeft -= 1;
this._operations.consumeOperation();
actor = await this.client.users.getAuthenticated();
this._logger.info('Actor is from client')
} catch (error) {
this._logger.info('Actor is from context')
return context.actor;
}
@@ -323,8 +475,7 @@ export class IssuesProcessor {
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
try {
this._operationsLeft -= 1;
this._statistics?.incrementFetchedIssuesCount();
this._operations.consumeOperation();
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
{
owner: context.repo.owner,
@@ -335,6 +486,7 @@ export class IssuesProcessor {
page
}
);
this._statistics?.incrementFetchedItemsCount(issueResult.data.length);
return issueResult.data.map(
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
@@ -353,10 +505,10 @@ export class IssuesProcessor {
): Promise<string | undefined> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Checking for label on $$type`);
issueLogger.info(`Checking for label on this $$type`);
this._operationsLeft -= 1;
this._statistics?.incrementFetchedIssuesEventsCount();
this._consumeIssueOperation(issue);
this._statistics?.incrementFetchedItemsEventsCount();
const options = this.client.issues.listEvents.endpoint.merge({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -390,31 +542,38 @@ export class IssuesProcessor {
const issueLogger: IssueLogger = new IssueLogger(issue);
const markedStaleOn: string =
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
issueLogger.info(`$$type marked stale on: ${chalk.cyan(markedStaleOn)}`);
const issueHasComments: boolean = await this._hasCommentsSince(
issue,
markedStaleOn,
actor
);
issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
issueLogger.info(
`$$type has been commented on: ${chalk.cyan(issueHasComments)}`
);
const isPr: boolean = isPullRequest(issue);
const daysBeforeClose: number = isPr
const daysBeforeClose: number = issue.isPullRequest
? this._getDaysBeforePrClose()
: this._getDaysBeforeIssueClose();
issueLogger.info(`Days before $$type close: ${daysBeforeClose}`);
issueLogger.info(
`Days before $$type close: ${chalk.cyan(daysBeforeClose)}`
);
const issueHasUpdate: boolean = IssuesProcessor._updatedSince(
issue.updated_at,
daysBeforeClose
);
issueLogger.info(`$$type has been updated: ${issueHasUpdate}`);
issueLogger.info(`$$type has been updated: ${chalk.cyan(issueHasUpdate)}`);
// should we un-stale this issue?
if (this.options.removeStaleWhenUpdated && issueHasComments) {
if (this._shouldRemoveStaleWhenUpdated(issue) && issueHasComments) {
await this._removeStaleLabel(issue, staleLabel);
issueLogger.info(`Skipping the process since the $$type is now un-stale`);
return; // nothing to do because it is no longer stale
}
// now start closing logic
@@ -424,13 +583,17 @@ export class IssuesProcessor {
if (!issueHasComments && !issueHasUpdate) {
issueLogger.info(
`Closing $$type because it was last updated on ${issue.updated_at}`
`Closing $$type because it was last updated on! ${chalk.cyan(
issue.updated_at
)}`
);
await this._closeIssue(issue, closeMessage, closeLabel);
if (this.options.deleteBranch && issue.pull_request) {
issueLogger.info(
`Deleting branch for as delete-branch option was specified`
`Deleting the branch the option ${issueLogger.createOptionLink(
Option.DeleteBranch
)} was specified`
);
await this._deleteBranch(issue);
this.deletedBranchIssues.push(issue);
@@ -450,7 +613,9 @@ export class IssuesProcessor {
): Promise<boolean> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Checking for comments on $$type since ${sinceDate}`);
issueLogger.info(
`Checking for comments on $$type since: ${chalk.cyan(sinceDate)}`
);
if (!sinceDate) {
return true;
@@ -459,13 +624,14 @@ export class IssuesProcessor {
// find any comments since the date
const comments = await this.listIssueComments(issue.number, sinceDate);
issueLogger.info('Not filtering for debug purposes')
const filteredComments = comments.filter(
comment => comment.user.type === 'User' && comment.user.login !== actor
);
issueLogger.info(
`Comments not made by actor or another bot: ${filteredComments.length}`
`Comments not made by actor or another bot: ${chalk.cyan(
filteredComments.length
)}`
);
// if there are any user comments returned
@@ -481,7 +647,7 @@ export class IssuesProcessor {
): Promise<void> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Marking $$type as stale`);
issueLogger.info(`Marking this $$type as stale`);
this.staleIssues.push(issue);
// if the issue is being marked stale, the updated date should be changed to right now
@@ -495,8 +661,8 @@ export class IssuesProcessor {
if (!skipMessage) {
try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedComment();
this._consumeIssueOperation(issue);
this._statistics?.incrementAddedItemsComment(issue);
await this.client.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -504,14 +670,14 @@ export class IssuesProcessor {
body: staleMessage
});
} catch (error) {
issueLogger.error(`Error creating a comment: ${error.message}`);
issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedLabel();
this._statistics?.incrementStaleIssuesCount();
this._consumeIssueOperation(issue);
this._statistics?.incrementAddedItemsLabel(issue);
this._statistics?.incrementStaleItemsCount(issue);
await this.client.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -519,7 +685,7 @@ export class IssuesProcessor {
labels: [staleLabel]
});
} catch (error) {
issueLogger.error(`Error adding a label: ${error.message}`);
issueLogger.error(`Error when adding a label: ${error.message}`);
}
}
@@ -540,8 +706,8 @@ export class IssuesProcessor {
if (closeMessage) {
try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedComment();
this._consumeIssueOperation(issue);
this._statistics?.incrementAddedItemsComment(issue);
await this.client.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -549,14 +715,14 @@ export class IssuesProcessor {
body: closeMessage
});
} catch (error) {
issueLogger.error(`Error creating a comment: ${error.message}`);
issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
if (closeLabel) {
try {
this._operationsLeft -= 1;
this._statistics?.incrementAddedLabel();
this._consumeIssueOperation(issue);
this._statistics?.incrementAddedItemsLabel(issue);
await this.client.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -564,13 +730,13 @@ export class IssuesProcessor {
labels: [closeLabel]
});
} catch (error) {
issueLogger.error(`Error adding a label: ${error.message}`);
issueLogger.error(`Error when adding a label: ${error.message}`);
}
}
try {
this._operationsLeft -= 1;
this._statistics?.incrementClosedIssuesCount();
this._consumeIssueOperation(issue);
this._statistics?.incrementClosedItemsCount(issue);
await this.client.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -578,7 +744,7 @@ export class IssuesProcessor {
state: 'closed'
});
} catch (error) {
issueLogger.error(`Error updating this $$type: ${error.message}`);
issueLogger.error(`Error when updating this $$type: ${error.message}`);
}
}
@@ -592,7 +758,7 @@ export class IssuesProcessor {
}
try {
this._operationsLeft -= 1;
this._consumeIssueOperation(issue);
this._statistics?.incrementFetchedPullRequestsCount();
const pullRequest = await this.client.pulls.get({
owner: context.repo.owner,
@@ -602,7 +768,7 @@ export class IssuesProcessor {
return pullRequest.data;
} catch (error) {
issueLogger.error(`Error getting this $$type: ${error.message}`);
issueLogger.error(`Error when getting this $$type: ${error.message}`);
}
}
@@ -616,7 +782,7 @@ export class IssuesProcessor {
if (!pullRequest) {
issueLogger.info(
`Not deleting branch as pull request not found for this $$type`
`Not deleting this branch as no pull request was found for this $$type`
);
return;
}
@@ -626,10 +792,12 @@ export class IssuesProcessor {
}
const branch = pullRequest.head.ref;
issueLogger.info(`Deleting branch ${branch} from closed $$type`);
issueLogger.info(
`Deleting the branch "${chalk.cyan(branch)}" from closed $$type`
);
try {
this._operationsLeft -= 1;
this._consumeIssueOperation(issue);
this._statistics?.incrementDeletedBranchesCount();
await this.client.git.deleteRef({
owner: context.repo.owner,
@@ -638,34 +806,40 @@ export class IssuesProcessor {
});
} catch (error) {
issueLogger.error(
`Error deleting branch ${branch} from $$type: ${error.message}`
`Error when deleting the branch "${chalk.cyan(branch)}" from $$type: ${
error.message
}`
);
}
}
// Remove a label from an issue
// Remove a label from an issue or a pull request
private async _removeLabel(issue: Issue, label: string): Promise<void> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Removing label "${label}" from $$type`);
issueLogger.info(
`Removing the label "${chalk.cyan(label)}" from this $$type...`
);
this.removedLabelIssues.push(issue);
// @todo remove the debug only to be able to test the code below
if (this.options.debugOnly) {
return;
}
try {
this._operationsLeft -= 1;
this._statistics?.incrementDeletedLabelsCount();
this._consumeIssueOperation(issue);
this._statistics?.incrementDeletedItemsLabelsCount(issue);
await this.client.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: label
});
issueLogger.info(`The label "${chalk.cyan(label)}" was removed`);
} catch (error) {
issueLogger.error(`Error removing a label: ${error.message}`);
issueLogger.error(
`Error when removing the label: "${chalk.cyan(error.message)}"`
);
}
}
@@ -707,6 +881,36 @@ export class IssuesProcessor {
return this.options.onlyLabels;
}
private _getAnyOfLabels(issue: Issue): string {
if (issue.isPullRequest) {
if (this.options.anyOfPrLabels !== '') {
return this.options.anyOfPrLabels;
}
} else {
if (this.options.anyOfIssueLabels !== '') {
return this.options.anyOfIssueLabels;
}
}
return this.options.anyOfLabels;
}
private _shouldRemoveStaleWhenUpdated(issue: Issue): boolean {
if (issue.isPullRequest) {
if (isBoolean(this.options.removePrStaleWhenUpdated)) {
return this.options.removePrStaleWhenUpdated;
}
return this.options.removeStaleWhenUpdated;
}
if (isBoolean(this.options.removeIssueStaleWhenUpdated)) {
return this.options.removeIssueStaleWhenUpdated;
}
return this.options.removeStaleWhenUpdated;
}
private async _removeStaleLabel(
issue: Issue,
staleLabel: Readonly<string>
@@ -718,7 +922,7 @@ export class IssuesProcessor {
);
await this._removeLabel(issue, staleLabel);
this._statistics?.incrementUndoStaleIssuesCount();
this._statistics?.incrementUndoStaleItemsCount(issue);
}
private async _removeCloseLabel(
@@ -739,11 +943,45 @@ export class IssuesProcessor {
if (isLabeled(issue, closeLabel)) {
issueLogger.info(
`The $$type has a close label "${closeLabel}". Removing the close label...`
`The $$type has a close label "${chalk.cyan(
closeLabel
)}". Removing the close label...`
);
await this._removeLabel(issue, closeLabel);
this._statistics?.incrementDeletedCloseLabelsCount();
this._statistics?.incrementDeletedCloseItemsLabelsCount(issue);
}
}
private _consumeIssueOperation(issue: Readonly<Issue>): void {
this._operations.consumeOperation();
issue.operations.consumeOperation();
}
private _getDaysBeforeStaleUsedOptionName(
issue: Readonly<Issue>
):
| Option.DaysBeforeStale
| Option.DaysBeforeIssueStale
| Option.DaysBeforePrStale {
return issue.isPullRequest
? this._getDaysBeforePrStaleUsedOptionName()
: this._getDaysBeforeIssueStaleUsedOptionName();
}
private _getDaysBeforeIssueStaleUsedOptionName():
| Option.DaysBeforeStale
| Option.DaysBeforeIssueStale {
return isNaN(this.options.daysBeforeIssueStale)
? Option.DaysBeforeStale
: Option.DaysBeforeIssueStale;
}
private _getDaysBeforePrStaleUsedOptionName():
| Option.DaysBeforeStale
| Option.DaysBeforePrStale {
return isNaN(this.options.daysBeforePrStale)
? Option.DaysBeforeStale
: Option.DaysBeforePrStale;
}
}

View File

@@ -1,4 +1,4 @@
import * as core from '@actions/core';
import chalk from 'chalk';
import {Issue} from '../issue';
import {Logger} from './logger';
@@ -15,23 +15,24 @@ import {Logger} from './logger';
* @example
* warning('The $$type will stale') => "The pull request will stale"
*/
export class IssueLogger implements Logger {
export class IssueLogger extends Logger {
private readonly _issue: Issue;
constructor(issue: Issue) {
super();
this._issue = issue;
}
warning(message: Readonly<string>): void {
core.warning(this._format(message));
warning(...message: string[]): void {
super.warning(this._format(...message));
}
info(message: Readonly<string>): void {
core.info(this._format(message));
info(...message: string[]): void {
super.info(this._format(...message));
}
error(message: Readonly<string>): void {
core.error(this._format(message));
error(...message: string[]): void {
super.error(this._format(...message));
}
private _replaceTokens(message: Readonly<string>): string {
@@ -51,14 +52,28 @@ export class IssueLogger implements Logger {
}
private _prefixWithIssueNumber(message: Readonly<string>): string {
return `[#${this._getIssueNumber()}] ${message}`;
return `${this._getPrefix()} ${message}`;
}
private _getIssueNumber(): number {
return this._issue.number;
}
private _format(message: Readonly<string>): string {
return this._prefixWithIssueNumber(this._replaceTokens(message));
private _format(...message: string[]): string {
return this._prefixWithIssueNumber(this._replaceTokens(message.join(' ')));
}
private _getPrefix(): string {
return this._issue.isPullRequest
? this._getPullRequestPrefix()
: this._getIssuePrefix();
}
private _getIssuePrefix(): string {
return chalk.red(`[#${this._getIssueNumber()}]`);
}
private _getPullRequestPrefix(): string {
return chalk.blue(`[#${this._getIssueNumber()}]`);
}
}

View File

@@ -1,15 +1,28 @@
import * as core from '@actions/core';
import chalk from 'chalk';
import terminalLink from 'terminal-link';
import {Option} from '../../enums/option';
export class Logger {
warning(message: Readonly<string>): void {
core.warning(message);
warning(...message: string[]): void {
core.warning(chalk.whiteBright(...message));
}
info(message: Readonly<string>): void {
core.info(message);
info(...message: string[]): void {
core.info(chalk.whiteBright(...message));
}
error(message: Readonly<string>): void {
core.error(message);
error(...message: string[]): void {
core.error(chalk.whiteBright(...message));
}
createLink(name: Readonly<string>, link: Readonly<string>): string {
return terminalLink(name, link);
}
createOptionLink(option: Readonly<Option>): string {
return chalk.magenta(
this.createLink(option, `https://github.com/actions/stale#${option}`)
);
}
}

View File

@@ -12,7 +12,7 @@ describe('Milestones', (): void => {
let issueInterface: IIssue;
beforeEach((): void => {
optionsInterface = {...DefaultProcessorOptions};
optionsInterface = {...DefaultProcessorOptions, exemptAllMilestones: false};
issueInterface = generateIIssue();
});
@@ -285,21 +285,49 @@ describe('Milestones', (): void => {
});
});
});
});
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 => {
describe('when the given options are configured to exempt all milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptMilestones = '';
optionsInterface.exemptAllMilestones = true;
});
describe('when the given options are not configured to exempt a pull request milestone', (): void => {
describe('when the given issue does not have a milestone', (): void => {
beforeEach((): void => {
optionsInterface.exemptPrMilestones = '';
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-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 not configured to exempt all issue milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllIssueMilestones = false;
});
describe('when the given issue does not have a milestone', (): void => {
@@ -319,6 +347,98 @@ describe('Milestones', (): void => {
});
describe('when the given issue does have a 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 options are configured to exempt all issue milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllIssueMilestones = true;
});
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-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 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 pull request 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 pull request does have a milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-title'
@@ -342,7 +462,7 @@ describe('Milestones', (): void => {
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
});
describe('when the given issue does not have a milestone', (): void => {
describe('when the given pull request does not have a milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = undefined;
});
@@ -358,7 +478,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
describe('when the given pull request does have a milestone different than the exempt pull request milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-title'
@@ -376,7 +496,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
describe('when the given pull request does have a milestone equaling the exempt pull request milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-exempt-pr-milestone'
@@ -406,7 +526,7 @@ describe('Milestones', (): void => {
optionsInterface.exemptPrMilestones = '';
});
describe('when the given issue does not have a milestone', (): void => {
describe('when the given pull request does not have a milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = undefined;
});
@@ -422,7 +542,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
describe('when the given pull request does have a milestone different than the exempt milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-title'
@@ -440,7 +560,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
describe('when the given pull request does have a milestone equaling the exempt milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-exempt-milestone'
@@ -464,7 +584,7 @@ describe('Milestones', (): void => {
optionsInterface.exemptPrMilestones = 'dummy-exempt-pr-milestone';
});
describe('when the given issue does not have a milestone', (): void => {
describe('when the given pull request does not have a milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = undefined;
});
@@ -480,7 +600,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone different than the exempt pull request milestone', (): void => {
describe('when the given pull request does have a milestone different than the exempt pull request milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-title'
@@ -498,7 +618,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone equaling the exempt pull request milestone', (): void => {
describe('when the given pull request does have a milestone equaling the exempt pull request milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-exempt-pr-milestone'
@@ -516,7 +636,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone different than the exempt milestone', (): void => {
describe('when the given pull request does have a milestone different than the exempt milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-title'
@@ -534,7 +654,7 @@ describe('Milestones', (): void => {
});
});
describe('when the given issue does have a milestone equaling the exempt milestone', (): void => {
describe('when the given pull request does have a milestone equaling the exempt milestone', (): void => {
beforeEach((): void => {
issueInterface.milestone = {
title: 'dummy-exempt-milestone'
@@ -553,6 +673,126 @@ describe('Milestones', (): void => {
});
});
});
describe('when the given options are configured to exempt all milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllMilestones = true;
});
describe('when the given pull request 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 pull request does have a 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 not configured to exempt all pull request milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllPrMilestones = false;
});
describe('when the given pull request 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 pull request does have a 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 options are configured to exempt all pull request milestones', (): void => {
beforeEach((): void => {
optionsInterface.exemptAllPrMilestones = true;
});
describe('when the given pull request 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 pull request does have a 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);
});
});
});
});
});
});
});

View File

@@ -1,7 +1,10 @@
import chalk from 'chalk';
import deburr from 'lodash.deburr';
import {Option} from '../enums/option';
import {wordsToList} from '../functions/words-to-list';
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {Issue} from './issue';
import {IssueLogger} from './loggers/issue-logger';
type CleanMilestone = string;
@@ -12,42 +15,179 @@ export class Milestones {
private readonly _options: IIssuesProcessorOptions;
private readonly _issue: Issue;
private readonly _issueLogger: IssueLogger;
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
this._options = options;
this._issue = issue;
this._issueLogger = new IssueLogger(issue);
}
shouldExemptMilestones(): boolean {
if (!this._issue.milestone) {
this._issueLogger.info('This $$type has no milestone');
this._logSkip();
return false;
}
if (this._shouldExemptAllMilestones()) {
this._issueLogger.info(
chalk.white('└──'),
'Skipping this $$type because it has a milestone'
);
return true;
}
const exemptMilestones: string[] = this._getExemptMilestones();
return exemptMilestones.some((exemptMilestone: Readonly<string>): boolean =>
this._hasMilestone(exemptMilestone)
if (exemptMilestones.length === 0) {
this._issueLogger.info(
chalk.white('├──'),
`No milestone option was specified to skip the stale process for this $$type`
);
this._logSkip();
return false;
}
this._issueLogger.info(
chalk.white('├──'),
`Found ${chalk.cyan(exemptMilestones.length)} milestone${
exemptMilestones.length > 1 ? 's' : ''
} that can exempt stale on this $$type`
);
const hasExemptMilestone: boolean = exemptMilestones.some(
(exemptMilestone: Readonly<string>): boolean =>
this._hasMilestone(exemptMilestone)
);
if (!hasExemptMilestone) {
this._issueLogger.info(
chalk.white('├──'),
'No milestone on this $$type can exempt the stale process'
);
this._logSkip();
} else {
this._issueLogger.info(
chalk.white('└──'),
'Skipping this $$type because it has an exempt milestone'
);
}
return hasExemptMilestone;
}
private _getExemptMilestones(): string[] {
return wordsToList(
this._issue.isPullRequest
? this._getExemptPullRequestMilestones()
: this._getExemptIssueMilestones()
return this._issue.isPullRequest
? this._getExemptPullRequestMilestones()
: this._getExemptIssueMilestones();
}
private _getExemptIssueMilestones(): string[] {
if (this._options.exemptIssueMilestones === '') {
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptIssueMilestones
)} is disabled. No specific milestone can skip the stale process for this $$type`
);
if (this._options.exemptMilestones === '') {
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptMilestones
)} is disabled. No specific milestone can skip the stale process for this $$type`
);
return [];
}
const exemptMilestones: string[] = wordsToList(
this._options.exemptMilestones
);
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptMilestones
)} is set. ${chalk.cyan(exemptMilestones.length)} milestone${
exemptMilestones.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
return exemptMilestones;
}
const exemptMilestones: string[] = wordsToList(
this._options.exemptIssueMilestones
);
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptIssueMilestones
)} is set. ${chalk.cyan(exemptMilestones.length)} milestone${
exemptMilestones.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
return exemptMilestones;
}
private _getExemptIssueMilestones(): string {
return this._options.exemptIssueMilestones !== ''
? this._options.exemptIssueMilestones
: this._options.exemptMilestones;
}
private _getExemptPullRequestMilestones(): string[] {
if (this._options.exemptPrMilestones === '') {
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptPrMilestones
)} is disabled. No specific milestone can skip the stale process for this $$type`
);
private _getExemptPullRequestMilestones(): string {
return this._options.exemptPrMilestones !== ''
? this._options.exemptPrMilestones
: this._options.exemptMilestones;
if (this._options.exemptMilestones === '') {
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptMilestones
)} is disabled. No specific milestone can skip the stale process for this $$type`
);
return [];
}
const exemptMilestones: string[] = wordsToList(
this._options.exemptMilestones
);
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptMilestones
)} is set. ${chalk.cyan(exemptMilestones.length)} milestone${
exemptMilestones.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
return exemptMilestones;
}
const exemptMilestones: string[] = wordsToList(
this._options.exemptPrMilestones
);
this._issueLogger.info(
chalk.white('├──'),
`The option ${this._issueLogger.createOptionLink(
Option.ExemptPrMilestones
)} is set. ${chalk.cyan(exemptMilestones.length)} milestone${
exemptMilestones.length === 1 ? '' : 's'
} can skip the stale process for this $$type`
);
return exemptMilestones;
}
private _hasMilestone(milestone: Readonly<string>): boolean {
@@ -55,10 +195,22 @@ export class Milestones {
return false;
}
return (
Milestones._cleanMilestone(milestone) ===
Milestones._cleanMilestone(this._issue.milestone.title)
const cleanMilestone: CleanMilestone = Milestones._cleanMilestone(
milestone
);
const isSameMilestone: boolean =
cleanMilestone ===
Milestones._cleanMilestone(this._issue.milestone.title);
if (isSameMilestone) {
this._issueLogger.info(
chalk.white('├──'),
`The milestone "${milestone}" is set on this $$type and is an exempt milestone`
);
}
return isSameMilestone;
}
private _shouldExemptAllMilestones(): boolean {
@@ -73,21 +225,69 @@ export class Milestones {
private _shouldExemptAllIssueMilestones(): boolean {
if (this._options.exemptAllIssueMilestones === true) {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllIssueMilestones
)} is enabled. Any milestone on this $$type will skip the stale process`
);
return true;
} else if (this._options.exemptAllIssueMilestones === false) {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllIssueMilestones
)} is disabled. Only some specific milestones on this $$type will skip the stale process`
);
return false;
}
this._logExemptAllMilestonesOption();
return this._options.exemptAllMilestones;
}
private _shouldExemptAllPullRequestMilestones(): boolean {
if (this._options.exemptAllPrMilestones === true) {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllPrMilestones
)} is enabled. Any milestone on this $$type will skip the stale process`
);
return true;
} else if (this._options.exemptAllPrMilestones === false) {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllPrMilestones
)} is disabled. Only some specific milestones on this $$type will skip the stale process`
);
return false;
}
this._logExemptAllMilestonesOption();
return this._options.exemptAllMilestones;
}
private _logExemptAllMilestonesOption(): void {
if (this._options.exemptAllMilestones) {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllMilestones
)} is enabled. Any milestone on this $$type will skip the stale process`
);
} else {
this._issueLogger.info(
`The option ${this._issueLogger.createOptionLink(
Option.ExemptAllMilestones
)} is disabled. Only some specific milestones on this $$type will skip the stale process`
);
}
}
private _logSkip(): void {
this._issueLogger.info(chalk.white('└──'), 'Skip the milestones checks');
}
}

View File

@@ -0,0 +1,49 @@
import {Operations} from './operations';
describe('Operations', (): void => {
let operations: Operations;
describe('consumeOperation()', (): void => {
beforeEach((): void => {
operations = new Operations();
});
it('should increase the count of operation consume by 1', (): void => {
expect.assertions(1);
operations.consumeOperation();
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(1);
});
});
describe('consumeOperations()', (): void => {
beforeEach((): void => {
operations = new Operations();
});
it('should increase the count of operation consume by the provided quantity', (): void => {
expect.assertions(1);
operations.consumeOperations(8);
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(8);
});
});
describe('getConsumedOperationsCount()', (): void => {
beforeEach((): void => {
operations = new Operations();
});
it('should return 0 by default', (): void => {
expect.assertions(1);
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(0);
});
});
});

17
src/classes/operations.ts Normal file
View File

@@ -0,0 +1,17 @@
export class Operations {
protected _operationsConsumed = 0;
consumeOperation(): Operations {
return this.consumeOperations(1);
}
consumeOperations(quantity: Readonly<number>): Operations {
this._operationsConsumed += quantity;
return this;
}
getConsumedOperationsCount(): number {
return this._operationsConsumed;
}
}

View File

@@ -0,0 +1,134 @@
import {DefaultProcessorOptions} from '../../__tests__/constants/default-processor-options';
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {StaleOperations} from './stale-operations';
interface IHasRemainingOperationsMatrix {
operationsPerRun: number;
consumeOperations: number;
hasRemainingOperations: number;
}
interface IGetRemainingOperationsCountMatrix {
operationsPerRun: number;
consumeOperations: number;
getRemainingOperationsCount: number;
}
describe('StaleOperations', (): void => {
let operations: StaleOperations;
let options: IIssuesProcessorOptions;
beforeEach((): void => {
options = {...DefaultProcessorOptions};
});
describe('consumeOperation()', (): void => {
beforeEach((): void => {
operations = new StaleOperations(options);
});
it('should increase the count of operation consume by 1', (): void => {
expect.assertions(1);
operations.consumeOperation();
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(1);
});
});
describe('consumeOperations()', (): void => {
beforeEach((): void => {
operations = new StaleOperations(options);
});
it('should increase the count of operation consume by the provided quantity', (): void => {
expect.assertions(1);
operations.consumeOperations(8);
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(8);
});
});
describe('getConsumedOperationsCount()', (): void => {
beforeEach((): void => {
operations = new StaleOperations(options);
});
it('should return 0 by default', (): void => {
expect.assertions(1);
const result = operations.getConsumedOperationsCount();
expect(result).toStrictEqual(0);
});
});
describe('hasRemainingOperations()', (): void => {
beforeEach((): void => {
operations = new StaleOperations(options);
});
describe.each`
operationsPerRun | consumeOperations | hasRemainingOperations
${1} | ${1} | ${false}
${2} | ${1} | ${true}
`(
'when the operations per run is $operationsPerRun and $consumeOperations operations were consumed',
({
operationsPerRun,
consumeOperations,
hasRemainingOperations
}: IHasRemainingOperationsMatrix): void => {
beforeEach((): void => {
options.operationsPerRun = operationsPerRun;
operations = new StaleOperations(options);
});
it(`should return ${hasRemainingOperations}`, (): void => {
expect.assertions(1);
operations.consumeOperations(consumeOperations);
const result = operations.hasRemainingOperations();
expect(result).toStrictEqual(hasRemainingOperations);
});
}
);
});
describe('getRemainingOperationsCount()', (): void => {
beforeEach((): void => {
operations = new StaleOperations(options);
});
describe.each`
operationsPerRun | consumeOperations | getRemainingOperationsCount
${1} | ${1} | ${0}
${2} | ${1} | ${1}
`(
'when the operations per run is $operationsPerRun and $consumeOperations operations were consumed',
({
operationsPerRun,
consumeOperations,
getRemainingOperationsCount
}: IGetRemainingOperationsCountMatrix): void => {
beforeEach((): void => {
options.operationsPerRun = operationsPerRun;
operations = new StaleOperations(options);
});
it(`should return ${getRemainingOperationsCount}`, (): void => {
expect.assertions(1);
operations.consumeOperations(consumeOperations);
const result = operations.getRemainingOperationsCount();
expect(result).toStrictEqual(getRemainingOperationsCount);
});
}
);
});
});

View File

@@ -0,0 +1,19 @@
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import {Operations} from './operations';
export class StaleOperations extends Operations {
private readonly _options: IIssuesProcessorOptions;
constructor(options: Readonly<IIssuesProcessorOptions>) {
super();
this._options = options;
}
hasRemainingOperations(): boolean {
return this._operationsConsumed < this._options.operationsPerRun;
}
getRemainingOperationsCount(): number {
return this._options.operationsPerRun - this._operationsConsumed;
}
}

View File

@@ -1,108 +1,155 @@
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
import chalk from 'chalk';
import {Issue} from './issue';
import {Logger} from './loggers/logger';
interface IGroupValue {
name: string;
count: number;
}
export class Statistics {
private readonly _logger: Logger = new Logger();
private readonly _options: IIssuesProcessorOptions;
private _processedIssuesCount = 0;
private _processedPullRequestsCount = 0;
private _staleIssuesCount = 0;
private _stalePullRequestsCount = 0;
private _undoStaleIssuesCount = 0;
private _undoStalePullRequestsCount = 0;
private _operationsCount = 0;
private _closedIssuesCount = 0;
private _deletedLabelsCount = 0;
private _deletedCloseLabelsCount = 0;
private _closedPullRequestsCount = 0;
private _deletedIssuesLabelsCount = 0;
private _deletedPullRequestsLabelsCount = 0;
private _deletedCloseIssuesLabelsCount = 0;
private _deletedClosePullRequestsLabelsCount = 0;
private _deletedBranchesCount = 0;
private _addedLabelsCount = 0;
private _addedCommentsCount = 0;
private _fetchedIssuesCount = 0;
private _fetchedIssuesEventsCount = 0;
private _fetchedIssuesCommentsCount = 0;
private _addedIssuesLabelsCount = 0;
private _addedPullRequestsLabelsCount = 0;
private _addedIssuesCommentsCount = 0;
private _addedPullRequestsCommentsCount = 0;
private _fetchedItemsCount = 0;
private _fetchedItemsEventsCount = 0;
private _fetchedItemsCommentsCount = 0;
private _fetchedPullRequestsCount = 0;
constructor(options: IIssuesProcessorOptions) {
this._options = options;
}
incrementProcessedIssuesCount(increment: Readonly<number> = 1): Statistics {
this._processedIssuesCount += increment;
return this;
}
incrementStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
this._staleIssuesCount += increment;
return this;
}
incrementUndoStaleIssuesCount(increment: Readonly<number> = 1): Statistics {
this._undoStaleIssuesCount += increment;
return this;
}
setOperationsLeft(operationsLeft: Readonly<number>): Statistics {
this._operationsCount = this._options.operationsPerRun - operationsLeft;
return this;
}
incrementClosedIssuesCount(increment: Readonly<number> = 1): Statistics {
this._closedIssuesCount += increment;
return this;
}
incrementDeletedLabelsCount(increment: Readonly<number> = 1): Statistics {
this._deletedLabelsCount += increment;
return this;
}
incrementDeletedCloseLabelsCount(
incrementProcessedItemsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
this._deletedCloseLabelsCount += increment;
if (issue.isPullRequest) {
return this._incrementProcessedPullRequestsCount(increment);
}
return this._incrementProcessedIssuesCount(increment);
}
incrementStaleItemsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementStalePullRequestsCount(increment);
}
return this._incrementStaleIssuesCount(increment);
}
incrementUndoStaleItemsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementUndoStalePullRequestsCount(increment);
}
return this._incrementUndoStaleIssuesCount(increment);
}
setRemainingOperations(remainingOperations: Readonly<number>): Statistics {
this._operationsCount = remainingOperations;
return this;
}
incrementClosedItemsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementClosedPullRequestsCount(increment);
}
return this._incrementClosedIssuesCount(increment);
}
incrementDeletedItemsLabelsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementDeletedPullRequestsLabelsCount(increment);
}
return this._incrementDeletedIssuesLabelsCount(increment);
}
incrementDeletedCloseItemsLabelsCount(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementDeletedClosePullRequestsLabelsCount(increment);
}
return this._incrementDeletedCloseIssuesLabelsCount(increment);
}
incrementDeletedBranchesCount(increment: Readonly<number> = 1): Statistics {
this._deletedBranchesCount += increment;
return this;
}
incrementAddedLabel(increment: Readonly<number> = 1): Statistics {
this._addedLabelsCount += increment;
return this;
}
incrementAddedComment(increment: Readonly<number> = 1): Statistics {
this._addedCommentsCount += increment;
return this;
}
incrementFetchedIssuesCount(increment: Readonly<number> = 1): Statistics {
this._fetchedIssuesCount += increment;
return this;
}
incrementFetchedIssuesEventsCount(
incrementAddedItemsLabel(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
this._fetchedIssuesEventsCount += increment;
if (issue.isPullRequest) {
return this._incrementAddedPullRequestsLabel(increment);
}
return this._incrementAddedIssuesLabel(increment);
}
incrementAddedItemsComment(
issue: Readonly<Issue>,
increment: Readonly<number> = 1
): Statistics {
if (issue.isPullRequest) {
return this._incrementAddedPullRequestsComment(increment);
}
return this._incrementAddedIssuesComment(increment);
}
incrementFetchedItemsCount(increment: Readonly<number> = 1): Statistics {
this._fetchedItemsCount += increment;
return this;
}
incrementFetchedIssuesCommentsCount(
incrementFetchedItemsEventsCount(
increment: Readonly<number> = 1
): Statistics {
this._fetchedIssuesCommentsCount += increment;
this._fetchedItemsEventsCount += increment;
return this;
}
incrementFetchedItemsCommentsCount(
increment: Readonly<number> = 1
): Statistics {
this._fetchedItemsCommentsCount += increment;
return this;
}
@@ -116,85 +163,358 @@ export class Statistics {
}
logStats(): Statistics {
this._logger.info('Statistics');
this._logProcessedIssuesCount();
this._logStaleIssuesCount();
this._logUndoStaleIssuesCount();
this._logOperationsCount();
this._logClosedIssuesCount();
this._logDeletedLabelsCount();
this._logDeletedCloseLabelsCount();
this._logger.info(chalk.yellow.bold('Statistics:'));
this._logProcessedIssuesAndPullRequestsCount();
this._logStaleIssuesAndPullRequestsCount();
this._logUndoStaleIssuesAndPullRequestsCount();
this._logClosedIssuesAndPullRequestsCount();
this._logDeletedIssuesAndPullRequestsLabelsCount();
this._logDeletedCloseIssuesAndPullRequestsLabelsCount();
this._logDeletedBranchesCount();
this._logAddedLabelsCount();
this._logAddedCommentsCount();
this._logFetchedIssuesCount();
this._logFetchedIssuesEventsCount();
this._logFetchedIssuesCommentsCount();
this._logAddedIssuesAndPullRequestsLabelsCount();
this._logAddedIssuesAndPullRequestsCommentsCount();
this._logFetchedItemsCount();
this._logFetchedItemsEventsCount();
this._logFetchedItemsCommentsCount();
this._logFetchedPullRequestsCount();
this._logger.info('---');
this._logOperationsCount();
return this;
}
private _logProcessedIssuesCount(): void {
this._logCount('Processed issues/PRs', this._processedIssuesCount);
private _incrementProcessedIssuesCount(
increment: Readonly<number> = 1
): Statistics {
this._processedIssuesCount += increment;
return this;
}
private _logStaleIssuesCount(): void {
this._logCount('New stale issues/PRs', this._staleIssuesCount);
private _incrementProcessedPullRequestsCount(
increment: Readonly<number> = 1
): Statistics {
this._processedPullRequestsCount += increment;
return this;
}
private _logUndoStaleIssuesCount(): void {
this._logCount('No longer stale issues/PRs', this._undoStaleIssuesCount);
private _incrementStaleIssuesCount(
increment: Readonly<number> = 1
): Statistics {
this._staleIssuesCount += increment;
return this;
}
private _logOperationsCount(): void {
this._logCount('Operations performed', this._operationsCount);
private _incrementStalePullRequestsCount(
increment: Readonly<number> = 1
): Statistics {
this._stalePullRequestsCount += increment;
return this;
}
private _logClosedIssuesCount(): void {
this._logCount('Closed issues', this._closedIssuesCount);
private _incrementUndoStaleIssuesCount(
increment: Readonly<number> = 1
): Statistics {
this._undoStaleIssuesCount += increment;
return this;
}
private _logDeletedLabelsCount(): void {
this._logCount('Deleted labels', this._deletedLabelsCount);
private _incrementUndoStalePullRequestsCount(
increment: Readonly<number> = 1
): Statistics {
this._undoStalePullRequestsCount += increment;
return this;
}
private _logDeletedCloseLabelsCount(): void {
this._logCount('Deleted close labels', this._deletedCloseLabelsCount);
private _incrementClosedIssuesCount(
increment: Readonly<number> = 1
): Statistics {
this._closedIssuesCount += increment;
return this;
}
private _incrementClosedPullRequestsCount(
increment: Readonly<number> = 1
): Statistics {
this._closedPullRequestsCount += increment;
return this;
}
private _incrementDeletedIssuesLabelsCount(
increment: Readonly<number> = 1
): Statistics {
this._deletedIssuesLabelsCount += increment;
return this;
}
private _incrementDeletedPullRequestsLabelsCount(
increment: Readonly<number> = 1
): Statistics {
this._deletedPullRequestsLabelsCount += increment;
return this;
}
private _incrementDeletedCloseIssuesLabelsCount(
increment: Readonly<number> = 1
): Statistics {
this._deletedCloseIssuesLabelsCount += increment;
return this;
}
private _incrementDeletedClosePullRequestsLabelsCount(
increment: Readonly<number> = 1
): Statistics {
this._deletedClosePullRequestsLabelsCount += increment;
return this;
}
private _incrementAddedIssuesLabel(
increment: Readonly<number> = 1
): Statistics {
this._addedIssuesLabelsCount += increment;
return this;
}
private _incrementAddedPullRequestsLabel(
increment: Readonly<number> = 1
): Statistics {
this._addedPullRequestsLabelsCount += increment;
return this;
}
private _incrementAddedIssuesComment(
increment: Readonly<number> = 1
): Statistics {
this._addedIssuesCommentsCount += increment;
return this;
}
private _incrementAddedPullRequestsComment(
increment: Readonly<number> = 1
): Statistics {
this._addedPullRequestsCommentsCount += increment;
return this;
}
private _logProcessedIssuesAndPullRequestsCount(): void {
this._logGroup('Processed items', [
{
name: 'Processed issues',
count: this._processedIssuesCount
},
{
name: 'Processed PRs',
count: this._processedPullRequestsCount
}
]);
}
private _logStaleIssuesAndPullRequestsCount(): void {
this._logGroup('New stale items', [
{
name: 'New stale issues',
count: this._staleIssuesCount
},
{
name: 'New stale PRs',
count: this._stalePullRequestsCount
}
]);
}
private _logUndoStaleIssuesAndPullRequestsCount(): void {
this._logGroup('No longer stale items', [
{
name: 'No longer stale issues',
count: this._undoStaleIssuesCount
},
{
name: 'No longer stale PRs',
count: this._undoStalePullRequestsCount
}
]);
}
private _logClosedIssuesAndPullRequestsCount(): void {
this._logGroup('Closed items', [
{
name: 'Closed issues',
count: this._closedIssuesCount
},
{
name: 'Closed PRs',
count: this._closedPullRequestsCount
}
]);
}
private _logDeletedIssuesAndPullRequestsLabelsCount(): void {
this._logGroup('Deleted items labels', [
{
name: 'Deleted issues labels',
count: this._deletedIssuesLabelsCount
},
{
name: 'Deleted PRs labels',
count: this._deletedPullRequestsLabelsCount
}
]);
}
private _logDeletedCloseIssuesAndPullRequestsLabelsCount(): void {
this._logGroup('Deleted close items labels', [
{
name: 'Deleted close issues labels',
count: this._deletedCloseIssuesLabelsCount
},
{
name: 'Deleted close PRs labels',
count: this._deletedClosePullRequestsLabelsCount
}
]);
}
private _logDeletedBranchesCount(): void {
this._logCount('Deleted branches', this._deletedBranchesCount);
}
private _logAddedLabelsCount(): void {
this._logCount('Added labels', this._addedLabelsCount);
private _logAddedIssuesAndPullRequestsLabelsCount(): void {
this._logGroup('Added items labels', [
{
name: 'Added issues labels',
count: this._addedIssuesLabelsCount
},
{
name: 'Added PRs labels',
count: this._addedPullRequestsLabelsCount
}
]);
}
private _logAddedCommentsCount(): void {
this._logCount('Added comments', this._addedCommentsCount);
private _logAddedIssuesAndPullRequestsCommentsCount(): void {
this._logGroup('Added items comments', [
{
name: 'Added issues comments',
count: this._addedIssuesCommentsCount
},
{
name: 'Added PRs comments',
count: this._addedPullRequestsCommentsCount
}
]);
}
private _logFetchedIssuesCount(): void {
this._logCount('Fetched issues', this._fetchedIssuesCount);
private _logFetchedItemsCount(): void {
this._logCount('Fetched items', this._fetchedItemsCount);
}
private _logFetchedIssuesEventsCount(): void {
this._logCount('Fetched issues events', this._fetchedIssuesEventsCount);
private _logFetchedItemsEventsCount(): void {
this._logCount('Fetched items events', this._fetchedItemsEventsCount);
}
private _logFetchedIssuesCommentsCount(): void {
this._logCount('Fetched issues comments', this._fetchedIssuesCommentsCount);
private _logFetchedItemsCommentsCount(): void {
this._logCount('Fetched items comments', this._fetchedItemsCommentsCount);
}
private _logFetchedPullRequestsCount(): void {
this._logCount('Fetched pull requests', this._fetchedPullRequestsCount);
}
private _logOperationsCount(): void {
this._logCount('Operations performed', this._operationsCount);
}
private _logCount(name: Readonly<string>, count: Readonly<number>): void {
if (count > 0) {
this._logger.info(`${name}: ${count}`);
this._logger.info(`${name}:`, chalk.cyan(count));
}
}
private _logGroup(groupName: Readonly<string>, values: IGroupValue[]): void {
if (this._isGroupValuesPartiallySet(values)) {
this._logCount(groupName, this._getGroupValuesTotalCount(values));
this._logGroupValues(values);
} else {
// Only one value will be display
for (const value of values) {
this._logCount(value.name, value.count);
}
}
}
/**
* @private
* @description
* If there is a least two elements with a valid count then it's partially set
* Useful to defined if we should display the values as a group or not
*
* @param {IGroupValue[]} values The list of group values to check
*/
private _isGroupValuesPartiallySet(values: IGroupValue[]): boolean {
return (
values
.map((value: Readonly<IGroupValue>): boolean => {
return value.count > 0;
})
.filter((isSet: Readonly<boolean>): boolean => isSet).length >= 2
);
}
private _getGroupValuesTotalCount(values: IGroupValue[]): number {
return values.reduce(
(count: Readonly<number>, value: Readonly<IGroupValue>): number => {
return count + value.count;
},
0
);
}
private _getAllGroupValuesSet(values: IGroupValue[]): IGroupValue[] {
return values.filter((value: Readonly<IGroupValue>): boolean => {
return value.count > 0;
});
}
private _logGroupValues(values: IGroupValue[]): void {
const onlyValuesSet: IGroupValue[] = this._getAllGroupValuesSet(values);
const longestValue: number = this._getLongestGroupValue(onlyValuesSet);
for (const [index, value] of onlyValuesSet.entries()) {
const prefix = index === onlyValuesSet.length - 1 ? '└──' : '├──';
this._logCount(
`${chalk.white(prefix)} ${value.name.padEnd(longestValue, ' ')}`,
value.count
);
}
}
private _getLongestGroupValue(values: IGroupValue[]): number {
return values.reduce(
(
longestValue: Readonly<number>,
value: Readonly<IGroupValue>
): number => {
return value.name.length > longestValue
? value.name.length
: longestValue;
},
0
);
}
}

44
src/enums/option.ts Normal file
View File

@@ -0,0 +1,44 @@
export enum Option {
RepoToken = 'repo-token',
StaleIssueMessage = 'stale-issue-message',
StalePrMessage = 'stale-pr-message',
CloseIssueMessage = 'close-issue-message',
ClosePrMessage = 'close-pr-message',
DaysBeforeStale = 'days-before-stale',
DaysBeforeIssueStale = 'days-before-issue-stale',
DaysBeforePrStale = 'days-before-pr-stale',
DaysBeforeClose = 'days-before-close',
DaysBeforeIssueClose = 'days-before-issue-close',
DaysBeforePrClose = 'days-before-pr-close',
StaleIssueLabel = 'stale-issue-label',
CloseIssueLabel = 'close-issue-label',
ExemptIssueLabels = 'exempt-issue-labels',
StalePrLabel = 'stale-pr-label',
ClosePrLabel = 'close-pr-label',
ExemptPrLabels = 'exempt-pr-labels',
OnlyLabels = 'only-labels',
OnlyIssueLabels = 'only-issue-labels',
OnlyPrLabels = 'only-pr-labels',
AnyOfLabels = 'any-of-labels',
OperationsPerRun = 'operations-per-run',
RemoveStaleWhenUpdated = 'remove-stale-when-updated',
DebugOnly = 'debug-only',
Ascending = 'ascending',
SkipStaleIssueMessage = 'skip-stale-issue-message',
SkipStalePrMessage = 'skip-stale-pr-message',
DeleteBranch = 'delete-branch',
StartDate = 'start-date',
ExemptMilestones = 'exempt-milestones',
ExemptIssueMilestones = 'exempt-issue-milestones',
ExemptPrMilestones = 'exempt-pr-milestones',
ExemptAllMilestones = 'exempt-all-milestones',
ExemptAllIssueMilestones = 'exempt-all-issue-milestones',
ExemptAllPrMilestones = 'exempt-all-pr-milestones',
ExemptAssignees = 'exempt-assignees',
ExemptIssueAssignees = 'exempt-issue-assignees',
ExemptPrAssignees = 'exempt-pr-assignees',
ExemptAllAssignees = 'exempt-all-assignees',
ExemptAllIssueAssignees = 'exempt-all-issue-assignees',
ExemptAllPrAssignees = 'exempt-all-pr-assignees',
EnableStatistics = 'enable-statistics'
}

View File

@@ -0,0 +1,29 @@
import {isBoolean} from './is-boolean';
describe('isBoolean()', (): void => {
describe.each([0, 1, undefined, null, ''])(
'when the given value is not a boolean',
(value): void => {
it('should return false', (): void => {
expect.assertions(1);
const result = isBoolean(value);
expect(result).toStrictEqual(false);
});
}
);
describe.each([false, true])(
'when the given value is a boolean',
(value): void => {
it('should return true', (): void => {
expect.assertions(1);
const result = isBoolean(value);
expect(result).toStrictEqual(true);
});
}
);
});

View File

@@ -0,0 +1,3 @@
export function isBoolean(value: unknown): value is boolean {
return value === true || value === false;
}

View File

@@ -22,8 +22,12 @@ export interface IIssuesProcessorOptions {
onlyIssueLabels: string;
onlyPrLabels: string;
anyOfLabels: string;
anyOfIssueLabels: string;
anyOfPrLabels: string;
operationsPerRun: number;
removeStaleWhenUpdated: boolean;
removeIssueStaleWhenUpdated: boolean | undefined;
removePrStaleWhenUpdated: boolean | undefined;
debugOnly: boolean;
ascending: boolean;
skipStaleIssueMessage: boolean;

View File

@@ -1,14 +1,13 @@
import * as core from '@actions/core';
import {isValidDate} from './functions/dates/is-valid-date';
import {IssuesProcessor} from './classes/issues-processor';
import {isValidDate} from './functions/dates/is-valid-date';
import {IIssuesProcessorOptions} from './interfaces/issues-processor-options';
async function _run(): Promise<void> {
try {
const args = _getAndValidateArgs();
const processor: IssuesProcessor = new IssuesProcessor(args);
await processor.processIssues();
await new IssuesProcessor(args).processIssues();
} catch (error) {
core.error(error);
core.setFailed(error.message);
@@ -42,12 +41,20 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
onlyIssueLabels: core.getInput('only-issue-labels'),
onlyPrLabels: core.getInput('only-pr-labels'),
anyOfLabels: core.getInput('any-of-labels'),
anyOfIssueLabels: core.getInput('any-of-issue-labels'),
anyOfPrLabels: core.getInput('any-of-pr-labels'),
operationsPerRun: parseInt(
core.getInput('operations-per-run', {required: true})
),
removeStaleWhenUpdated: !(
core.getInput('remove-stale-when-updated') === 'false'
),
removeIssueStaleWhenUpdated: _toOptionalBoolean(
core.getInput('remove-issue-stale-when-updated')
),
removePrStaleWhenUpdated: _toOptionalBoolean(
core.getInput('remove-pr-stale-when-updated')
),
debugOnly: core.getInput('debug-only') === 'true',
ascending: core.getInput('ascending') === 'true',
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
@@ -78,7 +85,9 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
'operations-per-run'
]) {
if (isNaN(parseInt(core.getInput(numberInput)))) {
throw Error(`input ${numberInput} did not parse to a valid integer`);
const errorMessage = `Option "${numberInput}" did not parse to a valid integer`;
core.setFailed(errorMessage);
throw new Error(errorMessage);
}
}
@@ -86,9 +95,9 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
if (core.getInput(optionalDateInput) !== '') {
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
throw new Error(
`input ${optionalDateInput} did not parse to a valid date`
);
const errorMessage = `Option "${optionalDateInput}" did not parse to a valid date`;
core.setFailed(errorMessage);
throw new Error(errorMessage);
}
}
}