repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
test/fixture/env.ts
Raw// 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<string, string | undefined>;
/** 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<T>(
env: NodeJS.ProcessEnv,
fn: () => T | Promise<T>,
): Promise<T> {
const original = { ...process.env };
replaceProcessEnv({ ...env });
try {
return await fn();
} finally {
replaceProcessEnv(original);
}
}
export function withProcessEnvSync<T>(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;
}