Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The base match object is defined as:
- all-globs-to-all-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
- title: ['list', 'of', 'regexps']
```

There are two top-level keys, `any` and `all`, which both accept the same configuration options:
Expand All @@ -49,6 +50,7 @@ There are two top-level keys, `any` and `all`, which both accept the same config
- all-globs-to-all-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
- title: ['list', 'of', 'regexps']
- all:
- changed-files:
- any-glob-to-any-file: ['list', 'of', 'globs']
Expand All @@ -57,6 +59,7 @@ There are two top-level keys, `any` and `all`, which both accept the same config
- all-globs-to-all-files: ['list', 'of', 'globs']
- base-branch: ['list', 'of', 'regexps']
- head-branch: ['list', 'of', 'regexps']
- title: ['list', 'of', 'regexps']
```

From a boolean logic perspective, top-level match objects, and options within `all` are `AND`-ed together and individual match rules within the `any` object are `OR`-ed.
Expand All @@ -65,13 +68,14 @@ One or all fields can be provided for fine-grained matching.
The fields are defined as follows:
- `all`: ALL of the provided options must match for the label to be applied
- `any`: if ANY of the provided options match then the label will be applied
- `base-branch`: match regexps against the base branch name
- `head-branch`: match regexps against the head branch name
- `changed-files`: match glob patterns against the changed paths
- `any-glob-to-any-file`: ANY glob must match against ANY changed file
- `any-glob-to-all-files`: ANY glob must match against ALL changed files
- `all-globs-to-any-file`: ALL globs must match against ANY changed file
- `all-globs-to-all-files`: ALL globs must match against ALL changed files
- `base-branch`: match regexps against the base branch name
- `head-branch`: match regexps against the head branch name
- `title`: match regexps against the pull request title

If a base option is provided without a top-level key, then it will default to `any`. More specifically, the following two configurations are equivalent:
```yml
Expand Down Expand Up @@ -144,6 +148,19 @@ feature:
# Add 'release' label to any PR that is opened against the `main` branch
release:
- base-branch: 'main'

# Add 'chore' label to any PR where the title starts with `chore`
chore:
- title: '^chore'

# Add 'ci' label to any PR where the title starts with `ci` or `build`:
ci:
- title: ['^ci', '^build']

# Add 'web' label to any PR where the title includes conventional commits optional scope
web:
- title: '^\w+\(web\):'

```

### Create Workflow
Expand Down
3 changes: 2 additions & 1 deletion __mocks__/@actions/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const context = {
},
base: {
ref: 'base-branch-name'
}
},
title: 'pr-title'
}
},
repo: {
Expand Down
143 changes: 143 additions & 0 deletions __tests__/title.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
getTitle,
checkAnyTitle,
checkAllTitle,
toTitleMatchConfig,
TitleMatchConfig
} from '../src/title';
import * as github from '@actions/github';

jest.mock('@actions/core');
jest.mock('@actions/github');

describe('getTitle', () => {
describe('when the pull requests title is requested', () => {
it('returns the title', () => {
const result = getTitle();
expect(result).toEqual('pr-title');
});
});
});

describe('checkAllTitle', () => {
beforeEach(() => {
github.context.payload.pull_request!.title = 'type(scope): description';
});

describe('when a single pattern is provided', () => {
describe('and the pattern matches the title', () => {
it('returns true', () => {
const result = checkAllTitle(['^type']);
expect(result).toBe(true);
});
});

describe('and the pattern does not match the title', () => {
it('returns false', () => {
const result = checkAllTitle(['^feature/']);
expect(result).toBe(false);
});
});
});

describe('when multiple patterns are provided', () => {
describe('and not all patterns matched', () => {
it('returns false', () => {
const result = checkAllTitle(['^type', '^test']);
expect(result).toBe(false);
});
});

describe('and all patterns match', () => {
it('returns true', () => {
const result = checkAllTitle(['^type', '^\\w+\\(scope\\):']);
expect(result).toBe(true);
});
});

describe('and no patterns match', () => {
it('returns false', () => {
const result = checkAllTitle(['^feature', 'test$']);
expect(result).toBe(false);
});
});
});
});

describe('checkAnyTitle', () => {
beforeEach(() => {
github.context.payload.pull_request!.title = 'type(scope): description';
});

describe('when a single pattern is provided', () => {
describe('and the pattern matches the title', () => {
it('returns true', () => {
const result = checkAnyTitle(['^type']);
expect(result).toBe(true);
});
});

describe('and the pattern does not match the title', () => {
it('returns false', () => {
const result = checkAnyTitle(['^test']);
expect(result).toBe(false);
});
});
});

describe('when multiple patterns are provided', () => {
describe('and at least one pattern matches', () => {
it('returns true', () => {
const result = checkAnyTitle(['^type', '^test']);
expect(result).toBe(true);
});
});

describe('and all patterns match', () => {
it('returns true', () => {
const result = checkAnyTitle(['^type', '^\\w+\\(scope\\):']);
expect(result).toBe(true);
});
});

describe('and no patterns match', () => {
it('returns false', () => {
const result = checkAllTitle(['^feature', 'test$']);
expect(result).toBe(false);
});
});
});
});

