import * as fs from 'fs/promises'; import * as os from 'os'; import * as path from 'path'; const DEFAULT_PROJECT_ROOT_TOKEN = '__PROJECT_ROOT__'; const FIXTURE_ROOT = path.join(process.cwd(), 'test', 'fixtures', 'team', 'task-change-ledger'); export type TaskChangeLedgerFixtureManifest = { schemaVersion: number; name: string; taskId: string; description: string; projectRootToken?: string; expected?: { totalFiles?: number; warnings?: string[]; relativePaths?: string[]; relationKinds?: Array<'rename' | 'copy'>; }; }; export type MaterializedTaskChangeLedgerFixture = { rootDir: string; projectDir: string; manifest: TaskChangeLedgerFixtureManifest; cleanup: () => Promise; }; function replaceTokenInValue(value: T, token: string, replacement: string): T { if (typeof value === 'string') { return value.split(token).join(replacement) as T; } if (Array.isArray(value)) { return value.map((item) => replaceTokenInValue(item, token, replacement)) as T; } if (value && typeof value === 'object') { return Object.fromEntries( Object.entries(value as Record).map(([key, item]) => [ key, replaceTokenInValue(item, token, replacement), ]) ) as T; } return value; } async function rewriteProjectRootTokens(rootDir: string, token: string, projectDir: string): Promise { const jsonStringReplacement = JSON.stringify(projectDir).slice(1, -1); const entries = await fs.readdir(rootDir, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(rootDir, entry.name); if (entry.isDirectory()) { await rewriteProjectRootTokens(entryPath, token, projectDir); continue; } if (!['.json', '.jsonl'].includes(path.extname(entry.name))) { continue; } const raw = await fs.readFile(entryPath, 'utf8'); await fs.writeFile(entryPath, raw.split(token).join(jsonStringReplacement), 'utf8'); } } export async function materializeTaskChangeLedgerFixture( fixtureName: string ): Promise { const sourceDir = path.join(FIXTURE_ROOT, fixtureName); const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), `task-change-ledger-${fixtureName}-`)); await fs.cp(sourceDir, rootDir, { recursive: true }); const manifestPath = path.join(rootDir, 'manifest.json'); const manifest = JSON.parse( await fs.readFile(manifestPath, 'utf8') ) as TaskChangeLedgerFixtureManifest; const projectDir = path.join(rootDir, 'project'); const token = manifest.projectRootToken ?? DEFAULT_PROJECT_ROOT_TOKEN; await rewriteProjectRootTokens(rootDir, token, projectDir); return { rootDir, projectDir, manifest: replaceTokenInValue(manifest, token, projectDir), cleanup: async () => { await fs.rm(rootDir, { recursive: true, force: true }); }, }; }