Skip to content

Commit c7e619b

Browse files
Merge pull request #112 from JoshuaKGoldberg/more-commit-meanings
feat: add understanding of more commit types
2 parents d746446 + 1d1acc2 commit c7e619b

6 files changed

+99
-56
lines changed

README.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828

2929
## Usage
3030

31-
This CLI script determines whether a semantic release should occur for a package based on Git history.
32-
Specifically, it returns truthy only if a commit whose type _isn't_ `chore` or `docs` has come since the most recent release commit.
31+
This function determines whether a semantic release should occur for a package based on Git history.
32+
It returns true if a "meaningful" commit has come since the most recent release commit.
3333

3434
```shell
3535
if npx should-semantic-release ; then npx release-it ; fi
@@ -53,6 +53,16 @@ Checking commit: chore: release v1.27.31
5353
This is a release commit. Returning false.
5454
```
5555

56+
### Commit Purposes
57+
58+
Based on a commit's conventional commit message type:
59+
60+
1. If the type is `feat` `fix`, or `perf`, it's considered "meaningful"
61+
2. If the type is `docs`, `refactor`, `style`, or `test`, it's ignored
62+
3. If the message looks like `v1.2.3`, `chore: release 1.2.3`, or similar, it's considered a "release"
63+
64+
See [`getCommitMeaning`](./src/getCommitMeaning.ts) for the exact logic used.
65+
5666
### Node API
5767

5868
Alternately, you can call this import asynchronous `shouldSemanticRelease` function into Node scripts:

src/getCommitMeaning.test.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { getCommitMeaning } from "./getCommitMeaning.js";
4+
5+
describe("getCommitMeaning", () => {
6+
it.each([
7+
["chore", { type: undefined }],
8+
["chore: deps", { type: "chore" }],
9+
["chore(deps): bump", { type: "chore" }],
10+
["docs", { type: undefined }],
11+
["docs: bump", { type: "docs" }],
12+
["docs: message", { type: "docs" }],
13+
["feat", { type: undefined }],
14+
["feat: bump", "meaningful"],
15+
["feat: bump version to 1.2.3", "meaningful"],
16+
["feat: message", "meaningful"],
17+
["fix", { type: undefined }],
18+
["fix: bump", "meaningful"],
19+
["fix: bump version to 1.2.3", "meaningful"],
20+
["fix: message", "meaningful"],
21+
["perf", { type: undefined }],
22+
["perf: bump", "meaningful"],
23+
["perf: bump version to 1.2.3", "meaningful"],
24+
["perf: message", "meaningful"],
25+
["style", { type: undefined }],
26+
["style: bump", { type: "style" }],
27+
["style: bump version to 1.2.3", { type: "style" }],
28+
["style: message", { type: "style" }],
29+
["0.0.0", "release"],
30+
["v0.0.0", "release"],
31+
["1.2.3", "release"],
32+
["v1.2.3", "release"],
33+
["1.23.456", "release"],
34+
["v1.23.456", "release"],
35+
["12.345.6789", "release"],
36+
["v12.345.6789", "release"],
37+
["chore: release", "release"],
38+
["chore: release 1.2.3", "release"],
39+
["chore: release v1.2.3", "release"],
40+
["chore(deps): release", "release"],
41+
["chore(deps): release 1.2.3", "release"],
42+
["chore(deps): release v1.2.3", "release"],
43+
])("returns %j for %s", (input, expected) => {
44+
expect(getCommitMeaning(input)).toEqual(expected);
45+
});
46+
});

src/getCommitMeaning.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import conventionalCommitsParser from "conventional-commits-parser";
2+
3+
const alwaysMeaningfulTypes = new Set(["feat", "fix", "perf"]);
4+
5+
const alwaysIgnoredTypes = new Set(["docs", "refactor", "style", "test"]);
6+
7+
const releaseCommitTester =
8+
/^(?:chore(?:\(.*\))?:?)?\s*release|v?\d+\.\d+\.\d+/;
9+
10+
export function getCommitMeaning(message: string) {
11+
// Some types are always meaningful or ignored, regardless of potentially release-like messages
12+
const { type } = conventionalCommitsParser.sync(message);
13+
if (type) {
14+
if (alwaysMeaningfulTypes.has(type)) {
15+
return "meaningful";
16+
}
17+
if (alwaysIgnoredTypes.has(type)) {
18+
return { type };
19+
}
20+
}
21+
22+
// If we've hit a release commit, we know we don't need to release
23+
if (releaseCommitTester.test(message)) {
24+
return "release";
25+
}
26+
27+
return { type: type ?? undefined };
28+
}

src/isReleaseCommit.test.ts

-32
This file was deleted.

src/isReleaseCommit.ts

-5
This file was deleted.

src/shouldSemanticRelease.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import conventionalCommitsParser from "conventional-commits-parser";
2-
3-
import { isReleaseCommit } from "./isReleaseCommit.js";
1+
import { getCommitMeaning } from "./getCommitMeaning.js";
42
import { ShouldSemanticReleaseOptions } from "./types.js";
53
import { execOrThrow } from "./utils.js";
64

7-
const ignoredTypes = new Set(["chore", "docs"]);
8-
95
export async function shouldSemanticRelease({
106
verbose,
117
}: ShouldSemanticReleaseOptions) {
@@ -21,20 +17,20 @@ export async function shouldSemanticRelease({
2117

2218
for (const message of history) {
2319
log(`Checking commit: ${message}`);
24-
// If we've hit a release commit, we know we don't need to release
25-
if (isReleaseCommit(message)) {
26-
log(`Found a release commit. Returning false.`);
27-
return false;
28-
}
20+
const meaning = getCommitMeaning(message);
2921

30-
// Otherwise, we should release if a non-ignored commit type is found
31-
const { type } = conventionalCommitsParser.sync(message);
32-
if (type && !ignoredTypes.has(type)) {
33-
log(`Found a meaningful commit. Returning true.`);
34-
return true;
35-
}
22+
switch (meaning) {
23+
case "release":
24+
log(`Found a release commit. Returning false.`);
25+
return false;
26+
27+
case "meaningful":
28+
log(`Found a meaningful commit. Returning true.`);
29+
return true;
3630

37-
log(`Found type ${type}. Continuing.`);
31+
default:
32+
log(`Found type ${meaning.type}. Continuing.`);
33+
}
3834
}
3935

4036
// If we've seen every commit in the history and none match, don't release

0 commit comments

Comments
 (0)