diff --git a/README.md b/README.md index d8800356..e3935cc4 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,9 @@ This is a release commit. Returning false. Based on a commit's conventional commit message type: 1. If the type is `feat` `fix`, or `perf`, it's considered "meaningful" -2. If the type is `docs`, `refactor`, `style`, or `test`, it's ignored -3. If the message looks like `v1.2.3`, `chore: release 1.2.3`, or similar, it's considered a "release" +1. If the commit is marked as being a breaking change, either via a `BREAKING CHANGE:` at the start of any commit message lines or via an `!` appended to the type, it's considered "meaningful" +1. If the type is `docs`, `refactor`, `style`, or `test`, it's ignored +1. If the message looks like `v1.2.3`, `chore: release 1.2.3`, or similar, it's considered a "release" See [`getCommitMeaning`](./src/getCommitMeaning.ts) for the exact logic used. diff --git a/src/getCommitMeaning.test.ts b/src/getCommitMeaning.test.ts index 23165883..71839ad6 100644 --- a/src/getCommitMeaning.test.ts +++ b/src/getCommitMeaning.test.ts @@ -40,6 +40,41 @@ describe("getCommitMeaning", () => { ["chore(deps): release", "release"], ["chore(deps): release 1.2.3", "release"], ["chore(deps): release v1.2.3", "release"], + ["chore!: message", "meaningful"], + ["docs!: message", "meaningful"], + ["chore!: release", "meaningful"], + ["feat!: a feature with a breaking change", "meaningful"], + ["chore: bump\n\nBREAKING CHANGE: breaks things", "meaningful"], + ["chore: bump\n\nBREAKING-CHANGE: breaks things", "meaningful"], + ["docs: bump\n\nBREAKING CHANGE: breaks things", "meaningful"], + ["docs: bump\n\nBREAKING-CHANGE: breaks things", "meaningful"], + [ + "chore: deps\n\nBREAKING-CHANGE: breaks things\nMultiple: footer notes", + "meaningful", + ], + ["chore: deps\n\n! in the commit body", { type: "chore" }], + [ + "chore: deps\n\nFooter-note: other than BREAKING CHANGE", + { type: "chore" }, + ], + [ + "chore: deps\n\nMultiple: footer notes\nMultiple: footer notes", + { type: "chore" }, + ], + // This test may should be { type: "chore" }, + // but conventionalCommitsParser looks like can't parse this case correctly + [ + "chore: deps\n\nBREAKING CHANGE line starts with BREAKING CHANGE (no :)", + "meaningful", + ], + [ + "chore: deps\n\nline contains BREAKING CHANGE: inside it", + { type: "chore" }, + ], + [ + "BREAKING CHANGE (major): line starts with something like BREAKING CHANGE", + { type: undefined }, + ], ])("returns %j for %s", (input, expected) => { expect(getCommitMeaning(input)).toEqual(expected); }); diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 3935dbda..3e250b46 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -9,7 +9,14 @@ const releaseCommitTester = export function getCommitMeaning(message: string) { // Some types are always meaningful or ignored, regardless of potentially release-like messages - const { type } = conventionalCommitsParser.sync(message); + // options from https://github.com/conventional-changelog/conventional-changelog/issues/648#issuecomment-704867077 + const { notes, type } = conventionalCommitsParser.sync(message, { + breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, + } as object); + if (notes.some((note) => note.title.match(/^BREAKING[ -]CHANGE$/))) { + return "meaningful"; + } + if (type) { if (alwaysMeaningfulTypes.has(type)) { return "meaningful";