import { createHash, randomUUID } from "node:crypto"; import { mkdir, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import path from "node:path"; export interface StateDirOptions { platform?: NodeJS.Platform; env?: NodeJS.ProcessEnv; home?: string; } export interface SavedJsonResult { namespace: string; responseId: string; fullOutputPath: string; byteLength: number; } export function platformStateDir( platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env, home = homedir(), ): string { const paths = platform === "win32" ? path.win32 : path.posix; // Treat XDG_STATE_HOME as an explicit override for hermetic tests/CI, // even on platforms where it is not the native convention. if (env.XDG_STATE_HOME) return paths.join(env.XDG_STATE_HOME, "pi"); if (platform === "win32") return paths.join( env.LOCALAPPDATA ?? paths.join(home, "AppData", "Local"), "pi", ); if (platform === "darwin") return paths.join(home, "Library", "Application Support", "pi"); return paths.join(home, ".local", "state", "pi"); } export function projectStoreDir( namespace: string, cwd: string, options: StateDirOptions = {}, ): string { const paths = pathModule(options.platform); return paths.join( platformStateDir(options.platform, options.env, options.home), namespace, hash(cwd), ); } export async function saveJsonResult( namespace: string, cwd: string, value: unknown, options: StateDirOptions = {}, ): Promise { const responseId = randomUUID(); const paths = pathModule(options.platform); const dir = paths.join(projectStoreDir(namespace, cwd, options), "results"); await mkdir(dir, { recursive: true, mode: 0o700 }); const fullOutputPath = paths.join(dir, `${responseId}.json`); const body = `${JSON.stringify(value, null, 2)}\n`; await writeFile(fullOutputPath, body, { mode: 0o600 }); return { namespace, responseId, fullOutputPath, byteLength: Buffer.byteLength(body), }; } function pathModule(platform: NodeJS.Platform = process.platform) { return platform === "win32" ? path.win32 : path.posix; } function hash(input: string): string { return createHash("sha256").update(input).digest("hex"); }