Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/__tests__/env-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,66 @@ scope: 'user',
});
});

// ─── detectShellProfile ──────────────────────────────────

describe('detectShellProfile', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('should return .zshrc when SHELL contains zsh', () => {
vi.stubEnv('SHELL', '/bin/zsh');
expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.zshrc'));
});

it('should return .bashrc on non-Windows when SHELL is unset (unchanged behavior)', () => {
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('linux');
expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.bashrc'));
});

it('should prefer an existing .bash_profile on Windows over .bashrc (#682)', async () => {
// Windows never sets SHELL, and Git Bash's default launch is a login
// shell, which reads .bash_profile — never .bashrc.
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
await fse.writeFile(path.join(homeDir, '.bash_profile'), '# existing\n');

expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.bash_profile'));
});

it('should fall back to .bash_login on Windows when only that exists', async () => {
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
await fse.writeFile(path.join(homeDir, '.bash_login'), '# existing\n');

expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.bash_login'));
});

it('should fall back to .profile on Windows when only that exists', async () => {
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
await fse.writeFile(path.join(homeDir, '.profile'), '# existing\n');

expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.profile'));
});

it('should default to .bash_profile on Windows when none of the login files exist', () => {
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');

expect(handler.detectShellProfile()).toBe(path.join(homeDir, '.bash_profile'));
});

it('should never return .bashrc on Windows, even if it exists alongside login files', async () => {
vi.stubEnv('SHELL', '');
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
await fse.writeFile(path.join(homeDir, '.bashrc'), '# existing\n');

expect(handler.detectShellProfile()).not.toBe(path.join(homeDir, '.bashrc'));
});
});

// ─── pullItem ────────────────────────────────────────────

describe('pullItem', () => {
Expand Down
18 changes: 18 additions & 0 deletions src/resources/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'node:path';
import { existsSync } from 'node:fs';
import { z } from 'zod';
import YAML from 'yaml';
import { ResourceHandler } from './base.js';
Expand Down Expand Up @@ -345,6 +346,23 @@ export class EnvHandler extends ResourceHandler {
if (shell.includes('zsh')) {
return path.join(home, '.zshrc');
}

// SHELL is a POSIX convention that Windows never sets, so this bash
// fallback used to always mean .bashrc — but Git Bash on Windows starts
// as a login shell (its default launch is effectively `bash --login`),
// and a login shell reads .bash_profile / .bash_login / .profile, never
// .bashrc. Prefer whichever of those already exists (bash's own lookup
// order); if none do, create .bash_profile, the file most Windows dev
// tooling (nvm-windows, etc.) already targets. Scoped to win32 only —
// non-Windows bash is correctly a non-login shell here and keeps .bashrc.
if (process.platform === 'win32') {
for (const name of ['.bash_profile', '.bash_login', '.profile']) {
const candidate = path.join(home, name);
if (existsSync(candidate)) return candidate;
}
return path.join(home, '.bash_profile');
}

return path.join(home, '.bashrc');
}

Expand Down
Loading