Shell approval rework (#11073)

This commit is contained in:
cornmander
2025-10-14 12:51:32 -04:00
committed by GitHub
parent 061a89fc2b
commit 92dbdbb93b
12 changed files with 662 additions and 280 deletions

View File

@@ -24,9 +24,14 @@ const mockSerializeTerminalToObject = vi.hoisted(() => vi.fn());
vi.mock('@lydell/node-pty', () => ({
spawn: mockPtySpawn,
}));
vi.mock('child_process', () => ({
spawn: mockCpSpawn,
}));
vi.mock('node:child_process', async (importOriginal) => {
const actual =
(await importOriginal()) as typeof import('node:child_process');
return {
...actual,
spawn: mockCpSpawn,
};
});
vi.mock('../utils/textUtils.js', () => ({
isBinary: mockIsBinary,
}));
@@ -465,15 +470,15 @@ describe('ShellExecutionService', () => {
});
describe('Platform-Specific Behavior', () => {
it('should use cmd.exe on Windows', async () => {
it('should use powershell.exe on Windows', async () => {
mockPlatform.mockReturnValue('win32');
await simulateExecution('dir "foo bar"', (pty) =>
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null }),
);
expect(mockPtySpawn).toHaveBeenCalledWith(
'cmd.exe',
'/c dir "foo bar"',
'powershell.exe',
['-NoProfile', '-Command', 'dir "foo bar"'],
expect.any(Object),
);
});
@@ -637,9 +642,9 @@ describe('ShellExecutionService child_process fallback', () => {
});
expect(mockCpSpawn).toHaveBeenCalledWith(
'ls -l',
[],
expect.objectContaining({ shell: 'bash' }),
'bash',
['-c', 'ls -l'],
expect.objectContaining({ shell: false, detached: true }),
);
expect(result.exitCode).toBe(0);
expect(result.signal).toBeNull();
@@ -905,18 +910,19 @@ describe('ShellExecutionService child_process fallback', () => {
});
describe('Platform-Specific Behavior', () => {
it('should use cmd.exe on Windows', async () => {
it('should use powershell.exe on Windows', async () => {
mockPlatform.mockReturnValue('win32');
await simulateExecution('dir "foo bar"', (cp) =>
cp.emit('exit', 0, null),
);
expect(mockCpSpawn).toHaveBeenCalledWith(
'dir "foo bar"',
[],
'powershell.exe',
['-NoProfile', '-Command', 'dir "foo bar"'],
expect.objectContaining({
shell: true,
shell: false,
detached: false,
windowsVerbatimArguments: false,
}),
);
});
@@ -926,10 +932,10 @@ describe('ShellExecutionService child_process fallback', () => {
await simulateExecution('ls "foo bar"', (cp) => cp.emit('exit', 0, null));
expect(mockCpSpawn).toHaveBeenCalledWith(
'ls "foo bar"',
[],
'bash',
['-c', 'ls "foo bar"'],
expect.objectContaining({
shell: 'bash',
shell: false,
detached: true,
}),
);

View File

@@ -12,6 +12,7 @@ import { TextDecoder } from 'node:util';
import os from 'node:os';
import type { IPty } from '@lydell/node-pty';
import { getCachedEncodingForBuffer } from '../utils/systemEncoding.js';
import { getShellConfiguration } from '../utils/shell-utils.js';
import { isBinary } from '../utils/textUtils.js';
import pkg from '@xterm/headless';
import {
@@ -189,12 +190,14 @@ export class ShellExecutionService {
): ShellExecutionHandle {
try {
const isWindows = os.platform() === 'win32';
const { executable, argsPrefix } = getShellConfiguration();
const spawnArgs = [...argsPrefix, commandToExecute];
const child = cpSpawn(commandToExecute, [], {
const child = cpSpawn(executable, spawnArgs, {
cwd,
stdio: ['ignore', 'pipe', 'pipe'],
windowsVerbatimArguments: true,
shell: isWindows ? true : 'bash',
windowsVerbatimArguments: isWindows ? false : undefined,
shell: false,
detached: !isWindows,
env: {
...process.env,
@@ -400,13 +403,10 @@ export class ShellExecutionService {
try {
const cols = shellExecutionConfig.terminalWidth ?? 80;
const rows = shellExecutionConfig.terminalHeight ?? 30;
const isWindows = os.platform() === 'win32';
const shell = isWindows ? 'cmd.exe' : 'bash';
const args = isWindows
? `/c ${commandToExecute}`
: ['-c', commandToExecute];
const { executable, argsPrefix } = getShellConfiguration();
const args = [...argsPrefix, commandToExecute];
const ptyProcess = ptyInfo.module.spawn(shell, args, {
const ptyProcess = ptyInfo.module.spawn(executable, args, {
cwd,
name: 'xterm',
cols,