Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ yarn build # Compile TypeScript → dist/ via esbuild (bundles + mini
yarn test # Run unit + integration + e2e tests (requires Docker)
yarn test:coverage # Run all tests with coverage report
yarn test:unit # Unit tests only (no Docker required)
yarn test:integration # Integration tests in Docker containers (Node 14, 16, 18, 20)
yarn test:integration # Integration tests in Docker containers (Node 20, 22, 24)
yarn test:e2e # E2E tests in Docker containers (runs yarn build first)
yarn test:smoke # Smoke tests against the published npm package
yarn check:lint # ESLint
Expand All @@ -28,7 +28,7 @@ yarn test:unit --testPathPattern=git-interop

`branch-commit-msg` is a git `commit-msg` hook tool with two entry points that get bundled separately:

- **`src/index.ts`** → `dist/index.js` (the `branch-commit-msg` CLI): uses yargs to expose the `install` command, which copies the compiled hook binary into `.git/hooks/commit-msg`.
- **`src/index.ts`** → `dist/index.mjs` (the `branch-commit-msg` CLI): uses yargs to expose the `install` command, which copies the compiled hook binary into `.git/hooks/commit-msg`.
- **`src/commit-msg-hook.ts`** → `dist/commit-msg` (the hook itself): runs at commit time, reads `.commitmsgrc.json`, extracts the regex match from the active branch name, and rewrites the commit message file in-place.

Supporting modules:
Expand Down Expand Up @@ -59,12 +59,12 @@ If the file is missing or invalid, the hook exits 0 (no-op).

Tests are split across four suites with distinct purposes:

| Suite | Location | Docker | Notes |
| ----------- | ------------------- | ------ | ---------------------------------------------------------------- |
| Unit | `test/unit/` | No | Mocks git via dependency injection |
| Integration | `test/integration/` | Yes | Runs against real filesystem/git; tested across Node 14/16/18/20 |
| E2E | `test/e2e/` | Yes | Requires built `dist/`; tests the full hook flow |
| Smoke | `test/smoke/` | No | Tests the published npm package post-release |
| Suite | Location | Docker | Notes |
| ----------- | ------------------- | ------ | ------------------------------------------------------------- |
| Unit | `test/unit/` | No | Mocks git via dependency injection |
| Integration | `test/integration/` | Yes | Runs against real filesystem/git; tested across Node 20/22/24 |
| E2E | `test/e2e/` | Yes | Requires built `dist/`; tests the full hook flow |
| Smoke | `test/smoke/` | No | Tests the published npm package post-release |

`test/containerized-test.ts` is a utility that spins up Docker containers for integration/e2e suites. Each suite runs sequentially across all supported Node versions.

