mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(build): merge-time fixes for #500
- pre-push hook: skip with a notice when npm is not on PATH (GUI git clients and IDEs often run hooks with a minimal PATH), instead of blocking every push on "npm: not found"; real-push test with a stripped PATH - test/git-hooks.test.ts: pin GIT_CONFIG_NOSYSTEM=1 and GIT_CONFIG_GLOBAL=/dev/null around the resolveGitHooksDir tests, so an exported global or a system core.hooksPath no longer fails them - watch tsconfig.json, .prettierignore and .editorconfig too: typecheck and format:check read them - check:browser-excludes: fail loudly when the vitest list output and the walked test/**/*.test.ts tree share no path (format drift would otherwise pass vacuously) - Reword the PRE_PUSH_MARKER comment: bumping its version would make every installed v1 hook read as foreign and never refresh again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+58
-12
@@ -24,6 +24,7 @@ import {
|
||||
realpathSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
@@ -133,6 +134,24 @@ describe('planHookInstall', () => {
|
||||
});
|
||||
|
||||
describe('resolveGitHooksDir (temp repos)', () => {
|
||||
// resolveGitHooksDir runs git with process.env, so an exported GIT_CONFIG_GLOBAL or a system
|
||||
// gitconfig carrying core.hooksPath would otherwise redirect every expectation below.
|
||||
// test/setup.ts swaps HOME, which only covers ~/.gitconfig.
|
||||
const ambient = {
|
||||
GIT_CONFIG_GLOBAL: process.env.GIT_CONFIG_GLOBAL,
|
||||
GIT_CONFIG_NOSYSTEM: process.env.GIT_CONFIG_NOSYSTEM,
|
||||
};
|
||||
beforeAll(() => {
|
||||
process.env.GIT_CONFIG_NOSYSTEM = '1';
|
||||
process.env.GIT_CONFIG_GLOBAL = '/dev/null';
|
||||
});
|
||||
afterAll(() => {
|
||||
for (const [k, v] of Object.entries(ambient)) {
|
||||
if (v === undefined) delete process.env[k];
|
||||
else process.env[k] = v;
|
||||
}
|
||||
});
|
||||
|
||||
it('resolves <root>/.git/hooks in a plain checkout', () => {
|
||||
const repo = newRepo();
|
||||
expect(resolveGitHooksDir(repo)).toBe(join(repo, '.git', 'hooks'));
|
||||
@@ -352,18 +371,24 @@ describe('the installed hook on a real push (temp repos)', () => {
|
||||
expect(ran()).toEqual(expectedRuns);
|
||||
});
|
||||
|
||||
it.each(['src/wip.ts', 'config/wip.json', 'scripts/wip.mjs', 'test/wip.test.ts', 'install.sh'])(
|
||||
'skips when %s is untracked (another session may own it)',
|
||||
(rel) => {
|
||||
const { ran, push, repo } = setup({ failing: 'lint' });
|
||||
mkdirSync(join(repo, rel, '..'), { recursive: true });
|
||||
writeFileSync(join(repo, rel), 'wip\n');
|
||||
const r = push(['origin', 'main']);
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
expect(r.stdout + r.stderr).toContain('pre-push: skipping static checks: uncommitted changes under');
|
||||
expect(ran()).toEqual([]);
|
||||
}
|
||||
);
|
||||
it.each([
|
||||
'src/wip.ts',
|
||||
'config/wip.json',
|
||||
'scripts/wip.mjs',
|
||||
'test/wip.test.ts',
|
||||
'install.sh',
|
||||
'tsconfig.json',
|
||||
'.prettierignore',
|
||||
'.editorconfig',
|
||||
])('skips when %s is untracked (another session may own it)', (rel) => {
|
||||
const { ran, push, repo } = setup({ failing: 'lint' });
|
||||
mkdirSync(join(repo, rel, '..'), { recursive: true });
|
||||
writeFileSync(join(repo, rel), 'wip\n');
|
||||
const r = push(['origin', 'main']);
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
expect(r.stdout + r.stderr).toContain('pre-push: skipping static checks: uncommitted changes under');
|
||||
expect(ran()).toEqual([]);
|
||||
});
|
||||
|
||||
it('skips when a tracked package.json has an unstaged edit', () => {
|
||||
const { ran, push, repo } = setup({ failing: 'lint' });
|
||||
@@ -395,6 +420,9 @@ describe('the installed hook on a real push (temp repos)', () => {
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'install.sh',
|
||||
'tsconfig.json',
|
||||
'.prettierignore',
|
||||
'.editorconfig',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -405,6 +433,24 @@ describe('the installed hook on a real push (temp repos)', () => {
|
||||
expect(r.stdout + r.stderr).toContain('node_modules missing');
|
||||
expect(ran()).toEqual([]);
|
||||
});
|
||||
|
||||
it('skips (never blocks) when npm is not on PATH, as under a GUI git client', () => {
|
||||
const { ran, push } = setup({ failing: 'lint' });
|
||||
// A PATH holding only what git and the hook need, and no npm/node. Symlinks rather than
|
||||
// the real directories, since /usr/bin usually holds npm right next to git.
|
||||
const bin = join(scratch, `bin-${counter}`);
|
||||
mkdirSync(bin);
|
||||
for (const tool of ['git', 'sh', 'mktemp', 'tail', 'rm', 'cat']) {
|
||||
const found = spawnSync('sh', ['-c', `command -v ${tool}`], { encoding: 'utf8' }).stdout.trim();
|
||||
expect(found, `${tool} not found on the test PATH`).toMatch(/^\//);
|
||||
symlinkSync(found, join(bin, tool));
|
||||
}
|
||||
expect(spawnSync('sh', ['-c', 'command -v npm'], { env: { PATH: bin } }).status).not.toBe(0);
|
||||
const r = push(['origin', 'main'], { PATH: bin });
|
||||
expect(r.status, r.stderr + r.stdout).toBe(0);
|
||||
expect(r.stdout + r.stderr).toContain('pre-push: npm not on PATH, skipping checks.');
|
||||
expect(ran()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('postinstall wiring', () => {
|
||||
|
||||
Reference in New Issue
Block a user