diff --git a/apps/server/src/os-jank.test.ts b/apps/server/src/os-jank.test.ts new file mode 100644 index 00000000000..a157efb665f --- /dev/null +++ b/apps/server/src/os-jank.test.ts @@ -0,0 +1,40 @@ +import * as NodeOS from "node:os"; +import { assert, it } from "vite-plus/test"; + +import { hydratePosixHome } from "./os-jank.ts"; + +it("hydrates HOME for minimal service environments from the user account", () => { + const env: NodeJS.ProcessEnv = {}; + + hydratePosixHome(env); + + assert.equal(env.HOME, NodeOS.userInfo().homedir); +}); + +it("hydrates HOME independently of a blank process HOME", () => { + const originalHome = process.env.HOME; + const env: NodeJS.ProcessEnv = { HOME: " " }; + + try { + process.env.HOME = " "; + hydratePosixHome(env); + } finally { + if (originalHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = originalHome; + } + } + + assert.equal(env.HOME, NodeOS.userInfo().homedir); +}); + +it("preserves an explicitly configured HOME", () => { + const env: NodeJS.ProcessEnv = { HOME: "/custom/home" }; + + hydratePosixHome(env, () => { + throw new Error("HOME lookup should not run"); + }); + + assert.equal(env.HOME, "/custom/home"); +}); diff --git a/apps/server/src/os-jank.ts b/apps/server/src/os-jank.ts index bc72758bc71..18ddbc66c0c 100644 --- a/apps/server/src/os-jank.ts +++ b/apps/server/src/os-jank.ts @@ -36,6 +36,18 @@ function hydratePosixPath(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): vo } } +export function hydratePosixHome( + env: NodeJS.ProcessEnv, + resolveHomeDir = () => NodeOS.userInfo().homedir, +): void { + if ((env.HOME?.trim() ?? "").length > 0) return; + + const homeDir = resolveHomeDir(); + if (homeDir.length > 0) { + env.HOME = homeDir; + } +} + export const fixPath = Effect.fn("fixPath")(function* (): Effect.fn.Return< void, never, @@ -63,6 +75,13 @@ export const fixPath = Effect.fn("fixPath")(function* (): Effect.fn.Return< if (platform !== "darwin" && platform !== "linux") return; + yield* Effect.sync(() => hydratePosixHome(env)).pipe( + Effect.catchDefect((defect) => + Effect.sync(() => { + logPathHydrationWarning("Failed to hydrate HOME from the user account.", defect); + }), + ), + ); yield* Effect.sync(() => hydratePosixPath(env, platform)).pipe( Effect.catchDefect((defect) => Effect.sync(() => {