From d4e405a788c9e2bfe78cd808dc3d0231a3850b78 Mon Sep 17 00:00:00 2001 From: Filipe Miranda Date: Wed, 10 May 2023 11:37:48 -0300 Subject: [PATCH 1/7] feat: add authors matchconfig option Closes #100 --- src/labeler.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/labeler.ts b/src/labeler.ts index 96e152bc3..0f847d77d 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -6,6 +6,7 @@ import {Minimatch} from 'minimatch'; interface MatchConfig { all?: string[]; any?: string[]; + authors?: string[]; } type StringOrMatchConfig = string | MatchConfig; @@ -71,6 +72,15 @@ function getPrNumber(): number | undefined { return pullRequest.number; } +function getPrAuthor(): string | undefined { + const pullRequest = github.context.payload.pull_request; + if (!pullRequest) { + return undefined; + } + + return pullRequest.user.login; +} + async function getChangedFiles( client: ClientType, prNumber: number @@ -213,6 +223,22 @@ function checkAll(changedFiles: string[], globs: string[]): boolean { return true; } +function checkAuthors(authors: string[]): boolean { + const prAuthor = getPrAuthor(); + if (!prAuthor) { + core.info('Could not get pull request author from context, exiting'); + return false; + } + + if (authors.includes(prAuthor)) { + core.debug(` author ${prAuthor} is on the list`); + return true; + } + + core.debug(` author ${prAuthor} is not on the list`); + return false; +} + function checkMatch(changedFiles: string[], matchConfig: MatchConfig): boolean { if (matchConfig.all !== undefined) { if (!checkAll(changedFiles, matchConfig.all)) { @@ -226,6 +252,12 @@ function checkMatch(changedFiles: string[], matchConfig: MatchConfig): boolean { } } + if (matchConfig.authors !== undefined) { + if (!checkAuthors(matchConfig.authors)) { + return false; + } + } + return true; } From 444d88415b2ce2a78605cf40cd4d37aeafe2f3df Mon Sep 17 00:00:00 2001 From: Filipe Miranda Date: Wed, 10 May 2023 11:45:49 -0300 Subject: [PATCH 2/7] chore: update README.md add new author matchconfig explanation --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 47bc342c7..6e1cee00b 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,17 @@ For more control over matching, you can provide a match object instead of a simp ```yml - any: ['list', 'of', 'globs'] all: ['list', 'of', 'globs'] + authors: ['list', 'of', 'authors'] # github users ``` One or both fields can be provided for fine-grained matching. Unlike the top-level list, the list of path globs provided to `any` and `all` must ALL match against a path for the label to be applied. +Also, the `authors` field is a list of GitHub usernames that must ALL match the PR author for the label to be applied. + The fields are defined as follows: * `any`: match ALL globs against ANY changed path * `all`: match ALL globs against ALL changed paths +* `authors`: match ALL authors against the PR author A simple path glob is the equivalent to `any: ['glob']`. More specifically, the following two configurations are equivalent: ```yml @@ -80,6 +84,10 @@ source: frontend: - any: ['src/**/*.js'] all: ['!src/main.js'] + +dev-team: +- any: ['src/**/*'] + authors: ['user1', 'user2', 'user3'] ``` ### Create Workflow From 165aa71bccc2760c2ce2775a7bf50b524a92c41f Mon Sep 17 00:00:00 2001 From: Filipe Miranda Date: Wed, 10 May 2023 12:04:24 -0300 Subject: [PATCH 3/7] test: add tests for new author matchconfig property --- __mocks__/@actions/github.ts | 5 ++++- __tests__/labeler.test.ts | 24 ++++++++++++++++++++++-- src/labeler.ts | 3 +-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/__mocks__/@actions/github.ts b/__mocks__/@actions/github.ts index ea8319f63..47ae8b11d 100644 --- a/__mocks__/@actions/github.ts +++ b/__mocks__/@actions/github.ts @@ -1,7 +1,10 @@ export const context = { payload: { pull_request: { - number: 123 + number: 123, + user: { + login: 'monalisa' + } } }, repo: { diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index d684d28bd..3d8c52baf 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -1,4 +1,4 @@ -import {checkGlobs} from '../src/labeler'; +import {MatchConfig, checkGlobs} from '../src/labeler'; import * as core from '@actions/core'; @@ -10,7 +10,7 @@ beforeAll(() => { }); }); -const matchConfig = [{any: ['*.txt']}]; +const matchConfig: MatchConfig[] = [{any: ['*.txt']}]; describe('checkGlobs', () => { it('returns true when our pattern does match changed files', () => { @@ -26,4 +26,24 @@ describe('checkGlobs', () => { expect(result).toBeFalsy(); }); + + it('returns true when PR author is in the list of authors', () => { + const matchConfigWithAuthor: MatchConfig[] = [ + {any: ['*.txt'], authors: ['monalisa', 'hubot']} + ]; + const changedFiles = ['foo.txt']; + + const result = checkGlobs(changedFiles, matchConfigWithAuthor); + expect(result).toBeTruthy(); + }); + + it('returns false when PR author is not in the list of authors', () => { + const matchConfigWithAuthor: MatchConfig[] = [ + {any: ['*.txt'], authors: ['foo', 'bar']} + ]; + const changedFiles = ['foo.txt']; + + const result = checkGlobs(changedFiles, matchConfigWithAuthor); + expect(result).toBeFalsy(); + }); }); diff --git a/src/labeler.ts b/src/labeler.ts index 0f847d77d..2955249b7 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -3,7 +3,7 @@ import * as github from '@actions/github'; import * as yaml from 'js-yaml'; import {Minimatch} from 'minimatch'; -interface MatchConfig { +export interface MatchConfig { all?: string[]; any?: string[]; authors?: string[]; @@ -77,7 +77,6 @@ function getPrAuthor(): string | undefined { if (!pullRequest) { return undefined; } - return pullRequest.user.login; } From 91d01da8b9456ba6e78c6c18f17fde2bf4e08dc0 Mon Sep 17 00:00:00 2001 From: Filipe Miranda Date: Wed, 10 May 2023 12:20:06 -0300 Subject: [PATCH 4/7] build: add build file --- dist/index.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dist/index.js b/dist/index.js index 6d753d52f..c0678fd58 100644 --- a/dist/index.js +++ b/dist/index.js @@ -96,6 +96,13 @@ function getPrNumber() { } return pullRequest.number; } +function getPrAuthor() { + const pullRequest = github.context.payload.pull_request; + if (!pullRequest) { + return undefined; + } + return pullRequest.user.login; +} function getChangedFiles(client, prNumber) { return __awaiter(this, void 0, void 0, function* () { const listFilesOptions = client.rest.pulls.listFiles.endpoint.merge({ @@ -207,6 +214,19 @@ function checkAll(changedFiles, globs) { core.debug(` "all" patterns matched all files`); return true; } +function checkAuthors(authors) { + const prAuthor = getPrAuthor(); + if (!prAuthor) { + core.info('Could not get pull request author from context, exiting'); + return false; + } + if (authors.includes(prAuthor)) { + core.debug(` author ${prAuthor} is on the list`); + return true; + } + core.debug(` author ${prAuthor} is not on the list`); + return false; +} function checkMatch(changedFiles, matchConfig) { if (matchConfig.all !== undefined) { if (!checkAll(changedFiles, matchConfig.all)) { @@ -218,6 +238,11 @@ function checkMatch(changedFiles, matchConfig) { return false; } } + if (matchConfig.authors !== undefined) { + if (!checkAuthors(matchConfig.authors)) { + return false; + } + } return true; } function addLabels(client, prNumber, labels) { From 0ac510f61b11690ffca84c5f8ef2632af66e8be1 Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Fri, 19 Dec 2025 09:52:42 -0500 Subject: [PATCH 5/7] Fix tests --- __tests__/labeler.test.ts | 30 +++++++++++++++++++++------ dist/index.js | 39 +++++++++++++++++++++++++++++++++++- src/api/get-label-configs.ts | 15 ++++++++++++-- src/labeler.ts | 15 +++++++++++++- 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index b8c7074eb..c1bccaeea 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -235,21 +235,39 @@ describe('labeler error handling', () => { it('returns true when PR author is in the list of authors', () => { const matchConfigWithAuthor: MatchConfig[] = [ - {any: ['*.txt'], authors: ['monalisa', 'hubot']} + { + any: [ + {changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}, + {authors: ['monalisa', 'hubot']} + ] + } ]; - const changedFiles = ['foo.txt']; + const changedFiles = ['not_match.pdf']; - const result = checkGlobs(changedFiles, matchConfigWithAuthor); + const result = checkMatchConfigs( + changedFiles, + matchConfigWithAuthor, + false + ); expect(result).toBeTruthy(); }); it('returns false when PR author is not in the list of authors', () => { const matchConfigWithAuthor: MatchConfig[] = [ - {any: ['*.txt'], authors: ['foo', 'bar']} + { + any: [ + {changedFiles: [{anyGlobToAnyFile: ['*.txt']}]}, + {authors: ['foo', 'bar']} + ] + } ]; - const changedFiles = ['foo.txt']; + const changedFiles = ['not_match.pdf']; - const result = checkGlobs(changedFiles, matchConfigWithAuthor); + const result = checkMatchConfigs( + changedFiles, + matchConfigWithAuthor, + false + ); expect(result).toBeFalsy(); }); }); diff --git a/dist/index.js b/dist/index.js index 35bb10846..0d503c562 100644 --- a/dist/index.js +++ b/dist/index.js @@ -277,7 +277,12 @@ const fs_1 = __importDefault(__nccwpck_require__(9896)); const get_content_1 = __nccwpck_require__(6519); const changedFiles_1 = __nccwpck_require__(5145); const branch_1 = __nccwpck_require__(2234); -const ALLOWED_CONFIG_KEYS = ['changed-files', 'head-branch', 'base-branch']; +const ALLOWED_CONFIG_KEYS = [ + 'changed-files', + 'head-branch', + 'base-branch', + 'authors' +]; const getLabelConfigs = (client, configurationPath) => Promise.resolve() .then(() => { if (!fs_1.default.existsSync(configurationPath)) { @@ -1117,6 +1122,13 @@ function labeler() { } }); } +function getPrAuthor() { + const pullRequest = github.context.payload.pull_request; + if (!pullRequest) { + return undefined; + } + return pullRequest.user.login; +} function checkMatchConfigs(changedFiles, matchConfigs, dot) { for (const config of matchConfigs) { core.debug(` checking config ${JSON.stringify(config)}`); @@ -1170,6 +1182,12 @@ function checkAny(matchConfigs, changedFiles, dot) { return true; } } + if (matchConfig.authors) { + if (checkAuthors(matchConfig.authors)) { + core.debug(` "any" patterns matched`); + return true; + } + } } core.debug(` "any" patterns did not match any configs`); return false; @@ -1205,10 +1223,29 @@ function checkAll(matchConfigs, changedFiles, dot) { return false; } } + if (matchConfig.authors) { + if (!checkAuthors(matchConfig.authors)) { + core.debug(` "all" patterns did not match`); + return false; + } + } } core.debug(` "all" patterns matched all configs`); return true; } +function checkAuthors(authors) { + const prAuthor = getPrAuthor(); + if (!prAuthor) { + core.info('Could not get pull request author from context, exiting'); + return false; + } + if (authors.includes(prAuthor)) { + core.debug(` author ${prAuthor} is on the list`); + return true; + } + core.debug(` author ${prAuthor} is not on the list`); + return false; +} /***/ }), diff --git a/src/api/get-label-configs.ts b/src/api/get-label-configs.ts index 4db33f28e..8b9e9eba4 100644 --- a/src/api/get-label-configs.ts +++ b/src/api/get-label-configs.ts @@ -11,14 +11,25 @@ import { import {toBranchMatchConfig, BranchMatchConfig} from '../branch'; +export interface AuthorsMatchConfig { + authors?: string[]; +} + export interface MatchConfig { all?: BaseMatchConfig[]; any?: BaseMatchConfig[]; } -export type BaseMatchConfig = BranchMatchConfig & ChangedFilesMatchConfig; +export type BaseMatchConfig = BranchMatchConfig & + ChangedFilesMatchConfig & + AuthorsMatchConfig; -const ALLOWED_CONFIG_KEYS = ['changed-files', 'head-branch', 'base-branch']; +const ALLOWED_CONFIG_KEYS = [ + 'changed-files', + 'head-branch', + 'base-branch', + 'authors' +]; export const getLabelConfigs = ( client: ClientType, diff --git a/src/labeler.ts b/src/labeler.ts index 30ae36194..f187a4a94 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -191,6 +191,13 @@ export function checkAny( return true; } } + + if (matchConfig.authors) { + if (checkAuthors(matchConfig.authors)) { + core.debug(` "any" patterns matched`); + return true; + } + } } core.debug(` "any" patterns did not match any configs`); @@ -238,13 +245,19 @@ export function checkAll( return false; } } + + if (matchConfig.authors) { + if (!checkAuthors(matchConfig.authors)) { + core.debug(` "all" patterns did not match`); + return false; + } + } } core.debug(` "all" patterns matched all configs`); return true; } - function checkAuthors(authors: string[]): boolean { const prAuthor = getPrAuthor(); if (!prAuthor) { From 92f1c43ff6e9269c6952965f392b33bf5882cf00 Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Fri, 19 Dec 2025 10:09:15 -0500 Subject: [PATCH 6/7] Simplify code a bit --- dist/index.js | 10 ++-------- src/labeler.ts | 10 +--------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/dist/index.js b/dist/index.js index 0d503c562..907ac5e6b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1122,13 +1122,6 @@ function labeler() { } }); } -function getPrAuthor() { - const pullRequest = github.context.payload.pull_request; - if (!pullRequest) { - return undefined; - } - return pullRequest.user.login; -} function checkMatchConfigs(changedFiles, matchConfigs, dot) { for (const config of matchConfigs) { core.debug(` checking config ${JSON.stringify(config)}`); @@ -1234,7 +1227,8 @@ function checkAll(matchConfigs, changedFiles, dot) { return true; } function checkAuthors(authors) { - const prAuthor = getPrAuthor(); + var _a, _b; + const prAuthor = (_b = (_a = github.context.payload.pull_request) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.login; if (!prAuthor) { core.info('Could not get pull request author from context, exiting'); return false; diff --git a/src/labeler.ts b/src/labeler.ts index f187a4a94..1b0211a81 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -107,14 +107,6 @@ export async function labeler() { } } -function getPrAuthor(): string | undefined { - const pullRequest = github.context.payload.pull_request; - if (!pullRequest) { - return undefined; - } - return pullRequest.user.login; -} - export function checkMatchConfigs( changedFiles: string[], matchConfigs: MatchConfig[], @@ -259,7 +251,7 @@ export function checkAll( } function checkAuthors(authors: string[]): boolean { - const prAuthor = getPrAuthor(); + const prAuthor = github.context.payload.pull_request?.user?.login; if (!prAuthor) { core.info('Could not get pull request author from context, exiting'); return false; From 1bfaa154820689b97fcdbed0fe1d8239e860f491 Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Fri, 19 Dec 2025 10:31:25 -0500 Subject: [PATCH 7/7] Ooops lol --- __tests__/labeler.test.ts | 6 ++++-- dist/index.js | 3 ++- src/api/get-label-configs.ts | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index c1bccaeea..71c239f34 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -67,12 +67,14 @@ describe('toMatchConfig', () => { const config = { 'changed-files': [{'any-glob-to-any-file': ['testing-files']}], 'head-branch': ['testing-head'], - 'base-branch': ['testing-base'] + 'base-branch': ['testing-base'], + authors: ['testing-author'] }; const expected: BaseMatchConfig = { changedFiles: [{anyGlobToAnyFile: ['testing-files']}], headBranch: ['testing-head'], - baseBranch: ['testing-base'] + baseBranch: ['testing-base'], + authors: ['testing-author'] }; it('returns a MatchConfig object with all options', () => { diff --git a/dist/index.js b/dist/index.js index 907ac5e6b..1ff3ab8a6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -357,7 +357,8 @@ function getLabelConfigMapFromObject(configObject) { function toMatchConfig(config) { const changedFilesConfig = (0, changedFiles_1.toChangedFilesMatchConfig)(config); const branchConfig = (0, branch_1.toBranchMatchConfig)(config); - return Object.assign(Object.assign({}, changedFilesConfig), branchConfig); + const authorsConfig = config['authors'] ? { authors: config['authors'] } : {}; + return Object.assign(Object.assign(Object.assign({}, changedFilesConfig), branchConfig), authorsConfig); } diff --git a/src/api/get-label-configs.ts b/src/api/get-label-configs.ts index 8b9e9eba4..3e87bbc6c 100644 --- a/src/api/get-label-configs.ts +++ b/src/api/get-label-configs.ts @@ -129,9 +129,11 @@ export function getLabelConfigMapFromObject( export function toMatchConfig(config: any): BaseMatchConfig { const changedFilesConfig = toChangedFilesMatchConfig(config); const branchConfig = toBranchMatchConfig(config); + const authorsConfig = config['authors'] ? {authors: config['authors']} : {}; return { ...changedFilesConfig, - ...branchConfig + ...branchConfig, + ...authorsConfig }; }