Compare commits

..

1 Commits

7 changed files with 528 additions and 286 deletions

View File

@@ -4,6 +4,7 @@ import {IComment} from '../../src/interfaces/comment';
import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-options';
import {IPullRequest} from '../../src/interfaces/pull-request';
import {IState} from '../../src/interfaces/state/state';
import {IIssueEvent} from '../../src/interfaces/issue-event';
export class IssuesProcessorMock extends IssuesProcessor {
constructor(
@@ -17,7 +18,15 @@ export class IssuesProcessorMock extends IssuesProcessor {
getLabelCreationDate?: (
issue: Issue,
label: string
) => Promise<string | undefined>,
) =>
| Promise<string | undefined>
| Promise<{creationDate?: string; events: IIssueEvent[]}>,
hasOnlyStaleLabelingEventsSince?: (
issue: Issue,
sinceDate: string,
staleLabel: string,
events: IIssueEvent[]
) => Promise<boolean>,
getPullRequest?: (issue: Issue) => Promise<IPullRequest | undefined | void>
) {
super(options, state);
@@ -31,7 +40,21 @@ export class IssuesProcessorMock extends IssuesProcessor {
}
if (getLabelCreationDate) {
this.getLabelCreationDate = getLabelCreationDate;
this.getLabelCreationDate = async (
issue: Issue,
label: string
): Promise<{creationDate?: string; events: IIssueEvent[]}> => {
const result = await getLabelCreationDate(issue, label);
if (typeof result === 'string' || typeof result === 'undefined') {
return {creationDate: result, events: []};
}
return result;
};
}
if (hasOnlyStaleLabelingEventsSince) {
this.hasOnlyStaleLabelingEventsSince = hasOnlyStaleLabelingEventsSince;
}
if (getPullRequest) {

View File

@@ -129,6 +129,7 @@ class IssuesProcessorBuilder {
async p => (p === 1 ? this._issues : []),
async () => [],
async () => new Date().toDateString(),
undefined,
async (): Promise<IPullRequest> => {
return Promise.resolve({
number: 0,

View File

@@ -0,0 +1,288 @@
import {Issue} from '../src/classes/issue';
import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options';
import {IssuesProcessorMock} from './classes/issues-processor-mock';
import {DefaultProcessorOptions} from './constants/default-processor-options';
import {generateIssue} from './functions/generate-issue';
import {alwaysFalseStateMock} from './classes/state-mock';
import {IState} from '../src/interfaces/state/state';
import {IIssueEvent} from '../src/interfaces/issue-event';
import {IssuesProcessor} from '../src/classes/issues-processor';
describe('remove-stale-when-updated with stale label events', (): void => {
const markedStaleOn = '2025-01-01T00:00:00Z';
const updatedAt = '2025-01-01T00:01:00Z';
let options: IIssuesProcessorOptions;
beforeEach((): void => {
options = {
...DefaultProcessorOptions,
removeStaleWhenUpdated: true
};
});
const buildIssue = (): Issue =>
generateIssue(
options,
1,
'dummy-title',
updatedAt,
markedStaleOn,
false,
false,
['Stale']
);
const buildEvents = (): IIssueEvent[] => [
{
event: 'labeled',
created_at: markedStaleOn,
label: {name: 'Stale'}
}
];
test('does not remove stale label when only stale label events occurred', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const processor = new IssuesProcessorMock(
options,
alwaysFalseStateMock,
async p => (p === 1 ? [issue] : []),
async () => [],
async () => ({creationDate: markedStaleOn, events: buildEvents()}),
async () => true
);
await processor.processIssues();
expect(processor.removedLabelIssues).toHaveLength(0);
});
test('removes stale label when updates are not just stale label events', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const processor = new IssuesProcessorMock(
options,
alwaysFalseStateMock,
async p => (p === 1 ? [issue] : []),
async () => [],
async () => ({creationDate: markedStaleOn, events: buildEvents()}),
async () => false
);
await processor.processIssues();
expect(processor.removedLabelIssues).toHaveLength(1);
});
});
class TestIssuesProcessor extends IssuesProcessor {
constructor(
options: IIssuesProcessorOptions,
state: IState,
events: IIssueEvent[]
) {
super(options, state);
const client = {
rest: {
issues: {
listEvents: {
endpoint: {
merge: () => ({})
}
}
}
},
paginate: {
iterator: async function* () {
yield {data: events};
}
}
};
(this as any).client = client;
}
async callhasOnlyStaleLabelingEventsSince(
issue: Issue,
sinceDate: string,
staleLabel: string,
events: IIssueEvent[]
): Promise<boolean> {
return this.hasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
}
}
describe('hasOnlyStaleLabelingEventsSince', (): void => {
const staleLabel = 'Stale';
const sinceDate = '2025-01-01T00:00:00Z';
const originalRepo = process.env.GITHUB_REPOSITORY;
let options: IIssuesProcessorOptions;
beforeEach((): void => {
process.env.GITHUB_REPOSITORY = 'owner/repo';
options = {
...DefaultProcessorOptions,
staleIssueLabel: staleLabel,
removeStaleWhenUpdated: true
};
});
afterEach((): void => {
if (originalRepo === undefined) {
delete process.env.GITHUB_REPOSITORY;
} else {
process.env.GITHUB_REPOSITORY = originalRepo;
}
});
const buildIssue = (): Issue =>
generateIssue(
options,
1,
'dummy-title',
'2025-01-01T00:02:00Z',
sinceDate,
false,
false,
[staleLabel]
);
test('returns true when only stale label events exist after the since date', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const events: IIssueEvent[] = [
// Event before the sinceDate should be ignored.
{
event: 'labeled',
created_at: '2024-12-31T23:59:00Z',
label: {name: staleLabel}
},
{
event: 'labeled',
created_at: '2025-01-01T00:00:10Z',
label: {name: staleLabel}
}
];
const processor = new TestIssuesProcessor(
options,
alwaysFalseStateMock,
events
);
const result = await processor.callhasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
expect(result).toBe(true);
});
test('returns false when a non-stale label event exists after the since date', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const events: IIssueEvent[] = [
{
event: 'labeled',
created_at: '2025-01-01T00:00:10Z',
label: {name: 'other-label'}
}
];
const processor = new TestIssuesProcessor(
options,
alwaysFalseStateMock,
events
);
const result = await processor.callhasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
expect(result).toBe(false);
});
test('returns false when stale label is removed after the since date', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const events: IIssueEvent[] = [
{
event: 'unlabeled',
created_at: '2025-01-01T00:00:10Z',
label: {name: staleLabel}
}
];
const processor = new TestIssuesProcessor(
options,
alwaysFalseStateMock,
events
);
const result = await processor.callhasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
expect(result).toBe(false);
});
test('returns false when a non-label event exists after the since date', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const events: IIssueEvent[] = [
{
event: 'commented',
created_at: '2025-01-01T00:00:10Z',
label: {name: staleLabel}
}
];
const processor = new TestIssuesProcessor(
options,
alwaysFalseStateMock,
events
);
const result = await processor.callhasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
expect(result).toBe(false);
});
test('includes events that occur exactly at the since date boundary', async (): Promise<void> => {
expect.assertions(1);
const issue = buildIssue();
const events: IIssueEvent[] = [
{
event: 'labeled',
created_at: sinceDate,
label: {name: staleLabel}
}
];
const processor = new TestIssuesProcessor(
options,
alwaysFalseStateMock,
events
);
const result = await processor.callhasOnlyStaleLabelingEventsSince(
issue,
sinceDate,
staleLabel,
events
);
expect(result).toBe(true);
});
});

45
dist/index.js vendored
View File

@@ -736,9 +736,35 @@ class IssuesProcessor {
(0, clean_label_1.cleanLabel)(event.label.name) === (0, clean_label_1.cleanLabel)(label));
if (!staleLabeledEvent) {
// Must be old rather than labeled
return undefined;
return { creationDate: undefined, events };
}
return staleLabeledEvent.created_at;
return { creationDate: staleLabeledEvent.created_at, events };
});
}
hasOnlyStaleLabelingEventsSince(issue, sinceDate, staleLabel, events) {
return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue);
issueLogger.info(`Checking if only stale label added events on $$type since: ${logger_service_1.LoggerService.cyan(sinceDate)}`);
if (!sinceDate) {
return false;
}
const sinceTimestamp = new Date(sinceDate).getTime();
if (Number.isNaN(sinceTimestamp)) {
return false;
}
const relevantEvents = events.filter(event => {
const eventTimestamp = new Date(event.created_at).getTime();
return !Number.isNaN(eventTimestamp) && eventTimestamp >= sinceTimestamp;
});
if (relevantEvents.length === 0) {
return false;
}
return relevantEvents.every(event => {
if (event.event !== 'labeled') {
return false;
}
return (0, clean_label_1.cleanLabel)(event.label.name) === (0, clean_label_1.cleanLabel)(staleLabel);
});
});
}
getPullRequest(issue) {
@@ -783,7 +809,8 @@ class IssuesProcessor {
_processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, labelsToRemoveWhenStale, closeMessage, closeLabel) {
return __awaiter(this, void 0, void 0, function* () {
const issueLogger = new issue_logger_1.IssueLogger(issue);
const markedStaleOn = (yield this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
const { creationDate, events } = yield this.getLabelCreationDate(issue, staleLabel);
const markedStaleOn = creationDate || issue.updated_at;
issueLogger.info(`$$type marked stale on: ${logger_service_1.LoggerService.cyan(markedStaleOn)}`);
const issueHasCommentsSinceStale = yield this._hasCommentsSince(issue, markedStaleOn, staleMessage);
issueLogger.info(`$$type has been commented on: ${logger_service_1.LoggerService.cyan(issueHasCommentsSinceStale)}`);
@@ -805,7 +832,17 @@ class IssuesProcessor {
}
// The issue.updated_at and markedStaleOn are not always exactly in sync (they can be off by a second or 2)
// isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case)
const issueHasUpdateSinceStale = (0, is_date_more_recent_than_1.isDateMoreRecentThan)(new Date(issue.updated_at), new Date(markedStaleOn), 15);
let issueHasUpdateSinceStale = (0, is_date_more_recent_than_1.isDateMoreRecentThan)(new Date(issue.updated_at), new Date(markedStaleOn), 15);
// Check if the only update was the stale label being added
if (issueHasUpdateSinceStale &&
shouldRemoveStaleWhenUpdated &&
!issue.markedStaleThisRun) {
const onlyStaleLabelAdded = yield this.hasOnlyStaleLabelingEventsSince(issue, markedStaleOn, staleLabel, events);
if (onlyStaleLabelAdded) {
issueHasUpdateSinceStale = false;
issueLogger.info(`Ignoring $$type update since only the stale label was added`);
}
}
issueLogger.info(`$$type has been updated since it was marked stale: ${logger_service_1.LoggerService.cyan(issueHasUpdateSinceStale)}`);
// Should we un-stale this issue?
if (shouldRemoveStaleWhenUpdated &&

373
package-lock.json generated
View File

@@ -12,7 +12,7 @@
"@actions/cache": "^5.0.2",
"@actions/core": "^1.11.1",
"@actions/github": "^7.0.0",
"@octokit/core": "^7.0.6",
"@octokit/core": "^5.2.0",
"@octokit/plugin-retry": "^4.1.1",
"lodash.deburr": "^4.1.0",
"semver": "^7.5.4"
@@ -157,128 +157,6 @@
"undici": "^5.28.5"
}
},
"node_modules/@actions/github/node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@actions/github/node_modules/@octokit/core": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.4.1",
"@octokit/request-error": "^5.1.1",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@actions/github/node_modules/@octokit/graphql": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^8.4.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@actions/github/node_modules/@octokit/openapi-types": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@actions/github/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@actions/github/node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/types": {
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@actions/github/node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
"license": "Apache-2.0"
},
"node_modules/@actions/glob": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.0.tgz",
@@ -1690,94 +1568,47 @@
}
},
"node_modules/@octokit/auth-token": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
"integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
"license": "MIT",
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/core": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz",
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
"integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.3",
"@octokit/request": "^10.0.6",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"before-after-hook": "^4.0.0",
"universal-user-agent": "^7.0.0"
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/core/node_modules/@octokit/endpoint": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz",
"integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/core/node_modules/@octokit/openapi-types": {
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/core/node_modules/@octokit/request": {
"version": "10.0.7",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz",
"integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.2",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"fast-content-type-parse": "^3.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/core/node_modules/@octokit/request-error": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz",
"integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/core/node_modules/@octokit/types": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz",
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^27.0.0"
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/core/node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/@octokit/endpoint": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
@@ -1807,86 +1638,99 @@
}
},
"node_modules/@octokit/graphql": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz",
"integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^10.0.6",
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.0"
"@octokit/request": "^8.4.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/endpoint": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz",
"integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz",
"integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==",
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/graphql/node_modules/@octokit/request": {
"version": "10.0.7",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz",
"integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^11.0.2",
"@octokit/request-error": "^7.0.2",
"@octokit/types": "^16.0.0",
"fast-content-type-parse": "^3.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz",
"integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^16.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz",
"integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==",
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^27.0.0"
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/graphql/node_modules/universal-user-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/@octokit/openapi-types": {
"version": "18.1.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz",
"integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz",
"integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz",
"integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^12.6.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "5"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": {
"version": "20.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz",
"integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz",
"integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^20.0.0"
}
},
"node_modules/@octokit/plugin-retry": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-4.1.6.tgz",
@@ -2652,10 +2496,9 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/before-after-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz",
"integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==",
"license": "Apache-2.0"
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"node_modules/bottleneck": {
"version": "2.19.5",
@@ -4019,22 +3862,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/fast-content-type-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz",
"integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",

View File

@@ -40,7 +40,7 @@
"@actions/cache": "^5.0.2",
"@actions/core": "^1.11.1",
"@actions/github": "^7.0.0",
"@octokit/core": "^7.0.6",
"@octokit/core": "^5.2.0",
"@octokit/plugin-retry": "^4.1.1",
"lodash.deburr": "^4.1.0",
"semver": "^7.5.4"

View File

@@ -608,7 +608,7 @@ export class IssuesProcessor {
async getLabelCreationDate(
issue: Issue,
label: string
): Promise<string | undefined> {
): Promise<{creationDate?: string; events: IIssueEvent[]}> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Checking for label on this $$type`);
@@ -623,6 +623,7 @@ export class IssuesProcessor {
});
const events: IIssueEvent[] = await this.client.paginate(options);
const reversedEvents = events.reverse();
const staleLabeledEvent = reversedEvents.find(
@@ -633,10 +634,51 @@ export class IssuesProcessor {
if (!staleLabeledEvent) {
// Must be old rather than labeled
return undefined;
return {creationDate: undefined, events};
}
return staleLabeledEvent.created_at;
return {creationDate: staleLabeledEvent.created_at, events};
}
protected async hasOnlyStaleLabelingEventsSince(
issue: Issue,
sinceDate: string,
staleLabel: string,
events: IIssueEvent[]
): Promise<boolean> {
const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(
`Checking if only stale label added events on $$type since: ${LoggerService.cyan(
sinceDate
)}`
);
if (!sinceDate) {
return false;
}
const sinceTimestamp = new Date(sinceDate).getTime();
if (Number.isNaN(sinceTimestamp)) {
return false;
}
const relevantEvents = events.filter(event => {
const eventTimestamp = new Date(event.created_at).getTime();
return !Number.isNaN(eventTimestamp) && eventTimestamp >= sinceTimestamp;
});
if (relevantEvents.length === 0) {
return false;
}
return relevantEvents.every(event => {
if (event.event !== 'labeled') {
return false;
}
return cleanLabel(event.label.name) === cleanLabel(staleLabel);
});
}
async getPullRequest(issue: Issue): Promise<IPullRequest | undefined | void> {
@@ -691,8 +733,11 @@ export class IssuesProcessor {
closeLabel?: string
) {
const issueLogger: IssueLogger = new IssueLogger(issue);
const markedStaleOn: string =
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
const {creationDate, events} = await this.getLabelCreationDate(
issue,
staleLabel
);
const markedStaleOn: string = creationDate || issue.updated_at;
issueLogger.info(
`$$type marked stale on: ${LoggerService.cyan(markedStaleOn)}`
);
@@ -744,12 +789,33 @@ export class IssuesProcessor {
// The issue.updated_at and markedStaleOn are not always exactly in sync (they can be off by a second or 2)
// isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case)
const issueHasUpdateSinceStale = isDateMoreRecentThan(
let issueHasUpdateSinceStale = isDateMoreRecentThan(
new Date(issue.updated_at),
new Date(markedStaleOn),
15
);
// Check if the only update was the stale label being added
if (
issueHasUpdateSinceStale &&
shouldRemoveStaleWhenUpdated &&
!issue.markedStaleThisRun
) {
const onlyStaleLabelAdded = await this.hasOnlyStaleLabelingEventsSince(
issue,
markedStaleOn,
staleLabel,
events
);
if (onlyStaleLabelAdded) {
issueHasUpdateSinceStale = false;
issueLogger.info(
`Ignoring $$type update since only the stale label was added`
);
}
}
issueLogger.info(
`$$type has been updated since it was marked stale: ${LoggerService.cyan(
issueHasUpdateSinceStale