- Set up TypeScript project with strict mode - Configure tsup build system (ESM output) - Configure vitest for testing (36 tests passing) - Configure ESLint with flat config for TypeScript - Implement Commander.js CLI with all 4 commands: - init: Configure API keys with validation - new: Create new Ralph Method project (stub) - validate: Validate PROMPT.md files - research: Research topics via Perplexity (stub) - Implement config management: - Keys stored in ~/.ralph-generator/config.json - File permissions set to 600 - Environment variables override file config - Implement logging utility with verbosity levels - Implement atomic file writes utility All Phase 1 acceptance criteria met. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { join } from 'path';
|
|
import { mkdir, rm, writeFile as fsWriteFile, readFile } from 'fs/promises';
|
|
import { tmpdir } from 'os';
|
|
import { fileExists, readFileContent, writeFileAtomic, readJsonFile, writeJsonFile, ensureDir } from '../utils/files.js';
|
|
|
|
describe('File Utilities', () => {
|
|
const testDir = join(tmpdir(), `ralph-files-test-${Date.now()}`);
|
|
|
|
beforeEach(async () => {
|
|
await mkdir(testDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(testDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('ensureDir', () => {
|
|
it('creates directory if it does not exist', async () => {
|
|
const newDir = join(testDir, 'new', 'nested', 'dir');
|
|
await ensureDir(newDir);
|
|
expect(await fileExists(newDir)).toBe(true);
|
|
});
|
|
|
|
it('does not throw if directory exists', async () => {
|
|
await expect(ensureDir(testDir)).resolves.not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('fileExists', () => {
|
|
it('returns true for existing file', async () => {
|
|
const filePath = join(testDir, 'test.txt');
|
|
await fsWriteFile(filePath, 'test');
|
|
expect(await fileExists(filePath)).toBe(true);
|
|
});
|
|
|
|
it('returns false for non-existing file', async () => {
|
|
expect(await fileExists(join(testDir, 'nonexistent.txt'))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('readFileContent', () => {
|
|
it('reads file content', async () => {
|
|
const filePath = join(testDir, 'read-test.txt');
|
|
await fsWriteFile(filePath, 'hello world');
|
|
const content = await readFileContent(filePath);
|
|
expect(content).toBe('hello world');
|
|
});
|
|
});
|
|
|
|
describe('writeFileAtomic', () => {
|
|
it('writes file atomically', async () => {
|
|
const filePath = join(testDir, 'atomic.txt');
|
|
await writeFileAtomic(filePath, 'atomic content');
|
|
const content = await readFile(filePath, 'utf-8');
|
|
expect(content).toBe('atomic content');
|
|
});
|
|
|
|
it('creates parent directories', async () => {
|
|
const filePath = join(testDir, 'new', 'deep', 'file.txt');
|
|
await writeFileAtomic(filePath, 'nested');
|
|
expect(await fileExists(filePath)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('readJsonFile', () => {
|
|
it('reads and parses JSON file', async () => {
|
|
const filePath = join(testDir, 'data.json');
|
|
await fsWriteFile(filePath, JSON.stringify({ name: 'test', value: 42 }));
|
|
const data = await readJsonFile<{ name: string; value: number }>(filePath);
|
|
expect(data.name).toBe('test');
|
|
expect(data.value).toBe(42);
|
|
});
|
|
});
|
|
|
|
describe('writeJsonFile', () => {
|
|
it('writes JSON file with formatting', async () => {
|
|
const filePath = join(testDir, 'output.json');
|
|
await writeJsonFile(filePath, { key: 'value' });
|
|
const content = await readFile(filePath, 'utf-8');
|
|
expect(content).toContain('"key"');
|
|
expect(content).toContain('"value"');
|
|
// Check it's formatted (has newlines)
|
|
expect(content).toContain('\n');
|
|
});
|
|
});
|
|
});
|