feat(stale-and-close): add new options to change the days before close (#224)
* docs(readme): add new options in the documentation * chore: update the action schema * chore: parse the new arguments * feat(stale-and-close): add new options to change the days before close to avoid a breaking change and simplify the configuration the old options 'daysBeforeStale' and 'daysBeforePrClose' are kept and new options are available to override them with 'daysBeforeIssueStale', 'daysBeforePrStale', 'daysBeforeIssueClose' and 'daysBeforePrClose' * chore: rename the issue type enum to remove the enum suffix * chore: add missing dependency for eslint and typescript also upgrade the parser * chore: fix an issue with the linter for the shadow rules it was not configured properly for TypeScript * chore: use camelCase for constants * chore: use camelCase for enum members * chore: fix the tests * chore: enhance prettier to also lint other kind of files it was configured to only work with ts and it was not working well to be honest also now the lint scripts will also run prettier
This commit is contained in:
parent
b12dccced8
commit
552e4c60f0
107
.eslintrc.json
107
.eslintrc.json
|
@ -1,52 +1,57 @@
|
|||
{
|
||||
"plugins": ["jest", "@typescript-eslint"],
|
||||
"extends": ["plugin:github/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"semi": "off",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest/globals": true
|
||||
}
|
||||
}
|
||||
"plugins": ["jest", "@typescript-eslint"],
|
||||
"extends": ["plugin:github/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{"accessibility": "no-public"}
|
||||
],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"semi": "off",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": "error"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest/globals": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@ allowed:
|
|||
- unlicense
|
||||
|
||||
reviewed:
|
||||
npm:
|
||||
npm:
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.idea
|
||||
.licenses
|
||||
.vscode
|
||||
dist
|
||||
node_modules
|
||||
package-lock.json
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript"
|
||||
}
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
|
104
README.md
104
README.md
|
@ -24,28 +24,32 @@ $ npm test
|
|||
|
||||
### Arguments
|
||||
|
||||
| Input | Description | Usage |
|
||||
| :-------------------------: | :-------------------------------------------------------------------------------: | :------: |
|
||||
| `repo-token` | PAT(Personal Access Token) for authorizing repository. | Optional |
|
||||
| `days-before-stale` | Idle number of days before marking an issue/pr as stale. \*Defaults to **60\*** | Optional |
|
||||
| `days-before-close` | Idle number of days before closing an stale issue/pr. \*Defaults to **7\*** | Optional |
|
||||
| `stale-issue-message` | Message to post on the stale issue. | Optional |
|
||||
| `stale-pr-message` | Message to post on the stale pr. | Optional |
|
||||
| `close-issue-message` | Message to post on the stale issue while closing it. | Optional |
|
||||
| `close-pr-message` | Message to post on the stale pr while closing it. | Optional |
|
||||
| `stale-issue-label` | Label to apply on the stale issue. \*Defaults to **stale\*** | Optional |
|
||||
| `close-issue-label` | Label to apply on closing issue. | Optional |
|
||||
| `stale-pr-label` | Label to apply on the stale pr. | Optional |
|
||||
| `close-pr-label` | Label to apply on the closing pr. | Optional |
|
||||
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
||||
| `exempt-pr-labels` | Labels on the pr exempted from being marked as stale. | Optional |
|
||||
| `only-labels` | Only labels checked for stale issue/pr. | Optional |
|
||||
| `operations-per-run` | Maximum number of operations per run. \*Defaults to **30\*** | Optional |
|
||||
| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. \*Defaults to **true\*** | Optional |
|
||||
| `debug-only` | Dry-run on action. \*Defaults to **false\*** | Optional |
|
||||
| `ascending` | Order to get issues/pr. \*Defaults to **false\*** | Optional |
|
||||
| `skip-stale-issue-message` | Skip adding stale message on stale issue. \*Defaults to **false\*** | Optional |
|
||||
| `skip-stale-pr-message` | Skip adding stale message on stale pr. \*Defaults to **false\*** | Optional |
|
||||
| Input | Description | Usage |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------ | -------- |
|
||||
| `repo-token` | PAT(Personal Access Token) for authorizing repository. | Optional |
|
||||
| `days-before-stale` | Idle number of days before marking an issue/pr as stale. \*Defaults to **60** | Optional |
|
||||
| `days-before-issue-stale` | Idle number of days before marking an issue as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-pr-stale` | Idle number of days before marking an pr as stale (override `days-before-stale`). | Optional |
|
||||
| `days-before-close` | Idle number of days before closing an stale issue/pr. \*Defaults to **7\*** | Optional |
|
||||
| `days-before-issue-close` | Idle number of days before closing an stale issue (override `days-before-close`). | Optional |
|
||||
| `days-before-pr-close` | Idle number of days before closing an stale pr (override `days-before-close`). | Optional |
|
||||
| `stale-issue-message` | Message to post on the stale issue. | Optional |
|
||||
| `stale-pr-message` | Message to post on the stale pr. | Optional |
|
||||
| `close-issue-message` | Message to post on the stale issue while closing it. | Optional |
|
||||
| `close-pr-message` | Message to post on the stale pr while closing it. | Optional |
|
||||
| `stale-issue-label` | Label to apply on the stale issue. \*Defaults to **stale\*** | Optional |
|
||||
| `close-issue-label` | Label to apply on closing issue. | Optional |
|
||||
| `stale-pr-label` | Label to apply on the stale pr. | Optional |
|
||||
| `close-pr-label` | Label to apply on the closing pr. | Optional |
|
||||
| `exempt-issue-labels` | Labels on an issue exempted from being marked as stale. | Optional |
|
||||
| `exempt-pr-labels` | Labels on the pr exempted from being marked as stale. | Optional |
|
||||
| `only-labels` | Only labels checked for stale issue/pr. | Optional |
|
||||
| `operations-per-run` | Maximum number of operations per run. \*Defaults to **30\*** | Optional |
|
||||
| `remove-stale-when-updated` | Remove stale label from issue/pr on updates or comments. \*Defaults to **true\*** | Optional |
|
||||
| `debug-only` | Dry-run on action. \*Defaults to **false\*** | Optional |
|
||||
| `ascending` | Order to get issues/pr. \*Defaults to **false\*** | Optional |
|
||||
| `skip-stale-issue-message` | Skip adding stale message on stale issue. \*Defaults to **false\*** | Optional |
|
||||
| `skip-stale-pr-message` | Skip adding stale message on stale pr. \*Defaults to **false\*** | Optional |
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -54,7 +58,7 @@ See [action.yml](./action.yml) For comprehensive list of options.
|
|||
Basic:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues'
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
@ -72,7 +76,7 @@ jobs:
|
|||
Configure stale timeouts:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues'
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
@ -83,15 +87,63 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
```
|
||||
|
||||
Configure different stale timeouts but never close a pr:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues and PR'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
stale-pr-message: 'This pr is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
days-before-pr-close: -1
|
||||
```
|
||||
|
||||
Configure different stale timeouts:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
stale-pr-message: 'This pr is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||
close-pr-message: 'This pr was closed because it has been stalled for 10 days with no activity.'
|
||||
days-before-issue-stale: 30
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-close: 10
|
||||
```
|
||||
|
||||
Configure labels:
|
||||
|
||||
```yaml
|
||||
name: 'Close stale issues'
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as github from '@actions/github';
|
||||
|
||||
import {
|
||||
IssueProcessor,
|
||||
Issue,
|
||||
IssueProcessor,
|
||||
IssueProcessorOptions
|
||||
} from '../src/IssueProcessor';
|
||||
|
||||
|
@ -35,7 +35,11 @@ const DefaultProcessorOptions: IssueProcessorOptions = Object.freeze({
|
|||
closeIssueMessage: 'This issue is being closed',
|
||||
closePrMessage: 'This PR is being closed',
|
||||
daysBeforeStale: 1,
|
||||
daysBeforeIssueStale: NaN,
|
||||
daysBeforePrStale: NaN,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeIssueClose: NaN,
|
||||
daysBeforePrClose: NaN,
|
||||
staleIssueLabel: 'Stale',
|
||||
closeIssueLabel: '',
|
||||
exemptIssueLabels: '',
|
||||
|
@ -73,8 +77,36 @@ test('processing an issue with no label will make it stale and close it, if it i
|
|||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts = {...DefaultProcessorOptions};
|
||||
opts.daysBeforeClose = 0;
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 0
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(1);
|
||||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will make it stale and close it, if it is old enough only if days-before-close is set to > 0 and days-before-issue-close is set to 0', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 1,
|
||||
daysBeforeIssueClose: 0
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
|
@ -92,16 +124,70 @@ test('processing an issue with no label will make it stale and close it, if it i
|
|||
expect(processor.deletedBranchIssues.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will make it stale and not close it, if it is old enough only if days-before-close is set to > 0 and days-before-issue-close is set to > 0', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 1,
|
||||
daysBeforeIssueClose: 1
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(1);
|
||||
expect(processor.closedIssues.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts = {...DefaultProcessorOptions};
|
||||
opts.daysBeforeClose = 15;
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 15
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
DefaultProcessorOptions,
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(1);
|
||||
expect(processor.closedIssues.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will make it stale and not close it if days-before-close is set to -1 and days-before-issue-close is set to > 0', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: -1,
|
||||
daysBeforeIssueClose: 15
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
|
@ -120,7 +206,7 @@ test('processing an issue with no label will not make it stale if days-before-st
|
|||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts = {
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
staleIssueMessage: '',
|
||||
daysBeforeStale: -1
|
||||
|
@ -141,6 +227,33 @@ test('processing an issue with no label will not make it stale if days-before-st
|
|||
expect(processor.closedIssues.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will not make it stale if days-before-stale and days-before-issue-stale are set to -1', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
staleIssueMessage: '',
|
||||
daysBeforeStale: -1,
|
||||
daysBeforeIssueStale: -1
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(0);
|
||||
expect(processor.closedIssues.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('processing an issue with no label will make it stale but not close it', async () => {
|
||||
// issue should be from 2 days ago so it will be
|
||||
// stale but not close-able, based on default settings
|
||||
|
@ -177,8 +290,13 @@ test('processing a stale issue will close it', async () => {
|
|||
)
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
DefaultProcessorOptions,
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
|
@ -254,6 +372,38 @@ test('processing a stale issue containing a slash in the label will close it', a
|
|||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it when days-before-issue-stale override days-before-stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
1,
|
||||
'A stale issue that should be closed',
|
||||
'2020-01-01T17:00:00Z',
|
||||
false,
|
||||
['Stale']
|
||||
)
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforeIssueStale: 30
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(0);
|
||||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
|
@ -265,8 +415,45 @@ test('processing a stale PR will close it', async () => {
|
|||
)
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
DefaultProcessorOptions,
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(0);
|
||||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it when days-before-pr-stale override days-before-stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
1,
|
||||
'A stale PR that should be closed',
|
||||
'2020-01-01T17:00:00Z',
|
||||
true,
|
||||
['Stale']
|
||||
)
|
||||
];
|
||||
|
||||
const opts: IssueProcessorOptions = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeClose: 30,
|
||||
daysBeforePrClose: 30
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
|
@ -308,6 +495,35 @@ test('processing a stale issue will close it even if configured not to mark as s
|
|||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale issue will close it even if configured not to mark as stale when days-before-issue-stale override days-before-stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z', false, [
|
||||
'Stale'
|
||||
])
|
||||
];
|
||||
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: 0,
|
||||
daysBeforeIssueStale: -1,
|
||||
staleIssueMessage: ''
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(0);
|
||||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it even if configured not to mark as stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z', true, [
|
||||
|
@ -336,6 +552,35 @@ test('processing a stale PR will close it even if configured not to mark as stal
|
|||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('processing a stale PR will close it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z', true, [
|
||||
'Stale'
|
||||
])
|
||||
];
|
||||
|
||||
const opts = {
|
||||
...DefaultProcessorOptions,
|
||||
daysBeforeStale: 0,
|
||||
daysBeforePrStale: -1,
|
||||
stalePrMessage: ''
|
||||
};
|
||||
|
||||
const processor = new IssueProcessor(
|
||||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num, dt) => [],
|
||||
async (issue, label) => new Date().toDateString()
|
||||
);
|
||||
|
||||
// process our fake issue list
|
||||
await processor.processIssues(1);
|
||||
|
||||
expect(processor.staleIssues.length).toEqual(0);
|
||||
expect(processor.closedIssues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('closed issues will not be marked stale', async () => {
|
||||
const TestIssueList: Issue[] = [
|
||||
generateIssue(
|
||||
|
@ -692,7 +937,14 @@ test('stale label should be removed if a comment was added to a stale issue', as
|
|||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num: number, dt: string) => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update
|
||||
async (num: number, dt: string) => [
|
||||
{
|
||||
user: {
|
||||
login: 'notme',
|
||||
type: 'User'
|
||||
}
|
||||
}
|
||||
], // return a fake comment to indicate there was an update
|
||||
async (issue: Issue, label: string) => new Date().toDateString()
|
||||
);
|
||||
|
||||
|
@ -723,7 +975,14 @@ test('stale label should not be removed if a comment was added by the bot (and t
|
|||
opts,
|
||||
async () => 'abot',
|
||||
async p => (p == 1 ? TestIssueList : []),
|
||||
async (num: number, dt: string) => [{user: {login: 'abot', type: 'User'}}], // return a fake comment to indicate there was an update by the bot
|
||||
async (num: number, dt: string) => [
|
||||
{
|
||||
user: {
|
||||
login: 'abot',
|
||||
type: 'User'
|
||||
}
|
||||
}
|
||||
], // return a fake comment to indicate there was an update by the bot
|
||||
async (issue: Issue, label: string) => new Date().toDateString()
|
||||
);
|
||||
|
||||
|
|
54
action.yml
54
action.yml
|
@ -7,58 +7,90 @@ inputs:
|
|||
default: ${{ github.token }}
|
||||
stale-issue-message:
|
||||
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
|
||||
required: false
|
||||
stale-pr-message:
|
||||
description: 'The message to post on the pr when tagging it. If none provided, will not mark pull requests stale.'
|
||||
required: false
|
||||
close-issue-message:
|
||||
description: 'The message to post on the issue when closing it. If none provided, will not comment when closing an issue.'
|
||||
required: false
|
||||
close-pr-message:
|
||||
description: 'The message to post on the pr when closing it. If none provided, will not comment when closing a pull requests.'
|
||||
required: false
|
||||
days-before-stale:
|
||||
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically.'
|
||||
default: 60
|
||||
description: 'The number of days old an issue or a pull request can be before marking it stale. Set to -1 to never mark issues or pull requests as stale automatically.'
|
||||
required: false
|
||||
default: '60'
|
||||
days-before-issue-stale:
|
||||
description: 'The number of days old an issue can be before marking it stale. Set to -1 to never mark issues as stale automatically. Override "days-before-stale" option regarding the issues only.'
|
||||
required: false
|
||||
days-before-pr-stale:
|
||||
description: 'The number of days old a pull request can be before marking it stale. Set to -1 to never mark pull requests as stale automatically. Override "days-before-stale" option regarding the pull requests only.'
|
||||
required: false
|
||||
days-before-close:
|
||||
description: 'The number of days to wait to close an issue or pull request after it being marked stale. Set to -1 to never close stale issues.'
|
||||
default: 7
|
||||
description: 'The number of days to wait to close an issue or a pull request after it being marked stale. Set to -1 to never close stale issues or pull requests.'
|
||||
required: false
|
||||
default: '7'
|
||||
days-before-issue-close:
|
||||
description: 'The number of days to wait to close an issue after it being marked stale. Set to -1 to never close stale issues. Override "days-before-close" option regarding the issues only.'
|
||||
required: false
|
||||
days-before-pr-close:
|
||||
description: 'The number of days to wait to close a pull request after it being marked stale. Set to -1 to never close stale pull requests. Override "days-before-close" option regarding the pull requests only.'
|
||||
required: false
|
||||
stale-issue-label:
|
||||
description: 'The label to apply when an issue is stale.'
|
||||
required: false
|
||||
default: 'Stale'
|
||||
close-issue-label:
|
||||
description: 'The label to apply when an issue is closed.'
|
||||
required: false
|
||||
exempt-issue-labels:
|
||||
description: 'The labels that mean an issue is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
||||
default: ''
|
||||
required: false
|
||||
stale-pr-label:
|
||||
description: 'The label to apply when a pull request is stale.'
|
||||
default: 'Stale'
|
||||
required: false
|
||||
close-pr-label:
|
||||
description: 'The label to apply when a pull request is closed.'
|
||||
required: false
|
||||
exempt-pr-labels:
|
||||
description: 'The labels that mean a pull request is exempt from being marked stale. Separate multiple labels with commas (eg. "label1,label2")'
|
||||
default: ''
|
||||
required: false
|
||||
only-labels:
|
||||
description: 'Only issues or pull requests with all of these labels are checked if stale. Defaults to `[]` (disabled) and can be a comma-separated list of labels.'
|
||||
default: ''
|
||||
required: false
|
||||
operations-per-run:
|
||||
description: 'The maximum number of operations per run, used to control rate limiting.'
|
||||
default: 30
|
||||
default: '30'
|
||||
required: false
|
||||
remove-stale-when-updated:
|
||||
description: 'Remove stale labels from issues when they are updated or commented on.'
|
||||
default: true
|
||||
default: 'true'
|
||||
required: false
|
||||
debug-only:
|
||||
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
||||
default: false
|
||||
default: 'false'
|
||||
required: false
|
||||
ascending:
|
||||
description: 'The order to get issues or pull requests. Defaults to false, which is descending'
|
||||
default: false
|
||||
default: 'false'
|
||||
required: false
|
||||
skip-stale-pr-message:
|
||||
description: 'Skip adding stale message when marking a pull request as stale.'
|
||||
default: false
|
||||
default: 'false'
|
||||
required: false
|
||||
skip-stale-issue-message:
|
||||
description: 'Skip adding stale message when marking an issue as stale.'
|
||||
default: false
|
||||
default: 'false'
|
||||
required: false
|
||||
delete-branch:
|
||||
description: 'Delete the git branch after closing a stale pull request.'
|
||||
default: false
|
||||
default: 'false'
|
||||
required: false
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
|
|
|
@ -1912,53 +1912,60 @@
|
|||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz",
|
||||
"integrity": "sha512-4OEcPON3QIx0ntsuiuFP/TkldmBGXf0uKxPQlGtS/W2F3ndYm8Vgdpj/woPJkzUc65gd3iR+qi3K8SDQP/obFg==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz",
|
||||
"integrity": "sha512-ygqDUm+BUPvrr0jrXqoteMqmIaZ/bixYOc3A4BRwzEPTZPi6E+n44rzNZWaB0YvtukgP+aoj0i/fyx7FkM2p1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "3.7.0",
|
||||
"@typescript-eslint/experimental-utils": "4.13.0",
|
||||
"@typescript-eslint/scope-manager": "4.13.0",
|
||||
"debug": "^4.1.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"regexpp": "^3.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.7.0.tgz",
|
||||
"integrity": "sha512-xpfXXAfZqhhqs5RPQBfAFrWDHoNxD5+sVB5A46TF58Bq1hRfVROrWHcQHHUM9aCBdy9+cwATcvCbRg8aIRbaHQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.13.0.tgz",
|
||||
"integrity": "sha512-/ZsuWmqagOzNkx30VWYV3MNB/Re/CGv/7EzlqZo5RegBN8tMuPaBgNK6vPBCQA8tcYrbsrTdbx3ixMRRKEEGVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/types": "3.7.0",
|
||||
"@typescript-eslint/typescript-estree": "3.7.0",
|
||||
"@typescript-eslint/scope-manager": "4.13.0",
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/typescript-estree": "4.13.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.0.tgz",
|
||||
"integrity": "sha512-xr5oobkYRebejlACGr1TJ0Z/r0a2/HUf0SXqPvlgUMwiMqOCu/J+/Dr9U3T0IxpE5oLFSkqMx1FE/dKaZ8KsOQ==",
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.13.0.tgz",
|
||||
"integrity": "sha512-UpK7YLG2JlTp/9G4CHe7GxOwd93RBf3aHO5L+pfjIrhtBvZjHKbMhBXTIQNkbz7HZ9XOe++yKrXutYm5KmjWgQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "3.7.0",
|
||||
"@typescript-eslint/visitor-keys": "3.7.0",
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/visitor-keys": "4.13.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.13.0.tgz",
|
||||
"integrity": "sha512-9A0/DFZZLlGXn5XA349dWQFwPZxcyYyCFX5X88nWs2uachRDwGeyPz46oTsm9ZJE66EALvEns1lvBwa4d9QxMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/visitor-keys": "4.13.0",
|
||||
"debug": "^4.1.1",
|
||||
"glob": "^7.1.6",
|
||||
"globby": "^11.0.1",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1985,41 +1992,35 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.8.1.tgz",
|
||||
"integrity": "sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.13.0.tgz",
|
||||
"integrity": "sha512-KO0J5SRF08pMXzq9+abyHnaGQgUJZ3Z3ax+pmqz9vl81JxmTTOUfQmq7/4awVfq09b6C4owNlOgOwp61pYRBSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "4.8.1",
|
||||
"@typescript-eslint/types": "4.8.1",
|
||||
"@typescript-eslint/typescript-estree": "4.8.1",
|
||||
"@typescript-eslint/scope-manager": "4.13.0",
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/typescript-estree": "4.13.0",
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz",
|
||||
"integrity": "sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.13.0.tgz",
|
||||
"integrity": "sha512-UpK7YLG2JlTp/9G4CHe7GxOwd93RBf3aHO5L+pfjIrhtBvZjHKbMhBXTIQNkbz7HZ9XOe++yKrXutYm5KmjWgQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.8.1",
|
||||
"@typescript-eslint/visitor-keys": "4.8.1"
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/visitor-keys": "4.13.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.8.1.tgz",
|
||||
"integrity": "sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz",
|
||||
"integrity": "sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.13.0.tgz",
|
||||
"integrity": "sha512-9A0/DFZZLlGXn5XA349dWQFwPZxcyYyCFX5X88nWs2uachRDwGeyPz46oTsm9ZJE66EALvEns1lvBwa4d9QxMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.8.1",
|
||||
"@typescript-eslint/visitor-keys": "4.8.1",
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"@typescript-eslint/visitor-keys": "4.13.0",
|
||||
"debug": "^4.1.1",
|
||||
"globby": "^11.0.1",
|
||||
"is-glob": "^4.0.1",
|
||||
|
@ -2027,22 +2028,6 @@
|
|||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz",
|
||||
"integrity": "sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.8.1",
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2081,9 +2066,9 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.7.0.tgz",
|
||||
"integrity": "sha512-reCaK+hyKkKF+itoylAnLzFeNYAEktB0XVfSQvf0gcVgpz1l49Lt6Vo9x4MVCCxiDydA0iLAjTF/ODH0pbfnpg==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.13.0.tgz",
|
||||
"integrity": "sha512-/+aPaq163oX+ObOG00M0t9tKkOgdv9lq0IQv/y4SqGkAXmhFmCfgsELV7kOCTb2vVU5VOmVwXBXJTDr353C1rQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
|
@ -2127,12 +2112,21 @@
|
|||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.0.tgz",
|
||||
"integrity": "sha512-k5PiZdB4vklUpUX4NBncn5RBKty8G3ihTY+hqJsCdMuD0v4jofI5xuqwnVcWxfv6iTm2P/dfEa2wMUnsUY8ODw==",
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.13.0.tgz",
|
||||
"integrity": "sha512-6RoxWK05PAibukE7jElqAtNMq+RWZyqJ6Q/GdIxaiUj2Ept8jh8+FUVlbq9WxMYxkmEOPvCE5cRSyupMpwW31g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
"@typescript-eslint/types": "4.13.0",
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vercel/ncc": {
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
"build": "tsc",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"lint:fix": "eslint src/**/*.ts --fix",
|
||||
"lint": "prettier --check --ignore-unknown **/*.{md,json,yml,ts} && eslint src/**/*.ts",
|
||||
"lint:fix": "prettier --write --ignore-unknown **/*.{md,json,yml,ts} && eslint src/**/*.ts --fix",
|
||||
"pack": "ncc build",
|
||||
"test": "jest",
|
||||
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
|
||||
|
@ -37,7 +37,8 @@
|
|||
"@types/lodash.deburr": "^4.1.6",
|
||||
"@types/node": "^14.10.0",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@typescript-eslint/parser": "^4.8.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"@vercel/ncc": "^0.27.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-plugin-github": "^4.0.1",
|
||||
|
|
|
@ -2,8 +2,12 @@ import * as core from '@actions/core';
|
|||
import {context, getOctokit} from '@actions/github';
|
||||
import {GitHub} from '@actions/github/lib/utils';
|
||||
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
||||
import {IssueType} from './enums/issue-type.enum';
|
||||
import {getIssueType} from './functions/get-issue-type';
|
||||
import {isLabeled} from './functions/is-labeled';
|
||||
import {isPullRequest} from './functions/is-pull-request';
|
||||
import {labelsToList} from './functions/labels-to-list';
|
||||
import {shouldMarkWhenStale} from './functions/should-mark-when-stale';
|
||||
|
||||
export interface Issue {
|
||||
title: string;
|
||||
|
@ -48,7 +52,11 @@ export interface IssueProcessorOptions {
|
|||
closeIssueMessage: string;
|
||||
closePrMessage: string;
|
||||
daysBeforeStale: number;
|
||||
daysBeforeIssueStale: number; // Could be NaN
|
||||
daysBeforePrStale: number; // Could be NaN
|
||||
daysBeforeClose: number;
|
||||
daysBeforeIssueClose: number; // Could be NaN
|
||||
daysBeforePrClose: number; // Could be NaN
|
||||
staleIssueLabel: string;
|
||||
closeIssueLabel: string;
|
||||
exemptIssueLabels: string;
|
||||
|
@ -69,14 +77,21 @@ export interface IssueProcessorOptions {
|
|||
* Handle processing of issues for staleness/closure.
|
||||
*/
|
||||
export class IssueProcessor {
|
||||
private static updatedSince(timestamp: string, num_days: number): boolean {
|
||||
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
|
||||
const millisSinceLastUpdated =
|
||||
new Date().getTime() - new Date(timestamp).getTime();
|
||||
|
||||
return millisSinceLastUpdated <= daysInMillis;
|
||||
}
|
||||
|
||||
readonly client: InstanceType<typeof GitHub>;
|
||||
readonly options: IssueProcessorOptions;
|
||||
private operationsLeft = 0;
|
||||
|
||||
readonly staleIssues: Issue[] = [];
|
||||
readonly closedIssues: Issue[] = [];
|
||||
readonly deletedBranchIssues: Issue[] = [];
|
||||
readonly removedLabelIssues: Issue[] = [];
|
||||
private operationsLeft = 0;
|
||||
|
||||
constructor(
|
||||
options: IssueProcessorOptions,
|
||||
|
@ -131,7 +146,7 @@ export class IssueProcessor {
|
|||
}
|
||||
|
||||
for (const issue of issues.values()) {
|
||||
const isPr = !!issue.pull_request;
|
||||
const isPr = isPullRequest(issue);
|
||||
|
||||
core.info(
|
||||
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
|
||||
|
@ -156,10 +171,20 @@ export class IssueProcessor {
|
|||
const skipMessage = isPr
|
||||
? this.options.skipStalePrMessage
|
||||
: this.options.skipStaleIssueMessage;
|
||||
const issueType: string = isPr ? 'pr' : 'issue';
|
||||
const shouldMarkWhenStale = this.options.daysBeforeStale > -1;
|
||||
const issueType: IssueType = getIssueType(isPr);
|
||||
const daysBeforeStale: number = isPr
|
||||
? this._getDaysBeforePrStale()
|
||||
: this._getDaysBeforeIssueStale();
|
||||
|
||||
if (!staleMessage && shouldMarkWhenStale) {
|
||||
if (isPr) {
|
||||
core.info(`Days before pull request stale: ${daysBeforeStale}`);
|
||||
} else {
|
||||
core.info(`Days before issue stale: ${daysBeforeStale}`);
|
||||
}
|
||||
|
||||
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
|
||||
|
||||
if (!staleMessage && shouldMarkAsStale) {
|
||||
core.info(`Skipping ${issueType} due to empty stale message`);
|
||||
continue;
|
||||
}
|
||||
|
@ -199,7 +224,7 @@ export class IssueProcessor {
|
|||
);
|
||||
|
||||
// determine if this issue needs to be marked stale first
|
||||
if (!isStale && shouldBeStale && shouldMarkWhenStale) {
|
||||
if (!isStale && shouldBeStale && shouldMarkAsStale) {
|
||||
core.info(
|
||||
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
||||
);
|
||||
|
@ -233,7 +258,7 @@ export class IssueProcessor {
|
|||
// handle all of the stale issue logic when we find a stale issue
|
||||
private async processStaleIssue(
|
||||
issue: Issue,
|
||||
issueType: string,
|
||||
issueType: IssueType,
|
||||
staleLabel: string,
|
||||
actor: string,
|
||||
closeMessage?: string,
|
||||
|
@ -252,9 +277,20 @@ export class IssueProcessor {
|
|||
`Issue #${issue.number} has been commented on: ${issueHasComments}`
|
||||
);
|
||||
|
||||
const isPr: boolean = isPullRequest(issue);
|
||||
const daysBeforeClose: number = isPr
|
||||
? this._getDaysBeforePrClose()
|
||||
: this._getDaysBeforeIssueClose();
|
||||
|
||||
if (isPr) {
|
||||
core.info(`Days before pull request close: ${daysBeforeClose}`);
|
||||
} else {
|
||||
core.info(`Days before issue close: ${daysBeforeClose}`);
|
||||
}
|
||||
|
||||
const issueHasUpdate: boolean = IssueProcessor.updatedSince(
|
||||
issue.updated_at,
|
||||
this.options.daysBeforeClose
|
||||
daysBeforeClose
|
||||
);
|
||||
core.info(`Issue #${issue.number} has been updated: ${issueHasUpdate}`);
|
||||
|
||||
|
@ -267,7 +303,7 @@ export class IssueProcessor {
|
|||
}
|
||||
|
||||
// now start closing logic
|
||||
if (this.options.daysBeforeClose < 0) {
|
||||
if (daysBeforeClose < 0) {
|
||||
return; // nothing to do because we aren't closing stale issues
|
||||
}
|
||||
|
||||
|
@ -590,11 +626,27 @@ export class IssueProcessor {
|
|||
return staleLabeledEvent.created_at;
|
||||
}
|
||||
|
||||
private static updatedSince(timestamp: string, num_days: number): boolean {
|
||||
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
|
||||
const millisSinceLastUpdated =
|
||||
new Date().getTime() - new Date(timestamp).getTime();
|
||||
private _getDaysBeforeIssueStale(): number {
|
||||
return isNaN(this.options.daysBeforeIssueStale)
|
||||
? this.options.daysBeforeStale
|
||||
: this.options.daysBeforeIssueStale;
|
||||
}
|
||||
|
||||
return millisSinceLastUpdated <= daysInMillis;
|
||||
private _getDaysBeforePrStale(): number {
|
||||
return isNaN(this.options.daysBeforePrStale)
|
||||
? this.options.daysBeforeStale
|
||||
: this.options.daysBeforePrStale;
|
||||
}
|
||||
|
||||
private _getDaysBeforeIssueClose(): number {
|
||||
return isNaN(this.options.daysBeforeIssueClose)
|
||||
? this.options.daysBeforeClose
|
||||
: this.options.daysBeforeIssueClose;
|
||||
}
|
||||
|
||||
private _getDaysBeforePrClose(): number {
|
||||
return isNaN(this.options.daysBeforePrClose)
|
||||
? this.options.daysBeforeClose
|
||||
: this.options.daysBeforePrClose;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export enum IssueType {
|
||||
Issue = 'issue',
|
||||
PullRequest = 'pr'
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import {getIssueType} from './get-issue-type';
|
||||
|
||||
describe('getIssueType()', (): void => {
|
||||
let isPullRequest: boolean;
|
||||
|
||||
describe('when the issue is a not pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
isPullRequest = false;
|
||||
});
|
||||
|
||||
it('should return that the issue is really an issue', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = getIssueType(isPullRequest);
|
||||
|
||||
expect(result).toStrictEqual('issue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the issue is a pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
isPullRequest = true;
|
||||
});
|
||||
|
||||
it('should return that the issue is a pull request', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = getIssueType(isPullRequest);
|
||||
|
||||
expect(result).toStrictEqual('pr');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import {IssueType} from '../enums/issue-type.enum';
|
||||
|
||||
export function getIssueType(isPullRequest: Readonly<boolean>): IssueType {
|
||||
return isPullRequest ? IssueType.PullRequest : IssueType.Issue;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import {Issue} from '../IssueProcessor';
|
||||
import {isPullRequest} from './is-pull-request';
|
||||
|
||||
describe('isPullRequest()', (): void => {
|
||||
let issue: Issue;
|
||||
|
||||
describe('when the given issue has an undefined pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issue = {
|
||||
pull_request: undefined
|
||||
} as Issue;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = isPullRequest(issue);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given issue has a null pull request', (): void => {
|
||||
beforeEach((): void => {
|
||||
issue = {
|
||||
pull_request: null
|
||||
} as Issue;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = isPullRequest(issue);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([{}, true])(
|
||||
'when the given issue has pull request',
|
||||
(value): void => {
|
||||
beforeEach((): void => {
|
||||
issue = {
|
||||
pull_request: value
|
||||
} as Issue;
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = isPullRequest(issue);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import {Issue} from '../IssueProcessor';
|
||||
|
||||
export function isPullRequest(issue: Readonly<Issue>): boolean {
|
||||
return !!issue.pull_request;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import {shouldMarkWhenStale} from './should-mark-when-stale';
|
||||
|
||||
describe('shouldMarkWhenStale()', (): void => {
|
||||
let daysBeforeStale: number;
|
||||
|
||||
describe('when the given number of days indicate that it should be stalled', (): void => {
|
||||
beforeEach((): void => {
|
||||
daysBeforeStale = -1;
|
||||
});
|
||||
|
||||
it('should return false', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given number of days indicate that it should be stalled today', (): void => {
|
||||
beforeEach((): void => {
|
||||
daysBeforeStale = 0;
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the given number of days indicate that it should be stalled tomorrow', (): void => {
|
||||
beforeEach((): void => {
|
||||
daysBeforeStale = 1;
|
||||
});
|
||||
|
||||
it('should return true', (): void => {
|
||||
expect.assertions(1);
|
||||
|
||||
const result = shouldMarkWhenStale(daysBeforeStale);
|
||||
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
export function shouldMarkWhenStale(
|
||||
daysBeforeStale: Readonly<number>
|
||||
): boolean {
|
||||
return daysBeforeStale >= 0;
|
||||
}
|
|
@ -23,9 +23,13 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||
daysBeforeStale: parseInt(
|
||||
core.getInput('days-before-stale', {required: true})
|
||||
),
|
||||
daysBeforeIssueStale: parseInt(core.getInput('days-before-issue-stale')),
|
||||
daysBeforePrStale: parseInt(core.getInput('days-before-pr-stale')),
|
||||
daysBeforeClose: parseInt(
|
||||
core.getInput('days-before-close', {required: true})
|
||||
),
|
||||
daysBeforeIssueClose: parseInt(core.getInput('days-before-issue-close')),
|
||||
daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')),
|
||||
staleIssueLabel: core.getInput('stale-issue-label', {required: true}),
|
||||
closeIssueLabel: core.getInput('close-issue-label'),
|
||||
exemptIssueLabels: core.getInput('exempt-issue-labels'),
|
||||
|
@ -48,7 +52,11 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||
|
||||
for (const numberInput of [
|
||||
'days-before-stale',
|
||||
'days-before-issue-stale',
|
||||
'days-before-pr-stale',
|
||||
'days-before-close',
|
||||
'days-before-issue-close',
|
||||
'days-before-pr-close',
|
||||
'operations-per-run'
|
||||
]) {
|
||||
if (isNaN(parseInt(core.getInput(numberInput)))) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
//"sourceMap": true
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue