Skip to content

Commit 6a7ed2d

Browse files
authored
feat: Add disallowScopes option (#179)
1 parent bc0a635 commit 6a7ed2d

File tree

7 files changed

+96
-1
lines changed

7 files changed

+96
-1
lines changed

.github/workflows/lint-pr-title-preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Lint PR title preview (current branch)'
1+
name: "Lint PR title preview (current branch)"
22
on:
33
pull_request:
44
types:

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ The action works without configuration, however you can provide options for cust
5555
ui
5656
# Configure that a scope must always be provided.
5757
requireScope: true
58+
# Configure which scopes are disallowed in PR titles. For instance, by setting
59+
# the value below, `chore(release): ...` or `ci(e2e,release): ...` will be rejected.
60+
disallowScopes: |
61+
release
5862
# Configure additional validation for the subject based on a regex.
5963
# This example ensures the subject doesn't start with an uppercase character.
6064
subjectPattern: ^(?![A-Z]).+$

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ inputs:
1717
requireScope:
1818
description: "Configure that a scope must always be provided."
1919
required: false
20+
disallowScopes:
21+
description: 'Configure which scopes are disallowed in PR titles.'
22+
required: false
2023
subjectPattern:
2124
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."
2225
required: false

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = async function run() {
99
types,
1010
scopes,
1111
requireScope,
12+
disallowScopes,
1213
wip,
1314
subjectPattern,
1415
subjectPatternError,
@@ -67,6 +68,7 @@ module.exports = async function run() {
6768
types,
6869
scopes,
6970
requireScope,
71+
disallowScopes,
7072
subjectPattern,
7173
subjectPatternError,
7274
headerPattern,
@@ -108,6 +110,7 @@ module.exports = async function run() {
108110
types,
109111
scopes,
110112
requireScope,
113+
disallowScopes,
111114
subjectPattern,
112115
subjectPatternError,
113116
headerPattern,

src/parseConfig.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ module.exports = function parseConfig() {
1616
requireScope = ConfigParser.parseBoolean(process.env.INPUT_REQUIRESCOPE);
1717
}
1818

19+
let disallowScopes;
20+
if (process.env.INPUT_DISALLOWSCOPES) {
21+
disallowScopes = ConfigParser.parseEnum(process.env.INPUT_DISALLOWSCOPES);
22+
}
23+
1924
let subjectPattern;
2025
if (process.env.INPUT_SUBJECTPATTERN) {
2126
subjectPattern = ConfigParser.parseString(process.env.INPUT_SUBJECTPATTERN);
@@ -73,6 +78,7 @@ module.exports = function parseConfig() {
7378
types,
7479
scopes,
7580
requireScope,
81+
disallowScopes,
7682
wip,
7783
subjectPattern,
7884
subjectPatternError,

src/validatePrTitle.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = async function validatePrTitle(
1111
types,
1212
scopes,
1313
requireScope,
14+
disallowScopes,
1415
subjectPattern,
1516
subjectPatternError,
1617
headerPattern,
@@ -46,6 +47,10 @@ module.exports = async function validatePrTitle(
4647
return scopes && !scopes.includes(s);
4748
}
4849

50+
function isDisallowedScope(s) {
51+
return disallowScopes && disallowScopes.includes(s);
52+
}
53+
4954
if (!result.type) {
5055
throw new Error(
5156
`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()}`
@@ -76,6 +81,7 @@ module.exports = async function validatePrTitle(
7681
const givenScopes = result.scope
7782
? result.scope.split(',').map((scope) => scope.trim())
7883
: undefined;
84+
7985
const unknownScopes = givenScopes ? givenScopes.filter(isUnknownScope) : [];
8086
if (scopes && unknownScopes.length > 0) {
8187
throw new Error(
@@ -89,6 +95,17 @@ module.exports = async function validatePrTitle(
8995
);
9096
}
9197

98+
const disallowedScopes = givenScopes
99+
? givenScopes.filter(isDisallowedScope)
100+
: [];
101+
if (disallowScopes && disallowedScopes.length > 0) {
102+
throw new Error(
103+
`Disallowed ${
104+
disallowedScopes.length === 1 ? 'scope was' : 'scopes were'
105+
} found: ${disallowScopes.join(', ')}`
106+
);
107+
}
108+
92109
function throwSubjectPatternError(message) {
93110
if (subjectPatternError) {
94111
message = formatMessage(subjectPatternError, {

src/validatePrTitle.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,68 @@ describe('defined scopes', () => {
9898
});
9999
});
100100

101+
describe('disallow scopes', () => {
102+
it('passes when a single scope is provided, but not present in disallowScopes with one item', async () => {
103+
await validatePrTitle('fix(core): Bar', {disallowScopes: ['release']});
104+
});
105+
106+
it('passes when multiple scopes are provided, but not present in disallowScopes with one item', async () => {
107+
await validatePrTitle('fix(core,e2e,bar): Bar', {
108+
disallowScopes: ['release']
109+
});
110+
});
111+
112+
it('passes when a single scope is provided, but not present in disallowScopes with multiple items', async () => {
113+
await validatePrTitle('fix(core): Bar', {
114+
disallowScopes: ['release', 'test']
115+
});
116+
});
117+
118+
it('passes when multiple scopes are provided, but not present in disallowScopes with multiple items', async () => {
119+
await validatePrTitle('fix(core,e2e,bar): Bar', {
120+
disallowScopes: ['release', 'test']
121+
});
122+
});
123+
124+
it('throws when a single scope is provided and it is present in disallowScopes with one item', async () => {
125+
await expect(
126+
validatePrTitle('fix(release): Bar', {disallowScopes: ['release']})
127+
).rejects.toThrow('Disallowed scope was found: release');
128+
});
129+
130+
it('throws when a single scope is provided and it is present in disallowScopes with multiple item', async () => {
131+
await expect(
132+
validatePrTitle('fix(release): Bar', {
133+
disallowScopes: ['release', 'test']
134+
})
135+
).rejects.toThrow('Disallowed scope was found: release');
136+
});
137+
138+
it('throws when multiple scopes are provided and one of them is present in disallowScopes with one item ', async () => {
139+
await expect(
140+
validatePrTitle('fix(release,e2e): Bar', {
141+
disallowScopes: ['release']
142+
})
143+
).rejects.toThrow('Disallowed scope was found: release');
144+
});
145+
146+
it('throws when multiple scopes are provided and one of them is present in disallowScopes with multiple items ', async () => {
147+
await expect(
148+
validatePrTitle('fix(release,e2e): Bar', {
149+
disallowScopes: ['release', 'test']
150+
})
151+
).rejects.toThrow('Disallowed scope was found: release');
152+
});
153+
154+
it('throws when multiple scopes are provided and more than one of them are present in disallowScopes', async () => {
155+
await expect(
156+
validatePrTitle('fix(release,test): Bar', {
157+
disallowScopes: ['release', 'test']
158+
})
159+
).rejects.toThrow('Disallowed scopes were found: release, test');
160+
});
161+
});
162+
101163
describe('scope allowlist not defined', () => {
102164
it('passes when a scope is provided', async () => {
103165
await validatePrTitle('fix(core): Bar', {

0 commit comments

Comments
 (0)