| import fs from "node:fs"; |
| import path from "node:path"; |
| import { spawnSync } from "node:child_process"; |
| import { fileURLToPath } from "node:url"; |
|
|
| const DEFAULT_HOOKS_PATH = "git-hooks"; |
| const PRE_COMMIT_HOOK = "pre-commit"; |
|
|
| function getRepoRoot() { |
| const here = path.dirname(fileURLToPath(import.meta.url)); |
| return path.resolve(here, ".."); |
| } |
|
|
| function runGitCommand(args, options = {}) { |
| return spawnSync("git", args, { |
| cwd: options.cwd, |
| encoding: "utf-8", |
| stdio: options.stdio ?? "pipe", |
| }); |
| } |
|
|
| function ensureExecutable(targetPath) { |
| if (process.platform === "win32") return; |
| if (!fs.existsSync(targetPath)) return; |
| try { |
| const mode = fs.statSync(targetPath).mode & 0o777; |
| if (mode & 0o100) return; |
| fs.chmodSync(targetPath, 0o755); |
| } catch (err) { |
| console.warn(`[setup-git-hooks] chmod failed: ${err}`); |
| } |
| } |
|
|
| function isGitAvailable({ repoRoot = getRepoRoot(), runGit = runGitCommand } = {}) { |
| const result = runGit(["--version"], { cwd: repoRoot, stdio: "ignore" }); |
| return result.status === 0; |
| } |
|
|
| function isGitRepo({ repoRoot = getRepoRoot(), runGit = runGitCommand } = {}) { |
| const result = runGit(["rev-parse", "--is-inside-work-tree"], { |
| cwd: repoRoot, |
| stdio: "pipe", |
| }); |
| if (result.status !== 0) return false; |
| return String(result.stdout ?? "").trim() === "true"; |
| } |
|
|
| function setHooksPath({ |
| repoRoot = getRepoRoot(), |
| hooksPath = DEFAULT_HOOKS_PATH, |
| runGit = runGitCommand, |
| } = {}) { |
| const result = runGit(["config", "core.hooksPath", hooksPath], { |
| cwd: repoRoot, |
| stdio: "ignore", |
| }); |
| return result.status === 0; |
| } |
|
|
| function setupGitHooks({ |
| repoRoot = getRepoRoot(), |
| hooksPath = DEFAULT_HOOKS_PATH, |
| runGit = runGitCommand, |
| } = {}) { |
| if (!isGitAvailable({ repoRoot, runGit })) { |
| return { ok: false, reason: "git-missing" }; |
| } |
|
|
| if (!isGitRepo({ repoRoot, runGit })) { |
| return { ok: false, reason: "not-repo" }; |
| } |
|
|
| if (!setHooksPath({ repoRoot, hooksPath, runGit })) { |
| return { ok: false, reason: "config-failed" }; |
| } |
|
|
| ensureExecutable(path.join(repoRoot, hooksPath, PRE_COMMIT_HOOK)); |
|
|
| return { ok: true }; |
| } |
|
|
| export { |
| DEFAULT_HOOKS_PATH, |
| PRE_COMMIT_HOOK, |
| ensureExecutable, |
| getRepoRoot, |
| isGitAvailable, |
| isGitRepo, |
| runGitCommand, |
| setHooksPath, |
| setupGitHooks, |
| }; |
|
|
| if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { |
| setupGitHooks(); |
| } |
|
|