describe('toTitleMatchConfig', () => {
describe('when there are no title keys in the config', () => {
const config = {'changed-files': [{any: ['testing']}]};

it('returns an empty object', () => {
const result = toTitleMatchConfig(config);
expect(result).toEqual({});
});
});

describe('when the config contains a title option', () => {
const config = {title: ['testing']};

it('sets title in the matchConfig', () => {
const result = toTitleMatchConfig(config);
expect(result).toEqual<TitleMatchConfig>({
title: ['testing']
});
});

describe('and the matching option is a string', () => {
const stringConfig = {title: 'testing'};

it('sets title in the matchConfig', () => {
const result = toTitleMatchConfig(stringConfig);
expect(result).toEqual<TitleMatchConfig>({
title: ['testing']
});
});
});
});
});
135 changes: 133 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,13 @@ 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 title_1 = __nccwpck_require__(9798);
const ALLOWED_CONFIG_KEYS = [
'changed-files',
'head-branch',
'base-branch',
'title'
];
const getLabelConfigs = (client, configurationPath) => Promise.resolve()
.then(() => {
if (!fs_1.default.existsSync(configurationPath)) {
Expand Down Expand Up @@ -352,7 +358,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 titleConfig = (0, title_1.toTitleMatchConfig)(config);
return Object.assign(Object.assign(Object.assign({}, changedFilesConfig), branchConfig), titleConfig);
}


Expand Down Expand Up @@ -1039,6 +1046,7 @@ const lodash_isequal_1 = __importDefault(__nccwpck_require__(9471));
const get_inputs_1 = __nccwpck_require__(1219);
const changedFiles_1 = __nccwpck_require__(5145);
const branch_1 = __nccwpck_require__(2234);
const title_1 = __nccwpck_require__(9798);
// GitHub Issues cannot have more than 100 labels
const GITHUB_MAX_LABELS = 100;
const run = () => labeler().catch(error => {
Expand Down Expand Up @@ -1162,6 +1170,12 @@ function checkAny(matchConfigs, changedFiles, dot) {
return true;
}
}
if (matchConfig.title) {
if ((0, title_1.checkAnyTitle)(matchConfig.title)) {
core.debug(` "any" patterns matched`);
return true;
}
}
}
core.debug(` "any" patterns did not match any configs`);
return false;
Expand Down Expand Up @@ -1197,12 +1211,129 @@ function checkAll(matchConfigs, changedFiles, dot) {
return false;
}
}
if (matchConfig.title) {
if (!(0, title_1.checkAllTitle)(matchConfig.title)) {
core.debug(` "all" patterns did not match`);
return false;
}
}
}
core.debug(` "all" patterns matched all configs`);
return true;
}


/***/ }),

/***/ 9798:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.toTitleMatchConfig = toTitleMatchConfig;
exports.getTitle = getTitle;
exports.checkAnyTitle = checkAnyTitle;
exports.checkAllTitle = checkAllTitle;
const core = __importStar(__nccwpck_require__(7484));
const github = __importStar(__nccwpck_require__(3228));
function toTitleMatchConfig(config) {
if (!config['title']) {
return {};
}
const titleConfig = {
title: config['title']
};
if (typeof titleConfig.title === 'string') {
titleConfig.title = [titleConfig.title];
}
return titleConfig;
}
function getTitle() {
const pullRequest = github.context.payload.pull_request;
if (!pullRequest) {
return undefined;
}
return pullRequest.title;
}
function checkAnyTitle(regexps) {
const title = getTitle();
if (!title) {
core.debug(` cannot fetch title from the pull request`);
return false;
}
core.debug(` checking "title" pattern against ${title}`);
const matchers = regexps.map(regexp => new RegExp(regexp));
for (const matcher of matchers) {
if (matchTitlePattern(matcher, title)) {
core.debug(` "title" patterns matched against ${title}`);
return true;
}
}
core.debug(` "title" patterns did not match against ${title}`);
return false;
}
function checkAllTitle(regexps) {
const title = getTitle();
if (!title) {
core.debug(` cannot fetch title from the pull request`);
return false;
}
core.debug(` checking "title" pattern against ${title}`);
const matchers = regexps.map(regexp => new RegExp(regexp));
for (const matcher of matchers) {
if (!matchTitlePattern(matcher, title)) {
core.debug(` "title" patterns did not match against ${title}`);
return false;
}
}
core.debug(` "title" patterns matched against ${title}`);
return true;
}
function matchTitlePattern(matcher, title) {
core.debug(` - ${matcher}`);
if (matcher.test(title)) {
core.debug(` "title" pattern matched`);
return true;
}
core.debug(` ${matcher} did not match`);
return false;
}


/***/ }),

/***/ 9277:
Expand Down
Loading