Expand All @@ -88,7 +88,7 @@ PR titles must follow [Conventional Commits](https://www.conventionalcommits.org

The build produces two self-contained bundled binaries in `dist/` (no `node_modules` needed at runtime):

- `dist/index.js` — the CLI
- `dist/index.mjs` — the CLI
- `dist/commit-msg` — the hook script

Only the `dist/` directory is published to npm (see `"files"` in `package.json`).
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@
With no dependency:

```sh
$ npx branch-commit-msg install
npx branch-commit-msg install
```

With [husky](https://github.com/typicode/husky):

```sh
$ npm install -D branch-commit-msg
$ npx husky add .husky/commit-msg 'npx branch-commit-msg-hook "$1"'
npm install -D branch-commit-msg
echo 'npx branch-commit-msg-hook "$1"' > .husky/commit-msg
```

## Uninstall

With no dependency:

```sh
$ rm .git/hooks/commit-msg
rm .git/hooks/commit-msg
```

With [husky](https://github.com/typicode/husky):

Remove `npx branch-commit-msg-hook "$1"` from `.husky/commit-msg` and run:

```sh
$ npm uninstall branch-commit-msg
npm uninstall branch-commit-msg
```

## Usage
Expand Down Expand Up @@ -97,8 +97,8 @@ After your `.commitmsgrc.json` is configured, start making commits!

```sh
# Current branch: SC-123456/my-new-feature
$ git commit -m "added a thing"
$ git log -1 --pretty=%B
git commit -m "added a thing"
git log -1 --pretty=%B
# Output: SC-123456 - added a thing
```

Expand All @@ -114,8 +114,8 @@ $ git log -1 --pretty=%B

```sh
# Current branch: feature/someprj-123456
$ git commit -m "added a thing"
$ git log -1 --pretty=%B
git commit -m "added a thing"
git log -1 --pretty=%B
# Output: added a thing (SOMEPRJ-123456)
```

Expand All @@ -131,8 +131,8 @@ $ git log -1 --pretty=%B

```sh
# Current branch: some/CoMpLEX-123-5/BRANCH
$ git commit -m "added a thing"
$ git log -1 --pretty=%B
git commit -m "added a thing"
git log -1 --pretty=%B
# Output: ADDED A THING to SOME CoMpLEX-123-5 branch
```

Expand Down
14 changes: 14 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { build } from "esbuild";

await build({
entryPoints: ["src/index.ts"],
bundle: true,
minify: true,
platform: "node",
format: "esm",
packages: "bundle",
banner: {
js: `import{fileURLToPath as __fileURLToPath}from"url";import{dirname as __dirname_fn}from"path";const __dirname=__dirname_fn(__fileURLToPath(import.meta.url));`,
},
outfile: "dist/index.mjs",
});
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"description": "A git commit-msg hook that automatically inserts a matched pattern from the active branch name to the commit message.",
"license": "MIT",
"author": "Brandon Rajkowski <github@brajkowski.bulc.club>",
"main": "dist/index.js",
"main": "dist/index.mjs",
"engines": {
"node": ">=14.0.0"
"node": ">=20.0.0"
},
"packageManager": "yarn@3.3.0",
"bin": {
"branch-commit-msg": "dist/index.js",
"branch-commit-msg": "dist/index.mjs",
"branch-commit-msg-hook": "dist/commit-msg"
},
"files": [
Expand All @@ -31,13 +31,13 @@
"scripts": {
"build": "rimraf dist && yarn bundle:hook && yarn bundle:index",
"bundle:hook": "esbuild src/commit-msg-hook.ts --bundle --minify --platform=node --packages=bundle --outfile=dist/commit-msg",
"bundle:index": "esbuild src/index.ts --bundle --minify --platform=node --packages=bundle --outfile=dist/index.js",
"bundle:index": "node build.mjs",
"check:format": "prettier --check .",
"check:lint": "eslint .",
"check:types": "tsc --noEmit",
"test": "yarn test:unit && yarn test:integration && yarn test:e2e",
"test:coverage": "yarn test:unit --coverage && yarn test:integration --coverage && yarn test:e2e --coverage",
"test:unit": "jest --testPathPattern=unit --config test/config/jest.config.unit.ts",
"test:unit": "jest --testPathPatterns=unit --config test/config/jest.config.unit.ts",
"test:integration": "ts-node test/integration/index.ts --config test/config/jest.config.integration.ts",
"test:e2e": "yarn build && ts-node test/e2e/index.ts --config test/config/jest.config.e2e.ts",
"test:smoke": "ts-node test/smoke/index.ts --config test/config/jest.config.smoke.ts",
Expand All @@ -47,7 +47,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^10.0.1",
"@types/jest": "^29.5.14",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/yargs": "^17.0.33",
"@typescript-eslint/eslint-plugin": "^8.56.1",
Expand All @@ -57,14 +57,14 @@
"eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest": "^30.2.0",
"lint-staged": "^16.3.2",
"prettier": "3.8.1",
"rimraf": "^6.1.3",
"semantic-release": "^25.0.3",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"yargs": "^17.7.2"
"yargs": "^18.0.0"
}
}
2 changes: 1 addition & 1 deletion test/config/jest.config.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Config } from "@jest/types";
import sharedConfig from "./jest.config.shared";
import sharedConfig from "./jest.config.shared.ts";

const config: Config.InitialOptions = {
...sharedConfig,
Expand Down
2 changes: 1 addition & 1 deletion test/config/jest.config.integration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Config } from "@jest/types";
import sharedConfig from "./jest.config.shared";
import sharedConfig from "./jest.config.shared.ts";

const config: Config.InitialOptions = {
...sharedConfig,
Expand Down
2 changes: 1 addition & 1 deletion test/config/jest.config.smoke.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import sharedConfig from "./jest.config.shared";
import sharedConfig from "./jest.config.shared.ts";
export default sharedConfig;
2 changes: 1 addition & 1 deletion test/config/jest.config.unit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Config } from "@jest/types";
import sharedConfig from "./jest.config.shared";
import sharedConfig from "./jest.config.shared.ts";

const config: Config.InitialOptions = {
...sharedConfig,
Expand Down
5 changes: 1 addition & 4 deletions test/containerized-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ function removeContainer(containerName: string): void {
}

export enum SupportedNodeDockerImage {
v14 = "node:14",
v16 = "node:16",
v18 = "node:18",
v20 = "node:20",
v22 = "node:22",
vCurrent = "node:24",
Expand All @@ -31,7 +28,7 @@ export type ContainerizedTestOptions = {
const defaultTestOptions: Required<ContainerizedTestOptions> = {
containerName: "branch-commit-msg-test-env",
containerWorkingDirectory: "/app",
dockerImage: SupportedNodeDockerImage.v16,
dockerImage: SupportedNodeDockerImage.v20,
sharedHostFiles: [],
};

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Object.values(SupportedNodeDockerImage).forEach((dockerImage) => {
console.log(`Running tests for ${dockerImage}`);
containerizedTest(
"npx",
["jest", "--testPathPattern=e2e", "-i", ...yarnScriptArgs],
["jest", "--testPathPatterns=e2e", "-i", ...yarnScriptArgs],
{
dockerImage,
sharedHostFiles: [
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/install.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawnSync } from "child_process";

export function installHook(): void {
spawnSync("node", ["dist/index.js", "install"]);
spawnSync("node", ["dist/index.mjs", "install"]);
}
2 changes: 1 addition & 1 deletion test/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Object.values(SupportedNodeDockerImage).forEach((dockerImage) => {
console.log(`Running tests for ${dockerImage}`);
containerizedTest(
"npx",
["jest", "--testPathPattern=integration", "-i", ...yarnScriptArgs],
["jest", "--testPathPatterns=integration", "-i", ...yarnScriptArgs],
{
dockerImage,
sharedHostFiles: [
Expand Down
2 changes: 1 addition & 1 deletion test/integration/install.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { installAsGitHook } from "../../src/install";
describe("install", () => {
describe("installAsGitHook()", () => {
it("should throw a helpful error if the install occurs outside of a git repo", () => {
expect(() => installAsGitHook()).toThrowError(
expect(() => installAsGitHook()).toThrow(
"Could not find the root of the repository -- check that git is installed and that this install is running inside of a git repository.",
);
});
Expand Down
2 changes: 1 addition & 1 deletion test/smoke/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Object.values(SupportedNodeDockerImage).forEach((dockerImage) => {
console.log(`Running tests for ${dockerImage}`);
containerizedTest(
"npx",
["jest", "--testPathPattern=smoke", ...yarnScriptArgs],
["jest", "--testPathPatterns=smoke", ...yarnScriptArgs],
{
dockerImage,
sharedHostFiles: ["node_modules", "tsconfig.json", "src", "test"],
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"newLine": "lf"
"newLine": "lf",
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": ["./src/**/*", "./test/**/*"]
}
Loading
Loading