diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6dd8fc06859..fef7ded677d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -175,7 +175,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits type(scope)!: subject ``` -The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). +The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). Do not mark `build`, `chore`, `ci`, `docs`, `style`, or `test` as breaking. Use one of these types: diff --git a/tools/ci-tools.ts b/tools/ci-tools.ts index 6927dddd3cd..5752ded4641 100644 --- a/tools/ci-tools.ts +++ b/tools/ci-tools.ts @@ -18,6 +18,7 @@ const allowedTypes = [ type CommitType = typeof allowedTypes[number]; const allowedTypesList = allowedTypes.join(', '); +const nonBreakingTypes = new Set(['build', 'chore', 'ci', 'docs', 'style', 'test']); const titlePattern = new RegExp(`^(${allowedTypes.join('|')})(\\([\\w/.-]+\\))?(!)?: .+$`); function parsePrTitle(title: string): {type: CommitType, scope: string, breaking: boolean} | null { @@ -86,12 +87,17 @@ function lintPrTitle(): void { console.error('Missing PR_TITLE'); exit(1); } - if (!parsePrTitle(env.PR_TITLE)) { + const parsed = parsePrTitle(env.PR_TITLE); + if (!parsed) { console.error(`Invalid PR title: ${env.PR_TITLE}`); console.error('Expected format: type(scope): subject (scope optional, append "!" for breaking changes)'); console.error(`Allowed types: ${allowedTypesList}`); exit(1); } + if (parsed.breaking && nonBreakingTypes.has(parsed.type)) { + console.error(`Type "${parsed.type}" cannot be marked as breaking (remove "!" from the title)`); + exit(1); + } } // Command: sync the title-derived labels onto the PR via the GitHub API.