Skip to content

Commit 43cb59e

Browse files
fix(cli): add managed bin to hook path (#1647)
When committing via VS Code on MacOS I hit the error ```bash ./node_modules/.bin/vp: line 20: exec: node: not found VITE+ - pre-commit script failed (code 127) VITE+ - command not found in PATH=./node_modules/.bin:/Library/Developer/CommandLineTools/usr/libexec/git-core:/usr/bin:/bin:/usr/sbin:/sbin ``` But node was installed and `vp env doctor` was happy. The issues seemed that the managed Node is not available in the Git hook. I learned that Git hooks often run with a much smaller, non-interactive `PATH`, possibly not including the shims. In my case, path was limited to what is shared in the error above. That included the local `vp` bin but not `node`, leading to the error. The PR fixes that by making generated hooks add Vite+’s managed bin directory before running the user hook. Then `vp staged` can start even when Git launches the hook with a restricted PATH.
1 parent 2732f23 commit 43cb59e

4 files changed

Lines changed: 122 additions & 4 deletions

File tree

packages/cli/snap-tests-global/migration-add-git-hooks/snap.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,20 @@ i="${XDG_CONFIG_HOME:-$HOME/.config}/vite-plus/hooks-init.sh"
6060
{ [ "${HUSKY-}" = "0" ] || [ "${VITE_GIT_HOOKS-}" = "0" ]; } && exit 0
6161

6262
d="$(dirname "$(dirname "$(dirname "$0")")")"
63+
__vp_shell=/bin/sh
64+
[ -x "$__vp_shell" ] || __vp_shell=$(command -v sh)
65+
66+
if [ -n "${VP_HOME-}" ]; then
67+
__vp_bin="$VP_HOME/bin"
68+
elif [ -n "${HOME-}" ]; then
69+
__vp_bin="$HOME/.vite-plus/bin"
70+
else
71+
__vp_bin=""
72+
fi
73+
[ -n "$__vp_bin" ] && [ -d "$__vp_bin" ] && export PATH="$PATH:$__vp_bin"
74+
6375
export PATH="$d/node_modules/.bin:$PATH"
64-
sh -e "$s" "$@"
76+
"$__vp_shell" -e "$s" "$@"
6577
c=$?
6678

6779
[ $c != 0 ] && echo "VITE+ - $n script failed (code $c)"

packages/cli/snap-tests-global/migration-composed-husky-custom-dir/snap.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,20 @@ i="${XDG_CONFIG_HOME:-$HOME/.config}/vite-plus/hooks-init.sh"
5252
{ [ "${HUSKY-}" = "0" ] || [ "${VITE_GIT_HOOKS-}" = "0" ]; } && exit 0
5353

5454
d="$(dirname "$(dirname "$(dirname "$(dirname "$0")")")")"
55+
__vp_shell=/bin/sh
56+
[ -x "$__vp_shell" ] || __vp_shell=$(command -v sh)
57+
58+
if [ -n "${VP_HOME-}" ]; then
59+
__vp_bin="$VP_HOME/bin"
60+
elif [ -n "${HOME-}" ]; then
61+
__vp_bin="$HOME/.vite-plus/bin"
62+
else
63+
__vp_bin=""
64+
fi
65+
[ -n "$__vp_bin" ] && [ -d "$__vp_bin" ] && export PATH="$PATH:$__vp_bin"
66+
5567
export PATH="$d/node_modules/.bin:$PATH"
56-
sh -e "$s" "$@"
68+
"$__vp_shell" -e "$s" "$@"
5769
c=$?
5870

5971
[ $c != 0 ] && echo "VITE+ - $n script failed (code $c)"

packages/cli/src/config/__tests__/hooks.spec.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { execSync } from 'node:child_process';
2-
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
2+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
33
import { tmpdir } from 'node:os';
44
import { join } from 'node:path';
55

@@ -73,4 +73,86 @@ describe('hookScript', () => {
7373
expect(countDirnameCalls(withDot)).toBe(countDirnameCalls(withoutDot));
7474
expect(countDirnameCalls(withDot)).toBe(3);
7575
});
76+
77+
it.skipIf(process.platform === 'win32')(
78+
'should add Vite+ managed bin to PATH as a fallback before running user hook',
79+
() => {
80+
const tmp = mkdtempSync(join(tmpdir(), 'hooks-path-test-'));
81+
try {
82+
const hooksDir = join(tmp, '.vite-hooks');
83+
const internalHooksDir = join(hooksDir, '_');
84+
const nodeModulesBin = join(tmp, 'node_modules', '.bin');
85+
const vpHomeBin = join(tmp, 'vp-home', 'bin');
86+
const systemBin = join(tmp, 'system-bin');
87+
88+
mkdirSync(internalHooksDir, { recursive: true });
89+
mkdirSync(nodeModulesBin, { recursive: true });
90+
mkdirSync(vpHomeBin, { recursive: true });
91+
mkdirSync(systemBin, { recursive: true });
92+
93+
writeFileSync(join(internalHooksDir, 'h'), hookScript('.vite-hooks'), { mode: 0o755 });
94+
writeFileSync(
95+
join(internalHooksDir, 'pre-commit'),
96+
'#!/usr/bin/env sh\n. "$(dirname "$0")/h"',
97+
{ mode: 0o755 },
98+
);
99+
writeFileSync(join(hooksDir, 'pre-commit'), 'vp staged\n');
100+
101+
writeFileSync(
102+
join(nodeModulesBin, 'vp'),
103+
'#!/bin/sh\nbasedir=$(dirname "$0")\nexec node "$basedir/../vite-plus/bin/vp" "$@"\n',
104+
{ mode: 0o755 },
105+
);
106+
writeFileSync(
107+
join(vpHomeBin, 'node'),
108+
'#!/bin/sh\necho "fake-node $*" > "$VP_HOME/node-used"\n',
109+
{ mode: 0o755 },
110+
);
111+
writeFileSync(
112+
join(vpHomeBin, 'dirname'),
113+
'#!/bin/sh\necho "wrong dirname" > "$VP_HOME/dirname-used"\nexit 1\n',
114+
{ mode: 0o755 },
115+
);
116+
writeFileSync(
117+
join(vpHomeBin, 'sh'),
118+
'#!/bin/sh\necho "wrong sh" > "$VP_HOME/sh-used"\nexit 1\n',
119+
{ mode: 0o755 },
120+
);
121+
122+
writeFileSync(join(systemBin, 'sh'), '#!/bin/sh\nexec /bin/sh "$@"\n', {
123+
mode: 0o755,
124+
});
125+
writeFileSync(join(systemBin, 'dirname'), '#!/bin/sh\nexec /usr/bin/dirname "$@"\n', {
126+
mode: 0o755,
127+
});
128+
writeFileSync(join(systemBin, 'basename'), '#!/bin/sh\nexec /usr/bin/basename "$@"\n', {
129+
mode: 0o755,
130+
});
131+
132+
execSync('sh .vite-hooks/_/pre-commit', {
133+
cwd: tmp,
134+
env: {
135+
HOME: join(tmp, 'home'),
136+
PATH: systemBin,
137+
VP_HOME: join(tmp, 'vp-home'),
138+
},
139+
});
140+
141+
expect(existsSync(join(tmp, 'vp-home', 'node-used'))).toBe(true);
142+
expect(existsSync(join(tmp, 'vp-home', 'dirname-used'))).toBe(false);
143+
expect(existsSync(join(tmp, 'vp-home', 'sh-used'))).toBe(false);
144+
} finally {
145+
rmSync(tmp, { recursive: true, force: true });
146+
}
147+
},
148+
);
149+
150+
it('should compute root and shell before appending Vite+ managed bin', () => {
151+
const script = hookScript('.vite-hooks');
152+
expect(script.indexOf('d=')).toBeLessThan(script.indexOf('export PATH="$PATH:$__vp_bin"'));
153+
expect(script.indexOf('__vp_shell=')).toBeLessThan(
154+
script.indexOf('export PATH="$PATH:$__vp_bin"'),
155+
);
156+
expect(script).toContain('"$__vp_shell" -e "$s" "$@"');
157+
});
76158
});

packages/cli/src/config/hooks.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,20 @@ i="\${XDG_CONFIG_HOME:-$HOME/.config}/vite-plus/hooks-init.sh"
5050
{ [ "\${HUSKY-}" = "0" ] || [ "\${VITE_GIT_HOOKS-}" = "0" ]; } && exit 0
5151
5252
d=${rootExpr}
53+
__vp_shell=/bin/sh
54+
[ -x "$__vp_shell" ] || __vp_shell=$(command -v sh)
55+
56+
if [ -n "\${VP_HOME-}" ]; then
57+
__vp_bin="$VP_HOME/bin"
58+
elif [ -n "\${HOME-}" ]; then
59+
__vp_bin="$HOME/.vite-plus/bin"
60+
else
61+
__vp_bin=""
62+
fi
63+
[ -n "$__vp_bin" ] && [ -d "$__vp_bin" ] && export PATH="$PATH:$__vp_bin"
64+
5365
export PATH="$d/node_modules/.bin:$PATH"
54-
sh -e "$s" "$@"
66+
"$__vp_shell" -e "$s" "$@"
5567
c=$?
5668
5769
[ $c != 0 ] && echo "VITE+ - $n script failed (code $c)"

0 commit comments

Comments
 (0)