import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; import { execFileSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; export const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); export const CLI = join(ROOT, 'src', 'cli.mjs'); const created = []; /** * `lh doctor`/`lh init` treat CONTEXT7_API_KEY as a required, env-only value * (never persisted, never inferred). Tests must not depend on whatever the * developer's shell happens to export — inject an obviously-fake fixture so * behaviour is identical locally and in CI, without ever touching a real key. */ export const TEST_ENV = { ...process.env, CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY ?? 'fixture-not-a-real-key' }; export function tempRepo(files = {}) { const dir = mkdtempSync(join(tmpdir(), 'lh-test-')); created.push(dir); git(dir, ['init', '-q', '-b', 'main']); git(dir, ['config', 'user.email', 'test@example.com']); git(dir, ['config', 'user.name', 'Test']); git(dir, ['config', 'commit.gpgsign', 'false']); for (const [rel, body] of Object.entries(files)) write(dir, rel, body); if (Object.keys(files).length) commitAll(dir, 'init'); return dir; } export function write(dir, rel, body) { const full = join(dir, rel); mkdirSync(dirname(full), { recursive: true }); writeFileSync(full, body); return full; } export function git(dir, args) { return execFileSync('git', args, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); } export function commitAll(dir, msg) { git(dir, ['add', '-A']); git(dir, ['commit', '-qm', msg]); } /** Run the lh CLI. Never throws — returns {code, stdout, stderr}. */ export function lh(dir, args) { try { const stdout = execFileSync(process.execPath, [CLI, ...args], { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: TEST_ENV, }); return { code: 0, stdout, stderr: '' }; } catch (e) { return { code: e.status ?? 1, stdout: e.stdout ?? '', stderr: e.stderr ?? '' }; } } export function cleanup() { while (created.length) { try { rmSync(created.pop(), { recursive: true, force: true }); } catch {} } }