diff --git a/README.md b/README.md
index 839afb107..6fd4b20ac 100644
--- a/README.md
+++ b/README.md
@@ -8,52 +8,54 @@ Warns and then closes issues and PRs that have had no activity for a specified a
Every argument is optional.
-| Input | Description |
-| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `repo-token` | PAT(Personal Access Token) for authorizing repository.
_Defaults to **${{ github.token }}**_. |
-| `days-before-stale` | Idle number of days before marking an issue/PR as stale.
_Defaults to **60**_. |
-| `days-before-issue-stale` | Idle number of days before marking an issue as stale.
_Override `days-before-stale`_. |
-| `days-before-pr-stale` | Idle number of days before marking an PR as stale.
_Override `days-before-stale`_. |
-| `days-before-close` | Idle number of days before closing an stale issue/PR.
_Defaults to **7**_. |
-| `days-before-issue-close` | Idle number of days before closing an stale issue.
_Override `days-before-close`_. |
-| `days-before-pr-close` | Idle number of days before closing an stale PR.
_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.
_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.
_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").
_Override `only-labels`_. |
-| `only-pr-labels` | Only PRs with ALL these labels are checked.
Separate multiple labels with commas (eg. "question,answered").
_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-issue-labels` | Only issues with ANY of these labels are checked.
Separate multiple labels with commas (eg. "incomplete,waiting-feedback").
_Override `any-of-labels`_. |
-| `any-of-pr-labels` | Only PRs with ANY of these labels are checked.
Separate multiple labels with commas (eg. "incomplete,waiting-feedback").
_Override `any-of-labels`_. |
-| `operations-per-run` | Maximum number of operations per run.
GitHub API CRUD related.
_Defaults to **30**_. |
-| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments.
_Defaults to **true**_. |
-| `debug-only` | Dry-run on action.
_Defaults to **false**_. |
-| `ascending` | Order to get issues/PR.
`true` is ascending, `false` is descending.
_Defaults to **false**_. |
-| `skip-stale-issue-message` | Skip adding stale message on stale issue.
_Defaults to **false**_. |
-| `skip-stale-pr-message` | Skip adding stale message on stale PR.
_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.
_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.
_Override `exempt-milestones`_. |
-| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale.
_Override `exempt-milestones`_. |
-| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale.
_Priority over `exempt-milestones` rules_. |
-| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale.
_Override `exempt-all-milestones`_. |
-| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale.
_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.
_Override `exempt-assignees`_. |
-| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale.
_Override `exempt-assignees`_. |
-| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale.
_Priority over `exempt-assignees` rules_. |
-| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale.
_Override `exempt-all-assignees`_. |
-| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale.
_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.
_Defaults to **true**_. |
+| Input | Description |
+| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `repo-token` | PAT(Personal Access Token) for authorizing repository.
_Defaults to **${{ github.token }}**_. |
+| `days-before-stale` | Idle number of days before marking an issue/PR as stale.
_Defaults to **60**_. |
+| `days-before-issue-stale` | Idle number of days before marking an issue as stale.
_Override `days-before-stale`_. |
+| `days-before-pr-stale` | Idle number of days before marking an PR as stale.
_Override `days-before-stale`_. |
+| `days-before-close` | Idle number of days before closing an stale issue/PR.
_Defaults to **7**_. |
+| `days-before-issue-close` | Idle number of days before closing an stale issue.
_Override `days-before-close`_. |
+| `days-before-pr-close` | Idle number of days before closing an stale PR.
_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.
_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.
_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").
_Override `only-labels`_. |
+| `only-pr-labels` | Only PRs with ALL these labels are checked.
Separate multiple labels with commas (eg. "question,answered").
_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-issue-labels` | Only issues with ANY of these labels are checked.
Separate multiple labels with commas (eg. "incomplete,waiting-feedback").
_Override `any-of-labels`_. |
+| `any-of-pr-labels` | Only PRs with ANY of these labels are checked.
Separate multiple labels with commas (eg. "incomplete,waiting-feedback").
_Override `any-of-labels`_. |
+| `operations-per-run` | Maximum number of operations per run.
GitHub API CRUD related.
_Defaults to **30**_. |
+| `remove-stale-when-updated` | Remove stale label from issue/PR on updates or comments.
_Defaults to **true**_. |
+| `remove-issue-stale-when-updated` | Remove stale label from issue on updates or comments.
_Defaults to **true**_.
_Override `remove-stale-when-updated`_. |
+| `remove-pr-stale-when-updated` | Remove stale label from PR on updates or comments.
_Defaults to **true**_.
_Override `remove-stale-when-updated`_. |
+| `debug-only` | Dry-run on action.
_Defaults to **false**_. |
+| `ascending` | Order to get issues/PR.
`true` is ascending, `false` is descending.
_Defaults to **false**_. |
+| `skip-stale-issue-message` | Skip adding stale message on stale issue.
_Defaults to **false**_. |
+| `skip-stale-pr-message` | Skip adding stale message on stale PR.
_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.
_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.
_Override `exempt-milestones`_. |
+| `exempt-pr-milestones` | Milestones on the PR exempted from being marked as stale.
_Override `exempt-milestones`_. |
+| `exempt-all-milestones` | Exempt all issues and PRs with milestones from being marked as stale.
_Priority over `exempt-milestones` rules_. |
+| `exempt-all-issue-milestones` | Exempt all issues with milestones from being marked as stale.
_Override `exempt-all-milestones`_. |
+| `exempt-all-pr-milestones` | Exempt all PRs with milestones from being marked as stale.
_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.
_Override `exempt-assignees`_. |
+| `exempt-pr-assignees` | Assignees on the PR exempted from being marked as stale.
_Override `exempt-assignees`_. |
+| `exempt-all-assignees` | Exempt all issues and PRs with assignees from being marked as stale.
_Priority over `exempt-assignees` rules_. |
+| `exempt-all-issue-assignees` | Exempt all issues with assignees from being marked as stale.
_Override `exempt-all-assignees`_. |
+| `exempt-all-pr-assignees` | Exempt all PRs with assignees from being marked as stale.
_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.
_Defaults to **true**_. |
### Detailed options
diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts
index 035e4ac19..3b87f1e96 100644
--- a/__tests__/constants/default-processor-options.ts
+++ b/__tests__/constants/default-processor-options.ts
@@ -27,6 +27,8 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({
operationsPerRun: 100,
debugOnly: true,
removeStaleWhenUpdated: false,
+ removeIssueStaleWhenUpdated: undefined,
+ removePrStaleWhenUpdated: undefined,
ascending: false,
skipStaleIssueMessage: false,
skipStalePrMessage: false,
diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts
index 6d6a0a155..3a86eb227 100644
--- a/__tests__/main.spec.ts
+++ b/__tests__/main.spec.ts
@@ -1220,8 +1220,7 @@ test('stale issues should not be closed if days is set to -1', async () => {
});
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,
@@ -1257,8 +1256,7 @@ test('stale label should be removed if a comment was added to a stale issue', as
});
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(
diff --git a/__tests__/remove-stale-when-updated.spec.ts b/__tests__/remove-stale-when-updated.spec.ts
new file mode 100644
index 000000000..24eeef592
--- /dev/null
+++ b/__tests__/remove-stale-when-updated.spec.ts
@@ -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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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 => {
+ 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[]): IssuesProcessorBuilder {
+ this._issues = issues.map(
+ (issue: Readonly>, index: Readonly): 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[]): IssuesProcessorBuilder {
+ this.issuesOrPrs(
+ issues.map(
+ (issue: Readonly>): Partial => {
+ return {
+ ...issue,
+ pull_request: null
+ };
+ }
+ )
+ );
+
+ return this;
+ }
+
+ staleIssues(issues: Partial[]): IssuesProcessorBuilder {
+ this.issues(
+ issues.map(
+ (issue: Readonly>): Partial => {
+ return {
+ ...issue,
+ updated_at: '2020-01-01T17:00:00Z',
+ created_at: '2020-01-01T17:00:00Z',
+ labels: issue.labels?.map(
+ (label: Readonly): ILabel => {
+ return {
+ ...label,
+ name: 'Stale'
+ };
+ }
+ ) ?? [
+ {
+ name: 'Stale'
+ }
+ ]
+ };
+ }
+ )
+ );
+
+ return this;
+ }
+
+ prs(issues: Partial[]): IssuesProcessorBuilder {
+ this.issuesOrPrs(
+ issues.map(
+ (issue: Readonly>): Partial => {
+ return {
+ ...issue,
+ pull_request: {key: 'value'}
+ };
+ }
+ )
+ );
+
+ return this;
+ }
+
+ stalePrs(issues: Partial[]): IssuesProcessorBuilder {
+ this.prs(
+ issues.map(
+ (issue: Readonly>): Partial => {
+ return {
+ ...issue,
+ updated_at: '2020-01-01T17:00:00Z',
+ created_at: '2020-01-01T17:00:00Z',
+ labels: issue.labels?.map(
+ (label: Readonly): 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()
+ );
+ }
+}
diff --git a/action.yml b/action.yml
index 43f92678f..0fc0ac6ba 100644
--- a/action.yml
+++ b/action.yml
@@ -113,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:
diff --git a/dist/index.js b/dist/index.js
index 11c43e498..6367819d1 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -233,6 +233,7 @@ const option_1 = __nccwpck_require__(5931);
const get_humanized_date_1 = __nccwpck_require__(965);
const is_date_more_recent_than_1 = __nccwpck_require__(1473);
const is_valid_date_1 = __nccwpck_require__(891);
+const is_boolean_1 = __nccwpck_require__(8236);
const is_labeled_1 = __nccwpck_require__(6792);
const should_mark_when_stale_1 = __nccwpck_require__(2461);
const words_to_list_1 = __nccwpck_require__(1883);
@@ -519,18 +520,20 @@ class IssuesProcessor {
return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue);
const markedStaleOn = (yield this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
- issueLogger.info(`$$type marked stale on: ${markedStaleOn}`);
+ issueLogger.info(`$$type marked stale on: ${chalk_1.default.cyan(markedStaleOn)}`);
const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor);
- issueLogger.info(`$$type has been commented on: ${issueHasComments}`);
+ issueLogger.info(`$$type has been commented on: ${chalk_1.default.cyan(issueHasComments)}`);
const daysBeforeClose = issue.isPullRequest
? this._getDaysBeforePrClose()
: this._getDaysBeforeIssueClose();
issueLogger.info(`Days before $$type close: ${daysBeforeClose}`);
const issueHasUpdate = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose);
- issueLogger.info(`$$type has been updated: ${issueHasUpdate}`);
+ issueLogger.info(`$$type has been updated: ${chalk_1.default.cyan(issueHasUpdate)}`);
// should we un-stale this issue?
- if (this.options.removeStaleWhenUpdated && issueHasComments) {
+ if (this._shouldRemoveStaleWhenUpdated(issue) && issueHasComments) {
yield 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
if (daysBeforeClose < 0) {
@@ -592,7 +595,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error creating a comment: ${error.message}`);
+ issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
try {
@@ -607,7 +610,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error adding a label: ${error.message}`);
+ issueLogger.error(`Error when adding a label: ${error.message}`);
}
});
}
@@ -633,7 +636,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error creating a comment: ${error.message}`);
+ issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
if (closeLabel) {
@@ -648,7 +651,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error adding a label: ${error.message}`);
+ issueLogger.error(`Error when adding a label: ${error.message}`);
}
}
try {
@@ -662,7 +665,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error updating this $$type: ${error.message}`);
+ issueLogger.error(`Error when updating this $$type: ${error.message}`);
}
});
}
@@ -684,7 +687,7 @@ class IssuesProcessor {
return pullRequest.data;
}
catch (error) {
- issueLogger.error(`Error getting this $$type: ${error.message}`);
+ issueLogger.error(`Error when getting this $$type: ${error.message}`);
}
});
}
@@ -703,7 +706,7 @@ class IssuesProcessor {
return;
}
const branch = pullRequest.head.ref;
- issueLogger.info(`Deleting branch ${branch} from closed $$type`);
+ issueLogger.info(`Deleting the branch "${chalk_1.default.cyan(branch)}" from closed $$type`);
try {
this._operations.consumeOperation();
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedBranchesCount();
@@ -714,7 +717,7 @@ class IssuesProcessor {
});
}
catch (error) {
- issueLogger.error(`Error deleting branch ${branch} from $$type: ${error.message}`);
+ issueLogger.error(`Error when deleting the branch "${chalk_1.default.cyan(branch)}" from $$type: ${error.message}`);
}
});
}
@@ -723,7 +726,7 @@ class IssuesProcessor {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue);
- issueLogger.info(`Removing label "${label}" from $$type`);
+ issueLogger.info(`Removing the label "${chalk_1.default.cyan(label)}" from the $$type...`);
this.removedLabelIssues.push(issue);
if (this.options.debugOnly) {
return;
@@ -737,9 +740,10 @@ class IssuesProcessor {
issue_number: issue.number,
name: label
});
+ issueLogger.info(`The label "${chalk_1.default.cyan(label)}" was removed`);
}
catch (error) {
- issueLogger.error(`Error removing a label: ${error.message}`);
+ issueLogger.error(`Error when removing the label: "${chalk_1.default.cyan(error.message)}"`);
}
});
}
@@ -789,6 +793,18 @@ class IssuesProcessor {
}
return this.options.anyOfLabels;
}
+ _shouldRemoveStaleWhenUpdated(issue) {
+ if (issue.isPullRequest) {
+ if (is_boolean_1.isBoolean(this.options.removePrStaleWhenUpdated)) {
+ return this.options.removePrStaleWhenUpdated;
+ }
+ return this.options.removeStaleWhenUpdated;
+ }
+ if (is_boolean_1.isBoolean(this.options.removeIssueStaleWhenUpdated)) {
+ return this.options.removeIssueStaleWhenUpdated;
+ }
+ return this.options.removeStaleWhenUpdated;
+ }
_removeStaleLabel(issue, staleLabel) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
@@ -808,7 +824,7 @@ class IssuesProcessor {
return Promise.resolve();
}
if (is_labeled_1.isLabeled(issue, closeLabel)) {
- issueLogger.info(`The $$type has a close label "${closeLabel}". Removing the close label...`);
+ issueLogger.info(`The $$type has a close label "${chalk_1.default.cyan(closeLabel)}". Removing the close label...`);
yield this._removeLabel(issue, closeLabel);
(_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedCloseItemsLabelsCount(issue);
}
@@ -1619,6 +1635,21 @@ function isValidDate(date) {
exports.isValidDate = isValidDate;
+/***/ }),
+
+/***/ 8236:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.isBoolean = void 0;
+function isBoolean(value) {
+ return value === true || value === false;
+}
+exports.isBoolean = isBoolean;
+
+
/***/ }),
/***/ 6792:
@@ -1794,6 +1825,8 @@ function _getAndValidateArgs() {
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',
diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts
index 6d10ae9eb..139edf66a 100644
--- a/src/classes/issue.spec.ts
+++ b/src/classes/issue.spec.ts
@@ -35,6 +35,8 @@ describe('Issue', (): void => {
anyOfPrLabels: '',
operationsPerRun: 0,
removeStaleWhenUpdated: false,
+ removeIssueStaleWhenUpdated: undefined,
+ removePrStaleWhenUpdated: undefined,
repoToken: '',
skipStaleIssueMessage: false,
skipStalePrMessage: false,
diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts
index 2662b0f87..9d3d4356e 100644
--- a/src/classes/issues-processor.ts
+++ b/src/classes/issues-processor.ts
@@ -7,6 +7,7 @@ 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 {shouldMarkWhenStale} from '../functions/should-mark-when-stale';
import {wordsToList} from '../functions/words-to-list';
@@ -453,14 +454,16 @@ 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 daysBeforeClose: number = issue.isPullRequest
? this._getDaysBeforePrClose()
@@ -472,11 +475,15 @@ export class IssuesProcessor {
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
@@ -565,7 +572,7 @@ export class IssuesProcessor {
body: staleMessage
});
} catch (error) {
- issueLogger.error(`Error creating a comment: ${error.message}`);
+ issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
@@ -580,7 +587,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}`);
}
}
@@ -610,7 +617,7 @@ export class IssuesProcessor {
body: closeMessage
});
} catch (error) {
- issueLogger.error(`Error creating a comment: ${error.message}`);
+ issueLogger.error(`Error when creating a comment: ${error.message}`);
}
}
@@ -625,7 +632,7 @@ export class IssuesProcessor {
labels: [closeLabel]
});
} catch (error) {
- issueLogger.error(`Error adding a label: ${error.message}`);
+ issueLogger.error(`Error when adding a label: ${error.message}`);
}
}
@@ -639,7 +646,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}`);
}
}
@@ -663,7 +670,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}`);
}
}
@@ -687,7 +694,9 @@ 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._operations.consumeOperation();
@@ -699,7 +708,9 @@ 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
+ }`
);
}
}
@@ -708,7 +719,9 @@ export class IssuesProcessor {
private async _removeLabel(issue: Issue, label: string): Promise {
const issueLogger: IssueLogger = new IssueLogger(issue);
- issueLogger.info(`Removing label "${label}" from $$type`);
+ issueLogger.info(
+ `Removing the label "${chalk.cyan(label)}" from the $$type...`
+ );
this.removedLabelIssues.push(issue);
if (this.options.debugOnly) {
@@ -724,8 +737,11 @@ export class IssuesProcessor {
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)}"`
+ );
}
}
@@ -781,6 +797,22 @@ export class IssuesProcessor {
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
@@ -813,7 +845,9 @@ 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);
diff --git a/src/functions/is-boolean.spec.ts b/src/functions/is-boolean.spec.ts
new file mode 100644
index 000000000..62d9ea130
--- /dev/null
+++ b/src/functions/is-boolean.spec.ts
@@ -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);
+ });
+ }
+ );
+});
diff --git a/src/functions/is-boolean.ts b/src/functions/is-boolean.ts
new file mode 100644
index 000000000..4721bc505
--- /dev/null
+++ b/src/functions/is-boolean.ts
@@ -0,0 +1,3 @@
+export function isBoolean(value: unknown): value is boolean {
+ return value === true || value === false;
+}
diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts
index 0c981f865..dc00421d1 100644
--- a/src/interfaces/issues-processor-options.ts
+++ b/src/interfaces/issues-processor-options.ts
@@ -26,6 +26,8 @@ export interface IIssuesProcessorOptions {
anyOfPrLabels: string;
operationsPerRun: number;
removeStaleWhenUpdated: boolean;
+ removeIssueStaleWhenUpdated: boolean | undefined;
+ removePrStaleWhenUpdated: boolean | undefined;
debugOnly: boolean;
ascending: boolean;
skipStaleIssueMessage: boolean;
diff --git a/src/main.ts b/src/main.ts
index 0cdf69e30..7b8d75f55 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -49,6 +49,12 @@ function _getAndValidateArgs(): IIssuesProcessorOptions {
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',