diff --git a/src/daemon/service-env.test.ts b/src/daemon/service-env.test.ts index 9edb2d84d03..79216dadcf0 100644 --- a/src/daemon/service-env.test.ts +++ b/src/daemon/service-env.test.ts @@ -172,6 +172,102 @@ describe("getMinimalServicePathParts - Linux user directories", () => { }); }); +describe("getMinimalServicePathParts - Nix Home Manager", () => { + it("falls back to default Nix profile when NIX_PROFILES is absent on Linux", () => { + const result = getMinimalServicePathParts({ + platform: "linux", + home: "/home/testuser", + }); + + expect(result).toContain("/home/testuser/.nix-profile/bin"); + }); + + it("falls back to default Nix profile when NIX_PROFILES is absent on macOS", () => { + const result = getMinimalServicePathParts({ + platform: "darwin", + home: "/Users/testuser", + }); + + expect(result).toContain("/Users/testuser/.nix-profile/bin"); + }); + + it("places rightmost NIX_PROFILES entry before leftmost on Linux", () => { + const result = getMinimalServicePathPartsFromEnv({ + platform: "linux", + env: { + HOME: "/home/testuser", + NIX_PROFILES: "/nix/var/nix/profiles/default /home/testuser/.nix-profile", + }, + }); + + const userIdx = result.indexOf("/home/testuser/.nix-profile/bin"); + const defaultIdx = result.indexOf("/nix/var/nix/profiles/default/bin"); + expect(userIdx).toBeGreaterThan(-1); + expect(defaultIdx).toBeGreaterThan(-1); + expect(userIdx).toBeLessThan(defaultIdx); + }); + + it("places rightmost NIX_PROFILES entry before leftmost on macOS", () => { + const result = getMinimalServicePathPartsFromEnv({ + platform: "darwin", + env: { + HOME: "/Users/testuser", + NIX_PROFILES: "/nix/var/nix/profiles/default /Users/testuser/.nix-profile", + }, + }); + + const userIdx = result.indexOf("/Users/testuser/.nix-profile/bin"); + const defaultIdx = result.indexOf("/nix/var/nix/profiles/default/bin"); + expect(userIdx).toBeGreaterThan(-1); + expect(defaultIdx).toBeGreaterThan(-1); + expect(userIdx).toBeLessThan(defaultIdx); + }); + + it("includes single Nix profile from NIX_PROFILES on Linux", () => { + const result = getMinimalServicePathPartsFromEnv({ + platform: "linux", + env: { + HOME: "/home/testuser", + NIX_PROFILES: "/nix/var/nix/profiles/per-user/testuser/profile", + }, + }); + + expect(result).toContain("/nix/var/nix/profiles/per-user/testuser/profile/bin"); + }); + + it("includes single Nix profile from NIX_PROFILES on macOS", () => { + const result = getMinimalServicePathPartsFromEnv({ + platform: "darwin", + env: { + HOME: "/Users/testuser", + NIX_PROFILES: "/nix/var/nix/profiles/per-user/testuser/profile", + }, + }); + + expect(result).toContain("/nix/var/nix/profiles/per-user/testuser/profile/bin"); + }); + + it("preserves Nix precedence across three profiles", () => { + const result = getMinimalServicePathPartsFromEnv({ + platform: "linux", + env: { + HOME: "/home/testuser", + NIX_PROFILES: + "/nix/var/nix/profiles/default /nix/var/nix/profiles/per-user/testuser/custom /home/testuser/.nix-profile", + }, + }); + + const userIdx = result.indexOf("/home/testuser/.nix-profile/bin"); + const customIdx = result.indexOf("/nix/var/nix/profiles/per-user/testuser/custom/bin"); + const defaultIdx = result.indexOf("/nix/var/nix/profiles/default/bin"); + expect(userIdx).toBeGreaterThan(-1); + expect(customIdx).toBeGreaterThan(-1); + expect(defaultIdx).toBeGreaterThan(-1); + expect(userIdx).toBeLessThan(customIdx); + expect(customIdx).toBeLessThan(defaultIdx); + }); +}); + describe("buildMinimalServicePath", () => { const splitPath = (value: string, platform: NodeJS.Platform) => value.split(platform === "win32" ? path.win32.delimiter : path.posix.delimiter); diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 0c238df5a6c..3460af66865 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -106,6 +106,23 @@ function addCommonEnvConfiguredBinDirs( addNonEmptyDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims")); } +// Nix shell precedence: rightmost profile in NIX_PROFILES = highest priority. +// When NIX_PROFILES is absent, fall back to the default single-user profile. +function addNixProfileBinDirs( + dirs: string[], + home: string, + env: Record | undefined, +): void { + const nixProfiles = env?.NIX_PROFILES?.trim(); + if (nixProfiles) { + for (const profile of nixProfiles.split(/\s+/).toReversed()) { + addNonEmptyDir(dirs, appendSubdir(profile, "bin")); + } + } else { + dirs.push(`${home}/.nix-profile/bin`); + } +} + function resolveSystemPathDirs(platform: NodeJS.Platform): string[] { if (platform === "darwin") { return ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"]; @@ -148,6 +165,9 @@ export function resolveDarwinUserBinDirs( // Common user bin directories addCommonUserBinDirs(dirs, home); + // Nix Home Manager (cross-platform) + addNixProfileBinDirs(dirs, home, env); + // Node version managers - macOS specific paths // nvm: no stable default path, depends on user's shell configuration // fnm: macOS default is ~/Library/Application Support/fnm, not ~/.fnm @@ -182,6 +202,9 @@ export function resolveLinuxUserBinDirs( // Common user bin directories addCommonUserBinDirs(dirs, home); + // Nix Home Manager (cross-platform) + addNixProfileBinDirs(dirs, home, env); + // Node version managers dirs.push(`${home}/.nvm/current/bin`); // nvm with current symlink dirs.push(`${home}/.fnm/current/bin`); // fnm