// Sandboxed-environment helpers for tests, owned by the repository. // // Ported from the removed `@marcfargas/pi-test-harness` so tests that only need // a deterministic HOME/XDG sandbox (not a full Pi session) can swap their import // without behavior change. import path from "node:path"; export interface EnvOptions { /** Explicit env additions. Undefined deletes the key. */ env?: Record; /** Ambient env keys to copy intentionally. */ inheritEnv?: string[]; } const BASE_ENV_KEYS = [ "PATH", "PATHEXT", "SystemRoot", "WINDIR", "COMSPEC", "TMPDIR", "TMP", "TEMP", "SHELL", "TERM", "COLORTERM", "NO_COLOR", "FORCE_COLOR", "CI", "VITEST", "VITEST_POOL_ID", "VITEST_WORKER_ID", "NODE_OPTIONS", ]; export function sandboxEnv( root: string, options: EnvOptions = {}, ): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = {}; for (const key of BASE_ENV_KEYS) copyIfSet(env, process.env, key); for (const key of options.inheritEnv ?? []) copyIfSet(env, process.env, key); env["HOME"] = path.join(root, ".test-home"); env["USERPROFILE"] = env["HOME"]; env["XDG_CONFIG_HOME"] = path.join(root, ".test-config"); env["XDG_CACHE_HOME"] = path.join(root, ".test-cache"); env["XDG_STATE_HOME"] = path.join(root, ".test-state"); env["XDG_DATA_HOME"] = path.join(root, ".test-data"); // Native Windows consumers do not consult HOME or XDG_*. env["APPDATA"] = env["XDG_CONFIG_HOME"]; env["LOCALAPPDATA"] = env["XDG_STATE_HOME"]; for (const [key, value] of Object.entries(options.env ?? {})) setEnvValue(env, key, value); return env; } export async function withProcessEnv( env: NodeJS.ProcessEnv, fn: () => T | Promise, ): Promise { const original = { ...process.env }; replaceProcessEnv({ ...env }); try { return await fn(); } finally { replaceProcessEnv(original); } } export function withProcessEnvSync(env: NodeJS.ProcessEnv, fn: () => T): T { const original = { ...process.env }; replaceProcessEnv({ ...env }); try { return fn(); } finally { replaceProcessEnv(original); } } function replaceProcessEnv(env: NodeJS.ProcessEnv): void { for (const key of Object.keys(process.env)) delete process.env[key]; Object.assign(process.env, env); } function copyIfSet( to: NodeJS.ProcessEnv, from: NodeJS.ProcessEnv, key: string, ): void { const sourceKey = process.platform === "win32" ? Object.keys(from).find( (candidate) => candidate.toLowerCase() === key.toLowerCase(), ) : key; if (!sourceKey) return; setEnvValue(to, sourceKey, from[sourceKey]); } function setEnvValue( env: NodeJS.ProcessEnv, key: string, value: string | undefined, ): void { if (process.platform === "win32") { for (const candidate of Object.keys(env)) if (candidate.toLowerCase() === key.toLowerCase()) delete env[candidate]; } else { delete env[key]; } if (value !== undefined) env[key] = value; }