import { mkdir, open, realpath, rm } from 'node:fs/promises'; import { basename, dirname, isAbsolute, join, resolve } from 'node:path'; export interface FixtureOptions { dbPath: string; now: number; watch: boolean } export function parseFixtureOptions(args: string[], clock = Date.now()): FixtureOptions { let dbPath = '', now = clock, watch = false, explicitTime = false; const seen = new Set(); for (let index = 0; index < args.length; index++) { const option = args[index]; if (seen.has(option)) throw new Error(`Duplicate option: ${option}`); seen.add(option); if (option === '--watch') { watch = true; continue; } if (option !== '--db' && option !== '--now') throw new Error(`Unknown option: ${option}`); const value = args[++index]; if (!value || value.startsWith('--')) throw new Error(`Missing value for ${option}.`); if (option === '--db') dbPath = value; else { if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/.test(value) || !Number.isFinite(Date.parse(value))) throw new Error('--now must be an ISO timestamp with a timezone.'); now = Date.parse(value); explicitTime = true; } } if (!dbPath || !isAbsolute(dbPath)) throw new Error('Provide --db with an absolute path inside a new fixture directory. DB_PATH is never used.'); if (!Number.isFinite(now)) throw new Error('Invalid fixture clock.'); if (watch && explicitTime) throw new Error('Use --watch for live time or --now for a fixed snapshot, separately.'); return { dbPath: resolve(dbPath), now, watch }; } /** An atomic new-directory reservation rejects existing DBs, sidecars and symlinks together. */ export async function reserveFixture(dbPath: string): Promise<{ root: string; dbPath: string }> { if (!isAbsolute(dbPath)) throw new Error('Fixture database path must be absolute.'); const requested = resolve(dbPath); const requestedRoot = dirname(requested); // The destination's parent must already exist. Canonicalize /tmp on macOS, // but never follow a pre-existing fixture directory or database symlink. const parent = await realpath(dirname(requestedRoot)); const root = join(parent, basename(requestedRoot)); try { await mkdir(root, { mode: 0o700 }); } catch (cause) { if ((cause as NodeJS.ErrnoException).code === 'EEXIST') throw new Error('Fixture directory already exists. Choose a new directory; existing data is never overwritten.'); throw cause; } const target = join(root, basename(requested)); try { const file = await open(target, 'wx', 0o600); await file.close(); } catch (error) { await rm(root, { recursive: true, force: true }); throw error; } return { root, dbPath: target }; }