From 6255d06ecb54d9bf64c664cdf4541c4a87b88d67 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 2 Oct 2023 09:34:18 +0200 Subject: [PATCH 1/4] feat: add understanding of more commit types --- src/getCommitMeaning.test.ts | 42 ++++++++++++++++++++++++++++++++++++ src/getCommitMeaning.ts | 30 ++++++++++++++++++++++++++ src/isReleaseCommit.test.ts | 32 --------------------------- src/isReleaseCommit.ts | 5 ----- src/shouldSemanticRelease.ts | 30 +++++++++++--------------- 5 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 src/getCommitMeaning.test.ts create mode 100644 src/getCommitMeaning.ts delete mode 100644 src/isReleaseCommit.test.ts delete mode 100644 src/isReleaseCommit.ts diff --git a/src/getCommitMeaning.test.ts b/src/getCommitMeaning.test.ts new file mode 100644 index 00000000..4d8b4806 --- /dev/null +++ b/src/getCommitMeaning.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; + +import { getCommitMeaning } from "./getCommitMeaning.js"; + +describe("getCommitMeaning", () => { + it.each([ + ["chore", { type: undefined }], + ["chore: deps", { type: "chore" }], + ["chore(deps): bump", { type: "chore" }], + ["docs", { type: undefined }], + ["docs: bump", { type: "docs" }], + ["docs: message", { type: "docs" }], + ["feat", { type: undefined }], + ["feat: bump", "meaningful"], + ["feat: bump version to 1.2.3", "meaningful"], + ["feat: message", "meaningful"], + ["fix", { type: undefined }], + ["fix: bump", "meaningful"], + ["fix: bump version to 1.2.3", "meaningful"], + ["fix: message", "meaningful"], + ["style", { type: undefined }], + ["style: bump", { type: "style" }], + ["style: bump version to 1.2.3", { type: "style" }], + ["style: message", { type: "style" }], + ["0.0.0", "release"], + ["v0.0.0", "release"], + ["1.2.3", "release"], + ["v1.2.3", "release"], + ["1.23.456", "release"], + ["v1.23.456", "release"], + ["12.345.6789", "release"], + ["v12.345.6789", "release"], + ["chore: release", "release"], + ["chore: release 1.2.3", "release"], + ["chore: release v1.2.3", "release"], + ["chore(deps): release", "release"], + ["chore(deps): release 1.2.3", "release"], + ["chore(deps): release v1.2.3", "release"], + ])("returns %j for %s", (input, expected) => { + expect(getCommitMeaning(input)).toEqual(expected); + }); +}); diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts new file mode 100644 index 00000000..3b5ecaa9 --- /dev/null +++ b/src/getCommitMeaning.ts @@ -0,0 +1,30 @@ +import conventionalCommitsParser from "conventional-commits-parser"; + +export type CommitMeaning = "release" | "meaningful" | "other"; + +const alwaysMeaningfulTypes = new Set(["feat", "fix"]); + +const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]); + +const releaseCommitTester = + /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; + +export function getCommitMeaning(message: string) { + // Some times are always meaningful or ignored, regardless of potentially release-like messages + const { type } = conventionalCommitsParser.sync(message); + if (type) { + if (alwaysMeaningfulTypes.has(type)) { + return "meaningful"; + } + if (alwaysIgnoredTypes.has(type)) { + return { type }; + } + } + + // If we've hit a release commit, we know we don't need to release + if (releaseCommitTester.test(message)) { + return "release"; + } + + return { type: type ?? undefined }; +} diff --git a/src/isReleaseCommit.test.ts b/src/isReleaseCommit.test.ts deleted file mode 100644 index b4f4dacf..00000000 --- a/src/isReleaseCommit.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { isReleaseCommit } from "./isReleaseCommit.js"; - -describe("isReleaseCommit", () => { - it.each([ - [false, ""], - [false, "chore"], - [false, "chore: deps"], - [false, "chore(deps): bump"], - [false, "docs"], - [false, "docs: bump"], - [false, "feat"], - [false, "feat: bump"], - [true, "0.0.0"], - [true, "v0.0.0"], - [true, "1.2.3"], - [true, "v1.2.3"], - [true, "1.23.456"], - [true, "v1.23.456"], - [true, "12.345.6789"], - [true, "v12.345.6789"], - [true, "chore: release"], - [true, "chore: release 1.2.3"], - [true, "chore: release v1.2.3"], - [true, "chore(deps): release"], - [true, "chore(deps): release 1.2.3"], - [true, "chore(deps): release v1.2.3"], - ])("returns %j for %s", (expected, input) => { - expect(isReleaseCommit(input)).toBe(expected); - }); -}); diff --git a/src/isReleaseCommit.ts b/src/isReleaseCommit.ts deleted file mode 100644 index 0eed2626..00000000 --- a/src/isReleaseCommit.ts +++ /dev/null @@ -1,5 +0,0 @@ -const tester = /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; - -export function isReleaseCommit(message: string) { - return tester.test(message); -} diff --git a/src/shouldSemanticRelease.ts b/src/shouldSemanticRelease.ts index 55f737cc..e1011789 100644 --- a/src/shouldSemanticRelease.ts +++ b/src/shouldSemanticRelease.ts @@ -1,11 +1,7 @@ -import conventionalCommitsParser from "conventional-commits-parser"; - -import { isReleaseCommit } from "./isReleaseCommit.js"; +import { getCommitMeaning } from "./getCommitMeaning.js"; import { ShouldSemanticReleaseOptions } from "./types.js"; import { execOrThrow } from "./utils.js"; -const ignoredTypes = new Set(["chore", "docs"]); - export async function shouldSemanticRelease({ verbose, }: ShouldSemanticReleaseOptions) { @@ -21,20 +17,20 @@ export async function shouldSemanticRelease({ for (const message of history) { log(`Checking commit: ${message}`); - // If we've hit a release commit, we know we don't need to release - if (isReleaseCommit(message)) { - log(`Found a release commit. Returning false.`); - return false; - } + const meaning = getCommitMeaning(message); - // Otherwise, we should release if a non-ignored commit type is found - const { type } = conventionalCommitsParser.sync(message); - if (type && !ignoredTypes.has(type)) { - log(`Found a meaningful commit. Returning true.`); - return true; - } + switch (meaning) { + case "release": + log(`Found a release commit. Returning false.`); + return false; + + case "meaningful": + log(`Found a meaningful commit. Returning true.`); + return true; - log(`Found type ${type}. Continuing.`); + default: + log(`Found type ${meaning.type}. Continuing.`); + } } // If we've seen every commit in the history and none match, don't release From 6318c447e1200c302c739eeb8fde204e532a53f6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 2 Oct 2023 09:36:23 +0200 Subject: [PATCH 2/4] typo: times/types --- src/getCommitMeaning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 3b5ecaa9..56135bda 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -10,7 +10,7 @@ const releaseCommitTester = /^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/; export function getCommitMeaning(message: string) { - // Some times are always meaningful or ignored, regardless of potentially release-like messages + // Some types are always meaningful or ignored, regardless of potentially release-like messages const { type } = conventionalCommitsParser.sync(message); if (type) { if (alwaysMeaningfulTypes.has(type)) { From ecbd4aac3677720bf5e9044d83ac048dcb376926 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 2 Oct 2023 09:41:41 +0200 Subject: [PATCH 3/4] Fixed up docs --- README.md | 14 ++++++++++++-- src/getCommitMeaning.ts | 2 -- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 860a39d2..450747be 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ ## Usage -This CLI script determines whether a semantic release should occur for a package based on Git history. -Specifically, it returns truthy only if a commit whose type _isn't_ `chore` or `docs` has come since the most recent release commit. +This function determines whether a semantic release should occur for a package based on Git history. +It returns true if a "meaningful" commit has come since the most recent release commit. ```shell if npx should-semantic-release ; then npx release-it ; fi @@ -53,6 +53,16 @@ Checking commit: chore: release v1.27.31 This is a release commit. Returning false. ``` +### Commit Purposes + +Based on a commit's conventional commit message type: + +1. If the type is `feat` or `fix`, 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" + +See [`getCommitMeaning`](./src/getCommitMeaning.ts) for the exact logic used. + ### Node API Alternately, you can call this import asynchronous `shouldSemanticRelease` function into Node scripts: diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 56135bda..4f58a66b 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -1,7 +1,5 @@ import conventionalCommitsParser from "conventional-commits-parser"; -export type CommitMeaning = "release" | "meaningful" | "other"; - const alwaysMeaningfulTypes = new Set(["feat", "fix"]); const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]); From 1d1acc2200b8380b23e40fab59e118e59a3dd1e8 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 2 Oct 2023 09:44:25 +0200 Subject: [PATCH 4/4] Also allow perf --- README.md | 2 +- src/getCommitMeaning.test.ts | 4 ++++ src/getCommitMeaning.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 450747be..1bfc9d79 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ This is a release commit. Returning false. Based on a commit's conventional commit message type: -1. If the type is `feat` or `fix`, it's considered "meaningful" +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" diff --git a/src/getCommitMeaning.test.ts b/src/getCommitMeaning.test.ts index 4d8b4806..23165883 100644 --- a/src/getCommitMeaning.test.ts +++ b/src/getCommitMeaning.test.ts @@ -18,6 +18,10 @@ describe("getCommitMeaning", () => { ["fix: bump", "meaningful"], ["fix: bump version to 1.2.3", "meaningful"], ["fix: message", "meaningful"], + ["perf", { type: undefined }], + ["perf: bump", "meaningful"], + ["perf: bump version to 1.2.3", "meaningful"], + ["perf: message", "meaningful"], ["style", { type: undefined }], ["style: bump", { type: "style" }], ["style: bump version to 1.2.3", { type: "style" }], diff --git a/src/getCommitMeaning.ts b/src/getCommitMeaning.ts index 4f58a66b..bf54716c 100644 --- a/src/getCommitMeaning.ts +++ b/src/getCommitMeaning.ts @@ -1,6 +1,6 @@ import conventionalCommitsParser from "conventional-commits-parser"; -const alwaysMeaningfulTypes = new Set(["feat", "fix"]); +const alwaysMeaningfulTypes = new Set(["feat", "fix", "perf"]); const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]);