Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add subjectPatternError option to set custom sub validation error #76

Merged
merged 9 commits into from
Jan 18, 2021
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