From 53951bd70011db084266d7b1ae95128f86059c98 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Thu, 15 Feb 2024 09:14:35 +0800 Subject: [PATCH 1/8] feat: support BREAKING CHANGE --- README.md | 5 +++-- src/getCommitMeaning.test.ts | 21 +++++++++++++++++++++ src/getCommitMeaning.ts | 9 +++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8800356..de819241 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 note 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..5b16a8b5 100644 --- a/src/getCommitMeaning.test.ts +++ b/src/getCommitMeaning.test.ts @@ -40,6 +40,27 @@ 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" }, + ], ])("returns %j for %s", (input, expected) => { expect(getCommitMeaning(input)).toEqual(expected); }); diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 3935dbda..12bc260d 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -7,7 +7,16 @@ const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]); const releaseCommitTester = /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; +const breakChangingCommitTester = [ + /^\w+(?:\(.+\))?!:\s*/, + /^\w+(?:\(.+\))?!?:\s[^\n]*\n[^\n]*\n.*BREAKING[ |-]CHANGE: /s, +]; + export function getCommitMeaning(message: string) { + if (breakChangingCommitTester.some((tester) => tester.test(message))) { + return "meaningful"; + } + // Some types are always meaningful or ignored, regardless of potentially release-like messages const { type } = conventionalCommitsParser.sync(message); if (type) { From f960103c4fa17ef02ebffcd6f6807bc2de6ffc63 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 16 Feb 2024 03:08:10 +0800 Subject: [PATCH 2/8] refactor: use notes information --- src/getCommitMeaning.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 12bc260d..f67a7acf 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -7,18 +7,19 @@ const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]); const releaseCommitTester = /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; -const breakChangingCommitTester = [ - /^\w+(?:\(.+\))?!:\s*/, - /^\w+(?:\(.+\))?!?:\s[^\n]*\n[^\n]*\n.*BREAKING[ |-]CHANGE: /s, -]; +const breakChangingCommitTester = /^\w+(?:\(.+\))?!/; export function getCommitMeaning(message: string) { - if (breakChangingCommitTester.some((tester) => tester.test(message))) { + if (breakChangingCommitTester.test(message)) { return "meaningful"; } // Some types are always meaningful or ignored, regardless of potentially release-like messages - const { type } = conventionalCommitsParser.sync(message); + const { notes, type } = conventionalCommitsParser.sync(message); + if (notes.some((note) => note.title.match(/BREAKING[ -]CHANGE/))) { + return "meaningful"; + } + if (type) { if (alwaysMeaningfulTypes.has(type)) { return "meaningful"; From 6a414fd82e7f38504bed28791e51d7db3f3611b5 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 16 Feb 2024 03:19:01 +0800 Subject: [PATCH 3/8] refactor: use parser --- src/getCommitMeaning.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index f67a7acf..f4a8ffe7 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -7,15 +7,13 @@ const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]); const releaseCommitTester = /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; -const breakChangingCommitTester = /^\w+(?:\(.+\))?!/; - export function getCommitMeaning(message: string) { - if (breakChangingCommitTester.test(message)) { - return "meaningful"; - } - // Some types are always meaningful or ignored, regardless of potentially release-like messages - const { notes, type } = conventionalCommitsParser.sync(message); + const { notes, type } = conventionalCommitsParser.sync(message, { + // @ts-expect-error - options from https://github.com/conventional-changelog/conventional-changelog/issues/648#issuecomment-704867077 + breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, + headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, + }); if (notes.some((note) => note.title.match(/BREAKING[ -]CHANGE/))) { return "meaningful"; } From 573c84177c0f5361c037533f3b65ef7b4f56c02f Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 16 Feb 2024 03:21:56 +0800 Subject: [PATCH 4/8] fix: regex --- src/getCommitMeaning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index f4a8ffe7..dfd99e51 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -14,7 +14,7 @@ export function getCommitMeaning(message: string) { breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, }); - if (notes.some((note) => note.title.match(/BREAKING[ -]CHANGE/))) { + if (notes.some((note) => note.title.match(/^BREAKING[ -]CHANGE$/))) { return "meaningful"; } From df627896f3623993a3f8153b2b343e6ba82bb8aa Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 16 Feb 2024 03:22:55 +0800 Subject: [PATCH 5/8] chore: clean --- src/getCommitMeaning.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index dfd99e51..c1d14898 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -12,7 +12,6 @@ export function getCommitMeaning(message: string) { const { notes, type } = conventionalCommitsParser.sync(message, { // @ts-expect-error - options from https://github.com/conventional-changelog/conventional-changelog/issues/648#issuecomment-704867077 breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, - headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/, }); if (notes.some((note) => note.title.match(/^BREAKING[ -]CHANGE$/))) { return "meaningful"; From 9b74215ee3abd44bc8e770450f8ea56b393d360c Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Sat, 17 Feb 2024 08:26:28 +0800 Subject: [PATCH 6/8] docs: update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de819241..e3935cc4 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ 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" -1. If the commit is marked as being a breaking change, either via a note or via an `!` appended to the type, it's considered "meaningful" +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" From d5a9dda9ec5e4a69b6d735c0a013a7b97d2f34be Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Sat, 17 Feb 2024 08:29:40 +0800 Subject: [PATCH 7/8] refactor: replace `@ts-expect-error` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- src/getCommitMeaning.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index c1d14898..3e250b46 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -9,10 +9,10 @@ const releaseCommitTester = export function getCommitMeaning(message: string) { // Some types are always meaningful or ignored, regardless of potentially release-like messages + // options from https://github.com/conventional-changelog/conventional-changelog/issues/648#issuecomment-704867077 const { notes, type } = conventionalCommitsParser.sync(message, { - // @ts-expect-error - options from https://github.com/conventional-changelog/conventional-changelog/issues/648#issuecomment-704867077 breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/, - }); + } as object); if (notes.some((note) => note.title.match(/^BREAKING[ -]CHANGE$/))) { return "meaningful"; } From 9a3bd69ce864a54d1c8aec51f478ecce85700a7e Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Sat, 17 Feb 2024 08:44:26 +0800 Subject: [PATCH 8/8] test: more --- src/getCommitMeaning.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/getCommitMeaning.test.ts b/src/getCommitMeaning.test.ts index 5b16a8b5..71839ad6 100644 --- a/src/getCommitMeaning.test.ts +++ b/src/getCommitMeaning.test.ts @@ -61,6 +61,20 @@ describe("getCommitMeaning", () => { "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); });