mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
285 lines
12 KiB
JavaScript
285 lines
12 KiB
JavaScript
import { describe, test, expect, beforeEach, afterEach, mock, spyOn } from 'bun:test';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import * as utils from '../scripts/lib/utils.js';
|
|
import * as transformers from '../scripts/lib/transformers/index.js';
|
|
|
|
const TEST_DIR = path.join(process.cwd(), 'test-tmp-build');
|
|
|
|
describe('build orchestration', () => {
|
|
beforeEach(() => {
|
|
if (fs.existsSync(TEST_DIR)) {
|
|
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
}
|
|
fs.mkdirSync(TEST_DIR, { recursive: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (fs.existsSync(TEST_DIR)) {
|
|
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('should call readSourceFiles with root directory', () => {
|
|
const readSourceFilesSpy = spyOn(utils, 'readSourceFiles').mockReturnValue({
|
|
commands: [],
|
|
skills: []
|
|
});
|
|
|
|
const transformCursorSpy = spyOn(transformers, 'transformCursor').mockImplementation(() => {});
|
|
const transformClaudeCodeSpy = spyOn(transformers, 'transformClaudeCode').mockImplementation(() => {});
|
|
const transformGeminiSpy = spyOn(transformers, 'transformGemini').mockImplementation(() => {});
|
|
const transformCodexSpy = spyOn(transformers, 'transformCodex').mockImplementation(() => {});
|
|
|
|
// Simulate the build process
|
|
const ROOT_DIR = TEST_DIR;
|
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
|
|
const { commands, skills } = utils.readSourceFiles(ROOT_DIR);
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
expect(readSourceFilesSpy).toHaveBeenCalledWith(ROOT_DIR);
|
|
|
|
readSourceFilesSpy.mockRestore();
|
|
transformCursorSpy.mockRestore();
|
|
transformClaudeCodeSpy.mockRestore();
|
|
transformGeminiSpy.mockRestore();
|
|
transformCodexSpy.mockRestore();
|
|
});
|
|
|
|
test('should call all four transformers with correct arguments', () => {
|
|
const commands = [
|
|
{ name: 'cmd1', description: 'Command 1', args: [], body: 'Body 1' }
|
|
];
|
|
const skills = [
|
|
{ name: 'skill1', description: 'Skill 1', license: 'MIT', body: 'Skill body 1' }
|
|
];
|
|
|
|
const readSourceFilesSpy = spyOn(utils, 'readSourceFiles').mockReturnValue({
|
|
commands,
|
|
skills
|
|
});
|
|
|
|
const transformCursorSpy = spyOn(transformers, 'transformCursor').mockImplementation(() => {});
|
|
const transformClaudeCodeSpy = spyOn(transformers, 'transformClaudeCode').mockImplementation(() => {});
|
|
const transformGeminiSpy = spyOn(transformers, 'transformGemini').mockImplementation(() => {});
|
|
const transformCodexSpy = spyOn(transformers, 'transformCodex').mockImplementation(() => {});
|
|
|
|
const ROOT_DIR = TEST_DIR;
|
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
|
|
const sourceFiles = utils.readSourceFiles(ROOT_DIR);
|
|
transformers.transformCursor(sourceFiles.commands, sourceFiles.skills, DIST_DIR);
|
|
transformers.transformClaudeCode(sourceFiles.commands, sourceFiles.skills, DIST_DIR);
|
|
transformers.transformGemini(sourceFiles.commands, sourceFiles.skills, DIST_DIR);
|
|
transformers.transformCodex(sourceFiles.commands, sourceFiles.skills, DIST_DIR);
|
|
|
|
expect(transformCursorSpy).toHaveBeenCalledWith(commands, skills, DIST_DIR);
|
|
expect(transformClaudeCodeSpy).toHaveBeenCalledWith(commands, skills, DIST_DIR);
|
|
expect(transformGeminiSpy).toHaveBeenCalledWith(commands, skills, DIST_DIR);
|
|
expect(transformCodexSpy).toHaveBeenCalledWith(commands, skills, DIST_DIR);
|
|
|
|
readSourceFilesSpy.mockRestore();
|
|
transformCursorSpy.mockRestore();
|
|
transformClaudeCodeSpy.mockRestore();
|
|
transformGeminiSpy.mockRestore();
|
|
transformCodexSpy.mockRestore();
|
|
});
|
|
|
|
test('should handle empty source files', () => {
|
|
const readSourceFilesSpy = spyOn(utils, 'readSourceFiles').mockReturnValue({
|
|
commands: [],
|
|
skills: []
|
|
});
|
|
|
|
const transformCursorSpy = spyOn(transformers, 'transformCursor').mockImplementation(() => {});
|
|
const transformClaudeCodeSpy = spyOn(transformers, 'transformClaudeCode').mockImplementation(() => {});
|
|
const transformGeminiSpy = spyOn(transformers, 'transformGemini').mockImplementation(() => {});
|
|
const transformCodexSpy = spyOn(transformers, 'transformCodex').mockImplementation(() => {});
|
|
|
|
const ROOT_DIR = TEST_DIR;
|
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
|
|
const { commands, skills } = utils.readSourceFiles(ROOT_DIR);
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
expect(transformCursorSpy).toHaveBeenCalledWith([], [], DIST_DIR);
|
|
expect(transformClaudeCodeSpy).toHaveBeenCalledWith([], [], DIST_DIR);
|
|
expect(transformGeminiSpy).toHaveBeenCalledWith([], [], DIST_DIR);
|
|
expect(transformCodexSpy).toHaveBeenCalledWith([], [], DIST_DIR);
|
|
|
|
readSourceFilesSpy.mockRestore();
|
|
transformCursorSpy.mockRestore();
|
|
transformClaudeCodeSpy.mockRestore();
|
|
transformGeminiSpy.mockRestore();
|
|
transformCodexSpy.mockRestore();
|
|
});
|
|
|
|
test('integration: full build creates all expected outputs', () => {
|
|
// Create test source files
|
|
const commandContent = `---
|
|
name: test-command
|
|
description: A test command
|
|
args:
|
|
- name: target
|
|
description: Target parameter
|
|
required: false
|
|
---
|
|
|
|
This is a test command body with {{target}} placeholder.`;
|
|
|
|
const skillContent = `---
|
|
name: test-skill
|
|
description: A test skill
|
|
license: MIT
|
|
---
|
|
|
|
This is a test skill body.`;
|
|
|
|
utils.writeFile(path.join(TEST_DIR, 'source/commands/test-command.md'), commandContent);
|
|
utils.writeFile(path.join(TEST_DIR, 'source/skills/test-skill.md'), skillContent);
|
|
|
|
// Run the build process
|
|
const DIST_DIR = path.join(TEST_DIR, 'dist');
|
|
const { commands, skills } = utils.readSourceFiles(TEST_DIR);
|
|
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
// Verify Cursor outputs
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/commands/test-command.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/rules/test-skill.md'))).toBe(true);
|
|
|
|
// Verify Claude Code outputs
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'claude-code/commands/test-command.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'claude-code/skills/test-skill/SKILL.md'))).toBe(true);
|
|
|
|
// Verify Gemini outputs
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'gemini/commands/test-command.toml'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'gemini/GEMINI.test-skill.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'gemini/GEMINI.md'))).toBe(true);
|
|
|
|
// Verify Codex outputs
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'codex/prompts/test-command.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'codex/AGENTS.test-skill.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'codex/AGENTS.md'))).toBe(true);
|
|
});
|
|
|
|
test('integration: verify transformations are correct', () => {
|
|
const commandContent = `---
|
|
name: normalize
|
|
description: Normalize design
|
|
args:
|
|
- name: target
|
|
description: Target element
|
|
required: false
|
|
---
|
|
|
|
Please normalize {{target}} to match the design system.`;
|
|
|
|
utils.writeFile(path.join(TEST_DIR, 'source/commands/normalize.md'), commandContent);
|
|
|
|
const DIST_DIR = path.join(TEST_DIR, 'dist');
|
|
const { commands, skills } = utils.readSourceFiles(TEST_DIR);
|
|
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
// Verify Cursor: body only, no frontmatter
|
|
const cursorContent = fs.readFileSync(path.join(DIST_DIR, 'cursor/commands/normalize.md'), 'utf-8');
|
|
expect(cursorContent).not.toContain('---');
|
|
expect(cursorContent).toContain('{{target}}');
|
|
|
|
// Verify Claude Code: full frontmatter
|
|
const claudeContent = fs.readFileSync(path.join(DIST_DIR, 'claude-code/commands/normalize.md'), 'utf-8');
|
|
expect(claudeContent).toContain('---');
|
|
expect(claudeContent).toContain('name: normalize');
|
|
expect(claudeContent).toContain('{{target}}');
|
|
|
|
// Verify Gemini: TOML with {{args}}
|
|
const geminiContent = fs.readFileSync(path.join(DIST_DIR, 'gemini/commands/normalize.toml'), 'utf-8');
|
|
expect(geminiContent).toContain('description = "Normalize design"');
|
|
expect(geminiContent).toContain('{{args}}');
|
|
expect(geminiContent).not.toContain('{{target}}');
|
|
|
|
// Verify Codex: $VARIABLE
|
|
const codexContent = fs.readFileSync(path.join(DIST_DIR, 'codex/prompts/normalize.md'), 'utf-8');
|
|
expect(codexContent).toContain('$TARGET');
|
|
expect(codexContent).not.toContain('{{target}}');
|
|
});
|
|
|
|
test('integration: multiple commands and skills', () => {
|
|
utils.writeFile(path.join(TEST_DIR, 'source/commands/cmd1.md'), '---\nname: cmd1\n---\nBody1');
|
|
utils.writeFile(path.join(TEST_DIR, 'source/commands/cmd2.md'), '---\nname: cmd2\n---\nBody2');
|
|
utils.writeFile(path.join(TEST_DIR, 'source/skills/skill1.md'), '---\nname: skill1\n---\nSkill1');
|
|
utils.writeFile(path.join(TEST_DIR, 'source/skills/skill2.md'), '---\nname: skill2\n---\nSkill2');
|
|
|
|
const DIST_DIR = path.join(TEST_DIR, 'dist');
|
|
const { commands, skills } = utils.readSourceFiles(TEST_DIR);
|
|
|
|
expect(commands).toHaveLength(2);
|
|
expect(skills).toHaveLength(2);
|
|
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
// Verify all files exist
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/commands/cmd1.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/commands/cmd2.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/rules/skill1.md'))).toBe(true);
|
|
expect(fs.existsSync(path.join(DIST_DIR, 'cursor/rules/skill2.md'))).toBe(true);
|
|
});
|
|
|
|
test('should call transformers in correct order', () => {
|
|
const callOrder = [];
|
|
|
|
const readSourceFilesSpy = spyOn(utils, 'readSourceFiles').mockReturnValue({
|
|
commands: [],
|
|
skills: []
|
|
});
|
|
|
|
const transformCursorSpy = spyOn(transformers, 'transformCursor').mockImplementation(() => {
|
|
callOrder.push('cursor');
|
|
});
|
|
const transformClaudeCodeSpy = spyOn(transformers, 'transformClaudeCode').mockImplementation(() => {
|
|
callOrder.push('claude-code');
|
|
});
|
|
const transformGeminiSpy = spyOn(transformers, 'transformGemini').mockImplementation(() => {
|
|
callOrder.push('gemini');
|
|
});
|
|
const transformCodexSpy = spyOn(transformers, 'transformCodex').mockImplementation(() => {
|
|
callOrder.push('codex');
|
|
});
|
|
|
|
const ROOT_DIR = TEST_DIR;
|
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
|
|
const { commands, skills } = utils.readSourceFiles(ROOT_DIR);
|
|
transformers.transformCursor(commands, skills, DIST_DIR);
|
|
transformers.transformClaudeCode(commands, skills, DIST_DIR);
|
|
transformers.transformGemini(commands, skills, DIST_DIR);
|
|
transformers.transformCodex(commands, skills, DIST_DIR);
|
|
|
|
expect(callOrder).toEqual(['cursor', 'claude-code', 'gemini', 'codex']);
|
|
|
|
readSourceFilesSpy.mockRestore();
|
|
transformCursorSpy.mockRestore();
|
|
transformClaudeCodeSpy.mockRestore();
|
|
transformGeminiSpy.mockRestore();
|
|
transformCodexSpy.mockRestore();
|
|
});
|
|
});
|
|
|