Skip to content

Commit

Permalink
feat: Add ability to constrain scopes (#66)
Browse files Browse the repository at this point in the history
* feat: Constrain scopes

* Adapt action config

* Fix NPE

* Add logging

* Change env param name

* Remove logging

* Optional scope

* Remove config
  • Loading branch information
amannn authored Dec 21, 2020
1 parent 153d429 commit 95b7031
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 15 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Examples for valid PR titles:
- fix: Correct typo.
- feat: Add support for Node 12.
- refactor!: Drop support for Node 6.
- feat(ui): Add `Button` component.

Note that since PR titles only have a single line, you have to use the `!` syntax for breaking changes.

Expand Down Expand Up @@ -40,10 +41,19 @@ jobs:
- uses: amannn/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Optionally you can configure which types are allowed.
# Default: https://github.com/commitizen/conventional-commit-types
# Optionally, you can provide options for further constraints.
with:
types: fix, feat
# Configure which types are allowed.
# Default: https://github.com/commitizen/conventional-commit-types
types: |
fix
feat
# Configure which scopes are allowed.
scopes: |
core
ui
# Configure that a scope must always be provided.
requireScope: true
```
## Event triggers
Expand Down
7 changes: 7 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ branding:
inputs:
types:
description: "Provide custom types if you don't want the default ones from https://www.conventionalcommits.org"
required: false
scopes:
description: "Configure which scopes are allowed."
required: false
requireScope:
description: "Configure that a scope must always be provided."
required: false
14 changes: 14 additions & 0 deletions src/ConfigParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const ENUM_SPLIT_REGEX = /[,\s]\s*/;

module.exports = {
parseEnum(input) {
return input
.split(ENUM_SPLIT_REGEX)
.map((part) => part.trim())
.filter((part) => part.length > 0);
},

parseBoolean(input) {
return JSON.parse(input.trim());
}
};
21 changes: 21 additions & 0 deletions src/ConfigParser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const ConfigParser = require('./ConfigParser');

describe('parseEnum', () => {
it('parses commas', () => {
expect(ConfigParser.parseEnum('one, two,three, \nfour ')).toEqual([
'one',
'two',
'three',
'four'
]);
});

it('parses white space', () => {
expect(ConfigParser.parseEnum('one two\nthree \n\rfour')).toEqual([
'one',
'two',
'three',
'four'
]);
});
});
9 changes: 3 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
const core = require('@actions/core');
const github = require('@actions/github');
const parseConfig = require('./parseConfig');
const validatePrTitle = require('./validatePrTitle');

module.exports = async function run() {
try {
const client = github.getOctokit(process.env.GITHUB_TOKEN);

let types;
if (process.env.INPUT_TYPES) {
types = process.env.INPUT_TYPES.split(',').map((type) => type.trim());
}
const {types, scopes, requireScope} = parseConfig();

const contextPullRequest = github.context.payload.pull_request;
if (!contextPullRequest) {
Expand Down Expand Up @@ -37,7 +34,7 @@ module.exports = async function run() {
let validationError;
if (!isWip) {
try {
await validatePrTitle(pullRequest.title, types);
await validatePrTitle(pullRequest.title, {types, scopes, requireScope});
} catch (error) {
validationError = error;
}
Expand Down
20 changes: 20 additions & 0 deletions src/parseConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const ConfigParser = require('./ConfigParser');

module.exports = function parseConfig() {
let types;
if (process.env.INPUT_TYPES) {
types = ConfigParser.parseEnum(process.env.INPUT_TYPES);
}

let scopes;
if (process.env.INPUT_SCOPES) {
scopes = ConfigParser.parseEnum(process.env.INPUT_SCOPES);
}

let requireScope;
if (process.env.INPUT_REQUIRESCOPE) {
requireScope = ConfigParser.parseBoolean(process.env.INPUT_REQUIRESCOPE);
}

return {types, scopes, requireScope};
};
25 changes: 24 additions & 1 deletion src/validatePrTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ const parser = require('conventional-commits-parser').sync;

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

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

const {parserOpts} = await conventionalCommitsConfig();
const result = parser(prTitle, parserOpts);

Expand Down Expand Up @@ -35,4 +40,22 @@ module.exports = async function validatePrTitle(prTitle, types = defaultTypes) {
}" found in pull request title "${prTitle}". \n\n${printAvailableTypes()}`
);
}

if (requireScope && !result.scope) {
throw new Error(
`No scope found in pull request title "${prTitle}". Use one of the available scopes: ${scopes.join(
', '
)}.`
);
}

if (scopes && result.scope && !scopes.includes(result.scope)) {
throw new Error(
`Unknown scope "${
result.scope
}" found in pull request title "${prTitle}". Use one of the available scopes: ${scopes.join(
', '
)}.`
);
}
};
56 changes: 51 additions & 5 deletions src/validatePrTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,77 @@ it('allows valid PR titles that use the default types', async () => {

it('throws for PR titles without a type', async () => {
await expect(validatePrTitle('Fix bug')).rejects.toThrow(
/No release type found in pull request title "Fix bug"./
'No release type found in pull request title "Fix bug".'
);
});

it('throws for PR titles with an unknown type', async () => {
await expect(validatePrTitle('foo: Bar')).rejects.toThrow(
/Unknown release type "foo" found in pull request title "foo: Bar"./
'Unknown release type "foo" found in pull request title "foo: Bar".'
);
});

describe('defined scopes', () => {
it('allows a missing scope by default', async () => {
await validatePrTitle('fix: Bar');
});

it('allows all scopes by default', async () => {
await validatePrTitle('fix(core): Bar');
});

it('allows a missing scope when custom scopes are defined', async () => {
await validatePrTitle('fix: Bar', {scopes: ['foo']});
});

it('allows a matching scope', async () => {
await validatePrTitle('fix(core): Bar', {scopes: ['core']});
});

it('throws when an unknown scope is detected', async () => {
await expect(
validatePrTitle('fix(core): Bar', {scopes: ['foo']})
).rejects.toThrow(
'Unknown scope "core" found in pull request title "fix(core): Bar". Use one of the available scopes: foo.'
);
});

describe('require scope', () => {
it('passes when a scope is provided', async () => {
await validatePrTitle('fix(core): Bar', {
scopes: ['core'],
requireScope: true
});
});

it('throws when a scope is missing', async () => {
await expect(
validatePrTitle('fix: Bar', {
scopes: ['foo', 'bar'],
requireScope: true
})
).rejects.toThrow(
'No scope found in pull request title "fix: Bar". Use one of the available scopes: foo, bar.'
);
});
});
});

describe('custom types', () => {
it('allows PR titles with a supported type', async () => {
const inputs = ['foo: Foobar', 'bar: Foobar', 'baz: Foobar'];
const types = ['foo', 'bar', 'baz'];

for (let index = 0; index < inputs.length; index++) {
await validatePrTitle(inputs[index], types);
await validatePrTitle(inputs[index], {types});
}
});

it('throws for PR titles with an unknown type', async () => {
await expect(
validatePrTitle('fix: Foobar', ['foo', 'bar'])
validatePrTitle('fix: Foobar', {types: ['foo', 'bar']})
).rejects.toThrow(
/Unknown release type "fix" found in pull request title "fix: Foobar"./
'Unknown release type "fix" found in pull request title "fix: Foobar".'
);
});
});

0 comments on commit 95b7031

Please sign in to comment.