diff --git a/README.md b/README.md index eb65b46b9..f82dac969 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Every argument is optional. | [close-issue-reason](#close-issue-reason) | Reason to use when closing issues | `not_planned` | | [stale-pr-label](#stale-pr-label) | Label to apply on staled PRs | `Stale` | | [close-pr-label](#close-pr-label) | Label to apply on closed PRs | | +| [only-matching-filter](#only-matching-filter) | Only issues/PRs matching the search filter(s) will be retrieved and tested | | | [exempt-issue-labels](#exempt-issue-labels) | Labels on issues exempted from stale | | | [exempt-pr-labels](#exempt-pr-labels) | Labels on PRs exempted from stale | | | [only-labels](#only-labels) | Only issues/PRs with ALL these labels are checked | | @@ -258,6 +259,24 @@ It will be automatically removed if the pull requests are no longer closed nor l Default value: unset Required Permission: `pull-requests: write` +#### only-matching-filter + +One or more standard [GitHub Issues and Pull Requests search filters](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests) +which will be used to retrieve the set of issues/PRs to test and take action on. Normally, all open issues/PRs in the context's owner/repo are retrieved. + +GitHub only allows boolean logic and grouping in a Code Search not in Issues and Pull Requests search so there's no way to do an "OR" operation but you can get around this to +a limited degree by specifying multiple search requests separated by ` || `. Each request is run separately and the results are accumulated and duplicates +removed before any further processing is done. + +Each request is checked to ensure it contains an `owner:`, `org:`, `user:` or `repo:` search term. If it doesn't, the search will automatically be scoped to +the owner and repository in the context. This prevents accidental global searches. If the request doesn't already contain an `is:open` search term, it will automatically be added as well. + +Example: To retrieve all of the open PRs in your organization that have a review state of `changes_requested` or a label named `submitter-action-required`, you'd use: +`only-matching-filter: 'org:myorg is:pr is:open review:changes_requested || org:myorg is:pr is:open label:submitter-action-required'`. +From this set, all of the other label, milestone, date, assignee, etc. filters will be applied before taking any action. + +Default value: unset + #### exempt-issue-labels Comma separated list of labels that can be assigned to issues to exclude them from being marked as stale diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 0265b6446..72056694f 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -19,6 +19,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ exemptIssueLabels: '', stalePrLabel: 'Stale', closePrLabel: '', + onlyMatchingFilter: '', exemptPrLabels: '', onlyLabels: '', onlyIssueLabels: '', diff --git a/__tests__/functions/generate-issue.ts b/__tests__/functions/generate-issue.ts index 0198c42cb..9fe2f72de 100644 --- a/__tests__/functions/generate-issue.ts +++ b/__tests__/functions/generate-issue.ts @@ -39,6 +39,7 @@ export function generateIssue( login: assignee, type: 'User' }; - }) + }), + repository_url: 'https://api.github.com/repos/dummy/dummy' }); } diff --git a/action.yml b/action.yml index d55f8547c..f943c41e9 100644 --- a/action.yml +++ b/action.yml @@ -45,6 +45,10 @@ inputs: close-issue-label: description: 'The label to apply when an issue is closed.' required: false + only-matching-filter: + description: 'Only issues/PRs matching the search filter(s) will be retrieved and tested' + default: '' + 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: '' diff --git a/dist/index.js b/dist/index.js index f2786a0f6..144d379c8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -272,6 +272,7 @@ exports.Issue = void 0; const is_labeled_1 = __nccwpck_require__(6792); const is_pull_request_1 = __nccwpck_require__(5400); const operations_1 = __nccwpck_require__(7957); +const owner_repo_1 = __nccwpck_require__(6226); class Issue { constructor(options, issue) { this.operations = new operations_1.Operations(); @@ -287,8 +288,10 @@ class Issue { this.locked = issue.locked; this.milestone = issue.milestone; this.assignees = issue.assignees || []; + this.repository_url = issue.repository_url; this.isStale = (0, is_labeled_1.isLabeled)(this, this.staleLabel); this.markedStaleThisRun = false; + this.owner_repo = new owner_repo_1.OwnerRepo(issue.repository_url || ''); } get isPullRequest() { return (0, is_pull_request_1.isPullRequest)(this); @@ -426,7 +429,7 @@ class IssuesProcessor { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // get the next batch of issues - const issues = yield this.getIssues(page); + const issues = yield this.getIssuesWrapper(page); if (issues.length <= 0) { this._logger.info(logger_service_1.LoggerService.green(`No more issues found to process. Exiting...`)); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.setOperationsCount(this.operations.getConsumedOperationsCount()).logStats(); @@ -659,8 +662,8 @@ class IssuesProcessor { this._consumeIssueOperation(issue); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsCommentsCount(); const comments = yield this.client.rest.issues.listComments({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, since: sinceDate }); @@ -687,6 +690,7 @@ class IssuesProcessor { page }); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsCount(issueResult.data.length); + this._logger.info(logger_service_1.LoggerService.green(`Retrieved ${issueResult.data.length} issues/PRs for repo ${github_1.context.repo.owner}/${github_1.context.repo.repo}`)); return issueResult.data.map((issue) => new issue_1.Issue(this.options, issue)); } catch (error) { @@ -694,6 +698,54 @@ class IssuesProcessor { } }); } + // grab issues and/or prs from github in batches of 100 using search filter + getIssuesByFilter(page, search) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + try { + this.operations.consumeOperation(); + const issueResult = yield this.client.rest.search.issuesAndPullRequests({ + q: search, + per_page: 100, + direction: this.options.ascending ? 'asc' : 'desc', + page + }); + (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsCount(issueResult.data.total_count); + this._logger.info(logger_service_1.LoggerService.green(`Retrieved ${issueResult.data.total_count} issues/PRs for search '${search}'`)); + return issueResult.data.items.map((issue) => new issue_1.Issue(this.options, issue)); + } + catch (error) { + throw Error(`Getting issues was blocked by the error: ${error.message}`); + } + }); + } + _removeDupIssues(issues) { + return issues.reduce(function (a, b) { + if (!a.find(o => o.number == b.number)) + a.push(b); + return a; + }, []); + } + getIssuesWrapper(page) { + return __awaiter(this, void 0, void 0, function* () { + if (!this.options.onlyMatchingFilter) { + return this.getIssues(page); + } + const filter = this.options.onlyMatchingFilter; + const results = []; + for (let term of filter.split('||')) { + if (term.search(/repo:|owner:|org:|user:/) < 0) { + term = `repo:${github_1.context.repo.owner}/${github_1.context.repo.repo} ${this.options.onlyMatchingFilter}`; + } + if (term.search(/is:open/) < 0) { + term += ' is:open'; + } + const r = yield this.getIssuesByFilter(page, term); + results.push(...r); + } + return this._removeDupIssues(results); + }); + } // returns the creation date of a given label on an issue (or nothing if no label existed) ///see https://developer.github.com/v3/activity/events/ getLabelCreationDate(issue, label) { @@ -704,8 +756,8 @@ class IssuesProcessor { this._consumeIssueOperation(issue); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsEventsCount(); const options = this.client.rest.issues.listEvents.endpoint.merge({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, per_page: 100, issue_number: issue.number }); @@ -728,8 +780,8 @@ class IssuesProcessor { this._consumeIssueOperation(issue); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedPullRequestsCount(); const pullRequest = yield this.client.rest.pulls.get({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, pull_number: issue.number }); return pullRequest.data; @@ -848,8 +900,8 @@ class IssuesProcessor { (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedItemsComment(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.createComment({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, body: staleMessage }); @@ -865,8 +917,8 @@ class IssuesProcessor { (_c = this.statistics) === null || _c === void 0 ? void 0 : _c.incrementStaleItemsCount(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.addLabels({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: [staleLabel] }); @@ -891,8 +943,8 @@ class IssuesProcessor { this.addedCloseCommentIssues.push(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.createComment({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, body: closeMessage }); @@ -908,8 +960,8 @@ class IssuesProcessor { (_b = this.statistics) === null || _b === void 0 ? void 0 : _b.incrementAddedItemsLabel(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.addLabels({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: [closeLabel] }); @@ -924,8 +976,8 @@ class IssuesProcessor { (_c = this.statistics) === null || _c === void 0 ? void 0 : _c.incrementClosedItemsCount(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.update({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, state: 'closed', state_reason: this.options.closeIssueReason || undefined @@ -955,15 +1007,15 @@ class IssuesProcessor { const branch = pullRequest.head.ref; if (pullRequest.head.repo === null || pullRequest.head.repo.full_name === - `${github_1.context.repo.owner}/${github_1.context.repo.repo}`) { + `${issue.owner_repo.owner}/${issue.owner_repo.repo}`) { issueLogger.info(`Deleting the branch "${logger_service_1.LoggerService.cyan(branch)}" from closed $$type`); try { this._consumeIssueOperation(issue); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedBranchesCount(); if (!this.options.debugOnly) { yield this.client.rest.git.deleteRef({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, ref: `heads/${branch}` }); } @@ -989,8 +1041,8 @@ class IssuesProcessor { (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementDeletedItemsLabelsCount(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.removeLabel({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, name: label }); @@ -1089,8 +1141,8 @@ class IssuesProcessor { (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementAddedItemsLabel(issue); if (!this.options.debugOnly) { yield this.client.rest.issues.addLabels({ - owner: github_1.context.repo.owner, - repo: github_1.context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: labelsToAdd }); @@ -1499,6 +1551,31 @@ class Operations { exports.Operations = Operations; +/***/ }), + +/***/ 6226: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.OwnerRepo = void 0; +class OwnerRepo { + constructor(repo_url) { + const m = repo_url.match(/.*\/([^/]+)\/(.+)$/); + if (!m) { + this.owner = ''; + this.repo = ''; + } + else { + this.owner = m[1]; + this.repo = m[2]; + } + } +} +exports.OwnerRepo = OwnerRepo; + + /***/ }), /***/ 7069: @@ -2185,6 +2262,7 @@ var Option; Option["DaysBeforePrClose"] = "days-before-pr-close"; Option["StaleIssueLabel"] = "stale-issue-label"; Option["CloseIssueLabel"] = "close-issue-label"; + Option["OnlyMatchingFilter"] = "only-matching-filter"; Option["ExemptIssueLabels"] = "exempt-issue-labels"; Option["StalePrLabel"] = "stale-pr-label"; Option["ClosePrLabel"] = "close-pr-label"; @@ -2526,6 +2604,7 @@ function _getAndValidateArgs() { daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')), staleIssueLabel: core.getInput('stale-issue-label', { required: true }), closeIssueLabel: core.getInput('close-issue-label'), + onlyMatchingFilter: core.getInput('only-matching-filter'), exemptIssueLabels: core.getInput('exempt-issue-labels'), stalePrLabel: core.getInput('stale-pr-label', { required: true }), closePrLabel: core.getInput('close-pr-label'), diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index a2c82e268..2c8ea5b3d 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -2,6 +2,7 @@ import {IUserAssignee} from '../interfaces/assignee'; import {IIssue} from '../interfaces/issue'; import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; import {ILabel} from '../interfaces/label'; +import {IOwnerRepo} from '../interfaces/owner-repo'; import {IMilestone} from '../interfaces/milestone'; import {Issue} from './issue'; @@ -29,6 +30,7 @@ describe('Issue', (): void => { exemptPrLabels: '', onlyLabels: '', onlyIssueLabels: '', + onlyMatchingFilter: '', onlyPrLabels: '', anyOfLabels: '', anyOfIssueLabels: '', @@ -88,7 +90,8 @@ describe('Issue', (): void => { login: 'dummy-login', type: 'User' } - ] + ], + repository_url: 'https://api.github.com/repos/dummy/dummy' }; issue = new Issue(optionsInterface, issueInterface); }); diff --git a/src/classes/issue.ts b/src/classes/issue.ts index b90631835..c53cb9ae4 100644 --- a/src/classes/issue.ts +++ b/src/classes/issue.ts @@ -4,9 +4,11 @@ import {Assignee} from '../interfaces/assignee'; import {IIssue, OctokitIssue} from '../interfaces/issue'; import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; import {ILabel} from '../interfaces/label'; +import {IOwnerRepo} from '../interfaces/owner-repo'; import {IMilestone} from '../interfaces/milestone'; import {IsoDateString} from '../types/iso-date-string'; import {Operations} from './operations'; +import {OwnerRepo} from './owner-repo'; export class Issue implements IIssue { readonly title: string; @@ -20,8 +22,10 @@ export class Issue implements IIssue { readonly locked: boolean; readonly milestone?: IMilestone | null; readonly assignees: Assignee[]; + readonly repository_url?: string; isStale: boolean; markedStaleThisRun: boolean; + readonly owner_repo: IOwnerRepo; operations = new Operations(); private readonly _options: IIssuesProcessorOptions; @@ -41,8 +45,10 @@ export class Issue implements IIssue { this.locked = issue.locked; this.milestone = issue.milestone; this.assignees = issue.assignees || []; + this.repository_url = issue.repository_url; this.isStale = isLabeled(this, this.staleLabel); this.markedStaleThisRun = false; + this.owner_repo = new OwnerRepo(issue.repository_url || ''); } get isPullRequest(): boolean { diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index 486c6a78a..952035e99 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -106,7 +106,7 @@ export class IssuesProcessor { async processIssues(page: Readonly = 1): Promise { // get the next batch of issues - const issues: Issue[] = await this.getIssues(page); + const issues: Issue[] = await this.getIssuesWrapper(page); if (issues.length <= 0) { this._logger.info( @@ -549,8 +549,8 @@ export class IssuesProcessor { this._consumeIssueOperation(issue); this.statistics?.incrementFetchedItemsCommentsCount(); const comments = await this.client.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, since: sinceDate }); @@ -574,6 +574,11 @@ export class IssuesProcessor { page }); this.statistics?.incrementFetchedItemsCount(issueResult.data.length); + this._logger.info( + LoggerService.green( + `Retrieved ${issueResult.data.length} issues/PRs for repo ${context.repo.owner}/${context.repo.repo}` + ) + ); return issueResult.data.map( (issue): Issue => @@ -584,6 +589,58 @@ export class IssuesProcessor { } } + // grab issues and/or prs from github in batches of 100 using search filter + async getIssuesByFilter(page: number, search: string): Promise { + try { + this.operations.consumeOperation(); + const issueResult = await this.client.rest.search.issuesAndPullRequests({ + q: search, + per_page: 100, + direction: this.options.ascending ? 'asc' : 'desc', + page + }); + this.statistics?.incrementFetchedItemsCount(issueResult.data.total_count); + this._logger.info( + LoggerService.green( + `Retrieved ${issueResult.data.total_count} issues/PRs for search '${search}'` + ) + ); + + return issueResult.data.items.map( + (issue): Issue => + new Issue(this.options, issue as Readonly) + ); + } catch (error) { + throw Error(`Getting issues was blocked by the error: ${error.message}`); + } + } + + private _removeDupIssues(issues: Issue[]): Issue[] { + return issues.reduce(function (a: Issue[], b: Issue) { + if (!a.find(o => o.number == b.number)) a.push(b); + return a; + }, []); + } + + async getIssuesWrapper(page: number): Promise { + if (!this.options.onlyMatchingFilter) { + return this.getIssues(page); + } + const filter = this.options.onlyMatchingFilter; + const results: Issue[] = []; + for (let term of filter.split('||')) { + if (term.search(/repo:|owner:|org:|user:/) < 0) { + term = `repo:${context.repo.owner}/${context.repo.repo} ${this.options.onlyMatchingFilter}`; + } + if (term.search(/is:open/) < 0) { + term += ' is:open'; + } + const r: Issue[] = await this.getIssuesByFilter(page, term); + results.push(...r); + } + return this._removeDupIssues(results); + } + // returns the creation date of a given label on an issue (or nothing if no label existed) ///see https://developer.github.com/v3/activity/events/ async getLabelCreationDate( @@ -597,8 +654,8 @@ export class IssuesProcessor { this._consumeIssueOperation(issue); this.statistics?.incrementFetchedItemsEventsCount(); const options = this.client.rest.issues.listEvents.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, per_page: 100, issue_number: issue.number }); @@ -628,8 +685,8 @@ export class IssuesProcessor { this.statistics?.incrementFetchedPullRequestsCount(); const pullRequest = await this.client.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, pull_number: issue.number }); @@ -848,8 +905,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, body: staleMessage }); @@ -866,8 +923,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: [staleLabel] }); @@ -896,8 +953,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, body: closeMessage }); @@ -914,8 +971,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: [closeLabel] }); @@ -931,8 +988,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, state: 'closed', state_reason: this.options.closeIssueReason || undefined @@ -968,7 +1025,7 @@ export class IssuesProcessor { if ( pullRequest.head.repo === null || pullRequest.head.repo.full_name === - `${context.repo.owner}/${context.repo.repo}` + `${issue.owner_repo.owner}/${issue.owner_repo.repo}` ) { issueLogger.info( `Deleting the branch "${LoggerService.cyan(branch)}" from closed $$type` @@ -980,8 +1037,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, ref: `heads/${branch}` }); } @@ -1024,8 +1081,8 @@ export class IssuesProcessor { if (!this.options.debugOnly) { await this.client.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, name: label }); @@ -1162,8 +1219,8 @@ export class IssuesProcessor { this.statistics?.incrementAddedItemsLabel(issue); if (!this.options.debugOnly) { await this.client.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, + owner: issue.owner_repo.owner, + repo: issue.owner_repo.repo, issue_number: issue.number, labels: labelsToAdd }); diff --git a/src/classes/owner-repo.ts b/src/classes/owner-repo.ts new file mode 100644 index 000000000..a9479fa19 --- /dev/null +++ b/src/classes/owner-repo.ts @@ -0,0 +1,17 @@ +import {IOwnerRepo} from '../interfaces/owner-repo'; + +export class OwnerRepo implements IOwnerRepo { + readonly owner: string; + readonly repo: string; + + constructor(repo_url: string) { + const m = repo_url.match(/.*\/([^/]+)\/(.+)$/); + if (!m) { + this.owner = ''; + this.repo = ''; + } else { + this.owner = m[1]; + this.repo = m[2]; + } + } +} diff --git a/src/enums/option.ts b/src/enums/option.ts index 7a9bff026..b77fd5fdb 100644 --- a/src/enums/option.ts +++ b/src/enums/option.ts @@ -12,6 +12,7 @@ export enum Option { DaysBeforePrClose = 'days-before-pr-close', StaleIssueLabel = 'stale-issue-label', CloseIssueLabel = 'close-issue-label', + OnlyMatchingFilter = 'only-matching-filter', ExemptIssueLabels = 'exempt-issue-labels', StalePrLabel = 'stale-pr-label', ClosePrLabel = 'close-pr-label', diff --git a/src/interfaces/issue.ts b/src/interfaces/issue.ts index defdb75d6..af77e5657 100644 --- a/src/interfaces/issue.ts +++ b/src/interfaces/issue.ts @@ -3,6 +3,7 @@ import {Assignee} from './assignee'; import {ILabel} from './label'; import {IMilestone} from './milestone'; import {components} from '@octokit/openapi-types'; + export interface IIssue { title: string; number: number; @@ -15,6 +16,7 @@ export interface IIssue { locked: boolean; milestone?: IMilestone | null; assignees?: Assignee[] | null; + repository_url?: string; } export type OctokitIssue = components['schemas']['issue']; diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 930992284..a11771681 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -14,6 +14,7 @@ export interface IIssuesProcessorOptions { daysBeforePrClose: number; // Could be NaN staleIssueLabel: string; closeIssueLabel: string; + onlyMatchingFilter: string; exemptIssueLabels: string; stalePrLabel: string; closePrLabel: string; diff --git a/src/interfaces/owner-repo.ts b/src/interfaces/owner-repo.ts new file mode 100644 index 000000000..1d2821ad4 --- /dev/null +++ b/src/interfaces/owner-repo.ts @@ -0,0 +1,4 @@ +export interface IOwnerRepo { + owner: string; + repo: string; +} diff --git a/src/main.ts b/src/main.ts index a7836c160..85435732d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -73,6 +73,7 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { daysBeforePrClose: parseInt(core.getInput('days-before-pr-close')), staleIssueLabel: core.getInput('stale-issue-label', {required: true}), closeIssueLabel: core.getInput('close-issue-label'), + onlyMatchingFilter: core.getInput('only-matching-filter'), exemptIssueLabels: core.getInput('exempt-issue-labels'), stalePrLabel: core.getInput('stale-pr-label', {required: true}), closePrLabel: core.getInput('close-pr-label'), diff --git a/stale-action-9.0.0.tgz b/stale-action-9.0.0.tgz new file mode 100644 index 000000000..d2d335a25 Binary files /dev/null and b/stale-action-9.0.0.tgz differ