Skip to content

Commit 7a3b378

Browse files
authored
fix(hooks): robust pnpm + commit msg resolution (#1261)
* fix(hooks): resolve pnpm via mise * fix(hooks): resolve commit msg path
1 parent ef2d62c commit 7a3b378

File tree

3 files changed

+122
-4
lines changed

3 files changed

+122
-4
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@
183183
"zod-to-json-schema": "^3.25.0"
184184
},
185185
"simple-git-hooks": {
186-
"pre-commit": "pnpm lint-staged && pnpm typecheck",
187-
"commit-msg": "node scripts/verify-commit.js"
186+
"pre-commit": "node scripts/pre-commit.mjs",
187+
"commit-msg": "node scripts/verify-commit.js \"$1\""
188188
},
189189
"lint-staged": {
190190
"*.js": [

scripts/pre-commit.mjs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// @ts-check
2+
import { spawnSync } from 'node:child_process'
3+
4+
const tryCommand = (cmd, args) => {
5+
const result = spawnSync(cmd, args, { stdio: 'ignore' })
6+
if (result.error) {
7+
return false
8+
}
9+
return result.status === 0
10+
}
11+
12+
const resolvePnpm = () => {
13+
if (tryCommand('pnpm', ['--version'])) {
14+
return { cmd: 'pnpm', argsPrefix: [], source: 'PATH' }
15+
}
16+
if (
17+
tryCommand('mise', ['--version']) &&
18+
tryCommand('mise', ['exec', '--', 'pnpm', '--version'])
19+
) {
20+
return { cmd: 'mise', argsPrefix: ['exec', '--', 'pnpm'], source: 'mise' }
21+
}
22+
return null
23+
}
24+
25+
const runner = resolvePnpm()
26+
if (!runner) {
27+
console.error(
28+
[
29+
'ERROR: pnpm not found.',
30+
'Expected to find pnpm on PATH or via "mise exec -- pnpm".',
31+
'Fix one of:',
32+
'- Ensure pnpm is on PATH for git hooks',
33+
'- Or install/configure mise and run "mise install"',
34+
].join('\n'),
35+
)
36+
process.exit(1)
37+
}
38+
39+
const runPnpm = (args) => {
40+
const result = spawnSync(runner.cmd, [...runner.argsPrefix, ...args], {
41+
stdio: 'inherit',
42+
})
43+
if (result.error) {
44+
console.error(`ERROR: failed to run pnpm via ${runner.source}`)
45+
process.exit(1)
46+
}
47+
if (typeof result.status === 'number' && result.status !== 0) {
48+
process.exit(result.status)
49+
}
50+
if (result.signal) {
51+
process.exit(1)
52+
}
53+
}
54+
55+
const args = process.argv.slice(2)
56+
if (args.length > 0) {
57+
runPnpm(args)
58+
} else {
59+
runPnpm(['lint-staged'])
60+
runPnpm(['typecheck'])
61+
}

scripts/verify-commit.js

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,66 @@
11
// @ts-check
22
import pico from 'picocolors'
3-
import { readFileSync } from 'node:fs'
3+
import { existsSync, readFileSync, statSync } from 'node:fs'
44
import path from 'node:path'
55

6-
const msgPath = path.resolve('.git/COMMIT_EDITMSG')
6+
const findGitPath = (startDir) => {
7+
let current = startDir
8+
while (true) {
9+
const candidate = path.join(current, '.git')
10+
if (existsSync(candidate)) {
11+
return candidate
12+
}
13+
const parent = path.dirname(current)
14+
if (parent === current) {
15+
return null
16+
}
17+
current = parent
18+
}
19+
}
20+
21+
const resolveGitDir = (gitPath) => {
22+
try {
23+
const stat = statSync(gitPath)
24+
if (stat.isDirectory()) {
25+
return gitPath
26+
}
27+
const content = readFileSync(gitPath, 'utf-8').trim()
28+
if (content.startsWith('gitdir:')) {
29+
const gitDir = content.replace('gitdir:', '').trim()
30+
return path.resolve(path.dirname(gitPath), gitDir)
31+
}
32+
} catch {
33+
// ignore and fall through
34+
}
35+
return null
36+
}
37+
38+
const resolveMsgPath = () => {
39+
const argPath = process.argv[2]
40+
if (argPath && existsSync(argPath)) {
41+
return argPath
42+
}
43+
44+
const envGitDir = process.env.GIT_DIR
45+
if (envGitDir && existsSync(envGitDir)) {
46+
const resolved = resolveGitDir(envGitDir)
47+
if (resolved) {
48+
return path.resolve(resolved, 'COMMIT_EDITMSG')
49+
}
50+
}
51+
52+
const gitPath = findGitPath(process.cwd())
53+
if (gitPath) {
54+
const resolved = resolveGitDir(gitPath)
55+
if (resolved) {
56+
return path.resolve(resolved, 'COMMIT_EDITMSG')
57+
}
58+
}
59+
60+
return path.resolve('.git/COMMIT_EDITMSG')
61+
}
62+
63+
const msgPath = resolveMsgPath()
764
const msg = readFileSync(msgPath, 'utf-8').trim()
865

966
const commitRE =

0 commit comments

Comments
 (0)