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 disallowScopes option #179

Merged
merged 10 commits into from May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint-pr-title-preview.yml
@@ -1,4 +1,4 @@
name: 'Lint PR title preview (current branch)'
name: "Lint PR title preview (current branch)"
on:
pull_request:
types:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -55,6 +55,10 @@ The action works without configuration, however you can provide options for cust
ui
# Configure that a scope must always be provided.
requireScope: true
# Configure which scopes are disallowed in PR titles. For instance, by setting
# the value below, `chore(release): ...` or `ci(e2e,release): ...` will be rejected.
disallowScopes: |
release
# 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]).+$
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Expand Up @@ -17,6 +17,9 @@ inputs:
requireScope:
description: "Configure that a scope must always be provided."
required: false
disallowScopes:
description: 'Configure which scopes are disallowed in PR titles.'
required: false
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
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Expand Up @@ -9,6 +9,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
wip,
subjectPattern,
subjectPatternError,
Expand Down Expand Up @@ -67,6 +68,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down Expand Up @@ -108,6 +110,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down
6 changes: 6 additions & 0 deletions src/parseConfig.js
Expand Up @@ -16,6 +16,11 @@ module.exports = function parseConfig() {
requireScope = ConfigParser.parseBoolean(process.env.INPUT_REQUIRESCOPE);
}

let disallowScopes;
if (process.env.INPUT_DISALLOWSCOPES) {
disallowScopes = ConfigParser.parseEnum(process.env.INPUT_DISALLOWSCOPES);
}

let subjectPattern;
if (process.env.INPUT_SUBJECTPATTERN) {
subjectPattern = ConfigParser.parseString(process.env.INPUT_SUBJECTPATTERN);
Expand Down Expand Up @@ -73,6 +78,7 @@ module.exports = function parseConfig() {
types,
scopes,
requireScope,
disallowScopes,
wip,
subjectPattern,
subjectPatternError,
Expand Down
17 changes: 17 additions & 0 deletions src/validatePrTitle.js
Expand Up @@ -11,6 +11,7 @@ module.exports = async function validatePrTitle(
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down Expand Up @@ -46,6 +47,10 @@ module.exports = async function validatePrTitle(
return scopes && !scopes.includes(s);
}

function isDisallowedScope(s) {
return disallowScopes && disallowScopes.includes(s);
}

if (!result.type) {
throw new Error(
`No release type found in pull request title "${prTitle}". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/\n\n${printAvailableTypes()}`
Expand Down Expand Up @@ -76,6 +81,7 @@ module.exports = async function validatePrTitle(
const givenScopes = result.scope
? result.scope.split(',').map((scope) => scope.trim())
: undefined;

const unknownScopes = givenScopes ? givenScopes.filter(isUnknownScope) : [];
if (scopes && unknownScopes.length > 0) {
throw new Error(
Expand All @@ -89,6 +95,17 @@ module.exports = async function validatePrTitle(
);
}

const disallowedScopes = givenScopes
? givenScopes.filter(isDisallowedScope)
: [];
if (disallowScopes && disallowedScopes.length > 0) {
throw new Error(
`Disallowed ${
disallowedScopes.length === 1 ? 'scope was' : 'scopes were'
} found: ${disallowScopes.join(', ')}`
);
}

function throwSubjectPatternError(message) {
if (subjectPatternError) {
message = formatMessage(subjectPatternError, {
Expand Down
62 changes: 62 additions & 0 deletions src/validatePrTitle.test.js
Expand Up @@ -98,6 +98,68 @@ describe('defined scopes', () => {
});
});

describe('disallow scopes', () => {
it('passes when a single scope is provided, but not present in disallowScopes with one item', async () => {
await validatePrTitle('fix(core): Bar', {disallowScopes: ['release']});
});

it('passes when multiple scopes are provided, but not present in disallowScopes with one item', async () => {
await validatePrTitle('fix(core,e2e,bar): Bar', {
disallowScopes: ['release']
});
});

it('passes when a single scope is provided, but not present in disallowScopes with multiple items', async () => {
await validatePrTitle('fix(core): Bar', {
disallowScopes: ['release', 'test']
});
});

it('passes when multiple scopes are provided, but not present in disallowScopes with multiple items', async () => {
await validatePrTitle('fix(core,e2e,bar): Bar', {
disallowScopes: ['release', 'test']
});
});

it('throws when a single scope is provided and it is present in disallowScopes with one item', async () => {
await expect(
validatePrTitle('fix(release): Bar', {disallowScopes: ['release']})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when a single scope is provided and it is present in disallowScopes with multiple item', async () => {
await expect(
validatePrTitle('fix(release): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and one of them is present in disallowScopes with one item ', async () => {
await expect(
validatePrTitle('fix(release,e2e): Bar', {
disallowScopes: ['release']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and one of them is present in disallowScopes with multiple items ', async () => {
await expect(
validatePrTitle('fix(release,e2e): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and more than one of them are present in disallowScopes', async () => {
await expect(
validatePrTitle('fix(release,test): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scopes were found: release, test');
});
});
jmosawy marked this conversation as resolved.
Show resolved Hide resolved

describe('scope allowlist not defined', () => {
it('passes when a scope is provided', async () => {
await validatePrTitle('fix(core): Bar', {
Expand Down