import * as fs from 'fs'; import * as path from 'path'; import { execSync } from 'child_process'; describe('CLI', () => { let testDir: string; const projectPath = path.join(__dirname, '..'); const cliPath = path.join(projectPath, 'dist', 'index.js'); beforeAll(async () => { if (!fs.existsSync(cliPath)) { throw new Error('CLI not built. Run npm run build first.'); } }); beforeEach(async () => { testDir = path.join(__dirname, 'test-projects', `test-${Date.now()}`); await fs.promises.mkdir(testDir, { recursive: true }); }); afterEach(async () => { if (await fs.promises.stat(testDir).catch(() => null)) { await fs.promises.rm(testDir, { recursive: true }); } }); describe('--version', () => { it('should display version information', () => { const output = execSync(`node ${cliPath} --version`, { encoding: 'utf-8' }); expect(output).toContain('1.0.0'); }); }); describe('--help', () => { it('should display help information', () => { const output = execSync(`node ${cliPath} --help`, { encoding: 'utf-8' }); expect(output).toContain('Usage:'); expect(output).toContain('Options:'); }); }); describe('--dir option', () => { it('should analyze specified directory', async () => { await fs.promises.writeFile( path.join(testDir, 'tsconfig.json'), JSON.stringify({}) ); await fs.promises.writeFile( path.join(testDir, 'index.ts'), 'const x = 1;' ); const outputPath = path.join(testDir, 'output.json'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --no-conventions`, { encoding: 'utf-8' } ); expect(fs.existsSync(outputPath)).toBe(true); const content = fs.readFileSync(outputPath, 'utf-8'); expect(() => JSON.parse(content)).not.toThrow(); }); }); describe('--output option', () => { it('should save to specified file path', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const customPath = path.join(testDir, 'custom-context'); execSync( `node ${cliPath} --dir "${testDir}" --output "${customPath}" --format json --no-conventions`, { encoding: 'utf-8' } ); expect(fs.existsSync(`${customPath}.json`)).toBe(true); }); }); describe('--format option', () => { it('should generate JSON output', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --format json --no-conventions`, { encoding: 'utf-8' } ); const content = fs.readFileSync(`${outputPath}.json`, 'utf-8'); expect(() => JSON.parse(content)).not.toThrow(); }); it('should generate YAML output', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --format yaml --no-conventions`, { encoding: 'utf-8' } ); const content = fs.readFileSync(`${outputPath}.yaml`, 'utf-8'); expect(content).toContain('projectInfo:'); }); }); describe('--template option', () => { it('should accept default template', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --template default --no-conventions`, { encoding: 'utf-8' } ); expect(fs.existsSync(`${outputPath}.json`)).toBe(true); }); it('should accept cursor template', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --template cursor --no-conventions`, { encoding: 'utf-8' } ); expect(fs.existsSync(`${outputPath}.json`)).toBe(true); }); it('should accept copilot template', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --template copilot --no-conventions`, { encoding: 'utf-8' } ); expect(fs.existsSync(`${outputPath}.json`)).toBe(true); }); }); describe('--no-conventions option', () => { it('should skip convention analysis', async () => { await fs.promises.writeFile(path.join(testDir, 'tsconfig.json'), '{}'); const outputPath = path.join(testDir, 'output'); execSync( `node ${cliPath} --dir "${testDir}" --output "${outputPath}" --no-conventions`, { encoding: 'utf-8' } ); const content = fs.readFileSync(`${outputPath}.json`, 'utf-8'); const parsed = JSON.parse(content); expect(parsed.projectInfo.conventions).toBeNull(); }); }); describe('--verbose option', () => { it('should output verbose information', () => { const output = execSync( `node ${cliPath} --dir . --output /dev/null --format json --verbose --no-conventions 2>&1 || true`, { encoding: 'utf-8' } ); expect(output).toContain('Analyzing directory'); }); }); describe('error handling', () => { it('should handle non-existent directory', () => { expect(() => { execSync( `node ${cliPath} --dir "/non/existent/path" --output /dev/null --no-conventions`, { encoding: 'utf-8' } ); }).toThrow(); }); }); });