fix: preserve user PATH for managed opencode
This commit is contained in:
+11
-19
@@ -715,24 +715,14 @@ const inheritUserShellEnv = () => {
|
|||||||
if (!shellEnv) return;
|
if (!shellEnv) return;
|
||||||
|
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
const shellPathSegments = typeof shellEnv.PATH === 'string' ? shellEnv.PATH.split(':') : [];
|
const currentPath = process.env.PATH || '';
|
||||||
const processPathSegments = typeof process.env.PATH === 'string' ? process.env.PATH.split(':') : [];
|
const currentPathLooksUserConfigured = currentPath.split(':').some((segment) => (
|
||||||
const pathSegments = [
|
segment.startsWith(`${homeDir}${path.sep}`)
|
||||||
...shellPathSegments,
|
|| segment === homeDir
|
||||||
'/opt/homebrew/bin',
|
|| segment.startsWith('/opt/homebrew/')
|
||||||
'/usr/local/bin',
|
|| segment.startsWith('/opt/pkg/')
|
||||||
'/usr/bin',
|
|| segment.startsWith('/opt/pmk/')
|
||||||
'/bin',
|
));
|
||||||
'/usr/sbin',
|
|
||||||
'/sbin',
|
|
||||||
path.join(homeDir, '.opencode', 'bin'),
|
|
||||||
path.join(homeDir, '.local', 'bin'),
|
|
||||||
path.join(homeDir, '.bun', 'bin'),
|
|
||||||
path.join(homeDir, '.cargo', 'bin'),
|
|
||||||
path.join(homeDir, 'bin'),
|
|
||||||
...processPathSegments,
|
|
||||||
].filter(Boolean);
|
|
||||||
const uniquePath = Array.from(new Set(pathSegments)).join(':');
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(shellEnv)) {
|
for (const [key, value] of Object.entries(shellEnv)) {
|
||||||
if (key === 'PATH') continue;
|
if (key === 'PATH') continue;
|
||||||
@@ -740,7 +730,9 @@ const inheritUserShellEnv = () => {
|
|||||||
process.env[key] = value;
|
process.env[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
process.env.PATH = uniquePath;
|
if (!currentPathLooksUserConfigured && typeof shellEnv.PATH === 'string' && shellEnv.PATH.length > 0) {
|
||||||
|
process.env.PATH = shellEnv.PATH;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const spawnLocalServer = async () => {
|
const spawnLocalServer = async () => {
|
||||||
|
|||||||
@@ -756,6 +756,7 @@ const serverUtilsRuntime = createServerUtilsRuntime({
|
|||||||
const setOpenCodePort = (...args) => serverUtilsRuntime.setOpenCodePort(...args);
|
const setOpenCodePort = (...args) => serverUtilsRuntime.setOpenCodePort(...args);
|
||||||
const waitForOpenCodePort = (...args) => serverUtilsRuntime.waitForOpenCodePort(...args);
|
const waitForOpenCodePort = (...args) => serverUtilsRuntime.waitForOpenCodePort(...args);
|
||||||
const buildAugmentedPath = (...args) => serverUtilsRuntime.buildAugmentedPath(...args);
|
const buildAugmentedPath = (...args) => serverUtilsRuntime.buildAugmentedPath(...args);
|
||||||
|
const buildManagedOpenCodePath = (...args) => serverUtilsRuntime.buildManagedOpenCodePath(...args);
|
||||||
const parseSseDataPayload = (...args) => serverUtilsRuntime.parseSseDataPayload(...args);
|
const parseSseDataPayload = (...args) => serverUtilsRuntime.parseSseDataPayload(...args);
|
||||||
const staticRoutesRuntime = createStaticRoutesRuntime({
|
const staticRoutesRuntime = createStaticRoutesRuntime({
|
||||||
fs,
|
fs,
|
||||||
@@ -873,6 +874,8 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
|
|||||||
setupProxy: (...args) => setupProxy(...args),
|
setupProxy: (...args) => setupProxy(...args),
|
||||||
ensureOpenCodeApiPrefix,
|
ensureOpenCodeApiPrefix,
|
||||||
clearResolvedOpenCodeBinary,
|
clearResolvedOpenCodeBinary,
|
||||||
|
buildAugmentedPath,
|
||||||
|
buildManagedOpenCodePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
const restartOpenCode = (...args) => openCodeLifecycleRuntime.restartOpenCode(...args);
|
const restartOpenCode = (...args) => openCodeLifecycleRuntime.restartOpenCode(...args);
|
||||||
|
|||||||
@@ -162,24 +162,19 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergePathValues = (preferred, fallback) => {
|
const pathLooksUserConfigured = (value) => {
|
||||||
const merged = new Set();
|
if (typeof value !== 'string' || !value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const addSegments = (value) => {
|
const home = os.homedir();
|
||||||
if (typeof value !== 'string' || !value) {
|
return value.split(path.delimiter).some((segment) => (
|
||||||
return;
|
segment.startsWith(home + path.sep)
|
||||||
}
|
|| segment === home
|
||||||
for (const segment of value.split(path.delimiter)) {
|
|| segment.startsWith('/opt/homebrew/')
|
||||||
if (segment) {
|
|| segment.startsWith('/opt/pkg/')
|
||||||
merged.add(segment);
|
|| segment.startsWith('/opt/pmk/')
|
||||||
}
|
));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
addSegments(preferred);
|
|
||||||
addSegments(fallback);
|
|
||||||
|
|
||||||
return Array.from(merged).join(path.delimiter);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyLoginShellEnvSnapshot = () => {
|
const applyLoginShellEnvSnapshot = () => {
|
||||||
@@ -200,7 +195,11 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|||||||
process.env[key] = value;
|
process.env[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.PATH = mergePathValues(snapshot.PATH || '', process.env.PATH || '');
|
const currentPath = process.env.PATH || '';
|
||||||
|
const shellPath = snapshot.PATH || '';
|
||||||
|
if (!pathLooksUserConfigured(currentPath) && shellPath) {
|
||||||
|
process.env.PATH = shellPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isWslExecutableValue = (value) => {
|
const isWslExecutableValue = (value) => {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|||||||
setupProxy,
|
setupProxy,
|
||||||
ensureOpenCodeApiPrefix,
|
ensureOpenCodeApiPrefix,
|
||||||
clearResolvedOpenCodeBinary,
|
clearResolvedOpenCodeBinary,
|
||||||
|
buildAugmentedPath,
|
||||||
|
buildManagedOpenCodePath,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
const killProcessOnPort = (port) => {
|
const killProcessOnPort = (port) => {
|
||||||
@@ -389,6 +391,11 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|||||||
await applyOpencodeBinaryFromSettings();
|
await applyOpencodeBinaryFromSettings();
|
||||||
ensureOpencodeCliEnv();
|
ensureOpencodeCliEnv();
|
||||||
const openCodePassword = await ensureLocalOpenCodeServerPassword({ rotateManaged: true });
|
const openCodePassword = await ensureLocalOpenCodeServerPassword({ rotateManaged: true });
|
||||||
|
const envPath = typeof buildManagedOpenCodePath === 'function'
|
||||||
|
? buildManagedOpenCodePath()
|
||||||
|
: typeof buildAugmentedPath === 'function'
|
||||||
|
? buildAugmentedPath()
|
||||||
|
: process.env.PATH;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serverInstance = await createManagedOpenCodeServerProcess({
|
const serverInstance = await createManagedOpenCodeServerProcess({
|
||||||
@@ -398,6 +405,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|||||||
cwd: state.openCodeWorkingDirectory,
|
cwd: state.openCodeWorkingDirectory,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
PATH: envPath,
|
||||||
OPENCODE_SERVER_PASSWORD: openCodePassword,
|
OPENCODE_SERVER_PASSWORD: openCodePassword,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const spawnMock = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('node:child_process', () => ({
|
||||||
|
spawn: spawnMock,
|
||||||
|
spawnSync: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { createOpenCodeLifecycleRuntime } = await import('./lifecycle.js');
|
||||||
|
|
||||||
|
const originalOpencodeBinary = process.env.OPENCODE_BINARY;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
spawnMock.mockReset();
|
||||||
|
if (typeof originalOpencodeBinary === 'string') {
|
||||||
|
process.env.OPENCODE_BINARY = originalOpencodeBinary;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete process.env.OPENCODE_BINARY;
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMockChild = () => {
|
||||||
|
const child = new EventEmitter();
|
||||||
|
child.stdout = new EventEmitter();
|
||||||
|
child.stderr = new EventEmitter();
|
||||||
|
child.exitCode = null;
|
||||||
|
child.signalCode = null;
|
||||||
|
child.pid = 12345;
|
||||||
|
child.kill = vi.fn(() => {
|
||||||
|
child.signalCode = 'SIGTERM';
|
||||||
|
queueMicrotask(() => child.emit('close', null, 'SIGTERM'));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return child;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRuntime = (overrides = {}) => {
|
||||||
|
const state = {
|
||||||
|
openCodeWorkingDirectory: '/tmp/project',
|
||||||
|
openCodeProcess: null,
|
||||||
|
openCodePort: null,
|
||||||
|
openCodeBaseUrl: null,
|
||||||
|
currentRestartPromise: null,
|
||||||
|
isRestartingOpenCode: false,
|
||||||
|
openCodeApiPrefix: '',
|
||||||
|
openCodeApiPrefixDetected: false,
|
||||||
|
openCodeApiDetectionTimer: null,
|
||||||
|
lastOpenCodeError: null,
|
||||||
|
isOpenCodeReady: false,
|
||||||
|
openCodeNotReadySince: 0,
|
||||||
|
isExternalOpenCode: false,
|
||||||
|
isShuttingDown: false,
|
||||||
|
healthCheckInterval: null,
|
||||||
|
expressApp: null,
|
||||||
|
useWslForOpencode: false,
|
||||||
|
resolvedWslBinary: null,
|
||||||
|
resolvedWslOpencodePath: null,
|
||||||
|
resolvedWslDistro: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return createOpenCodeLifecycleRuntime({
|
||||||
|
state,
|
||||||
|
env: {
|
||||||
|
ENV_CONFIGURED_OPENCODE_PORT: 45678,
|
||||||
|
ENV_CONFIGURED_OPENCODE_HOST: null,
|
||||||
|
ENV_EFFECTIVE_PORT: 3001,
|
||||||
|
ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1',
|
||||||
|
ENV_SKIP_OPENCODE_START: false,
|
||||||
|
},
|
||||||
|
syncToHmrState: vi.fn(),
|
||||||
|
syncFromHmrState: vi.fn(),
|
||||||
|
getOpenCodeAuthHeaders: () => ({}),
|
||||||
|
buildOpenCodeUrl: (route) => `http://127.0.0.1:45678${route}`,
|
||||||
|
waitForReady: vi.fn(async () => true),
|
||||||
|
normalizeApiPrefix: vi.fn(() => ''),
|
||||||
|
applyOpencodeBinaryFromSettings: vi.fn(async () => null),
|
||||||
|
ensureOpencodeCliEnv: vi.fn(),
|
||||||
|
ensureLocalOpenCodeServerPassword: vi.fn(async () => 'password'),
|
||||||
|
buildWslExecArgs: vi.fn((args) => args),
|
||||||
|
resolveWslExecutablePath: vi.fn(),
|
||||||
|
resolveManagedOpenCodeLaunchSpec: vi.fn((binary) => ({ binary, args: [], wrapperType: null })),
|
||||||
|
setOpenCodePort: vi.fn((port) => {
|
||||||
|
state.openCodePort = port;
|
||||||
|
}),
|
||||||
|
setDetectedOpenCodeApiPrefix: vi.fn(),
|
||||||
|
setupProxy: vi.fn(),
|
||||||
|
ensureOpenCodeApiPrefix: vi.fn(),
|
||||||
|
clearResolvedOpenCodeBinary: vi.fn(),
|
||||||
|
buildAugmentedPath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
|
||||||
|
buildManagedOpenCodePath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('OpenCode lifecycle', () => {
|
||||||
|
it('launches managed OpenCode with the managed PATH', async () => {
|
||||||
|
delete process.env.OPENCODE_BINARY;
|
||||||
|
const child = createMockChild();
|
||||||
|
spawnMock.mockImplementationOnce(() => {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n');
|
||||||
|
});
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
const runtime = createRuntime();
|
||||||
|
const server = await runtime.startOpenCode();
|
||||||
|
const [binary, args, options] = spawnMock.mock.calls[0];
|
||||||
|
|
||||||
|
expect(binary).toBe('opencode');
|
||||||
|
expect(args).toEqual(['serve', '--hostname', '127.0.0.1', '--port', '45678']);
|
||||||
|
expect(options.env.PATH).toBe('/home/user/.bun/bin:/usr/local/bin:/usr/bin');
|
||||||
|
expect(options.env.OPENCODE_SERVER_PASSWORD).toBe('password');
|
||||||
|
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -45,6 +45,21 @@ export const createServerUtilsRuntime = (dependencies) => {
|
|||||||
clearLastOpenCodeError();
|
clearLastOpenCodeError();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pathLooksUserConfigured = (value) => {
|
||||||
|
if (typeof value !== 'string' || !value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const home = os.homedir();
|
||||||
|
return value.split(path.delimiter).some((segment) => (
|
||||||
|
segment.startsWith(home + path.sep)
|
||||||
|
|| segment === home
|
||||||
|
|| segment.startsWith('/opt/homebrew/')
|
||||||
|
|| segment.startsWith('/opt/pkg/')
|
||||||
|
|| segment.startsWith('/opt/pmk/')
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
const waitForOpenCodePort = async (timeoutMs = 15000) => {
|
const waitForOpenCodePort = async (timeoutMs = 15000) => {
|
||||||
if (getOpenCodePort() !== null) {
|
if (getOpenCodePort() !== null) {
|
||||||
return getOpenCodePort();
|
return getOpenCodePort();
|
||||||
@@ -62,23 +77,40 @@ export const createServerUtilsRuntime = (dependencies) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildAugmentedPath = () => {
|
const buildAugmentedPath = () => {
|
||||||
const augmented = new Set();
|
const home = os.homedir();
|
||||||
|
const currentPath = process.env.PATH || '';
|
||||||
const loginShellPath = getLoginShellPath();
|
const loginShellPath = getLoginShellPath();
|
||||||
if (loginShellPath) {
|
const currentPathLooksUserConfigured = pathLooksUserConfigured(currentPath);
|
||||||
for (const segment of loginShellPath.split(path.delimiter)) {
|
const primaryPath = currentPathLooksUserConfigured ? currentPath : loginShellPath;
|
||||||
if (segment) {
|
const fallbackPath = currentPathLooksUserConfigured ? loginShellPath : currentPath;
|
||||||
augmented.add(segment);
|
const seen = new Set();
|
||||||
|
const augmented = [];
|
||||||
|
|
||||||
|
const addSegments = (value) => {
|
||||||
|
if (typeof value !== 'string' || !value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const segment of value.split(path.delimiter)) {
|
||||||
|
if (segment && !seen.has(segment)) {
|
||||||
|
seen.add(segment);
|
||||||
|
augmented.push(segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addSegments(primaryPath);
|
||||||
|
addSegments(fallbackPath);
|
||||||
|
|
||||||
|
return augmented.join(path.delimiter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildManagedOpenCodePath = () => {
|
||||||
|
const currentPath = process.env.PATH || '';
|
||||||
|
if (pathLooksUserConfigured(currentPath)) {
|
||||||
|
return currentPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
return getLoginShellPath() || currentPath;
|
||||||
for (const segment of current) {
|
|
||||||
augmented.add(segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(augmented).join(path.delimiter);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseSseDataPayload = (block) => {
|
const parseSseDataPayload = (block) => {
|
||||||
@@ -159,6 +191,7 @@ export const createServerUtilsRuntime = (dependencies) => {
|
|||||||
setOpenCodePort,
|
setOpenCodePort,
|
||||||
waitForOpenCodePort,
|
waitForOpenCodePort,
|
||||||
buildAugmentedPath,
|
buildAugmentedPath,
|
||||||
|
buildManagedOpenCodePath,
|
||||||
parseSseDataPayload,
|
parseSseDataPayload,
|
||||||
fetchAgentsSnapshot,
|
fetchAgentsSnapshot,
|
||||||
fetchProvidersSnapshot,
|
fetchProvidersSnapshot,
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { createServerUtilsRuntime } from './server-utils-runtime.js';
|
||||||
|
|
||||||
|
const originalPath = process.env.PATH;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.PATH = originalPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
const createRuntime = (loginShellPath) => createServerUtilsRuntime({
|
||||||
|
fs: {},
|
||||||
|
os,
|
||||||
|
path,
|
||||||
|
process,
|
||||||
|
openCodeReadyGraceMs: 0,
|
||||||
|
longRequestTimeoutMs: 0,
|
||||||
|
getRuntime: () => ({}),
|
||||||
|
getOpenCodeAuthHeaders: () => ({}),
|
||||||
|
buildOpenCodeUrl: (route) => route,
|
||||||
|
ensureOpenCodeApiPrefix: () => {},
|
||||||
|
getUiNotificationClients: () => new Set(),
|
||||||
|
getOpenCodePort: () => null,
|
||||||
|
setOpenCodePortState: () => {},
|
||||||
|
syncToHmrState: () => {},
|
||||||
|
markOpenCodeNotReady: () => {},
|
||||||
|
setOpenCodeNotReadySince: () => {},
|
||||||
|
clearLastOpenCodeError: () => {},
|
||||||
|
getLoginShellPath: () => loginShellPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('server utils runtime', () => {
|
||||||
|
it('keeps managed OpenCode PATH literal when process PATH is user-configured', () => {
|
||||||
|
const home = os.homedir();
|
||||||
|
const currentPath = [
|
||||||
|
path.join(home, '.opencode', 'bin'),
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
path.join(home, 'Library', 'pnpm'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
].join(path.delimiter);
|
||||||
|
process.env.PATH = currentPath;
|
||||||
|
|
||||||
|
const runtime = createRuntime([
|
||||||
|
path.join(home, '.opencode', 'bin'),
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
path.join(home, '.cargo', 'bin'),
|
||||||
|
].join(path.delimiter));
|
||||||
|
|
||||||
|
expect(runtime.buildManagedOpenCodePath()).toBe(currentPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses login shell PATH for managed OpenCode when process PATH is minimal', () => {
|
||||||
|
const home = os.homedir();
|
||||||
|
const loginShellPath = [
|
||||||
|
path.join(home, '.opencode', 'bin'),
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
].join(path.delimiter);
|
||||||
|
process.env.PATH = ['/usr/local/bin', '/usr/bin', '/bin'].join(path.delimiter);
|
||||||
|
|
||||||
|
const runtime = createRuntime(loginShellPath);
|
||||||
|
|
||||||
|
expect(runtime.buildManagedOpenCodePath()).toBe(loginShellPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves user-configured process PATH order before appending shell-only entries', () => {
|
||||||
|
const home = os.homedir();
|
||||||
|
process.env.PATH = [
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
path.join(home, 'Library', 'pnpm'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
].join(path.delimiter);
|
||||||
|
|
||||||
|
const runtime = createRuntime([
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
path.join(home, '.cargo', 'bin'),
|
||||||
|
'/usr/bin',
|
||||||
|
].join(path.delimiter));
|
||||||
|
|
||||||
|
expect(runtime.buildAugmentedPath()).toBe([
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
path.join(home, 'Library', 'pnpm'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
path.join(home, '.cargo', 'bin'),
|
||||||
|
].join(path.delimiter));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prefers login shell PATH when current process PATH is minimal', () => {
|
||||||
|
const home = os.homedir();
|
||||||
|
process.env.PATH = ['/usr/local/bin', '/usr/bin', '/bin'].join(path.delimiter);
|
||||||
|
|
||||||
|
const runtime = createRuntime([
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
].join(path.delimiter));
|
||||||
|
|
||||||
|
expect(runtime.buildAugmentedPath()).toBe([
|
||||||
|
path.join(home, '.bun', 'bin'),
|
||||||
|
'/opt/homebrew/bin',
|
||||||
|
'/usr/bin',
|
||||||
|
'/usr/local/bin',
|
||||||
|
'/bin',
|
||||||
|
].join(path.delimiter));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user