mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-24 00:51:53 +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.
120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
import { fileURLToPath } from 'url';
|
|
import { setupCommand } from '../../src/cli/setup.js';
|
|
|
|
describe('setupCommand skills integration', () => {
|
|
let tempHome: string;
|
|
const originalHome = process.env.HOME;
|
|
const originalUserProfile = process.env.USERPROFILE;
|
|
const originalPath = process.env.PATH;
|
|
const testId = `${Date.now()}-${process.pid}`;
|
|
const flatSkillName = `test-flat-skill-${testId}`;
|
|
const dirSkillName = `test-dir-skill-${testId}`;
|
|
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const packageSkillsRoot = path.resolve(testDir, '..', '..', 'skills');
|
|
|
|
beforeAll(async () => {
|
|
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-setup-home-'));
|
|
process.env.HOME = tempHome;
|
|
process.env.USERPROFILE = tempHome; // os.homedir() checks USERPROFILE on Windows
|
|
await fs.mkdir(path.join(tempHome, '.cursor'), { recursive: true });
|
|
|
|
// Create temporary source skills to verify both supported source layouts:
|
|
// - flat file: skills/{name}.md
|
|
// - directory: skills/{name}/SKILL.md (+ nested files copied recursively)
|
|
await fs.writeFile(
|
|
path.join(packageSkillsRoot, `${flatSkillName}.md`),
|
|
`---\nname: ${flatSkillName}\ndescription: temp flat skill\n---\n\n# Flat Test Skill`,
|
|
'utf-8',
|
|
);
|
|
await fs.mkdir(path.join(packageSkillsRoot, dirSkillName, 'references'), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(packageSkillsRoot, dirSkillName, 'SKILL.md'),
|
|
`---\nname: ${dirSkillName}\ndescription: temp directory skill\n---\n\n# Directory Test Skill`,
|
|
'utf-8',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(packageSkillsRoot, dirSkillName, 'references', 'note.md'),
|
|
'# Directory Nested File',
|
|
'utf-8',
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await fs.rm(path.join(packageSkillsRoot, `${flatSkillName}.md`), { force: true });
|
|
await fs.rm(path.join(packageSkillsRoot, dirSkillName), { recursive: true, force: true });
|
|
process.env.HOME = originalHome;
|
|
process.env.USERPROFILE = originalUserProfile;
|
|
process.env.PATH = originalPath;
|
|
await fs.rm(tempHome, { recursive: true, force: true });
|
|
});
|
|
|
|
it('installs packaged, flat-file, and directory skills into cursor skills directory', async () => {
|
|
await setupCommand();
|
|
|
|
const cursorSkillsRoot = path.join(tempHome, '.cursor', 'skills');
|
|
const entries = await fs.readdir(cursorSkillsRoot, { withFileTypes: true });
|
|
const skillDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
|
|
expect(skillDirs.length).toBeGreaterThan(0);
|
|
expect(skillDirs).toContain('gitnexus-cli');
|
|
|
|
const skillContent = await fs.readFile(
|
|
path.join(cursorSkillsRoot, 'gitnexus-cli', 'SKILL.md'),
|
|
'utf-8',
|
|
);
|
|
expect(skillContent).toContain('GitNexus CLI Commands');
|
|
|
|
// Flat file source should be installed as {name}/SKILL.md.
|
|
const flatInstalled = await fs.readFile(
|
|
path.join(cursorSkillsRoot, flatSkillName, 'SKILL.md'),
|
|
'utf-8',
|
|
);
|
|
expect(flatInstalled).toContain('# Flat Test Skill');
|
|
|
|
// Directory source should be copied recursively with nested files preserved.
|
|
const dirInstalled = await fs.readFile(
|
|
path.join(cursorSkillsRoot, dirSkillName, 'SKILL.md'),
|
|
'utf-8',
|
|
);
|
|
expect(dirInstalled).toContain('# Directory Test Skill');
|
|
const nestedInstalled = await fs.readFile(
|
|
path.join(cursorSkillsRoot, dirSkillName, 'references', 'note.md'),
|
|
'utf-8',
|
|
);
|
|
expect(nestedInstalled).toContain('Directory Nested File');
|
|
});
|
|
|
|
it('falls back to Codex config.toml and installs skills into ~/.agents/skills when codex CLI is unavailable', async () => {
|
|
await fs.mkdir(path.join(tempHome, '.codex'), { recursive: true });
|
|
process.env.PATH = '';
|
|
|
|
await setupCommand();
|
|
|
|
const codexConfig = await fs.readFile(path.join(tempHome, '.codex', 'config.toml'), 'utf-8');
|
|
expect(codexConfig).toContain('[mcp_servers.gitnexus]');
|
|
expect(codexConfig).toContain('gitnexus@latest');
|
|
|
|
const codexSkill = await fs.readFile(
|
|
path.join(tempHome, '.agents', 'skills', 'gitnexus-cli', 'SKILL.md'),
|
|
'utf-8',
|
|
);
|
|
expect(codexSkill).toContain('GitNexus CLI Commands');
|
|
});
|
|
|
|
it('does not duplicate the Codex MCP section on repeated fallback setup runs', async () => {
|
|
await fs.mkdir(path.join(tempHome, '.codex'), { recursive: true });
|
|
process.env.PATH = '';
|
|
|
|
await setupCommand();
|
|
await setupCommand();
|
|
|
|
const codexConfig = await fs.readFile(path.join(tempHome, '.codex', 'config.toml'), 'utf-8');
|
|
const sectionMatches = codexConfig.match(/\[mcp_servers\.gitnexus\]/g) ?? [];
|
|
|
|
expect(sectionMatches).toHaveLength(1);
|
|
});
|
|
});
|