import { execSync } from 'child_process'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { stringify } from 'yaml'; export function createTempDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), 'nanoclaw-test-')); } export function setupNanoclawDir(tmpDir: string): void { fs.mkdirSync(path.join(tmpDir, '.nanoclaw', 'base', 'src'), { recursive: true }); fs.mkdirSync(path.join(tmpDir, '.nanoclaw', 'backup'), { recursive: true }); } export function writeState(tmpDir: string, state: any): void { const statePath = path.join(tmpDir, '.nanoclaw', 'state.yaml'); fs.writeFileSync(statePath, stringify(state), 'utf-8'); } export function createMinimalState(tmpDir: string): void { writeState(tmpDir, { skills_system_version: '0.1.0', core_version: '1.0.0', applied_skills: [], }); } export function createSkillPackage(tmpDir: string, opts: { skill?: string; version?: string; core_version?: string; adds?: string[]; modifies?: string[]; addFiles?: Record; modifyFiles?: Record; conflicts?: string[]; depends?: string[]; test?: string; structured?: any; file_ops?: any[]; post_apply?: string[]; min_skills_system_version?: string; dirName?: string; }): string { const skillDir = path.join(tmpDir, opts.dirName ?? 'skill-pkg'); fs.mkdirSync(skillDir, { recursive: true }); const manifest: Record = { skill: opts.skill ?? 'test-skill', version: opts.version ?? '1.0.0', description: 'Test skill', core_version: opts.core_version ?? '1.0.0', adds: opts.adds ?? [], modifies: opts.modifies ?? [], conflicts: opts.conflicts ?? [], depends: opts.depends ?? [], test: opts.test, structured: opts.structured, file_ops: opts.file_ops, }; if (opts.post_apply) manifest.post_apply = opts.post_apply; if (opts.min_skills_system_version) manifest.min_skills_system_version = opts.min_skills_system_version; fs.writeFileSync(path.join(skillDir, 'manifest.yaml'), stringify(manifest)); if (opts.addFiles) { const addDir = path.join(skillDir, 'add'); for (const [relPath, content] of Object.entries(opts.addFiles)) { const fullPath = path.join(addDir, relPath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, content); } } if (opts.modifyFiles) { const modDir = path.join(skillDir, 'modify'); for (const [relPath, content] of Object.entries(opts.modifyFiles)) { const fullPath = path.join(modDir, relPath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, content); } } return skillDir; } export function initGitRepo(dir: string): void { execSync('git init', { cwd: dir, stdio: 'pipe' }); execSync('git config user.email "test@test.com"', { cwd: dir, stdio: 'pipe' }); execSync('git config user.name "Test"', { cwd: dir, stdio: 'pipe' }); execSync('git config rerere.enabled true', { cwd: dir, stdio: 'pipe' }); fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\n'); execSync('git add -A && git commit -m "init"', { cwd: dir, stdio: 'pipe' }); } export function cleanup(dir: string): void { fs.rmSync(dir, { recursive: true, force: true }); }