mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
* feat: configure prettier with pre-commit hook integration Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo root with husky pre-commit hook integration. Moves husky from gitnexus/ to root package.json for reliable hook installation. - Root package.json with prepare/format/format:check scripts - .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4 - .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md - .gitattributes enforcing LF line endings for Windows consistency - Pre-commit hook uses direct node_modules/.bin/ paths (no npx) * style: apply prettier formatting to entire codebase One-time bulk format. No logic changes. Use .git-blame-ignore-revs to skip this commit in git blame. * chore: add .git-blame-ignore-revs for prettier format commit * perf: pre-commit hook runs only tests related to staged files Use vitest --related to scope test execution to tests that import the changed files, instead of running the full suite on every commit. * perf: remove vitest from pre-commit hook, keep in CI only Pre-commit now runs lint-staged + tsc only. Tests run in CI (ci-tests.yml) where they belong — keeps commits fast. * ci: add prettier format check to quality workflow PRs will now fail if code isn't formatted with prettier.
153 lines
4.6 KiB
TypeScript
153 lines
4.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import fs from 'fs/promises';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
|
|
const execFileMock = vi.fn((...args: any[]) => {
|
|
const callback = args.at(-1);
|
|
if (typeof callback === 'function') {
|
|
callback(null, '', '');
|
|
}
|
|
});
|
|
|
|
vi.mock('child_process', () => ({
|
|
execFile: execFileMock,
|
|
}));
|
|
|
|
describe('setupClaudeCode', () => {
|
|
let tempHome: string;
|
|
let originalHome: string | undefined;
|
|
let originalUserProfile: string | undefined;
|
|
let platformDescriptor: PropertyDescriptor | undefined;
|
|
|
|
const setPlatform = (value: NodeJS.Platform) => {
|
|
Object.defineProperty(process, 'platform', {
|
|
value,
|
|
configurable: true,
|
|
});
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
|
|
originalHome = process.env.HOME;
|
|
originalUserProfile = process.env.USERPROFILE;
|
|
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-claude-setup-'));
|
|
process.env.HOME = tempHome;
|
|
process.env.USERPROFILE = tempHome;
|
|
|
|
// Only create ~/.claude — no other editor directories so their
|
|
// setup functions skip and don't pollute assertions.
|
|
await fs.mkdir(path.join(tempHome, '.claude'), { recursive: true });
|
|
|
|
platformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks();
|
|
|
|
if (platformDescriptor) {
|
|
Object.defineProperty(process, 'platform', platformDescriptor);
|
|
}
|
|
|
|
process.env.HOME = originalHome;
|
|
process.env.USERPROFILE = originalUserProfile;
|
|
await fs.rm(tempHome, { recursive: true, force: true });
|
|
});
|
|
|
|
it('writes win32 MCP entry with cmd wrapper', async () => {
|
|
setPlatform('win32');
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8');
|
|
const config = JSON.parse(raw);
|
|
|
|
expect(config.mcpServers.gitnexus).toEqual({
|
|
command: 'cmd',
|
|
args: ['/c', 'npx', '-y', 'gitnexus@latest', 'mcp'],
|
|
});
|
|
});
|
|
|
|
it('writes non-win32 MCP entry with npx directly', async () => {
|
|
setPlatform('darwin');
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8');
|
|
const config = JSON.parse(raw);
|
|
|
|
expect(config.mcpServers.gitnexus).toEqual({
|
|
command: 'npx',
|
|
args: ['-y', 'gitnexus@latest', 'mcp'],
|
|
});
|
|
});
|
|
|
|
it('skips when ~/.claude directory does not exist', async () => {
|
|
await fs.rm(path.join(tempHome, '.claude'), { recursive: true, force: true });
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
await expect(fs.access(path.join(tempHome, '.claude.json'))).rejects.toThrow();
|
|
});
|
|
|
|
it('preserves existing keys in ~/.claude.json', async () => {
|
|
setPlatform('linux');
|
|
|
|
await fs.writeFile(
|
|
path.join(tempHome, '.claude.json'),
|
|
JSON.stringify({ existingKey: 'keep-me', mcpServers: { other: { command: 'foo' } } }),
|
|
'utf-8',
|
|
);
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8');
|
|
const config = JSON.parse(raw);
|
|
|
|
expect(config.existingKey).toBe('keep-me');
|
|
expect(config.mcpServers.other).toEqual({ command: 'foo' });
|
|
expect(config.mcpServers.gitnexus).toBeDefined();
|
|
});
|
|
|
|
it('handles missing ~/.claude.json (creates fresh)', async () => {
|
|
setPlatform('linux');
|
|
|
|
// Ensure no pre-existing file
|
|
await fs.rm(path.join(tempHome, '.claude.json'), { force: true });
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8');
|
|
const config = JSON.parse(raw);
|
|
|
|
expect(config.mcpServers.gitnexus).toBeDefined();
|
|
});
|
|
|
|
it('handles corrupt JSON gracefully', async () => {
|
|
setPlatform('linux');
|
|
|
|
await fs.writeFile(
|
|
path.join(tempHome, '.claude.json'),
|
|
'{ this is not valid json !!!',
|
|
'utf-8',
|
|
);
|
|
|
|
const { setupCommand } = await import('../../src/cli/setup.js');
|
|
await setupCommand();
|
|
|
|
// readJsonFile returns null on invalid JSON, so mergeMcpConfig
|
|
// creates a fresh config — the file should now be valid.
|
|
const raw = await fs.readFile(path.join(tempHome, '.claude.json'), 'utf-8');
|
|
const config = JSON.parse(raw);
|
|
|
|
expect(config.mcpServers.gitnexus).toBeDefined();
|
|
});
|
|
});
|