refactor(infra): share shell env timeout normalization

This commit is contained in:
Peter Steinberger
2026-02-18 17:22:26 +00:00
parent 5ae4595bb9
commit 0a78331536
2 changed files with 49 additions and 8 deletions

View File

@@ -1,6 +1,8 @@
import { describe, expect, it, vi } from "vitest";
import {
getShellPathFromLoginShell,
loadShellEnvFallback,
resetShellPathCacheForTests,
resolveShellEnvFallbackTimeoutMs,
shouldEnableShellEnvFallback,
} from "./shell-env.js";
@@ -71,4 +73,42 @@ describe("shell env fallback", () => {
expect(env.DISCORD_BOT_TOKEN).toBe("discord");
expect(exec2).not.toHaveBeenCalled();
});
it("resolves PATH via login shell and caches it", () => {
resetShellPathCacheForTests();
const exec = vi.fn(() => Buffer.from("PATH=/usr/local/bin:/usr/bin\0HOME=/tmp\0"));
const first = getShellPathFromLoginShell({
env: {} as NodeJS.ProcessEnv,
exec: exec as unknown as Parameters<typeof getShellPathFromLoginShell>[0]["exec"],
});
const second = getShellPathFromLoginShell({
env: {} as NodeJS.ProcessEnv,
exec: exec as unknown as Parameters<typeof getShellPathFromLoginShell>[0]["exec"],
});
expect(first).toBe("/usr/local/bin:/usr/bin");
expect(second).toBe("/usr/local/bin:/usr/bin");
expect(exec).toHaveBeenCalledOnce();
});
it("returns null on shell env read failure and caches null", () => {
resetShellPathCacheForTests();
const exec = vi.fn(() => {
throw new Error("exec failed");
});
const first = getShellPathFromLoginShell({
env: {} as NodeJS.ProcessEnv,
exec: exec as unknown as Parameters<typeof getShellPathFromLoginShell>[0]["exec"],
});
const second = getShellPathFromLoginShell({
env: {} as NodeJS.ProcessEnv,
exec: exec as unknown as Parameters<typeof getShellPathFromLoginShell>[0]["exec"],
});
expect(first).toBeNull();
expect(second).toBeNull();
expect(exec).toHaveBeenCalledOnce();
});
});

View File

@@ -6,6 +6,13 @@ const DEFAULT_MAX_BUFFER_BYTES = 2 * 1024 * 1024;
let lastAppliedKeys: string[] = [];
let cachedShellPath: string | null | undefined;
function resolveTimeoutMs(timeoutMs: number | undefined): number {
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
return DEFAULT_TIMEOUT_MS;
}
return Math.max(0, timeoutMs);
}
function resolveShell(env: NodeJS.ProcessEnv): string {
const shell = env.SHELL?.trim();
return shell && shell.length > 0 ? shell : "/bin/sh";
@@ -76,10 +83,7 @@ export function loadShellEnvFallback(opts: ShellEnvFallbackOptions): ShellEnvFal
return { ok: true, applied: [], skippedReason: "already-has-keys" };
}
const timeoutMs =
typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(0, opts.timeoutMs)
: DEFAULT_TIMEOUT_MS;
const timeoutMs = resolveTimeoutMs(opts.timeoutMs);
const shell = resolveShell(opts.env);
@@ -146,10 +150,7 @@ export function getShellPathFromLoginShell(opts: {
}
const exec = opts.exec ?? execFileSync;
const timeoutMs =
typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
? Math.max(0, opts.timeoutMs)
: DEFAULT_TIMEOUT_MS;
const timeoutMs = resolveTimeoutMs(opts.timeoutMs);
const shell = resolveShell(opts.env);
let stdout: Buffer;