fix(desktop): handle quoted paths with spaces in extension commands (#6430)

Signed-off-by: Abhijay007 <Abhijay007j@gmail.com>
This commit is contained in:
Abhijay Jain
2026-01-29 21:36:08 +05:30
committed by GitHub
parent 4156907209
commit 5bffc1c8ef
2 changed files with 30 additions and 7 deletions
@@ -251,6 +251,13 @@ describe('Extension Utils', () => {
"java -classpath '/path/with spaces/lib.jar' Main",
{ cmd: 'java', args: ['-classpath', '/path/with spaces/lib.jar', 'Main'] },
],
[
'"/Applications/IntelliJ IDEA.app/Contents/jbr/Contents/Home/bin/java" -classpath "/path/with spaces/lib.jar" Main',
{
cmd: '/Applications/IntelliJ IDEA.app/Contents/jbr/Contents/Home/bin/java',
args: ['-classpath', '/path/with spaces/lib.jar', 'Main'],
},
],
[
'node --max-old-space-size=4096 app.js',
{ cmd: 'node', args: ['--max-old-space-size=4096', 'app.js'] },
@@ -1,4 +1,6 @@
import { parse, quote } from 'shell-quote';
import type { FixedExtensionEntry } from '../../ConfigContext';
import type { ExtensionConfig } from '../../../api/types.gen';
import { parse as parseShellQuote, quote as quoteShell } from 'shell-quote';
// Default extension timeout in seconds
// TODO: keep in sync with rust better
@@ -17,9 +19,6 @@ export function nameToKey(name: string): string {
.toLowerCase();
}
import { FixedExtensionEntry } from '../../ConfigContext';
import { ExtensionConfig } from '../../../api/types.gen';
export interface ExtensionFormData {
name: string;
description: string;
@@ -105,7 +104,7 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
extension.type === 'platform'
? 'stdio'
: extension.type,
cmd: extension.type === 'stdio' ? quote([extension.cmd, ...extension.args]) : undefined,
cmd: extension.type === 'stdio' ? quoteShell([extension.cmd, ...extension.args]) : undefined,
endpoint:
extension.type === 'streamable_http' || extension.type === 'sse'
? (extension.uri ?? undefined)
@@ -170,8 +169,25 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
}
export function splitCmdAndArgs(str: string): { cmd: string; args: string[] } {
const parts = parse(str.trim()).filter((p): p is string => typeof p === 'string');
return { cmd: parts[0] || '', args: parts.slice(1) };
const trimmed = str.trim();
if (!trimmed) {
return { cmd: '', args: [] };
}
const parsed = parseShellQuote(trimmed);
const words = parsed.filter((item): item is string => typeof item === 'string').map(String);
const cmd = words[0] || '';
const args = words.slice(1);
return {
cmd,
args,
};
}
export function combineCmdAndArgs(cmd: string, args: string[]): string {
return quoteShell([cmd, ...args]);
}
export function extractCommand(link: string): string {