mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-02 10:33:39 +00:00
2dbfd85506
* ci(linter): add renovate/no-sync-io rule Flags calls to a fixed list of Node.js sync I/O APIs (readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, existsSync, copyFileSync, execSync, spawnSync, execFileSync) in tools/** and **/*.spec.ts only; production lib code is not targeted. Set to "warn" instead of "error": the repo has 40 pre-existing violations, exceeding the agreed fix threshold of 30, so violations are left in place for follow-up rather than mass-fixed here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): enforce renovate/no-sync-io at error Promote the rule from warn to error now that the codebase has been migrated off synchronous I/O in tools/ and spec files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(tools): migrate synchronous fs calls to async equivalents Replace *Sync fs calls with their async/promise-based counterparts in tools/ scripts (generate-imports, check runner, utils, cwdchanged hook) now that renovate/no-sync-io is enforced at error there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: migrate synchronous fs calls in specs to async equivalents Replace *Sync fs calls (readdirSync, writeFileSync, existsSync, etc.) with their async/promise-based counterparts across affected spec files, making the enclosing it()/module-scope initializers async as needed, to satisfy renovate/no-sync-io now enforced at error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): replace custom no-sync-io rule with built-in node/no-sync The oxc built-in covers a superset of the custom fixed-name list. Migrate the additionally flagged sites: async glob in test-shards, async execa in prepare-deps, promisified gzip/generateKeyPair in rpm/hex specs, and rename the misleading execSync mock alias (it mocks the async rawExec). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
// https://code.claude.com/docs/en/hooks#cwdchanged
|
|
|
|
const { readStdin } = vi.hoisted(() => ({
|
|
readStdin: vi.fn<() => Promise<string>>(),
|
|
}));
|
|
vi.mock('./utils/stdin.ts', () => ({ readStdin }));
|
|
|
|
const { getRepoRoot } = vi.hoisted(() => ({
|
|
getRepoRoot: vi.fn<(dir?: string) => Promise<string | null>>(),
|
|
}));
|
|
vi.mock('./utils/git.ts', () => ({ getRepoRoot, getChangedFiles: vi.fn() }));
|
|
|
|
const { provision } = vi.hoisted(() => ({ provision: vi.fn() }));
|
|
vi.mock('./utils/provision.ts', () => ({ provision }));
|
|
|
|
const { access } = vi.hoisted(() => ({
|
|
access: vi.fn<(path: string) => Promise<void>>(),
|
|
}));
|
|
vi.mock('node:fs/promises', () => ({ access }));
|
|
|
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
|
|
throw new Error('process.exit');
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
});
|
|
|
|
it('exits with code 1 when stdin is not valid JSON', async () => {
|
|
readStdin.mockResolvedValue('not json');
|
|
|
|
await expect(import('./cwdchanged-check.ts')).rejects.toThrow('process.exit');
|
|
|
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
expect(provision).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('exits with code 1 when hook_event_name is wrong', async () => {
|
|
readStdin.mockResolvedValue(
|
|
JSON.stringify({
|
|
session_id: 'x',
|
|
transcript_path: 'x',
|
|
cwd: '/some/dir',
|
|
hook_event_name: 'SessionStart',
|
|
}),
|
|
);
|
|
|
|
await expect(import('./cwdchanged-check.ts')).rejects.toThrow('process.exit');
|
|
|
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
expect(provision).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('exits silently when directory is not a git repo (getRepoRoot returns null)', async () => {
|
|
readStdin.mockResolvedValue(
|
|
JSON.stringify({
|
|
session_id: 'x',
|
|
transcript_path: 'x',
|
|
cwd: '/not/a/repo',
|
|
hook_event_name: 'CwdChanged',
|
|
}),
|
|
);
|
|
getRepoRoot.mockResolvedValue(null);
|
|
|
|
await expect(import('./cwdchanged-check.ts')).rejects.toThrow('process.exit');
|
|
|
|
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
expect(provision).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('exits silently when node_modules already exists (already installed checkout)', async () => {
|
|
readStdin.mockResolvedValue(
|
|
JSON.stringify({
|
|
session_id: 'x',
|
|
transcript_path: 'x',
|
|
cwd: '/repo/sub',
|
|
hook_event_name: 'CwdChanged',
|
|
}),
|
|
);
|
|
getRepoRoot.mockResolvedValue('/repo');
|
|
access.mockResolvedValue(undefined);
|
|
|
|
await expect(import('./cwdchanged-check.ts')).rejects.toThrow('process.exit');
|
|
|
|
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
expect(access).toHaveBeenCalledWith('/repo/node_modules');
|
|
expect(provision).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('provisions the root when it is a fresh worktree (no node_modules)', async () => {
|
|
readStdin.mockResolvedValue(
|
|
JSON.stringify({
|
|
session_id: 'x',
|
|
transcript_path: 'x',
|
|
cwd: '/worktrees/my-feature',
|
|
hook_event_name: 'CwdChanged',
|
|
}),
|
|
);
|
|
getRepoRoot.mockResolvedValue('/worktrees/my-feature');
|
|
access.mockRejectedValue(new Error('ENOENT'));
|
|
provision.mockResolvedValue(undefined);
|
|
|
|
await import('./cwdchanged-check.ts');
|
|
|
|
expect(getRepoRoot).toHaveBeenCalledWith('/worktrees/my-feature');
|
|
expect(access).toHaveBeenCalledWith('/worktrees/my-feature/node_modules');
|
|
expect(provision).toHaveBeenCalledWith('/worktrees/my-feature');
|
|
expect(exitSpy).not.toHaveBeenCalled();
|
|
});
|