export function prepareCommand()

in patched-vscode/src/vs/workbench/contrib/debug/node/terminals.ts [59:196]


export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string, env?: { [key: string]: string | null }): string {

	shell = shell.trim().toLowerCase();

	// try to determine the shell type
	let shellType;
	if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {
		shellType = ShellType.powershell;
	} else if (shell.indexOf('cmd.exe') >= 0) {
		shellType = ShellType.cmd;
	} else if (shell.indexOf('bash') >= 0) {
		shellType = ShellType.bash;
	} else if (platform.isWindows) {
		shellType = ShellType.cmd; // pick a good default for Windows
	} else {
		shellType = ShellType.bash;	// pick a good default for anything else
	}

	let quote: (s: string) => string;
	// begin command with a space to avoid polluting shell history
	let command = ' ';

	switch (shellType) {

		case ShellType.powershell:

			quote = (s: string) => {
				s = s.replace(/\'/g, '\'\'');
				if (s.length > 0 && s.charAt(s.length - 1) === '\\') {
					return `'${s}\\'`;
				}
				return `'${s}'`;
			};

			if (cwd) {
				const driveLetter = getDriveLetter(cwd);
				if (driveLetter) {
					command += `${driveLetter}:; `;
				}
				command += `cd ${quote(cwd)}; `;
			}
			if (env) {
				for (const key in env) {
					const value = env[key];
					if (value === null) {
						command += `Remove-Item env:${key}; `;
					} else {
						command += `\${env:${key}}='${value}'; `;
					}
				}
			}
			if (args.length > 0) {
				const arg = args.shift()!;
				const cmd = argsCanBeInterpretedByShell ? arg : quote(arg);
				command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;
				for (const a of args) {
					command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
					command += ' ';
				}
			}
			break;

		case ShellType.cmd:

			quote = (s: string) => {
				// Note: Wrapping in cmd /C "..." complicates the escaping.
				// cmd /C "node -e "console.log(process.argv)" """A^>0"""" # prints "A>0"
				// cmd /C "node -e "console.log(process.argv)" "foo^> bar"" # prints foo> bar
				// Outside of the cmd /C, it could be a simple quoting, but here, the ^ is needed too
				s = s.replace(/\"/g, '""');
				s = s.replace(/([><!^&|])/g, '^$1');
				return (' "'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
			};

			if (cwd) {
				const driveLetter = getDriveLetter(cwd);
				if (driveLetter) {
					command += `${driveLetter}: && `;
				}
				command += `cd ${quote(cwd)} && `;
			}
			if (env) {
				command += 'cmd /C "';
				for (const key in env) {
					let value = env[key];
					if (value === null) {
						command += `set "${key}=" && `;
					} else {
						value = value.replace(/[&^|<>]/g, s => `^${s}`);
						command += `set "${key}=${value}" && `;
					}
				}
			}
			for (const a of args) {
				command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
				command += ' ';
			}
			if (env) {
				command += '"';
			}
			break;

		case ShellType.bash: {

			quote = (s: string) => {
				s = s.replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1');
				return s.length === 0 ? `""` : s;
			};

			const hardQuote = (s: string) => {
				return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s;
			};

			if (cwd) {
				command += `cd ${quote(cwd)} ; `;
			}
			if (env) {
				command += '/usr/bin/env';
				for (const key in env) {
					const value = env[key];
					if (value === null) {
						command += ` -u ${hardQuote(key)}`;
					} else {
						command += ` ${hardQuote(`${key}=${value}`)}`;
					}
				}
				command += ' ';
			}
			for (const a of args) {
				command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a);
				command += ' ';
			}
			break;
		}
	}

	return command;
}