blob: 0e96c1f626654f4b1a8f83a31455b4f13fbaa437 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { buildPtyShellSpawnPlan, buildShellSpawnPlan, detectShell } from '../shell-detect.js';
const winEnv = (over: Record<string, string> = {}) => ({
Path: 'C:\\Windows\\System32;C:\\Users\\u\\bin',
ProgramFiles: 'C:\\Program Files',
SystemRoot: 'C:\\Windows',
...over,
});
const existsIn =
(...paths: string[]) =>
(p: string) =>
paths.includes(p);
describe('detectShell', () => {
test('on win32 picks pwsh from PATH ahead of everything else', () => {
const plan = detectShell({
platform: 'win32',
env: winEnv(),
fileExists: existsIn(
'C:\\Users\\u\\bin\\pwsh.exe',
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
),
});
assert.equal(plan.kind, 'pwsh');
assert.equal(plan.exe, 'C:\\Users\\u\\bin\\pwsh.exe');
});
test('on win32 finds pwsh at its default install location when not on PATH', () => {
const plan = detectShell({
platform: 'win32',
env: winEnv(),
fileExists: existsIn('C:\\Program Files\\PowerShell\\7\\pwsh.exe'),
});
assert.equal(plan.kind, 'pwsh');
assert.equal(plan.exe, 'C:\\Program Files\\PowerShell\\7\\pwsh.exe');
});
test('on win32 falls back to Windows PowerShell 5.1 when pwsh is absent', () => {
const plan = detectShell({
platform: 'win32',
env: winEnv(),
fileExists: existsIn('C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'),
});
assert.equal(plan.kind, 'powershell');
assert.equal(plan.exe, 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
});
test('on win32 falls back to cmd.exe when no PowerShell exists', () => {
const plan = detectShell({ platform: 'win32', env: winEnv(), fileExists: () => false });
assert.deepEqual(plan, { kind: 'cmd', displayName: 'cmd.exe' });
});
test('on POSIX platforms keeps the system default shell', () => {
const plan = detectShell({ platform: 'darwin', env: {}, fileExists: () => false });
assert.deepEqual(plan, { kind: 'posix', displayName: '/bin/sh' });
});
});
describe('buildShellSpawnPlan', () => {
test('spawns PowerShell explicitly with non-interactive flags and the command as one argument', () => {
const spawnPlan = buildShellSpawnPlan(
{ kind: 'pwsh', displayName: 'PowerShell 7 (pwsh)', exe: 'C:\\Users\\u\\bin\\pwsh.exe' },
'Get-ChildItem -Name',
);
assert.equal(spawnPlan.file, 'C:\\Users\\u\\bin\\pwsh.exe');
assert.equal(spawnPlan.useShellOption, false);
assert.deepEqual(spawnPlan.args.slice(0, 4), [
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-Command',
]);
assert.ok(
spawnPlan.args[4]!.startsWith('Get-ChildItem -Name\n'),
'command comes first, verbatim',
);
});
test('appends an exit-code wrapper so native command exit codes survive -Command', () => {
// pwsh -Command maps the process exit code to 0/1 from $? — a native
// command exiting 42 comes back as 1. The wrapper re-raises $LASTEXITCODE,
// and only when the final statement actually failed, so a recovered
// failure (native exit 3, then a succeeding cmdlet) still exits 0.
const spawnPlan = buildShellSpawnPlan(
{ kind: 'pwsh', displayName: 'PowerShell 7 (pwsh)', exe: 'C:\\pf\\pwsh.exe' },
'npm test',
);
assert.equal(
spawnPlan.args[4],
'npm test\n' +
'$__makaOk = $?\n' +
'if (-not $__makaOk) { if ($LASTEXITCODE -is [int] -and $LASTEXITCODE -ne 0) { exit $LASTEXITCODE } else { exit 1 } }',
);
});
test('keeps shell:true for the system default shells (posix and cmd)', () => {
for (const plan of [
{ kind: 'posix', displayName: '/bin/sh' } as const,
{ kind: 'cmd', displayName: 'cmd.exe' } as const,
]) {
assert.deepEqual(buildShellSpawnPlan(plan, 'echo hi'), {
file: 'echo hi',
args: [],
useShellOption: true,
});
}
});
});
describe('buildPtyShellSpawnPlan', () => {
test('uses explicit argv for POSIX, cmd, and PowerShell PTYs', () => {
assert.deepEqual(
buildPtyShellSpawnPlan({ kind: 'posix', displayName: '/bin/sh' }, 'printf ready'),
{ file: '/bin/sh', args: ['-c', 'printf ready'] },
);
assert.deepEqual(
buildPtyShellSpawnPlan({ kind: 'cmd', displayName: 'cmd.exe' }, 'echo ready', {
ComSpec: 'C:\\Windows\\System32\\cmd.exe',
}),
{
file: 'C:\\Windows\\System32\\cmd.exe',
args: ['/d', '/s', '/c', 'echo ready'],
},
);
const powershell = buildPtyShellSpawnPlan(
{ kind: 'pwsh', displayName: 'PowerShell 7 (pwsh)', exe: 'C:\\pf\\pwsh.exe' },
'Write-Output ready',
);
assert.equal(powershell.file, 'C:\\pf\\pwsh.exe');
assert.deepEqual(powershell.args.slice(0, 3), ['-NoLogo', '-NoProfile', '-Command']);
assert.doesNotMatch(powershell.args.join(' '), /-NonInteractive/);
assert.match(powershell.args[3] ?? '', /^Write-Output ready\n/);
});
});