fix: 统一 Windows trans 工作目录

This commit is contained in:
Codex
2026-07-13 05:13:47 +02:00
parent 35b0048364
commit e91f228327
2 changed files with 82 additions and 17 deletions
+42 -12
View File
@@ -547,6 +547,7 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
return parseWindowsGitInvocation(route, args.slice(1));
}
if (operation === "ps" || operation === "powershell" || operation === "powershell.exe") {
requireWindowsRouteWorkspace(route, "ps");
const commandArgs = args[1] === "--" ? args.slice(2) : args.slice(1);
if (commandArgs.length >= 2 && (commandArgs[0] === "-File" || commandArgs[0] === "-file")) {
const fileArgs = [commandArgs[1]?.replace(/\\/g, "/") ?? "", ...commandArgs.slice(2)];
@@ -581,6 +582,7 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
if (operation !== "cmd" && operation !== "cmd.exe") {
throw new Error(windowsUnsupportedOperationMessage(route, operation));
}
requireWindowsRouteWorkspace(route, "cmd");
const commandArgs = args[1] === "--" ? args.slice(2) : args.slice(1);
if (commandArgs.length === 0) {
return {
@@ -590,12 +592,29 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
};
}
return {
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsCmdLauncherScript(buildWindowsCmdLine(commandArgs.map((value) => windowsCmdArgument(value, route)).join(" "), route.workspace))),
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsCmdLauncherScript(
buildWindowsCmdLine(commandArgs.map((value) => windowsCmdArgument(value, route)).join(" ")),
route.workspace,
)),
requiresStdin: false,
invocationKind: "argv",
};
}
function requireWindowsRouteWorkspace(route: ParsedSshRoute, operation: "ps" | "cmd"): asserts route is ParsedSshRoute & { workspace: string } {
if (route.workspace !== null) return;
const entrypoint = sshDisplayEntrypoint();
const routeTemplate = `${route.providerId}:win/<drive>/<path>`;
throw new CliInputError(`Windows ${operation} requires an explicit route workspace; refusing to inherit C:\\Windows`, {
code: "trans-windows-route-cwd-required",
argument: route.raw,
usage: operation === "ps"
? [`${entrypoint} ${routeTemplate} ps '<PowerShell source>'`, `${entrypoint} ${routeTemplate} ps <<'PS'`]
: [`${entrypoint} ${routeTemplate} cmd <command> [args...]`, `${entrypoint} ${routeTemplate} cmd <<'CMD'`],
hint: "Put the Windows drive and working directory in the route. Send multiline source directly through a quoted heredoc on stdin.",
});
}
function looksLikeLocallyExpandedPowerShellSource(command: string): boolean {
return /(?:^|[;{}]\s*)=\s*[^=]/u.test(command);
}
@@ -628,7 +647,7 @@ function parseWindowsGitInvocation(route: ParsedSshRoute, gitArgs: string[]): Pa
if (subcommand === "commit") validateWindowsGitCommitArgs(route, gitArgs);
const commandLine = ["git", ...gitArgs].map((value) => windowsCmdArgument(value, route)).join(" ");
return {
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsCmdLauncherScript(buildWindowsCmdLine(commandLine, route.workspace))),
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsCmdLauncherScript(buildWindowsCmdLine(commandLine), route.workspace)),
requiresStdin: false,
invocationKind: "argv",
};
@@ -769,22 +788,16 @@ function validateWindowsGitCommitArgs(route: ParsedSshRoute, gitArgs: string[]):
throw new Error(`ssh win git commit would open an editor; use ${entrypoint} ${route.raw} git commit -m <message>, ${entrypoint} ${route.raw} git commit --no-edit, or wrap an interactive command with ${entrypoint} ${route.raw} ps <<'PS'`);
}
function buildWindowsCmdLine(userCommand: string, cwd: string | null): string {
function buildWindowsCmdLine(userCommand: string): string {
const parts = [
"chcp 65001>nul",
'set "PYTHONUTF8=1"',
'set "PYTHONIOENCODING=utf-8"',
];
if (cwd !== null) parts.push(`cd /d ${windowsCmdQuote(cwd)}`);
parts.push(userCommand);
return parts.join(" && ");
}
function windowsCmdQuote(value: string): string {
if (/[\r\n"]/u.test(value)) throw new Error("ssh win workspace path must not contain quotes or newlines");
return `"${value}"`;
}
function windowsCmdArgument(value: string, route: ParsedSshRoute): string {
if (value.length === 0) return "\"\"";
if (/[\r\n"]/u.test(value) || /[&|<>^%!]/u.test(value)) {
@@ -883,7 +896,23 @@ export function powerShellSingleQuote(value: string): string {
return `'${value.replace(/'/g, "''")}'`;
}
function buildWindowsCmdLauncherScript(cmdLine: string): string {
function windowsPowerShellRouteCwdGuardLines(cwd: string | null): string[] {
if (cwd === null) return [];
return [
`$unideskRouteCwd = ${powerShellSingleQuote(cwd)};`,
"try {",
" if (-not [System.IO.Directory]::Exists($unideskRouteCwd)) { throw 'route cwd unavailable' }",
" Set-Location -LiteralPath $unideskRouteCwd -ErrorAction Stop;",
" [Environment]::CurrentDirectory = (Get-Location).ProviderPath;",
"} catch {",
" $unideskCwdError = [ordered]@{ code = 'trans-windows-route-cwd-invalid'; failureKind = 'cwd-failed'; cwd = $unideskRouteCwd; message = 'requested Windows route cwd does not exist or is not accessible' };",
" [Console]::Error.WriteLine('UNIDESK_SSH_CWD_FAILED ' + ($unideskCwdError | Microsoft.PowerShell.Utility\\ConvertTo-Json -Compress));",
" exit 2;",
"}",
];
}
function buildWindowsCmdLauncherScript(cmdLine: string, cwd: string | null): string {
return [
"$ErrorActionPreference = 'Stop';",
"[Console]::InputEncoding = [System.Text.UTF8Encoding]::new();",
@@ -891,6 +920,7 @@ function buildWindowsCmdLauncherScript(cmdLine: string): string {
"$OutputEncoding = [System.Text.UTF8Encoding]::new();",
"$env:PYTHONUTF8 = '1';",
"$env:PYTHONIOENCODING = 'utf-8';",
...windowsPowerShellRouteCwdGuardLines(cwd),
`& ${powerShellSingleQuote(windowsCmdExeNativePath)} /d /s /c ${powerShellSingleQuote(cmdLine)};`,
"exit $LASTEXITCODE;",
].join(" ");
@@ -902,7 +932,6 @@ function buildWindowsCmdStdinLauncherScript(cwd: string | null): string {
"chcp 65001>nul",
'set "PYTHONUTF8=1"',
'set "PYTHONIOENCODING=utf-8"',
...(cwd === null ? [] : [`cd /d ${windowsCmdQuote(cwd)}`]),
];
return [
"$ErrorActionPreference = 'Stop';",
@@ -912,6 +941,7 @@ function buildWindowsCmdStdinLauncherScript(cwd: string | null): string {
"$OutputEncoding = [System.Text.UTF8Encoding]::new();",
"$env:PYTHONUTF8 = '1';",
"$env:PYTHONIOENCODING = 'utf-8';",
...windowsPowerShellRouteCwdGuardLines(cwd),
"$script = [Console]::In.ReadToEnd();",
"$script = $script -replace \"`r`n\", \"`n\";",
"$script = $script -replace \"`r\", \"`n\";",
@@ -993,8 +1023,8 @@ export function windowsPowerShellScriptPrelude(cwd: string | null): string {
"$PSDefaultParameterValues['Get-Content:Encoding'] = 'utf8'",
"$env:PYTHONUTF8 = '1'",
"$env:PYTHONIOENCODING = 'utf-8'",
...windowsPowerShellRouteCwdGuardLines(cwd),
...windowsPowerShellJsonSafetyPreludeLines,
...(cwd === null ? [] : [`Set-Location -LiteralPath ${powerShellSingleQuote(cwd)}`]),
].join("\r\n") + "\r\n";
}