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>
This commit is contained in:
parent
3b3c3f03cd
commit
b717aa9f47
|
@ -12,7 +12,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: |
|
- run: |
|
||||||
npm install
|
npm ci
|
||||||
npm run all
|
npm run all
|
||||||
test: # make sure the action works on a clean machine without building
|
test: # make sure the action works on a clean machine without building
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
104
README.md
104
README.md
|
@ -2,54 +2,68 @@
|
||||||
|
|
||||||
Warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
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.
|
Every argument is optional.
|
||||||
|
|
||||||
| Input | Description |
|
| Input | Description |
|
||||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `repo-token` | PAT(Personal Access Token) for authorizing repository.<br>_Defaults to **${{ github.token }}**_. |
|
| `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-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-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-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-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-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`_. |
|
| `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-issue-message` | Message to post on the stale issue. |
|
||||||
| `stale-pr-message` | Message to post on the stale PR. |
|
| `stale-pr-message` | Message to post on the stale PR. |
|
||||||
| `close-issue-message` | Message to post on the stale issue while closing it. |
|
| `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. |
|
| `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**_. |
|
| `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). |
|
| `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**_. |
|
| `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). |
|
| `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-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. |
|
| `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-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. Separate multiple labels with commas (eg. "question,answered").<br>_Override `only-labels`_. |
|
| `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. 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. Separate multiple labels with commas (eg. "incomplete,waiting-feedback"). |
|
| `any-of-labels` | Only issues and PRs with ANY of these labels are checked.<br>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**_. |
|
| `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-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**_. |
|
| `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**_. |
|
| `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-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**_. |
|
| `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). |
|
| `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**_. |
|
| `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-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-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-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-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-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-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-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-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-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-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-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`_. |
|
| `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**_. |
|
| `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
|
||||||
|
|
||||||
|
Used 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.
|
||||||
|
|
||||||
|
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 and the batch of issues (100 per batch) will consume the operations.
|
||||||
|
|
||||||
|
Default value: `30`
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
|
|
@ -42,5 +42,5 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
|
||||||
exemptAllAssignees: false,
|
exemptAllAssignees: false,
|
||||||
exemptAllIssueAssignees: undefined,
|
exemptAllIssueAssignees: undefined,
|
||||||
exemptAllPrAssignees: undefined,
|
exemptAllPrAssignees: undefined,
|
||||||
enableStatistics: false
|
enableStatistics: true
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -206,6 +206,19 @@
|
||||||
"@babel/helper-validator-identifier": "^7.9.0",
|
"@babel/helper-validator-identifier": "^7.9.0",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.0.0",
|
||||||
"js-tokens": "^4.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": {
|
"@babel/parser": {
|
||||||
|
@ -2643,14 +2656,54 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.2",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^4.1.0",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"supports-color": "^7.1.0"
|
||||||
"supports-color": "^5.3.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": {
|
"char-regex": {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"pack": "ncc build",
|
"pack": "ncc build",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:only-errors": "jest --reporters jest-silent-reporter --silent",
|
"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"
|
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -44,6 +44,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||||
"@typescript-eslint/parser": "^4.16.1",
|
"@typescript-eslint/parser": "^4.16.1",
|
||||||
"@vercel/ncc": "^0.27.0",
|
"@vercel/ncc": "^0.27.0",
|
||||||
|
"chalk": "^4.1.0",
|
||||||
"eslint": "^7.21.0",
|
"eslint": "^7.21.0",
|
||||||
"eslint-plugin-github": "^4.1.2",
|
"eslint-plugin-github": "^4.1.2",
|
||||||
"eslint-plugin-jest": "^24.1.5",
|
"eslint-plugin-jest": "^24.1.5",
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
"jest-silent-reporter": "^0.4.0",
|
"jest-silent-reporter": "^0.4.0",
|
||||||
"js-yaml": "^4.0.0",
|
"js-yaml": "^4.0.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
|
"terminal-link": "^2.1.1",
|
||||||
"ts-jest": "^26.5.3",
|
"ts-jest": "^26.5.3",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class Assignees {
|
||||||
|
|
||||||
if (exemptAssignees.length === 0) {
|
if (exemptAssignees.length === 0) {
|
||||||
this._issueLogger.info(
|
this._issueLogger.info(
|
||||||
`No option was specified to skip the stale process for this $$type`
|
`No assignee option was specified to skip the stale process for this $$type`
|
||||||
);
|
);
|
||||||
this._logSkip();
|
this._logSkip();
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export class Assignees {
|
||||||
this._issueLogger.info(
|
this._issueLogger.info(
|
||||||
`Found ${exemptAssignees.length} assignee${
|
`Found ${exemptAssignees.length} assignee${
|
||||||
exemptAssignees.length > 1 ? 's' : ''
|
exemptAssignees.length > 1 ? 's' : ''
|
||||||
} on this $$type`
|
} that can exempt stale on this $$type`
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasExemptAssignee: boolean = exemptAssignees.some(
|
const hasExemptAssignee: boolean = exemptAssignees.some(
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import * as core from '@actions/core';
|
||||||
import {context, getOctokit} from '@actions/github';
|
import {context, getOctokit} from '@actions/github';
|
||||||
import {GitHub} from '@actions/github/lib/utils';
|
import {GitHub} from '@actions/github/lib/utils';
|
||||||
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import {Option} from '../enums/option';
|
||||||
import {getHumanizedDate} from '../functions/dates/get-humanized-date';
|
import {getHumanizedDate} from '../functions/dates/get-humanized-date';
|
||||||
import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than';
|
import {isDateMoreRecentThan} from '../functions/dates/is-date-more-recent-than';
|
||||||
import {isValidDate} from '../functions/dates/is-valid-date';
|
import {isValidDate} from '../functions/dates/is-valid-date';
|
||||||
import {isLabeled} from '../functions/is-labeled';
|
import {isLabeled} from '../functions/is-labeled';
|
||||||
import {isPullRequest} from '../functions/is-pull-request';
|
|
||||||
import {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
|
import {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
|
||||||
import {wordsToList} from '../functions/words-to-list';
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
import {IComment} from '../interfaces/comment';
|
import {IComment} from '../interfaces/comment';
|
||||||
|
@ -18,6 +20,7 @@ import {Issue} from './issue';
|
||||||
import {IssueLogger} from './loggers/issue-logger';
|
import {IssueLogger} from './loggers/issue-logger';
|
||||||
import {Logger} from './loggers/logger';
|
import {Logger} from './loggers/logger';
|
||||||
import {Milestones} from './milestones';
|
import {Milestones} from './milestones';
|
||||||
|
import {Operations} from './operations';
|
||||||
import {Statistics} from './statistics';
|
import {Statistics} from './statistics';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
@ -33,8 +36,8 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _logger: Logger = new Logger();
|
private readonly _logger: Logger = new Logger();
|
||||||
|
private readonly _operations: Operations;
|
||||||
private readonly _statistics: Statistics | undefined;
|
private readonly _statistics: Statistics | undefined;
|
||||||
private _operationsLeft = 0;
|
|
||||||
readonly client: InstanceType<typeof GitHub>;
|
readonly client: InstanceType<typeof GitHub>;
|
||||||
readonly options: IIssuesProcessorOptions;
|
readonly options: IIssuesProcessorOptions;
|
||||||
readonly staleIssues: Issue[] = [];
|
readonly staleIssues: Issue[] = [];
|
||||||
|
@ -44,31 +47,49 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
constructor(options: IIssuesProcessorOptions) {
|
constructor(options: IIssuesProcessorOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this._operationsLeft = this.options.operationsPerRun;
|
|
||||||
this.client = getOctokit(this.options.repoToken);
|
this.client = getOctokit(this.options.repoToken);
|
||||||
|
this._operations = new Operations(this.options);
|
||||||
|
|
||||||
|
this._logger.info(chalk.yellow('Starting the stale action process...'));
|
||||||
|
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
|
this._logger.warning(chalk.yellowBright('Executing in debug mode!'));
|
||||||
this._logger.warning(
|
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) {
|
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
|
// get the next batch of issues
|
||||||
const issues: Issue[] = await this.getIssues(page);
|
const issues: Issue[] = await this.getIssues(page);
|
||||||
const actor: string = await this.getActor();
|
const actor: string = await this.getActor();
|
||||||
|
|
||||||
if (issues.length <= 0) {
|
if (issues.length <= 0) {
|
||||||
this._logger.info('---');
|
this._logger.info(
|
||||||
this._statistics?.setOperationsLeft(this._operationsLeft).logStats();
|
chalk.green('No more issues found to process. Exiting...')
|
||||||
this._logger.info('No more issues found to process. Exiting.');
|
);
|
||||||
|
this._statistics
|
||||||
|
?.setOperationsLeft(this._operations.getUnconsumedOperationsCount())
|
||||||
|
.logStats();
|
||||||
|
|
||||||
return this._operationsLeft;
|
return this._operations.getOperationsLeftCount();
|
||||||
|
} 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()) {
|
for (const issue of issues.values()) {
|
||||||
|
@ -160,8 +181,10 @@ export class IssuesProcessor {
|
||||||
// Expecting that GitHub will always set a creation date on the issues and PRs
|
// Expecting that GitHub will always set a creation date on the issues and PRs
|
||||||
// But you never know!
|
// But you never know!
|
||||||
if (!isValidDate(createdAt)) {
|
if (!isValidDate(createdAt)) {
|
||||||
throw new Error(
|
core.setFailed(
|
||||||
`Invalid issue field: "created_at". Expected a valid date`
|
new Error(
|
||||||
|
`Invalid issue field: "created_at". Expected a valid date`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +230,7 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const anyOfLabels: string[] = wordsToList(this.options.anyOfLabels);
|
const anyOfLabels: string[] = wordsToList(this.options.anyOfLabels);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
anyOfLabels.length &&
|
anyOfLabels.length &&
|
||||||
!anyOfLabels.some((label: Readonly<string>): boolean =>
|
!anyOfLabels.some((label: Readonly<string>): boolean =>
|
||||||
|
@ -222,9 +246,6 @@ export class IssuesProcessor {
|
||||||
const milestones: Milestones = new Milestones(this.options, issue);
|
const milestones: Milestones = new Milestones(this.options, issue);
|
||||||
|
|
||||||
if (milestones.shouldExemptMilestones()) {
|
if (milestones.shouldExemptMilestones()) {
|
||||||
issueLogger.info(
|
|
||||||
`Skipping $$type because it has an exempted milestone`
|
|
||||||
);
|
|
||||||
continue; // don't process exempt milestones
|
continue; // don't process exempt milestones
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,13 +287,27 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._operationsLeft <= 0) {
|
if (this._operations.hasOperationsLeft()) {
|
||||||
this._logger.warning(
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._logger.info(
|
||||||
|
chalk.green(`Batch ${chalk.cyan(`#${page}`)} processed.`)
|
||||||
|
);
|
||||||
|
|
||||||
// do the next batch
|
// do the next batch
|
||||||
return this.processIssues(page + 1);
|
return this.processIssues(page + 1);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +319,7 @@ export class IssuesProcessor {
|
||||||
): Promise<IComment[]> {
|
): Promise<IComment[]> {
|
||||||
// find any comments since date on the given issue
|
// find any comments since date on the given issue
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementFetchedIssuesCommentsCount();
|
this._statistics?.incrementFetchedIssuesCommentsCount();
|
||||||
const comments = await this.client.issues.listComments({
|
const comments = await this.client.issues.listComments({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -304,7 +339,7 @@ export class IssuesProcessor {
|
||||||
let actor;
|
let actor;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
actor = await this.client.users.getAuthenticated();
|
actor = await this.client.users.getAuthenticated();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return context.actor;
|
return context.actor;
|
||||||
|
@ -320,8 +355,7 @@ export class IssuesProcessor {
|
||||||
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
|
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementFetchedIssuesCount();
|
|
||||||
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
||||||
{
|
{
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -332,6 +366,7 @@ export class IssuesProcessor {
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this._statistics?.incrementFetchedIssuesCount(issueResult.data.length);
|
||||||
|
|
||||||
return issueResult.data.map(
|
return issueResult.data.map(
|
||||||
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
|
(issue: Readonly<IIssue>): Issue => new Issue(this.options, issue)
|
||||||
|
@ -352,7 +387,7 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
issueLogger.info(`Checking for label on $$type`);
|
issueLogger.info(`Checking for label on $$type`);
|
||||||
|
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementFetchedIssuesEventsCount();
|
this._statistics?.incrementFetchedIssuesEventsCount();
|
||||||
const options = this.client.issues.listEvents.endpoint.merge({
|
const options = this.client.issues.listEvents.endpoint.merge({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -396,8 +431,7 @@ export class IssuesProcessor {
|
||||||
);
|
);
|
||||||
issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
|
issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
|
||||||
|
|
||||||
const isPr: boolean = isPullRequest(issue);
|
const daysBeforeClose: number = issue.isPullRequest
|
||||||
const daysBeforeClose: number = isPr
|
|
||||||
? this._getDaysBeforePrClose()
|
? this._getDaysBeforePrClose()
|
||||||
: this._getDaysBeforeIssueClose();
|
: this._getDaysBeforeIssueClose();
|
||||||
|
|
||||||
|
@ -491,7 +525,7 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
if (!skipMessage) {
|
if (!skipMessage) {
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementAddedComment();
|
this._statistics?.incrementAddedComment();
|
||||||
await this.client.issues.createComment({
|
await this.client.issues.createComment({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -505,7 +539,7 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementAddedLabel();
|
this._statistics?.incrementAddedLabel();
|
||||||
this._statistics?.incrementStaleIssuesCount();
|
this._statistics?.incrementStaleIssuesCount();
|
||||||
await this.client.issues.addLabels({
|
await this.client.issues.addLabels({
|
||||||
|
@ -536,7 +570,7 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
if (closeMessage) {
|
if (closeMessage) {
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementAddedComment();
|
this._statistics?.incrementAddedComment();
|
||||||
await this.client.issues.createComment({
|
await this.client.issues.createComment({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -551,7 +585,7 @@ export class IssuesProcessor {
|
||||||
|
|
||||||
if (closeLabel) {
|
if (closeLabel) {
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementAddedLabel();
|
this._statistics?.incrementAddedLabel();
|
||||||
await this.client.issues.addLabels({
|
await this.client.issues.addLabels({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -565,7 +599,7 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementClosedIssuesCount();
|
this._statistics?.incrementClosedIssuesCount();
|
||||||
await this.client.issues.update({
|
await this.client.issues.update({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -588,7 +622,7 @@ export class IssuesProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementFetchedPullRequestsCount();
|
this._statistics?.incrementFetchedPullRequestsCount();
|
||||||
const pullRequest = await this.client.pulls.get({
|
const pullRequest = await this.client.pulls.get({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -625,7 +659,7 @@ export class IssuesProcessor {
|
||||||
issueLogger.info(`Deleting branch ${branch} from closed $$type`);
|
issueLogger.info(`Deleting branch ${branch} from closed $$type`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementDeletedBranchesCount();
|
this._statistics?.incrementDeletedBranchesCount();
|
||||||
await this.client.git.deleteRef({
|
await this.client.git.deleteRef({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
@ -646,13 +680,12 @@ export class IssuesProcessor {
|
||||||
issueLogger.info(`Removing label "${label}" from $$type`);
|
issueLogger.info(`Removing label "${label}" from $$type`);
|
||||||
this.removedLabelIssues.push(issue);
|
this.removedLabelIssues.push(issue);
|
||||||
|
|
||||||
// @todo remove the debug only to be able to test the code below
|
|
||||||
if (this.options.debugOnly) {
|
if (this.options.debugOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._operationsLeft -= 1;
|
this._operations.consumeOperation();
|
||||||
this._statistics?.incrementDeletedLabelsCount();
|
this._statistics?.incrementDeletedLabelsCount();
|
||||||
await this.client.issues.removeLabel({
|
await this.client.issues.removeLabel({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as core from '@actions/core';
|
import chalk from 'chalk';
|
||||||
import {Issue} from '../issue';
|
import {Issue} from '../issue';
|
||||||
import {Logger} from './logger';
|
import {Logger} from './logger';
|
||||||
|
|
||||||
|
@ -15,23 +15,24 @@ import {Logger} from './logger';
|
||||||
* @example
|
* @example
|
||||||
* warning('The $$type will stale') => "The pull request will stale"
|
* warning('The $$type will stale') => "The pull request will stale"
|
||||||
*/
|
*/
|
||||||
export class IssueLogger implements Logger {
|
export class IssueLogger extends Logger {
|
||||||
private readonly _issue: Issue;
|
private readonly _issue: Issue;
|
||||||
|
|
||||||
constructor(issue: Issue) {
|
constructor(issue: Issue) {
|
||||||
|
super();
|
||||||
this._issue = issue;
|
this._issue = issue;
|
||||||
}
|
}
|
||||||
|
|
||||||
warning(message: Readonly<string>): void {
|
warning(...message: string[]): void {
|
||||||
core.warning(this._format(message));
|
super.warning(this._format(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message: Readonly<string>): void {
|
info(...message: string[]): void {
|
||||||
core.info(this._format(message));
|
super.info(this._format(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: Readonly<string>): void {
|
error(...message: string[]): void {
|
||||||
core.error(this._format(message));
|
super.error(this._format(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _replaceTokens(message: Readonly<string>): string {
|
private _replaceTokens(message: Readonly<string>): string {
|
||||||
|
@ -51,14 +52,28 @@ export class IssueLogger implements Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _prefixWithIssueNumber(message: Readonly<string>): string {
|
private _prefixWithIssueNumber(message: Readonly<string>): string {
|
||||||
return `[#${this._getIssueNumber()}] ${message}`;
|
return `${this._getPrefix()} ${message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getIssueNumber(): number {
|
private _getIssueNumber(): number {
|
||||||
return this._issue.number;
|
return this._issue.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _format(message: Readonly<string>): string {
|
private _format(...message: string[]): string {
|
||||||
return this._prefixWithIssueNumber(this._replaceTokens(message));
|
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()}]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import terminalLink from 'terminal-link';
|
||||||
|
import {Option} from '../../enums/option';
|
||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
warning(message: Readonly<string>): void {
|
warning(...message: string[]): void {
|
||||||
core.warning(message);
|
core.warning(chalk.whiteBright(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message: Readonly<string>): void {
|
info(...message: string[]): void {
|
||||||
core.info(message);
|
core.info(chalk.whiteBright(...message));
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: Readonly<string>): void {
|
error(...message: string[]): void {
|
||||||
core.error(message);
|
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}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import deburr from 'lodash.deburr';
|
||||||
import {wordsToList} from '../functions/words-to-list';
|
import {wordsToList} from '../functions/words-to-list';
|
||||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
import {Issue} from './issue';
|
import {Issue} from './issue';
|
||||||
|
import {IssueLogger} from './loggers/issue-logger';
|
||||||
|
|
||||||
type CleanMilestone = string;
|
type CleanMilestone = string;
|
||||||
|
|
||||||
|
@ -12,42 +13,158 @@ export class Milestones {
|
||||||
|
|
||||||
private readonly _options: IIssuesProcessorOptions;
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
private readonly _issue: Issue;
|
private readonly _issue: Issue;
|
||||||
|
private readonly _issueLogger: IssueLogger;
|
||||||
|
|
||||||
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
|
constructor(options: Readonly<IIssuesProcessorOptions>, issue: Issue) {
|
||||||
this._options = options;
|
this._options = options;
|
||||||
this._issue = issue;
|
this._issue = issue;
|
||||||
|
this._issueLogger = new IssueLogger(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldExemptMilestones(): boolean {
|
shouldExemptMilestones(): boolean {
|
||||||
|
if (!this._issue.milestone) {
|
||||||
|
this._issueLogger.info('This $$type has no milestone');
|
||||||
|
this._logSkip();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this._shouldExemptAllMilestones()) {
|
if (this._shouldExemptAllMilestones()) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'Skipping $$type because it has an exempt milestone'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exemptMilestones: string[] = this._getExemptMilestones();
|
const exemptMilestones: string[] = this._getExemptMilestones();
|
||||||
|
|
||||||
return exemptMilestones.some((exemptMilestone: Readonly<string>): boolean =>
|
if (exemptMilestones.length === 0) {
|
||||||
this._hasMilestone(exemptMilestone)
|
this._issueLogger.info(
|
||||||
|
`No milestone option was specified to skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
this._logSkip();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`Found ${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(
|
||||||
|
'No milestone on this $$type can exempt the stale process'
|
||||||
|
);
|
||||||
|
this._logSkip();
|
||||||
|
} else {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'Skipping this $$type because it has an exempt milestone'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasExemptMilestone;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getExemptMilestones(): string[] {
|
private _getExemptMilestones(): string[] {
|
||||||
return wordsToList(
|
return this._issue.isPullRequest
|
||||||
this._issue.isPullRequest
|
? this._getExemptPullRequestMilestones()
|
||||||
? this._getExemptPullRequestMilestones()
|
: this._getExemptIssueMilestones();
|
||||||
: this._getExemptIssueMilestones()
|
}
|
||||||
|
|
||||||
|
private _getExemptIssueMilestones(): string[] {
|
||||||
|
if (this._options.exemptIssueMilestones === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptIssueMilestones" is disabled. No specific milestone can skip the stale process for this $$type'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this._options.exemptMilestones === '') {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The 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(
|
||||||
|
`The option "exemptMilestones" is set. ${
|
||||||
|
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(
|
||||||
|
`The option "exemptIssueMilestones" is set. ${
|
||||||
|
exemptMilestones.length
|
||||||
|
} milestone${
|
||||||
|
exemptMilestones.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptMilestones;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getExemptIssueMilestones(): string {
|
private _getExemptPullRequestMilestones(): string[] {
|
||||||
return this._options.exemptIssueMilestones !== ''
|
if (this._options.exemptPrMilestones === '') {
|
||||||
? this._options.exemptIssueMilestones
|
this._issueLogger.info(
|
||||||
: this._options.exemptMilestones;
|
'The option "exemptPrMilestones" is disabled. No specific milestone can skip the stale process for this $$type'
|
||||||
}
|
);
|
||||||
|
|
||||||
private _getExemptPullRequestMilestones(): string {
|
if (this._options.exemptMilestones === '') {
|
||||||
return this._options.exemptPrMilestones !== ''
|
this._issueLogger.info(
|
||||||
? this._options.exemptPrMilestones
|
'The option "exemptMilestones" is disabled. No specific milestone can skip the stale process for this $$type'
|
||||||
: this._options.exemptMilestones;
|
);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exemptMilestones: string[] = wordsToList(
|
||||||
|
this._options.exemptMilestones
|
||||||
|
);
|
||||||
|
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The option "exemptMilestones" is set. ${
|
||||||
|
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(
|
||||||
|
`The option "exemptPrMilestones" is set. ${
|
||||||
|
exemptMilestones.length
|
||||||
|
} milestone${
|
||||||
|
exemptMilestones.length === 1 ? '' : 's'
|
||||||
|
} can skip the stale process for this $$type`
|
||||||
|
);
|
||||||
|
|
||||||
|
return exemptMilestones;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hasMilestone(milestone: Readonly<string>): boolean {
|
private _hasMilestone(milestone: Readonly<string>): boolean {
|
||||||
|
@ -55,10 +172,21 @@ export class Milestones {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const cleanMilestone: CleanMilestone = Milestones._cleanMilestone(
|
||||||
Milestones._cleanMilestone(milestone) ===
|
milestone
|
||||||
Milestones._cleanMilestone(this._issue.milestone.title)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isSameMilestone: boolean =
|
||||||
|
cleanMilestone ===
|
||||||
|
Milestones._cleanMilestone(this._issue.milestone.title);
|
||||||
|
|
||||||
|
if (isSameMilestone) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
`The milestone "${milestone}" is set on this $$type and is an exempt milestone`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSameMilestone;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shouldExemptAllMilestones(): boolean {
|
private _shouldExemptAllMilestones(): boolean {
|
||||||
|
@ -73,21 +201,57 @@ export class Milestones {
|
||||||
|
|
||||||
private _shouldExemptAllIssueMilestones(): boolean {
|
private _shouldExemptAllIssueMilestones(): boolean {
|
||||||
if (this._options.exemptAllIssueMilestones === true) {
|
if (this._options.exemptAllIssueMilestones === true) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllIssueMilestones" is enabled. Any milestone on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (this._options.exemptAllIssueMilestones === false) {
|
} else if (this._options.exemptAllIssueMilestones === false) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllIssueMilestones" is disabled. Only some specific milestones on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._logExemptAllMilestonesOption();
|
||||||
|
|
||||||
return this._options.exemptAllMilestones;
|
return this._options.exemptAllMilestones;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shouldExemptAllPullRequestMilestones(): boolean {
|
private _shouldExemptAllPullRequestMilestones(): boolean {
|
||||||
if (this._options.exemptAllPrMilestones === true) {
|
if (this._options.exemptAllPrMilestones === true) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllPrMilestones" is enabled. Any milestone on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (this._options.exemptAllPrMilestones === false) {
|
} else if (this._options.exemptAllPrMilestones === false) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllPrMilestones" is disabled. Only some specific milestones on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._logExemptAllMilestonesOption();
|
||||||
|
|
||||||
return this._options.exemptAllMilestones;
|
return this._options.exemptAllMilestones;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _logExemptAllMilestonesOption(): void {
|
||||||
|
if (this._options.exemptAllMilestones) {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllMilestones" is enabled. Any milestone on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._issueLogger.info(
|
||||||
|
'The option "exemptAllMilestones" is disabled. Only some specific milestones on this $$type will skip the stale process'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logSkip(): void {
|
||||||
|
this._issueLogger.info('Skip the milestones checks');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
||||||
|
|
||||||
|
export class Operations {
|
||||||
|
private readonly _options: IIssuesProcessorOptions;
|
||||||
|
private _operationsLeft;
|
||||||
|
|
||||||
|
constructor(options: Readonly<IIssuesProcessorOptions>) {
|
||||||
|
this._options = options;
|
||||||
|
this._operationsLeft = this._options.operationsPerRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeOperation(): Operations {
|
||||||
|
return this.consumeOperations(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeOperations(quantity: Readonly<number>): Operations {
|
||||||
|
this._operationsLeft -= quantity;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnconsumedOperationsCount(): number {
|
||||||
|
return this._options.operationsPerRun - this._operationsLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasOperationsLeft(): boolean {
|
||||||
|
return this._operationsLeft <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOperationsLeftCount(): number {
|
||||||
|
return this._operationsLeft;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options';
|
import chalk from 'chalk';
|
||||||
import {Logger} from './loggers/logger';
|
import {Logger} from './loggers/logger';
|
||||||
|
|
||||||
export class Statistics {
|
export class Statistics {
|
||||||
private readonly _logger: Logger = new Logger();
|
private readonly _logger: Logger = new Logger();
|
||||||
private readonly _options: IIssuesProcessorOptions;
|
|
||||||
private _processedIssuesCount = 0;
|
private _processedIssuesCount = 0;
|
||||||
private _staleIssuesCount = 0;
|
private _staleIssuesCount = 0;
|
||||||
private _undoStaleIssuesCount = 0;
|
private _undoStaleIssuesCount = 0;
|
||||||
|
@ -19,10 +18,6 @@ export class Statistics {
|
||||||
private _fetchedIssuesCommentsCount = 0;
|
private _fetchedIssuesCommentsCount = 0;
|
||||||
private _fetchedPullRequestsCount = 0;
|
private _fetchedPullRequestsCount = 0;
|
||||||
|
|
||||||
constructor(options: IIssuesProcessorOptions) {
|
|
||||||
this._options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
incrementProcessedIssuesCount(increment: Readonly<number> = 1): Statistics {
|
incrementProcessedIssuesCount(increment: Readonly<number> = 1): Statistics {
|
||||||
this._processedIssuesCount += increment;
|
this._processedIssuesCount += increment;
|
||||||
|
|
||||||
|
@ -42,7 +37,7 @@ export class Statistics {
|
||||||
}
|
}
|
||||||
|
|
||||||
setOperationsLeft(operationsLeft: Readonly<number>): Statistics {
|
setOperationsLeft(operationsLeft: Readonly<number>): Statistics {
|
||||||
this._operationsCount = this._options.operationsPerRun - operationsLeft;
|
this._operationsCount = operationsLeft;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -116,7 +111,7 @@ export class Statistics {
|
||||||
}
|
}
|
||||||
|
|
||||||
logStats(): Statistics {
|
logStats(): Statistics {
|
||||||
this._logger.info('Statistics');
|
this._logger.info(chalk.yellow.bold('Statistics:'));
|
||||||
this._logProcessedIssuesCount();
|
this._logProcessedIssuesCount();
|
||||||
this._logStaleIssuesCount();
|
this._logStaleIssuesCount();
|
||||||
this._logUndoStaleIssuesCount();
|
this._logUndoStaleIssuesCount();
|
||||||
|
@ -131,7 +126,6 @@ export class Statistics {
|
||||||
this._logFetchedIssuesEventsCount();
|
this._logFetchedIssuesEventsCount();
|
||||||
this._logFetchedIssuesCommentsCount();
|
this._logFetchedIssuesCommentsCount();
|
||||||
this._logFetchedPullRequestsCount();
|
this._logFetchedPullRequestsCount();
|
||||||
this._logger.info('---');
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +188,7 @@ export class Statistics {
|
||||||
|
|
||||||
private _logCount(name: Readonly<string>, count: Readonly<number>): void {
|
private _logCount(name: Readonly<string>, count: Readonly<number>): void {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
this._logger.info(`${name}: ${count}`);
|
this._logger.info(`${name}:`, chalk.cyan(count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
}
|
13
src/main.ts
13
src/main.ts
|
@ -7,8 +7,7 @@ async function _run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const args = _getAndValidateArgs();
|
const args = _getAndValidateArgs();
|
||||||
|
|
||||||
const processor: IssuesProcessor = new IssuesProcessor(args);
|
await new IssuesProcessor(args).processIssues();
|
||||||
await processor.processIssues();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.error(error);
|
core.error(error);
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
|
@ -78,7 +77,9 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||||
'operations-per-run'
|
'operations-per-run'
|
||||||
]) {
|
]) {
|
||||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||||
throw Error(`input ${numberInput} did not parse to a valid integer`);
|
core.setFailed(
|
||||||
|
new Error(`Option "${numberInput}" did not parse to a valid integer`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +87,10 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
|
||||||
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
|
// Ignore empty dates because it is considered as the right type for a default value (so a valid one)
|
||||||
if (core.getInput(optionalDateInput) !== '') {
|
if (core.getInput(optionalDateInput) !== '') {
|
||||||
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
|
if (!isValidDate(new Date(core.getInput(optionalDateInput)))) {
|
||||||
throw new Error(
|
core.setFailed(
|
||||||
`input ${optionalDateInput} did not parse to a valid date`
|
new Error(
|
||||||
|
`Option "${optionalDateInput}" did not parse to a valid date`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue