diff --git a/package.json b/package.json index 99e5c81..8b3a640 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "build:test:watch": "tsc --watch --project config/test.tsconfig.json", "test:cli": "npm run ci:download-web-uis && vitest run --bail 1 --config config/vitest.config.ts", "commit": "cz", - "prepare": "test -d .git && husky install || true", + "prepare": "test -e .git && husky install || true", "release": "HUSKY=0 release-it --config config/.release-it.json", "postinstall": "node bin/postinstall.js", "generate:readme": "oclif readme && sed -i 's@/src/commands/@/src/cli/commands/@g' README.md && sed -i 's@src/commands@src/cli/commands@g' README.md", diff --git a/test/cli/prepare-husky.test.ts b/test/cli/prepare-husky.test.ts new file mode 100644 index 0000000..2b81c59 --- /dev/null +++ b/test/cli/prepare-husky.test.ts @@ -0,0 +1,54 @@ +import { execFileSync, spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { describe, it, expect } from "vitest"; +import { directory as randomDirectory } from "tempy"; + +// `prepare` runs `husky install`, which writes .husky/_/husky.sh. core.hooksPath lives in the +// shared repo config, so a checkout that skips the install still runs .husky/commit-msg and every +// commit fails sourcing the missing husky.sh. In a linked worktree .git is a file (issue #150). +const prepareScript: string = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8")).scripts.prepare; + +const git = (cwd: string, ...args: string[]) => execFileSync("git", args, { cwd, stdio: "pipe" }); + +const runPrepare = (cwd: string) => { + const env: NodeJS.ProcessEnv = { ...process.env, PATH: `${path.join(process.cwd(), "node_modules", ".bin")}${path.delimiter}${process.env.PATH}` }; + delete env.HUSKY; + delete env.GIT_DIR; + return spawnSync("sh", ["-c", prepareScript], { cwd, env, encoding: "utf-8" }); +}; + +// npm runs scripts through cmd.exe on Windows, where `test` is not a builtin. +describe.skipIf(process.platform === "win32")("npm prepare script", () => { + const setupRepo = () => { + const root = randomDirectory(); + const main = path.join(root, "main"); + const worktree = path.join(root, "worktree"); + fs.mkdirSync(main); + git(main, "init", "-q"); + git(main, "-c", "user.name=test", "-c", "user.email=test@example.com", "commit", "-q", "--allow-empty", "-m", "init"); + git(main, "worktree", "add", "-q", worktree); + return { root, main, worktree }; + }; + + it("installs husky hooks in a normal checkout", () => { + const { root, main } = setupRepo(); + try { + expect(runPrepare(main).status).toBe(0); + expect(fs.existsSync(path.join(main, ".husky", "_", "husky.sh"))).toBe(true); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); + + it("installs husky hooks in a linked worktree, where .git is a file", () => { + const { root, worktree } = setupRepo(); + try { + expect(fs.statSync(path.join(worktree, ".git")).isFile()).toBe(true); + expect(runPrepare(worktree).status).toBe(0); + expect(fs.existsSync(path.join(worktree, ".husky", "_", "husky.sh"))).toBe(true); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); +});