Skip to content

Commit 9e5599c

Browse files
fix(core): handle multi-line escaped quotes in stripShellWrapper (#27467)
Co-authored-by: luisfelipe-alt <luisfelipe@google.com>
1 parent ba12896 commit 9e5599c

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

packages/core/src/utils/shell-utils.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ vi.mock('node:child_process', () => ({
5555
}));
5656

5757
const mockQuote = vi.hoisted(() => vi.fn());
58-
vi.mock('shell-quote', () => ({
59-
quote: mockQuote,
60-
}));
58+
vi.mock('shell-quote', async (importOriginal) => {
59+
const actual = await importOriginal<typeof import('shell-quote')>();
60+
return {
61+
...actual,
62+
quote: mockQuote,
63+
};
64+
});
6165

6266
const mockDebugLogger = vi.hoisted(() => ({
6367
error: vi.fn(),
@@ -388,6 +392,12 @@ describe('stripShellWrapper', () => {
388392
it('should not strip anything if no wrapper is present', () => {
389393
expect(stripShellWrapper('ls -l')).toEqual('ls -l');
390394
});
395+
396+
it('should handle multi-line escaped double quotes correctly', () => {
397+
const multiLine = 'bash -c "hg commit -m \\"title\n\nbody\\""';
398+
const expected = 'hg commit -m "title\n\nbody"';
399+
expect(stripShellWrapper(multiLine)).toEqual(expected);
400+
});
391401
});
392402

393403
describe('escapeShellArg', () => {

packages/core/src/utils/shell-utils.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import os from 'node:os';
88
import fs from 'node:fs';
99
import path from 'node:path';
10-
import { quote, type ParseEntry } from 'shell-quote';
10+
import { quote, parse, type ParseEntry } from 'shell-quote';
1111
import {
1212
spawn,
1313
spawnSync,
@@ -846,10 +846,26 @@ export function stripShellWrapper(command: string): string {
846846
if (match) {
847847
let newCommand = command.substring(match[0].length).trim();
848848
if (
849-
(newCommand.startsWith('"') && newCommand.endsWith('"')) ||
850-
(newCommand.startsWith("'") && newCommand.endsWith("'"))
849+
newCommand.length >= 2 &&
850+
((newCommand.startsWith('"') && newCommand.endsWith('"')) ||
851+
(newCommand.startsWith("'") && newCommand.endsWith("'")))
851852
) {
852-
newCommand = newCommand.substring(1, newCommand.length - 1);
853+
const isPosixShell = match[0].trim().endsWith('-c');
854+
if (isPosixShell && newCommand.startsWith('"')) {
855+
try {
856+
const parsed = parse(newCommand, (key) => '$' + key);
857+
const firstEntry = parsed[0];
858+
if (parsed.length === 1 && typeof firstEntry === 'string') {
859+
newCommand = firstEntry;
860+
} else {
861+
newCommand = newCommand.substring(1, newCommand.length - 1);
862+
}
863+
} catch {
864+
newCommand = newCommand.substring(1, newCommand.length - 1);
865+
}
866+
} else {
867+
newCommand = newCommand.substring(1, newCommand.length - 1);
868+
}
853869
}
854870
return newCommand;
855871
}

0 commit comments

Comments
 (0)