Skip to content

Commit

Permalink
feat: Add subjectPatternError option to allow users to override the…
Browse files Browse the repository at this point in the history
… default error when `subjectPattern` doesn't match (#76)

* feat: add subjectPatternError option to set custom sub validation error

* fix: pu same string into one variable in test

* chore: some console log for debugging stuff that doesn't happen on local

* fix variable name

* feat: added templates

* Apply suggestions from code review

Co-authored-by: Jan Amann <jan@amann.me>

* message formatter generalized

* Update src/formatMessage.test.js

* Fix lint

Co-authored-by: Jan Amann <jan@amann.me>
  • Loading branch information
derberg and amannn committed Jan 18, 2021
1 parent 054c977 commit e617d81
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ jobs:
# Configure additional validation for the subject based on a regex.
# This example ensures the subject doesn't start with an uppercase character.
subjectPattern: ^(?![A-Z]).+$
# If `subjectPattern` is configured, you can use this property to override
# the default error message that is shown when the pattern doesn't match.
# The variables `subject` and `title` can be used within the message.
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}" didn't match the configured pattern.
Please ensure that the subject doesn't start with an uppercase character.
# For work-in-progress PRs you can typically use draft pull requests
# from Github. However, private repositories on the free plan don't have
# this option and therefore this action allows you to opt-in to using the
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ inputs:
subjectPattern:
description: "Configure additional validation for the subject based on a regex. E.g. '^(?![A-Z]).+$' ensures the subject doesn't start with an uppercase character."
required: false
subjectPatternError:
description: "If `subjectPattern` is configured, you can use this property to override the default error message that is shown when the pattern doesn't match. The variables `subject` and `title` can be used within the message."
required: false
wip:
description: "For work-in-progress PRs you can typically use draft pull requests from Github. However, private repositories on the free plan don't have this option and therefore this action allows you to opt-in to using the special '[WIP]' prefix to indicate this state. This will avoid the validation of the PR title and the pull request checks remain pending. Note that a second check will be reported if this is enabled."
required: false
9 changes: 9 additions & 0 deletions src/formatMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = function formatMessage(message, values) {
let formatted = message;
if (values) {
Object.entries(values).forEach(([key, value]) => {
formatted = formatted.replace(new RegExp(`{${key}}`, 'g'), value);
});
}
return formatted;
};
30 changes: 30 additions & 0 deletions src/formatMessage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const formatMessage = require('./formatMessage');

it('handles a string without variables', () => {
const message = 'this is test';
expect(formatMessage(message)).toEqual(message);
});

it('replaces a variable', () => {
expect(
formatMessage('this is {subject} test', {subject: 'my subject'})
).toEqual('this is my subject test');
});

it('replaces multiple variables', () => {
expect(
formatMessage('this {title} is {subject} test', {
subject: 'my subject',
title: 'my title'
})
).toEqual('this my title is my subject test');
});

it('replaces multiple instances of a variable', () => {
expect(
formatMessage(
'99 bottles of {beverage} on the wall, 99 bottles of {beverage}.',
{beverage: 'beer'}
)
).toEqual('99 bottles of beer on the wall, 99 bottles of beer.');
});
12 changes: 10 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ const validatePrTitle = require('./validatePrTitle');
module.exports = async function run() {
try {
const client = github.getOctokit(process.env.GITHUB_TOKEN);
const {types, scopes, requireScope, wip, subjectPattern} = parseConfig();
const {
types,
scopes,
requireScope,
wip,
subjectPattern,
subjectPatternError
} = parseConfig();

const contextPullRequest = github.context.payload.pull_request;
if (!contextPullRequest) {
Expand Down Expand Up @@ -38,7 +45,8 @@ module.exports = async function run() {
types,
scopes,
requireScope,
subjectPattern
subjectPattern,
subjectPatternError
});
} catch (error) {
validationError = error;
Expand Down
16 changes: 15 additions & 1 deletion src/parseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,24 @@ module.exports = function parseConfig() {
subjectPattern = ConfigParser.parseString(process.env.INPUT_SUBJECTPATTERN);
}

let subjectPatternError;
if (process.env.INPUT_SUBJECTPATTERNERROR) {
subjectPatternError = ConfigParser.parseString(
process.env.INPUT_SUBJECTPATTERNERROR
);
}

let wip;
if (process.env.INPUT_WIP) {
wip = ConfigParser.parseBoolean(process.env.INPUT_WIP);
}

return {types, scopes, requireScope, wip, subjectPattern};
return {
types,
scopes,
requireScope,
wip,
subjectPattern,
subjectPatternError
};
};
12 changes: 11 additions & 1 deletion src/validatePrTitle.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const conventionalCommitsConfig = require('conventional-changelog-conventionalcommits');
const conventionalCommitTypes = require('conventional-commit-types');
const parser = require('conventional-commits-parser').sync;
const formatMessage = require('./formatMessage');

const defaultTypes = Object.keys(conventionalCommitTypes.types);

module.exports = async function validatePrTitle(
prTitle,
{types, scopes, requireScope, subjectPattern} = {}
{types, scopes, requireScope, subjectPattern, subjectPatternError} = {}
) {
if (!types) types = defaultTypes;

Expand Down Expand Up @@ -66,6 +67,15 @@ module.exports = async function validatePrTitle(
if (subjectPattern) {
const match = result.subject.match(new RegExp(subjectPattern));

if (subjectPatternError) {
throw new Error(
formatMessage(subjectPatternError, {
subject: result.subject,
title: prTitle
})
);
}

if (!match) {
throw new Error(
`The subject "${result.subject}" found in pull request title "${prTitle}" doesn't match the configured pattern "${subjectPattern}".`
Expand Down
23 changes: 23 additions & 0 deletions src/validatePrTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@ describe('description validation', () => {
await validatePrTitle('fix: sK!"§4123');
});

it('uses the `subjectPatternError` if available when the `subjectPattern` does not match', async () => {
const customError =
'The subject found in the pull request title cannot start with an uppercase character.';
await expect(
validatePrTitle('fix: Foobar', {
subjectPattern: '^(?![A-Z]).+$',
subjectPatternError: customError
})
).rejects.toThrow(customError);
});

it('interpolates variables into `subjectPatternError`', async () => {
await expect(
validatePrTitle('fix: Foobar', {
subjectPattern: '^(?![A-Z]).+$',
subjectPatternError:
'The subject "{subject}" found in the pull request title "{title}" cannot start with an uppercase character.'
})
).rejects.toThrow(
'The subject "Foobar" found in the pull request title "fix: Foobar" cannot start with an uppercase character.'
);
});

it('throws for invalid subjects', async () => {
await expect(
validatePrTitle('fix: Foobar', {
Expand Down

0 comments on commit e617d81

Please sign in to comment.