import { advanceQuoteEscapeState, readShellChars, type QuoteEscapeState } from "./shell-quote-state.js"; interface WindowsBashCompatibilityResult { command: string; applied: string[]; } interface LeadingCdSlashDParse { rawPath: string; operator: string; tail: string; } const PYTHON_UTF8_ENV_PREFIX = "PYTHONIOENCODING=utf-8"; function normalizeWindowsPathForBash(rawPath: string): string { const trimmed = rawPath.trim(); const unquoted = (trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")) ? trimmed.slice(1, -1) : trimmed; return unquoted.replace(/\\/g, "/"); } function quoteForBash(value: string): string { const escaped = value.replace(/"/g, '\\"'); return `"${escaped}"`; } function parseLeadingCdSlashD(command: string): LeadingCdSlashDParse | null { const prefixMatch = command.match(/^\s*cd\s+\/d\s+/i); if (!prefixMatch) { return null; } const pathStart = prefixMatch[0].length; const state: QuoteEscapeState = { quote: null, escaped: false }; for (let index = pathStart; index < command.length; index += 1) { const { character, nextCharacter } = readShellChars(command, index); if (advanceQuoteEscapeState(state, character, "\"'")) { continue; } if (character === "&" && nextCharacter === "&") { return { rawPath: command.slice(pathStart, index), operator: "&&", tail: command.slice(index + 2), }; } if (character === "|" && nextCharacter === "|") { return { rawPath: command.slice(pathStart, index), operator: "||", tail: command.slice(index + 2), }; } if (character === "|" || character === ";") { return { rawPath: command.slice(pathStart, index), operator: character, tail: command.slice(index + 1), }; } } return { rawPath: command.slice(pathStart), operator: "", tail: "", }; } function rewriteLeadingCdSlashD(command: string): { command: string; changed: boolean } { const parsed = parseLeadingCdSlashD(command); if (!parsed) { return { command, changed: false }; } const normalizedPath = quoteForBash(normalizeWindowsPathForBash(parsed.rawPath)); if (!parsed.operator) { return { command: `cd ${normalizedPath}`, changed: true, }; } return { command: `cd ${normalizedPath} ${parsed.operator} ${parsed.tail.trimStart()}`, changed: true, }; } function ensurePythonUtf8(command: string): { command: string; changed: boolean } { if (/\bPYTHONIOENCODING\s*=/.test(command)) { return { command, changed: false }; } if (!/(^|[;&|]\s*|&&\s*|\|\|\s*)python(?:3(?:\.\d+)?)?\b/i.test(command)) { return { command, changed: false }; } return { command: `${PYTHON_UTF8_ENV_PREFIX} ${command}`, changed: true, }; } export function applyWindowsBashCompatibilityFixes( command: string, platform: string = process.platform, ): WindowsBashCompatibilityResult { if (platform !== "win32") { return { command, applied: [] }; } let nextCommand = command; const applied: string[] = []; const cdFix = rewriteLeadingCdSlashD(nextCommand); if (cdFix.changed) { nextCommand = cdFix.command; applied.push("cd-/d"); } const pythonFix = ensurePythonUtf8(nextCommand); if (pythonFix.changed) { nextCommand = pythonFix.command; applied.push("python-utf8"); } return { command: nextCommand, applied }; }