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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pnpm exec ai-commit init
| Husky | Runs **`npx husky@9 init`** at the **git root** if **`husky.sh`** is missing under the resolved hooks directory. |
| Hooks directory | **`core.hooksPath`** relative to the git root when set; otherwise **`<git-root>/.husky`**. Falls back to **`.husky`** at the git root with a warning if the config path is invalid or outside the repo. |
| `package.json` | Adds missing **`commit`**, **`prepare`**, **`husky`** entries when **`package.json`** exists at the package root. |
| Hooks | Writes **`prepare-commit-msg`** and **`commit-msg`** in the hooks directory. If package root ≠ git root, each hook **`cd`s** into the package directory before **`pnpm exec ai-commit`** / **`npx`**. |
| Hooks | Writes **`prepare-commit-msg`** and **`commit-msg`** in the hooks directory. If package root ≠ git root, each hook **`cd`s** into the package directory before **`pnpm exec ai-commit`** / **`npx`**. Removes a **stock** **`.husky/pre-commit`** that is only **`npm`**/**`pnpm`**/**`yarn`** **`test`** (Husky’s **`init`** default) so that hook does not block commits; custom **pre-commit** files are kept. |

If **`package.json`** changed, run **`pnpm install`** (or `npm install`) again.

Expand Down Expand Up @@ -171,6 +171,8 @@ pnpm exec ai-commit lint --edit "$1"

Hooks from **`init`** use **`pnpm exec ai-commit`** when **`pnpm-lock.yaml`** exists in the **package root**; otherwise **`npx --no ai-commit`**. In a monorepo, generated hooks **`cd`** from the git root into that package directory first. Edit the files if you use another runner.

**`pre-commit`:** Husky’s **`init`** often adds **`.husky/pre-commit`** with only **`pnpm test`** (or **`npm test`** / **`yarn test`**). That can block **`git commit`** when tests fail. On each **`ai-commit init`**, **`init`** removes **only** that stock one-liner (or the same command behind a minimal **`husky.sh`** wrapper). If you add other lines (e.g. **lint-staged**), the file is left unchanged. Add your own **pre-commit** or rely on **CI** if you still want tests on every commit.

**Already using Husky?** If **`.husky/_/husky.sh`** exists, **`init`** does not run **`npx husky@9 init`**. **`package.json`** is only amended for missing **`commit`**, **`prepare`**, or **`devDependencies.husky`**. Existing **`.husky/prepare-commit-msg`** and **`.husky/commit-msg`** are not overwritten unless you use **`ai-commit init --force`**.

---
Expand Down
8 changes: 8 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const {
detectPackageExec,
hookScript,
runHuskyInit,
removeHuskyDefaultPreCommitIfPresent,
mergePackageJsonForAiCommit,
warnIfPrepareMissingHusky,
} = require("../lib/init-workspace.js");
Expand Down Expand Up @@ -229,6 +230,13 @@ function cmdInit(argv) {
fs.mkdirSync(huskyDir, { recursive: true });
}

for (const abs of removeHuskyDefaultPreCommitIfPresent(gitRoot, huskyDir)) {
const rel = path.relative(cwd, abs) || path.basename(abs);
process.stdout.write(
`Removed Husky default pre-commit (${rel}); add your own .husky/pre-commit or use CI if you want tests on every commit.\n`,
);
}

const execPrefix = detectPackageExec(packageRoot);
const preparePath = path.join(huskyDir, "prepare-commit-msg");
const commitMsgPath = path.join(huskyDir, "commit-msg");
Expand Down
87 changes: 87 additions & 0 deletions lib/init-workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,92 @@ function runHuskyInit(cwd) {
return { ok: status === 0, status };
}

/**
* Husky `init` writes `.husky/pre-commit` with only `(npm|pnpm|yarn) test` (see husky bin.js).
* That often breaks commits when tests fail or are slow. Match that template and optional
* minimal shebang + husky.sh wrapper so we do not delete custom hooks.
* @param {string} raw
* @returns {boolean}
*/
function isHuskyDefaultPreCommitContent(raw) {
const text = raw.replace(/\r\n/g, "\n").trim();
const nonEmpty = text
.split("\n")
.map((l) => l.trim())
.filter((l) => {
if (l.length === 0) {
return false;
}
if (/^#!\//.test(l)) {
return true;
}
return !/^\s*#/.test(l);
});
if (nonEmpty.length === 0) {
return false;
}
if (nonEmpty.length === 1) {
return /^(npm|pnpm|yarn)\s+test$/i.test(nonEmpty[0]);
}
const last = nonEmpty[nonEmpty.length - 1];
if (!/^(npm|pnpm|yarn)\s+test$/i.test(last)) {
return false;
}
const head = nonEmpty.slice(0, -1);
const shebang = /^#!\/usr\/bin\/env\s+sh$/.test(head[0]);
const huskySource = head.some((l) => {
if (/\bhusky\.sh\b/.test(l)) {
return true;
}
if (l.includes("$(dirname") && (l.includes("_/husky.sh") || l.includes('_/h"'))) {
return true;
}
return false;
});
return shebang && huskySource && head.length <= 3;
}

/**
* Remove Husky’s stock `pre-commit` (e.g. `pnpm test`) from common paths. Custom hooks are kept.
* @param {string} gitRoot
* @param {string} huskyDir Resolved hooks directory (from `core.hooksPath` or `.husky`)
* @returns {string[]} Absolute paths of removed files
*/
function removeHuskyDefaultPreCommitIfPresent(gitRoot, huskyDir) {
const candidates = [
path.join(huskyDir, "pre-commit"),
path.join(gitRoot, ".husky", "pre-commit"),
];
const seen = new Set();
const removed = [];
for (const filePath of candidates) {
const abs = path.resolve(filePath);
if (seen.has(abs)) {
continue;
}
seen.add(abs);
if (!fs.existsSync(abs)) {
continue;
}
let raw;
try {
raw = fs.readFileSync(abs, "utf8");
} catch {
continue;
}
if (!isHuskyDefaultPreCommitContent(raw)) {
continue;
}
try {
fs.unlinkSync(abs);
removed.push(abs);
} catch {
// ignore
}
}
return removed;
}

/**
* Ensure `commit` script, `prepare` for husky, and `devDependencies.husky`. Does not remove existing scripts.
* @param {string} packageJsonPath
Expand Down Expand Up @@ -117,6 +203,7 @@ module.exports = {
detectPackageExec,
hookScript,
runHuskyInit,
removeHuskyDefaultPreCommitIfPresent,
mergePackageJsonForAiCommit,
warnIfPrepareMissingHusky,
};
Loading