mirror of
https://github.com/actions/stale.git
synced 2025-12-25 17:58:17 +00:00
Compare commits
22 Commits
handle_api
...
fix_octoki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702ce464a8 | ||
|
|
7a188de18b | ||
|
|
a585e82d64 | ||
|
|
456e2e0213 | ||
|
|
5420e6bbac | ||
|
|
fbaa974a12 | ||
|
|
213e2e3dbb | ||
|
|
d54efb59cb | ||
|
|
0c5c86b836 | ||
|
|
ade4f65ff5 | ||
|
|
1200f63335 | ||
|
|
321a0c1996 | ||
|
|
6599f03bfe | ||
|
|
facad6de84 | ||
|
|
2ede1ed234 | ||
|
|
c95554e7b3 | ||
|
|
93883857f8 | ||
|
|
f111c4f385 | ||
|
|
c2daa6ff62 | ||
|
|
32507178a3 | ||
|
|
e169e4e149 | ||
|
|
d7468118d7 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["jest", "@typescript-eslint"],
|
"plugins": ["jest", "@typescript-eslint"],
|
||||||
"extends": ["plugin:github/es6"],
|
"extends": ["plugin:github/recommended"],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 9,
|
"ecmaVersion": 9,
|
||||||
@@ -16,11 +16,10 @@
|
|||||||
"@typescript-eslint/no-require-imports": "error",
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
"@typescript-eslint/array-type": "error",
|
"@typescript-eslint/array-type": "error",
|
||||||
"@typescript-eslint/await-thenable": "error",
|
"@typescript-eslint/await-thenable": "error",
|
||||||
"@typescript-eslint/ban-ts-ignore": "error",
|
"@typescript-eslint/ban-ts-comment": "error",
|
||||||
"camelcase": "off",
|
"camelcase": "off",
|
||||||
"@typescript-eslint/class-name-casing": "error",
|
"@typescript-eslint/consistent-type-assertions": "error",
|
||||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
|
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
"@typescript-eslint/no-misused-new": "error",
|
"@typescript-eslint/no-misused-new": "error",
|
||||||
"@typescript-eslint/no-namespace": "error",
|
"@typescript-eslint/no-namespace": "error",
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||||
"@typescript-eslint/no-object-literal-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
"@typescript-eslint/no-useless-constructor": "error",
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
@@ -38,7 +36,6 @@
|
|||||||
"@typescript-eslint/prefer-for-of": "warn",
|
"@typescript-eslint/prefer-for-of": "warn",
|
||||||
"@typescript-eslint/prefer-function-type": "warn",
|
"@typescript-eslint/prefer-function-type": "warn",
|
||||||
"@typescript-eslint/prefer-includes": "error",
|
"@typescript-eslint/prefer-includes": "error",
|
||||||
"@typescript-eslint/prefer-interface": "error",
|
|
||||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||||
"@typescript-eslint/promise-function-async": "error",
|
"@typescript-eslint/promise-function-async": "error",
|
||||||
"@typescript-eslint/require-array-sort-compare": "error",
|
"@typescript-eslint/require-array-sort-compare": "error",
|
||||||
|
|||||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Enable version updates for npm
|
||||||
|
- package-ecosystem: 'npm'
|
||||||
|
# Look for `package.json` and `lock` files in the `root` directory
|
||||||
|
directory: '/'
|
||||||
|
# Check the npm registry for updates every day (weekdays)
|
||||||
|
schedule:
|
||||||
|
interval: 'daily'
|
||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -3,7 +3,7 @@ on: # rebuild any PRs and main branch changes
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- 'releases/*'
|
- 'releases/*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ function generateIssue(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultProcessorOptions: IssueProcessorOptions = {
|
const DefaultProcessorOptions: IssueProcessorOptions = Object.freeze({
|
||||||
repoToken: 'none',
|
repoToken: 'none',
|
||||||
staleIssueMessage: 'This issue is stale',
|
staleIssueMessage: 'This issue is stale',
|
||||||
stalePrMessage: 'This PR is stale',
|
stalePrMessage: 'This PR is stale',
|
||||||
|
closeIssueMessage: 'This issue is being closed',
|
||||||
|
closePrMessage: 'This PR is being closed',
|
||||||
daysBeforeStale: 1,
|
daysBeforeStale: 1,
|
||||||
daysBeforeClose: 30,
|
daysBeforeClose: 30,
|
||||||
staleIssueLabel: 'Stale',
|
staleIssueLabel: 'Stale',
|
||||||
@@ -44,8 +46,11 @@ const DefaultProcessorOptions: IssueProcessorOptions = {
|
|||||||
onlyLabels: '',
|
onlyLabels: '',
|
||||||
operationsPerRun: 100,
|
operationsPerRun: 100,
|
||||||
debugOnly: true,
|
debugOnly: true,
|
||||||
removeStaleWhenUpdated: false
|
removeStaleWhenUpdated: false,
|
||||||
};
|
ascending: false,
|
||||||
|
skipStaleIssueMessage: false,
|
||||||
|
skipStalePrMessage: false
|
||||||
|
});
|
||||||
|
|
||||||
test('empty issue list results in 1 operation', async () => {
|
test('empty issue list results in 1 operation', async () => {
|
||||||
const processor = new IssueProcessor(
|
const processor = new IssueProcessor(
|
||||||
@@ -106,6 +111,31 @@ test('processing an issue with no label will make it stale and not close it if d
|
|||||||
expect(processor.closedIssues.length).toEqual(0);
|
expect(processor.closedIssues.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('processing an issue with no label will not make it stale if days-before-stale is set to -1', async () => {
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(1, 'An issue with no label', '2020-01-01T17:00:00Z')
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
...DefaultProcessorOptions,
|
||||||
|
staleIssueMessage: '',
|
||||||
|
daysBeforeStale: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
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 () => {
|
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
|
// issue should be from 2 days ago so it will be
|
||||||
// stale but not close-able, based on default settings
|
// stale but not close-able, based on default settings
|
||||||
@@ -180,6 +210,60 @@ test('processing a stale PR will close it', async () => {
|
|||||||
expect(processor.closedIssues.length).toEqual(1);
|
expect(processor.closedIssues.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('processing a stale issue 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', false, [
|
||||||
|
'Stale'
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
...DefaultProcessorOptions,
|
||||||
|
daysBeforeStale: -1,
|
||||||
|
staleIssueMessage: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
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, [
|
||||||
|
'Stale'
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
...DefaultProcessorOptions,
|
||||||
|
daysBeforeStale: -1,
|
||||||
|
stalePrMessage: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
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 () => {
|
test('closed issues will not be marked stale', async () => {
|
||||||
const TestIssueList: Issue[] = [
|
const TestIssueList: Issue[] = [
|
||||||
generateIssue(
|
generateIssue(
|
||||||
@@ -514,7 +598,7 @@ test('stale label should be removed if a comment was added to a stale issue', as
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
const opts = DefaultProcessorOptions;
|
const opts = {...DefaultProcessorOptions};
|
||||||
opts.removeStaleWhenUpdated = true;
|
opts.removeStaleWhenUpdated = true;
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
const processor = new IssueProcessor(
|
||||||
@@ -544,7 +628,7 @@ test('stale label should not be removed if a comment was added by the bot (and t
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
const opts = DefaultProcessorOptions;
|
const opts = {...DefaultProcessorOptions};
|
||||||
opts.removeStaleWhenUpdated = true;
|
opts.removeStaleWhenUpdated = true;
|
||||||
|
|
||||||
const processor = new IssueProcessor(
|
const processor = new IssueProcessor(
|
||||||
@@ -574,7 +658,7 @@ test('stale issues should not be closed until after the closed number of days',
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
const opts = DefaultProcessorOptions;
|
const opts = {...DefaultProcessorOptions};
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
opts.daysBeforeClose = 1; // closes after 6 days
|
opts.daysBeforeClose = 1; // closes after 6 days
|
||||||
|
|
||||||
@@ -606,7 +690,7 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
const opts = DefaultProcessorOptions;
|
const opts = {...DefaultProcessorOptions};
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
opts.daysBeforeClose = 1; // closes after 6 days
|
opts.daysBeforeClose = 1; // closes after 6 days
|
||||||
|
|
||||||
@@ -637,7 +721,7 @@ test('stale issues should not be closed until after the closed number of days (l
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
const opts = DefaultProcessorOptions;
|
const opts = {...DefaultProcessorOptions};
|
||||||
opts.daysBeforeStale = 5; // stale after 5 days
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
opts.daysBeforeClose = 20; // closes after 25 days
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
|
||||||
@@ -655,3 +739,157 @@ test('stale issues should not be closed until after the closed number of days (l
|
|||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
expect(processor.staleIssues.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('skips stale message on issues when skip-stale-issue-message is set', async () => {
|
||||||
|
let lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.skipStaleIssueMessage = true;
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// for sake of testing, mocking private function
|
||||||
|
const markSpy = jest.spyOn(processor as any, 'markStale');
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues.length).toEqual(0);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(1);
|
||||||
|
|
||||||
|
// comment should not be created
|
||||||
|
expect(markSpy).toHaveBeenCalledWith(
|
||||||
|
TestIssueList[0],
|
||||||
|
opts.staleIssueMessage,
|
||||||
|
opts.staleIssueLabel,
|
||||||
|
// this option is skipMessage
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips stale message on prs when skip-stale-pr-message is set', async () => {
|
||||||
|
let lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.skipStalePrMessage = true;
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// for sake of testing, mocking private function
|
||||||
|
const markSpy = jest.spyOn(processor as any, 'markStale');
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues.length).toEqual(0);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(1);
|
||||||
|
|
||||||
|
// comment should not be created
|
||||||
|
expect(markSpy).toHaveBeenCalledWith(
|
||||||
|
TestIssueList[0],
|
||||||
|
opts.stalePrMessage,
|
||||||
|
opts.stalePrLabel,
|
||||||
|
// this option is skipMessage
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not providing state takes precedence over skipStaleIssueMessage', async () => {
|
||||||
|
let lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.skipStalePrMessage = true;
|
||||||
|
opts.staleIssueMessage = '';
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues.length).toEqual(0);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not providing stalePrMessage takes precedence over skipStalePrMessage', async () => {
|
||||||
|
let lastUpdate = new Date();
|
||||||
|
lastUpdate.setDate(lastUpdate.getDate() - 10);
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should be marked stale but not closed',
|
||||||
|
lastUpdate.toString(),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const opts = {...DefaultProcessorOptions};
|
||||||
|
opts.daysBeforeStale = 5; // stale after 5 days
|
||||||
|
opts.daysBeforeClose = 20; // closes after 25 days
|
||||||
|
opts.skipStalePrMessage = true;
|
||||||
|
opts.stalePrMessage = '';
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
// issue should be staled
|
||||||
|
expect(processor.closedIssues.length).toEqual(0);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|||||||
15
action.yml
15
action.yml
@@ -9,8 +9,12 @@ inputs:
|
|||||||
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
|
description: 'The message to post on the issue when tagging it. If none provided, will not mark issues stale.'
|
||||||
stale-pr-message:
|
stale-pr-message:
|
||||||
description: 'The message to post on the pr when tagging it. If none provided, will not mark pull requests stale.'
|
description: 'The message to post on the pr when tagging it. If none provided, will not mark pull requests stale.'
|
||||||
|
close-issue-message:
|
||||||
|
description: 'The message to post on the issue when closing it. If none provided, will not comment when closing an issue.'
|
||||||
|
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.'
|
||||||
days-before-stale:
|
days-before-stale:
|
||||||
description: 'The number of days old an issue can be before marking it 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
|
default: 60
|
||||||
days-before-close:
|
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.'
|
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.'
|
||||||
@@ -39,6 +43,15 @@ inputs:
|
|||||||
debug-only:
|
debug-only:
|
||||||
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
description: 'Run the processor in debug mode without actually performing any operations on live issues.'
|
||||||
default: false
|
default: false
|
||||||
|
ascending:
|
||||||
|
description: 'The order to get issues or pull requests. Defaults to false, which is descending'
|
||||||
|
default: false
|
||||||
|
skip-stale-pr-message:
|
||||||
|
description: 'Skip adding stale message when marking a pull request as stale.'
|
||||||
|
default: false
|
||||||
|
skip-stale-issue-message:
|
||||||
|
description: 'Skip adding stale message when marking an issue as stale.'
|
||||||
|
default: false
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
22457
dist/index.js
vendored
22457
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
2948
package-lock.json
generated
2948
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -26,24 +26,24 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.4",
|
"@actions/core": "^1.2.4",
|
||||||
"@actions/github": "^2.2.0",
|
"@actions/github": "^4.0.0",
|
||||||
"@octokit/rest": "^16.43.1",
|
"@octokit/rest": "^18.0.2",
|
||||||
"semver": "^6.1.1"
|
"semver": "^7.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^7.3.1",
|
||||||
"@types/jest": "^24.0.23",
|
"@types/jest": "^26.0.5",
|
||||||
"@types/node": "^12.7.12",
|
"@types/node": "^14.0.25",
|
||||||
"@typescript-eslint/parser": "^2.8.0",
|
"@typescript-eslint/parser": "^3.7.0",
|
||||||
"@zeit/ncc": "^0.20.5",
|
"@zeit/ncc": "^0.20.5",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-github": "^2.0.0",
|
"eslint-plugin-github": "^4.0.1",
|
||||||
"eslint-plugin-jest": "^22.21.0",
|
"eslint-plugin-jest": "^23.18.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"jest-circus": "^24.9.0",
|
"jest-circus": "^26.1.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"ts-jest": "^24.2.0",
|
"ts-jest": "^24.2.0",
|
||||||
"typescript": "^3.6.4"
|
"typescript": "^3.9.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as github from '@actions/github';
|
import {context, getOctokit} from '@actions/github';
|
||||||
import {Octokit} from '@octokit/rest';
|
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
|
||||||
|
|
||||||
type OctoKitIssueList = Octokit.Response<Octokit.IssuesListForRepoResponse>;
|
|
||||||
|
|
||||||
export interface Issue {
|
export interface Issue {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -37,6 +35,8 @@ export interface IssueProcessorOptions {
|
|||||||
repoToken: string;
|
repoToken: string;
|
||||||
staleIssueMessage: string;
|
staleIssueMessage: string;
|
||||||
stalePrMessage: string;
|
stalePrMessage: string;
|
||||||
|
closeIssueMessage: string;
|
||||||
|
closePrMessage: string;
|
||||||
daysBeforeStale: number;
|
daysBeforeStale: number;
|
||||||
daysBeforeClose: number;
|
daysBeforeClose: number;
|
||||||
staleIssueLabel: string;
|
staleIssueLabel: string;
|
||||||
@@ -47,15 +47,18 @@ export interface IssueProcessorOptions {
|
|||||||
operationsPerRun: number;
|
operationsPerRun: number;
|
||||||
removeStaleWhenUpdated: boolean;
|
removeStaleWhenUpdated: boolean;
|
||||||
debugOnly: boolean;
|
debugOnly: boolean;
|
||||||
|
ascending: boolean;
|
||||||
|
skipStaleIssueMessage: boolean;
|
||||||
|
skipStalePrMessage: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Handle processing of issues for staleness/closure.
|
* Handle processing of issues for staleness/closure.
|
||||||
*/
|
*/
|
||||||
export class IssueProcessor {
|
export class IssueProcessor {
|
||||||
readonly client: github.GitHub;
|
readonly client: any; // need to make this the correct type
|
||||||
readonly options: IssueProcessorOptions;
|
readonly options: IssueProcessorOptions;
|
||||||
private operationsLeft: number = 0;
|
private operationsLeft = 0;
|
||||||
|
|
||||||
readonly staleIssues: Issue[] = [];
|
readonly staleIssues: Issue[] = [];
|
||||||
readonly closedIssues: Issue[] = [];
|
readonly closedIssues: Issue[] = [];
|
||||||
@@ -75,7 +78,7 @@ export class IssueProcessor {
|
|||||||
) {
|
) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.operationsLeft = options.operationsPerRun;
|
this.operationsLeft = options.operationsPerRun;
|
||||||
this.client = new github.GitHub(options.repoToken);
|
this.client = getOctokit(options.repoToken);
|
||||||
|
|
||||||
if (getIssues) {
|
if (getIssues) {
|
||||||
this.getIssues = getIssues;
|
this.getIssues = getIssues;
|
||||||
@@ -96,7 +99,7 @@ export class IssueProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processIssues(page: number = 1): Promise<number> {
|
async processIssues(page = 1): Promise<number> {
|
||||||
// get the next batch of issues
|
// get the next batch of issues
|
||||||
const issues: Issue[] = await this.getIssues(page);
|
const issues: Issue[] = await this.getIssues(page);
|
||||||
this.operationsLeft -= 1;
|
this.operationsLeft -= 1;
|
||||||
@@ -117,15 +120,22 @@ export class IssueProcessor {
|
|||||||
const staleMessage: string = isPr
|
const staleMessage: string = isPr
|
||||||
? this.options.stalePrMessage
|
? this.options.stalePrMessage
|
||||||
: this.options.staleIssueMessage;
|
: this.options.staleIssueMessage;
|
||||||
|
const closeMessage: string = isPr
|
||||||
|
? this.options.closePrMessage
|
||||||
|
: this.options.closeIssueMessage;
|
||||||
const staleLabel: string = isPr
|
const staleLabel: string = isPr
|
||||||
? this.options.stalePrLabel
|
? this.options.stalePrLabel
|
||||||
: this.options.staleIssueLabel;
|
: this.options.staleIssueLabel;
|
||||||
const exemptLabels = IssueProcessor.parseCommaSeparatedString(
|
const exemptLabels = IssueProcessor.parseCommaSeparatedString(
|
||||||
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
|
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
|
||||||
);
|
);
|
||||||
|
const skipMessage = isPr
|
||||||
|
? this.options.skipStalePrMessage
|
||||||
|
: this.options.skipStaleIssueMessage;
|
||||||
const issueType: string = isPr ? 'pr' : 'issue';
|
const issueType: string = isPr ? 'pr' : 'issue';
|
||||||
|
const shouldMarkWhenStale = this.options.daysBeforeStale > -1;
|
||||||
|
|
||||||
if (!staleMessage) {
|
if (!staleMessage && shouldMarkWhenStale) {
|
||||||
core.info(`Skipping ${issueType} due to empty stale message`);
|
core.info(`Skipping ${issueType} due to empty stale message`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -159,18 +169,23 @@ export class IssueProcessor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// determine if this issue needs to be marked stale first
|
// determine if this issue needs to be marked stale first
|
||||||
if (!isStale && shouldBeStale) {
|
if (!isStale && shouldBeStale && shouldMarkWhenStale) {
|
||||||
core.info(
|
core.info(
|
||||||
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
|
||||||
);
|
);
|
||||||
await this.markStale(issue, staleMessage, staleLabel);
|
await this.markStale(issue, staleMessage, staleLabel, skipMessage);
|
||||||
isStale = true; // this issue is now considered stale
|
isStale = true; // this issue is now considered stale
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the issue if it was marked stale
|
// process the issue if it was marked stale
|
||||||
if (isStale) {
|
if (isStale) {
|
||||||
core.info(`Found a stale ${issueType}`);
|
core.info(`Found a stale ${issueType}`);
|
||||||
await this.processStaleIssue(issue, issueType, staleLabel);
|
await this.processStaleIssue(
|
||||||
|
issue,
|
||||||
|
issueType,
|
||||||
|
staleLabel,
|
||||||
|
closeMessage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +202,8 @@ export class IssueProcessor {
|
|||||||
private async processStaleIssue(
|
private async processStaleIssue(
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
issueType: string,
|
issueType: string,
|
||||||
staleLabel: string
|
staleLabel: string,
|
||||||
|
closeMessage?: string
|
||||||
) {
|
) {
|
||||||
const markedStaleOn: string =
|
const markedStaleOn: string =
|
||||||
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
|
||||||
@@ -224,7 +240,7 @@ export class IssueProcessor {
|
|||||||
core.info(
|
core.info(
|
||||||
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
||||||
);
|
);
|
||||||
await this.closeIssue(issue);
|
await this.closeIssue(issue, closeMessage);
|
||||||
} else {
|
} else {
|
||||||
core.info(
|
core.info(
|
||||||
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate}`
|
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate}`
|
||||||
@@ -250,12 +266,11 @@ export class IssueProcessor {
|
|||||||
|
|
||||||
const filteredComments = comments.filter(
|
const filteredComments = comments.filter(
|
||||||
comment =>
|
comment =>
|
||||||
comment.user.type === 'User' &&
|
comment.user.type === 'User' && comment.user.login !== context.actor
|
||||||
comment.user.login !== github.context.actor
|
|
||||||
);
|
);
|
||||||
|
|
||||||
core.info(
|
core.info(
|
||||||
`Comments not made by ${github.context.actor} or another bot: ${filteredComments.length}`
|
`Comments not made by ${context.actor} or another bot: ${filteredComments.length}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// if there are any user comments returned
|
// if there are any user comments returned
|
||||||
@@ -270,8 +285,8 @@ export class IssueProcessor {
|
|||||||
// find any comments since date on the given issue
|
// find any comments since date on the given issue
|
||||||
try {
|
try {
|
||||||
const comments = await this.client.issues.listComments({
|
const comments = await this.client.issues.listComments({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
since: sinceDate
|
since: sinceDate
|
||||||
});
|
});
|
||||||
@@ -284,14 +299,19 @@ export class IssueProcessor {
|
|||||||
|
|
||||||
// grab issues from github in baches of 100
|
// grab issues from github in baches of 100
|
||||||
private async getIssues(page: number): Promise<Issue[]> {
|
private async getIssues(page: number): Promise<Issue[]> {
|
||||||
|
// generate type for response
|
||||||
|
const endpoint = this.client.issues.listForRepo;
|
||||||
|
type OctoKitIssueList = GetResponseTypeFromEndpointMethod<typeof endpoint>;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
const issueResult: OctoKitIssueList = await this.client.issues.listForRepo(
|
||||||
{
|
{
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
state: 'open',
|
state: 'open',
|
||||||
labels: this.options.onlyLabels,
|
labels: this.options.onlyLabels,
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
|
direction: this.options.ascending ? 'asc' : 'desc',
|
||||||
page
|
page
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -306,7 +326,8 @@ export class IssueProcessor {
|
|||||||
private async markStale(
|
private async markStale(
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
staleMessage: string,
|
staleMessage: string,
|
||||||
staleLabel: string
|
staleLabel: string,
|
||||||
|
skipMessage: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
core.info(`Marking issue #${issue.number} - ${issue.title} as stale`);
|
core.info(`Marking issue #${issue.number} - ${issue.title} as stale`);
|
||||||
|
|
||||||
@@ -323,21 +344,23 @@ export class IssueProcessor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!skipMessage) {
|
||||||
try {
|
try {
|
||||||
await this.client.issues.createComment({
|
await this.client.issues.createComment({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
body: staleMessage
|
body: staleMessage
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.error(`Error creating a comment: ${error.message}`);
|
core.error(`Error creating a comment: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.issues.addLabels({
|
await this.client.issues.addLabels({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
labels: [staleLabel]
|
labels: [staleLabel]
|
||||||
});
|
});
|
||||||
@@ -347,7 +370,7 @@ export class IssueProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close an issue based on staleness
|
// Close an issue based on staleness
|
||||||
private async closeIssue(issue: Issue): Promise<void> {
|
private async closeIssue(issue: Issue, closeMessage?: string): Promise<void> {
|
||||||
core.info(
|
core.info(
|
||||||
`Closing issue #${issue.number} - ${issue.title} for being stale`
|
`Closing issue #${issue.number} - ${issue.title} for being stale`
|
||||||
);
|
);
|
||||||
@@ -360,10 +383,23 @@ export class IssueProcessor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (closeMessage) {
|
||||||
|
try {
|
||||||
|
await this.client.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: closeMessage
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Error creating a comment: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.issues.update({
|
await this.client.issues.update({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
state: 'closed'
|
state: 'closed'
|
||||||
});
|
});
|
||||||
@@ -388,8 +424,8 @@ export class IssueProcessor {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.issues.removeLabel({
|
await this.client.issues.removeLabel({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
name: encodeURIComponent(label) // A label can have a "?" in the name
|
name: encodeURIComponent(label) // A label can have a "?" in the name
|
||||||
});
|
});
|
||||||
@@ -409,8 +445,8 @@ export class IssueProcessor {
|
|||||||
this.operationsLeft -= 1;
|
this.operationsLeft -= 1;
|
||||||
|
|
||||||
const options = this.client.issues.listEvents.endpoint.merge({
|
const options = this.client.issues.listEvents.endpoint.merge({
|
||||||
owner: github.context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: github.context.repo.repo,
|
repo: context.repo.repo,
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
issue_number: issue.number
|
issue_number: issue.number
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
repoToken: core.getInput('repo-token', {required: true}),
|
repoToken: core.getInput('repo-token', {required: true}),
|
||||||
staleIssueMessage: core.getInput('stale-issue-message'),
|
staleIssueMessage: core.getInput('stale-issue-message'),
|
||||||
stalePrMessage: core.getInput('stale-pr-message'),
|
stalePrMessage: core.getInput('stale-pr-message'),
|
||||||
|
closeIssueMessage: core.getInput('close-issue-message'),
|
||||||
|
closePrMessage: core.getInput('close-pr-message'),
|
||||||
daysBeforeStale: parseInt(
|
daysBeforeStale: parseInt(
|
||||||
core.getInput('days-before-stale', {required: true})
|
core.getInput('days-before-stale', {required: true})
|
||||||
),
|
),
|
||||||
@@ -35,7 +37,10 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
|||||||
removeStaleWhenUpdated: !(
|
removeStaleWhenUpdated: !(
|
||||||
core.getInput('remove-stale-when-updated') === 'false'
|
core.getInput('remove-stale-when-updated') === 'false'
|
||||||
),
|
),
|
||||||
debugOnly: core.getInput('debug-only') === 'true'
|
debugOnly: core.getInput('debug-only') === 'true',
|
||||||
|
ascending: core.getInput('ascending') === 'true',
|
||||||
|
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
|
||||||
|
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const numberInput of [
|
for (const numberInput of [
|
||||||
|
|||||||
Reference in New Issue
Block a user