ci/woodpecker/pr/ci Pipeline was successful
getShellProfilePath() preferred ~/.bashrc when it existed, and ~/.zshrc for
zsh. setupPath() in stages/finalize.ts appends the PATH export to whatever
it returns. Debian's default ~/.bashrc opens with
case $- in *i*) ;; *) return;; esac
so a line appended to the bottom of it never runs for 'bash -lc', for
systemd units, for 'ssh host cmd', or for any agent seat — precisely the
consumers that need the CLI. An install could print its summary and exit 0
while leaving 'mosaic: command not found'. .zshrc has the same problem:
zsh only reads it for interactive shells.
Now ~/.profile, which login shells read and which Debian's copy sources
.bashrc from for interactive shells, so one line covers both. For zsh the
always-sourced file is .zshenv. fish and PowerShell are unchanged.
__tests__/platform/detect.test.ts pins it, including a case asserting that
no shell resolves to an interactive-only rc file. Falsified by inverting
the fix: 5 failed / 1 passed; restored 6/6. Full package suite unchanged at
17 files / 4 tests failing, matching clean origin/next.
75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
// homedir/platform are read at call time, so they can be stubbed per case.
|
|
vi.mock('node:os', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('node:os')>();
|
|
return {
|
|
...actual,
|
|
homedir: () => '/home/tester',
|
|
platform: () => mockPlatform,
|
|
};
|
|
});
|
|
|
|
let mockPlatform: NodeJS.Platform = 'linux';
|
|
|
|
const { getShellProfilePath, detectShell } = await import('../../src/platform/detect.js');
|
|
|
|
describe('getShellProfilePath', () => {
|
|
const originalShell = process.env['SHELL'];
|
|
const originalZdotdir = process.env['ZDOTDIR'];
|
|
|
|
beforeEach(() => {
|
|
mockPlatform = 'linux';
|
|
delete process.env['ZDOTDIR'];
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalShell === undefined) delete process.env['SHELL'];
|
|
else process.env['SHELL'] = originalShell;
|
|
if (originalZdotdir === undefined) delete process.env['ZDOTDIR'];
|
|
else process.env['ZDOTDIR'] = originalZdotdir;
|
|
});
|
|
|
|
// The regression this guards: setupPath() in stages/finalize.ts appends the
|
|
// PATH export to whatever this returns. A line written to ~/.bashrc is
|
|
// unreachable to `bash -lc`, systemd units and agent seats, because Debian's
|
|
// default .bashrc returns early for non-interactive shells — so an install
|
|
// reported success and left `mosaic: command not found`. Same for .zshrc,
|
|
// which zsh only reads for interactive shells.
|
|
it('never targets an interactive-only rc file', () => {
|
|
for (const shell of ['/bin/bash', '/usr/bin/zsh']) {
|
|
process.env['SHELL'] = shell;
|
|
const profile = getShellProfilePath();
|
|
expect(profile).not.toMatch(/\.bashrc$/);
|
|
expect(profile).not.toMatch(/\.zshrc$/);
|
|
}
|
|
});
|
|
|
|
it('uses ~/.profile for bash', () => {
|
|
process.env['SHELL'] = '/bin/bash';
|
|
expect(getShellProfilePath()).toBe('/home/tester/.profile');
|
|
});
|
|
|
|
it('uses ~/.zshenv for zsh', () => {
|
|
process.env['SHELL'] = '/usr/bin/zsh';
|
|
expect(getShellProfilePath()).toBe('/home/tester/.zshenv');
|
|
});
|
|
|
|
it('honours ZDOTDIR for zsh', () => {
|
|
process.env['SHELL'] = '/usr/bin/zsh';
|
|
process.env['ZDOTDIR'] = '/custom/zdot';
|
|
expect(getShellProfilePath()).toBe('/custom/zdot/.zshenv');
|
|
});
|
|
|
|
it('falls back to ~/.profile for an unknown shell', () => {
|
|
process.env['SHELL'] = '/bin/somethingelse';
|
|
expect(detectShell()).toBe('unknown');
|
|
expect(getShellProfilePath()).toBe('/home/tester/.profile');
|
|
});
|
|
|
|
it('still routes fish to its own config', () => {
|
|
process.env['SHELL'] = '/usr/bin/fish';
|
|
expect(getShellProfilePath()).toBe('/home/tester/.config/fish/config.fish');
|
|
});
|
|
});
|