From 1c3a8a30a6aeff78fec03c6961c972131359f85d Mon Sep 17 00:00:00 2001 From: yyz945947732 <945947732@qq.com> Date: Mon, 7 Apr 2025 09:46:10 +0800 Subject: [PATCH 1/8] fix: optimize the migration experience from husky. --- README.md | 33 --------------------------------- simple-git-hooks.js | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e300e54..40734d3 100644 --- a/README.md +++ b/README.md @@ -96,11 +96,6 @@ If you need multiple verbose commands per git hook, flexible configuration or au 3. Run the CLI script to update the git hooks with the commands from the config: ```sh - # [Optional] These 2 steps can be skipped for non-husky users - git config core.hooksPath .git/hooks/ - rm -rf .git/hooks - - # Update ./git/hooks npx simple-git-hooks ``` @@ -211,34 +206,6 @@ If you have the option to set arguments or environment variables, you can use th If these options are not available, you may need to resort to using the terminal for skipping hooks. -### When migrating from `husky` git hooks are not running - -**Why is this happening?** - -Husky might change the `core.gitHooks` value to `.husky`, this way, git hooks would search `.husky` directory instead of `.git/hooks/`. - -Read more on git configuration in [Git book](https://git-scm.com/docs/githooks) - -You can check it by running this command inside of your repo: - -`git config core.hooksPath` - -If it outputs `.husky` then this is your case - -**How to fix?** - -you need to point `core.gitHooks` value to `your-awesome-project/.git/hooks`. You can use this command: - -`git config core.hooksPath .git/hooks/` - -validate the value is set: - -`git config core.hooksPath` - -should output: `.git/hooks/` - -Then remove the `.husky` folder that are generated previously by `husky`. - ### I am getting "npx: command not found" error in a GUI git client This happens when using a node version manager such as `nodenv`, `nvm`, `mise` which require diff --git a/simple-git-hooks.js b/simple-git-hooks.js index bf81507..67359e9 100644 --- a/simple-git-hooks.js +++ b/simple-git-hooks.js @@ -1,6 +1,7 @@ const fs = require('fs') const path = require('path') const url = require('url') +const { execSync } = require('child_process'); const VALID_GIT_HOOKS = [ 'applypatch-msg', @@ -183,7 +184,36 @@ async function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.ar } /** - * Creates or replaces an existing executable script in .git/hooks/ with provided command + * Returns the absolute path to the Git hooks directory. + * Respects user-defined core.hooksPath from Git config if present; + * otherwise defaults to /.git/hooks. + * + * @param {string} gitRoot - The absolute path to the Git project root + * @returns {string} - The resolved absolute path to the hooks directory + * @private + */ +function _getHooksPath(gitRoot) { + const defaultHooksPath = path.join(gitRoot, '.git', 'hooks') + try { + const customHooksPath = execSync('git config core.hooksPath', { + cwd: gitRoot, + encoding: 'utf8' + }).trim() + + if (!customHooksPath) { + return defaultHooksPath + } + + return path.isAbsolute(customHooksPath) + ? customHooksPath + : path.resolve(gitRoot, customHooksPath) + } catch { + return defaultHooksPath + } +} + +/** + * Creates or replaces an existing executable script in the git hooks directory with provided command * @param {string} hook * @param {string} command * @param {string} projectRoot @@ -198,12 +228,12 @@ function _setHook(hook, command, projectRoot=process.cwd()) { } const hookCommand = PREPEND_SCRIPT + command - const hookDirectory = gitRoot + '/hooks/' - const hookPath = path.normalize(hookDirectory + hook) + const hookDirectory = _getHooksPath(gitRoot) + const hookPath = path.join(hookDirectory, hook) const normalizedHookDirectory = path.normalize(hookDirectory) if (!fs.existsSync(normalizedHookDirectory)) { - fs.mkdirSync(normalizedHookDirectory) + fs.mkdirSync(normalizedHookDirectory, { recursive: true }) } fs.writeFileSync(hookPath, hookCommand) From 8e5d2631321f1274561cefecbf27209e86241b8b Mon Sep 17 00:00:00 2001 From: yyz945947732 <945947732@qq.com> Date: Mon, 7 Apr 2025 14:33:47 +0800 Subject: [PATCH 2/8] fix: code fix for _getHooksDirPath. --- simple-git-hooks.js | 30 ++++++++++++++---------------- uninstall.js | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/simple-git-hooks.js b/simple-git-hooks.js index 67359e9..72023da 100644 --- a/simple-git-hooks.js +++ b/simple-git-hooks.js @@ -192,23 +192,23 @@ async function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.ar * @returns {string} - The resolved absolute path to the hooks directory * @private */ -function _getHooksPath(gitRoot) { - const defaultHooksPath = path.join(gitRoot, '.git', 'hooks') +function _getHooksDirPath(projectRoot) { + const defaultHooksDirPath = path.join(projectRoot, '.git', 'hooks') try { - const customHooksPath = execSync('git config core.hooksPath', { - cwd: gitRoot, + const customHooksDirPath = execSync('git config core.hooksPath', { + cwd: projectRoot, encoding: 'utf8' }).trim() - if (!customHooksPath) { - return defaultHooksPath + if (!customHooksDirPath) { + return defaultHooksDirPath } - return path.isAbsolute(customHooksPath) - ? customHooksPath - : path.resolve(gitRoot, customHooksPath) + return path.isAbsolute(customHooksDirPath) + ? customHooksDirPath + : path.resolve(projectRoot, customHooksDirPath) } catch { - return defaultHooksPath + return defaultHooksDirPath } } @@ -228,14 +228,13 @@ function _setHook(hook, command, projectRoot=process.cwd()) { } const hookCommand = PREPEND_SCRIPT + command - const hookDirectory = _getHooksPath(gitRoot) + const hookDirectory = _getHooksDirPath(projectRoot) const hookPath = path.join(hookDirectory, hook) const normalizedHookDirectory = path.normalize(hookDirectory) if (!fs.existsSync(normalizedHookDirectory)) { fs.mkdirSync(normalizedHookDirectory, { recursive: true }) } - fs.writeFileSync(hookPath, hookCommand) fs.chmodSync(hookPath, 0o0755) @@ -253,15 +252,14 @@ function removeHooks(projectRoot=process.cwd()) { } /** - * Removes the pre-commit hook from .git/hooks + * Removes the pre-commit hook * @param {string} hook * @param {string} projectRoot * @private */ function _removeHook(hook, projectRoot=process.cwd()) { - const gitRoot = getGitProjectRoot(projectRoot) - const hookPath = path.normalize(gitRoot + '/hooks/' + hook) - + const hookDirectory = _getHooksDirPath(projectRoot) + const hookPath = path.join(hookDirectory, hook) if (fs.existsSync(hookPath)) { fs.unlinkSync(hookPath) } diff --git a/uninstall.js b/uninstall.js index 43a1916..714d733 100644 --- a/uninstall.js +++ b/uninstall.js @@ -7,7 +7,7 @@ const {removeHooks} = require("./simple-git-hooks"); * Removes the pre-commit from command in config by default */ function uninstall() { - console.log("[INFO] Removing git hooks from .git/hooks") + console.log("[INFO] Removing git hooks") try { removeHooks() From e6cb2a6639c88848064521a7b5721dbec9cf6ee3 Mon Sep 17 00:00:00 2001 From: yyz945947732 <945947732@qq.com> Date: Mon, 7 Apr 2025 16:57:03 +0800 Subject: [PATCH 3/8] test: add test case for .husky problem. --- simple-git-hooks.test.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index 36d32b4..b33cc84 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -468,6 +468,33 @@ describe("Simple Git Hooks tests", () => { }); }); + describe("Custom core.hooksPath", () => { + beforeAll(() => { + execSync('git config core.hooksPath .husky'); + }); + afterAll(() => { + execSync('git config --unset core.hooksPath'); + }); + const TEST_HUSKY_PROJECT = PROJECT_WITH_CONF_IN_SEPARATE_JS_ALT; + it("creates git hooks in .husky if core.hooksPath is set to .husky", async () => { + const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); + + if (!fs.existsSync(huskyDir)) { + fs.mkdirSync(huskyDir); + } + await simpleGitHooks.setHooksFromConfig(TEST_HUSKY_PROJECT); + + const installedHooks = getInstalledGitHooks(huskyDir); + expect(isEqual(installedHooks, COMMON_GIT_HOOKS)).toBe(true); + }) + it("remove git hooks in .husky if core.hooksPath is set to .husky", async () => { + simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); + const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); + const installedHooks = getInstalledGitHooks(huskyDir); + expect(isEqual(installedHooks, {})).toBe(true); + }) + }) + describe("CLI tests", () => { const testCases = [ ["npx", "simple-git-hooks", "./git-hooks.js"], From 6cb4751dc6b562f2896d8a017486c6392307e289 Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 7 Apr 2025 17:09:52 +0800 Subject: [PATCH 4/8] Update simple-git-hooks.test.js --- simple-git-hooks.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index 0723311..4de775c 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -519,7 +519,7 @@ describe("Simple Git Hooks tests", () => { expect(isEqual(installedHooks, COMMON_GIT_HOOKS)).toBe(true); }) it("remove git hooks in .husky if core.hooksPath is set to .husky", async () => { - simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); + async simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); const installedHooks = getInstalledGitHooks(huskyDir); expect(isEqual(installedHooks, {})).toBe(true); From bb77de90fd9651bf90ef85a73db731db661f0abb Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 7 Apr 2025 17:11:29 +0800 Subject: [PATCH 5/8] Update simple-git-hooks.test.js --- simple-git-hooks.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index 4de775c..19bf8ac 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -519,7 +519,7 @@ describe("Simple Git Hooks tests", () => { expect(isEqual(installedHooks, COMMON_GIT_HOOKS)).toBe(true); }) it("remove git hooks in .husky if core.hooksPath is set to .husky", async () => { - async simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); + await simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); const installedHooks = getInstalledGitHooks(huskyDir); expect(isEqual(installedHooks, {})).toBe(true); From 7123adaac575c65e2612c4807468327b605f8f16 Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 7 Apr 2025 17:29:23 +0800 Subject: [PATCH 6/8] Apply suggestions from code review --- simple-git-hooks.js | 2 ++ simple-git-hooks.test.js | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/simple-git-hooks.js b/simple-git-hooks.js index da3b695..75cc73b 100644 --- a/simple-git-hooks.js +++ b/simple-git-hooks.js @@ -237,6 +237,7 @@ function _setHook(hook, command, projectRoot=process.cwd()) { if (!fs.existsSync(normalizedHookDirectory)) { fs.mkdirSync(normalizedHookDirectory, { recursive: true }) } + fs.writeFileSync(hookPath, hookCommand) fs.chmodSync(hookPath, 0o0755) @@ -272,6 +273,7 @@ async function removeHooks(projectRoot = process.cwd()) { function _removeHook(hook, projectRoot=process.cwd()) { const hookDirectory = _getHooksDirPath(projectRoot) const hookPath = path.join(hookDirectory, hook) + if (fs.existsSync(hookPath)) { fs.unlinkSync(hookPath) } diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index 19bf8ac..a1a4e54 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -503,10 +503,13 @@ describe("Simple Git Hooks tests", () => { beforeAll(() => { execSync('git config core.hooksPath .husky'); }); + afterAll(() => { execSync('git config --unset core.hooksPath'); }); + const TEST_HUSKY_PROJECT = PROJECT_WITH_CONF_IN_SEPARATE_JS_ALT; + it("creates git hooks in .husky if core.hooksPath is set to .husky", async () => { const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); @@ -518,6 +521,7 @@ describe("Simple Git Hooks tests", () => { const installedHooks = getInstalledGitHooks(huskyDir); expect(isEqual(installedHooks, COMMON_GIT_HOOKS)).toBe(true); }) + it("remove git hooks in .husky if core.hooksPath is set to .husky", async () => { await simpleGitHooks.removeHooks(TEST_HUSKY_PROJECT); const huskyDir = path.join(TEST_HUSKY_PROJECT, ".husky"); From 49df9bac536ea4a1c2580839f611d1e6ed86512d Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 7 Apr 2025 17:30:50 +0800 Subject: [PATCH 7/8] Update simple-git-hooks.test.js --- simple-git-hooks.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/simple-git-hooks.test.js b/simple-git-hooks.test.js index a1a4e54..04d864f 100644 --- a/simple-git-hooks.test.js +++ b/simple-git-hooks.test.js @@ -516,6 +516,7 @@ describe("Simple Git Hooks tests", () => { if (!fs.existsSync(huskyDir)) { fs.mkdirSync(huskyDir); } + await simpleGitHooks.setHooksFromConfig(TEST_HUSKY_PROJECT); const installedHooks = getInstalledGitHooks(huskyDir); From 766277c98e36e0c33b19e9641f82e5255fa9edb0 Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 7 Apr 2025 17:31:59 +0800 Subject: [PATCH 8/8] Create smart-queens-ring.md --- .changeset/smart-queens-ring.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/smart-queens-ring.md diff --git a/.changeset/smart-queens-ring.md b/.changeset/smart-queens-ring.md new file mode 100644 index 0000000..8506cd7 --- /dev/null +++ b/.changeset/smart-queens-ring.md @@ -0,0 +1,5 @@ +--- +"simple-git-hooks": minor +--- + +feat: optimize the migration experience from `husky`