diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index aa7fbca5d..7e61e7c6f 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -4,8 +4,136 @@ import { buildMinimalServicePath, buildNodeServiceEnvironment, buildServiceEnvironment, + getMinimalServicePathParts, + resolveLinuxUserBinDirs, } from "./service-env.js"; +describe("resolveLinuxUserBinDirs", () => { + it("returns correct paths when HOME is provided", () => { + const dirs = resolveLinuxUserBinDirs("/home/testuser"); + + expect(dirs).toEqual([ + "/home/testuser/.local/bin", + "/home/testuser/.npm-global/bin", + "/home/testuser/bin", + "/home/testuser/.nvm/current/bin", + "/home/testuser/.fnm/current/bin", + "/home/testuser/.volta/bin", + "/home/testuser/.asdf/shims", + "/home/testuser/.local/share/pnpm", + "/home/testuser/.bun/bin", + ]); + }); + + it("returns empty array when HOME is undefined", () => { + const dirs = resolveLinuxUserBinDirs(undefined); + expect(dirs).toEqual([]); + }); + + it("returns empty array when HOME is empty string", () => { + const dirs = resolveLinuxUserBinDirs(""); + expect(dirs).toEqual([]); + }); + + it("handles root home directory correctly", () => { + const dirs = resolveLinuxUserBinDirs("/root"); + + expect(dirs).toContain("/root/.local/bin"); + expect(dirs).toContain("/root/.npm-global/bin"); + expect(dirs).toContain("/root/.nvm/current/bin"); + expect(dirs.length).toBe(9); + }); +}); + +describe("getMinimalServicePathParts - Linux user directories", () => { + it("includes user bin directories when HOME is set on Linux", () => { + const result = getMinimalServicePathParts({ + platform: "linux", + home: "/home/testuser", + }); + + // Should include all common user bin directories + expect(result).toContain("/home/testuser/.local/bin"); + expect(result).toContain("/home/testuser/.npm-global/bin"); + expect(result).toContain("/home/testuser/bin"); + expect(result).toContain("/home/testuser/.nvm/current/bin"); + expect(result).toContain("/home/testuser/.fnm/current/bin"); + expect(result).toContain("/home/testuser/.volta/bin"); + expect(result).toContain("/home/testuser/.asdf/shims"); + expect(result).toContain("/home/testuser/.local/share/pnpm"); + expect(result).toContain("/home/testuser/.bun/bin"); + }); + + it("excludes user bin directories when HOME is undefined on Linux", () => { + const result = getMinimalServicePathParts({ + platform: "linux", + home: undefined, + }); + + // Should only include system directories + expect(result).toEqual(["/usr/local/bin", "/usr/bin", "/bin"]); + + // Should not include any user-specific paths + expect(result.some((p) => p.includes(".local"))).toBe(false); + expect(result.some((p) => p.includes(".npm-global"))).toBe(false); + expect(result.some((p) => p.includes(".nvm"))).toBe(false); + }); + + it("places user directories before system directories on Linux", () => { + const result = getMinimalServicePathParts({ + platform: "linux", + home: "/home/testuser", + }); + + const userDirIndex = result.indexOf("/home/testuser/.local/bin"); + const systemDirIndex = result.indexOf("/usr/bin"); + + expect(userDirIndex).toBeGreaterThan(-1); + expect(systemDirIndex).toBeGreaterThan(-1); + expect(userDirIndex).toBeLessThan(systemDirIndex); + }); + + it("places extraDirs before user directories on Linux", () => { + const result = getMinimalServicePathParts({ + platform: "linux", + home: "/home/testuser", + extraDirs: ["/custom/bin"], + }); + + const extraDirIndex = result.indexOf("/custom/bin"); + const userDirIndex = result.indexOf("/home/testuser/.local/bin"); + + expect(extraDirIndex).toBeGreaterThan(-1); + expect(userDirIndex).toBeGreaterThan(-1); + expect(extraDirIndex).toBeLessThan(userDirIndex); + }); + + it("does not include Linux user directories on macOS", () => { + const result = getMinimalServicePathParts({ + platform: "darwin", + home: "/Users/testuser", + }); + + // Should not include Linux-specific user dirs even with HOME set + expect(result.some((p) => p.includes(".npm-global"))).toBe(false); + expect(result.some((p) => p.includes(".nvm"))).toBe(false); + + // Should only include macOS system directories + expect(result).toContain("/opt/homebrew/bin"); + expect(result).toContain("/usr/local/bin"); + }); + + it("does not include Linux user directories on Windows", () => { + const result = getMinimalServicePathParts({ + platform: "win32", + home: "C:\\Users\\testuser", + }); + + // Windows returns empty array (uses existing PATH) + expect(result).toEqual([]); + }); +}); + describe("buildMinimalServicePath", () => { it("includes Homebrew + system dirs on macOS", () => { const result = buildMinimalServicePath({ @@ -26,6 +154,51 @@ describe("buildMinimalServicePath", () => { expect(result).toBe("C:\\\\Windows\\\\System32"); }); + it("includes Linux user directories when HOME is set in env", () => { + const result = buildMinimalServicePath({ + platform: "linux", + env: { HOME: "/home/alice" }, + }); + const parts = result.split(path.delimiter); + + // Verify user directories are included + expect(parts).toContain("/home/alice/.local/bin"); + expect(parts).toContain("/home/alice/.npm-global/bin"); + expect(parts).toContain("/home/alice/.nvm/current/bin"); + + // Verify system directories are also included + expect(parts).toContain("/usr/local/bin"); + expect(parts).toContain("/usr/bin"); + expect(parts).toContain("/bin"); + }); + + it("excludes Linux user directories when HOME is not in env", () => { + const result = buildMinimalServicePath({ + platform: "linux", + env: {}, + }); + const parts = result.split(path.delimiter); + + // Should only have system directories + expect(parts).toEqual(["/usr/local/bin", "/usr/bin", "/bin"]); + + // No user-specific paths + expect(parts.some((p) => p.includes("home"))).toBe(false); + }); + + it("ensures user directories come before system directories on Linux", () => { + const result = buildMinimalServicePath({ + platform: "linux", + env: { HOME: "/home/bob" }, + }); + const parts = result.split(path.delimiter); + + const firstUserDirIdx = parts.indexOf("/home/bob/.local/bin"); + const firstSystemDirIdx = parts.indexOf("/usr/local/bin"); + + expect(firstUserDirIdx).toBeLessThan(firstSystemDirIdx); + }); + it("includes extra directories when provided", () => { const result = buildMinimalServicePath({ platform: "linux", diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 4fe78800c..a6d184e67 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -38,7 +38,7 @@ function resolveSystemPathDirs(platform: NodeJS.Platform): string[] { * Resolve common user bin directories for Linux. * These are paths where npm global installs and node version managers typically place binaries. */ -function resolveLinuxUserBinDirs(home: string | undefined): string[] { +export function resolveLinuxUserBinDirs(home: string | undefined): string[] { if (!home) return []; const dirs: string[] = [];