fix: harden Windows trans helpers
Resolve #1691 by preserving argv boundaries, adding bounded native rg and wc/skill query support, surfacing WSL-to-Windows hints, and splitting the oversized SSH module and embedded remote scripts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
if ($operation -ne 'rg') { Fail ('unsupported Windows fs operation: ' + $operation) 2 }
|
||||
|
||||
$ignoreCase = $false; $fixed = $false; $filesOnly = $false; $pattern = $null; $endOptions = $false
|
||||
$maxCount = [int]$rgPolicy.maxMatches; $maxFiles = [int]$rgPolicy.maxFiles; $maxBytes = [int]$rgPolicy.maxBytesPerFile; $timeoutMs = [int]$rgPolicy.timeoutMs
|
||||
$paths = [Collections.Generic.List[string]]::new(); $globs = [Collections.Generic.List[string]]::new()
|
||||
for ($i = 0; $i -lt $toolArgs.Count; $i++) {
|
||||
$arg = $toolArgs[$i]
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -eq '--') { $endOptions = $true; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -in @('-i', '--ignore-case')) { $ignoreCase = $true; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -in @('-F', '--fixed-strings')) { $fixed = $true; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -in @('-n', '--line-number')) { continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -eq '--files') { $filesOnly = $true; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -in @('-g', '--glob')) { $i += 1; if ($i -ge $toolArgs.Count) { Fail ($arg + ' requires a value') 2 }; $globs.Add($toolArgs[$i]); continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('--glob=')) { $globs.Add($arg.Substring(7)); continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -in @('-m', '--max-count')) { $i += 1; if ($i -ge $toolArgs.Count) { Fail ($arg + ' requires a value') 2 }; $maxCount = Parse-NonNegativeInt $arg $toolArgs[$i] 10000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('-m') -and $arg.Length -gt 2) { $maxCount = Parse-NonNegativeInt '-m' $arg.Substring(2) 10000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('--max-count=')) { $maxCount = Parse-NonNegativeInt '--max-count' $arg.Substring(12) 10000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -eq '--max-files') { $i += 1; if ($i -ge $toolArgs.Count) { Fail '--max-files requires a value' 2 }; $maxFiles = Parse-NonNegativeInt '--max-files' $toolArgs[$i] 50000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('--max-files=')) { $maxFiles = Parse-NonNegativeInt '--max-files' $arg.Substring(12) 50000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -eq '--max-bytes') { $i += 1; if ($i -ge $toolArgs.Count) { Fail '--max-bytes requires a value' 2 }; $maxBytes = Parse-NonNegativeInt '--max-bytes' $toolArgs[$i] 16777216; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('--max-bytes=')) { $maxBytes = Parse-NonNegativeInt '--max-bytes' $arg.Substring(12) 16777216; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg -eq '--timeout-ms') { $i += 1; if ($i -ge $toolArgs.Count) { Fail '--timeout-ms requires a value' 2 }; $timeoutMs = Parse-NonNegativeInt '--timeout-ms' $toolArgs[$i] 60000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('--timeout-ms=')) { $timeoutMs = Parse-NonNegativeInt '--timeout-ms' $arg.Substring(13) 60000; continue }
|
||||
if ($null -eq $pattern -and -not $endOptions -and $arg.StartsWith('-')) { Fail ('unsupported rg option on Windows route: ' + $arg + '; supported: --files -g/--glob -i -F -n -m/--max-count --max-files --max-bytes --timeout-ms') 2 }
|
||||
if ($filesOnly) { $paths.Add($arg) } elseif ($null -eq $pattern) { $pattern = $arg } else { $paths.Add($arg) }
|
||||
}
|
||||
if (-not $filesOnly -and $null -eq $pattern) { Fail 'rg requires a pattern on Windows routes (or use --files)' 2 }
|
||||
if ($paths.Count -eq 0) { $paths.Add('.') }
|
||||
|
||||
$nativeRg = Get-Command rg.exe -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($null -eq $nativeRg) { Fail 'rg.exe is unavailable on this Windows plane; install ripgrep or use ps with a reviewed PowerShell search' 127 }
|
||||
if ($null -ne $nativeRg) {
|
||||
function Quote-NativeArg([string]$Value) {
|
||||
if ($Value.Contains('"')) { Fail 'rg argument contains a quote; use ps for shell-reviewed quoting' 2 }
|
||||
if ($Value -match '\s') { return '"' + $Value + '"' }
|
||||
return $Value
|
||||
}
|
||||
$nativeArgs = [Collections.Generic.List[string]]::new()
|
||||
$nativeArgs.Add('--color=never'); $nativeArgs.Add('--no-heading'); $nativeArgs.Add('--line-number')
|
||||
$nativeArgs.Add('--max-filesize'); $nativeArgs.Add([string]$maxBytes)
|
||||
if ($ignoreCase) { $nativeArgs.Add('--ignore-case') }
|
||||
if ($fixed) { $nativeArgs.Add('--fixed-strings') }
|
||||
foreach ($glob in $globs) { $nativeArgs.Add('--glob'); $nativeArgs.Add($glob) }
|
||||
if ($filesOnly) { $nativeArgs.Add('--files') } else { $nativeArgs.Add($pattern) }
|
||||
foreach ($raw in $paths) { $nativeArgs.Add((Resolve-UnideskPath $raw)) }
|
||||
$startInfo = [Diagnostics.ProcessStartInfo]::new()
|
||||
$startInfo.FileName = $nativeRg.Source
|
||||
$startInfo.Arguments = [string]::Join(' ', @($nativeArgs | ForEach-Object { Quote-NativeArg $_ }))
|
||||
$startInfo.UseShellExecute = $false; $startInfo.CreateNoWindow = $true; $startInfo.RedirectStandardOutput = $true; $startInfo.RedirectStandardError = $true
|
||||
$process = [Diagnostics.Process]::new(); $process.StartInfo = $startInfo
|
||||
$stopwatch = [Diagnostics.Stopwatch]::StartNew()
|
||||
if (-not $process.Start()) { Fail 'failed to start native rg.exe' 2 }
|
||||
$stdoutTask = $process.StandardOutput.ReadToEndAsync(); $stderrTask = $process.StandardError.ReadToEndAsync()
|
||||
$timedOut = -not $process.WaitForExit($timeoutMs)
|
||||
if ($timedOut) { try { $process.Kill() } catch {}; $process.WaitForExit() }
|
||||
$stdoutText = $stdoutTask.Result; $stderrText = $stderrTask.Result; $stopwatch.Stop()
|
||||
$lines = Text-Lines $stdoutText; $outputLimit = if ($filesOnly) { $maxFiles } else { $maxCount }; $selected = [string[]]($lines | Select-Object -First $outputLimit)
|
||||
Print-Lines $selected
|
||||
if (-not [string]::IsNullOrWhiteSpace($stderrText)) { [Console]::Error.Write($stderrText) }
|
||||
$truncated = $lines.Count -gt $selected.Count
|
||||
$matchedFiles = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
|
||||
if (-not $filesOnly) { foreach ($line in $selected) { if ($line -match '^([A-Za-z]:\\.*?):\d+:') { [void]$matchedFiles.Add($Matches[1]) } } }
|
||||
$fileCount = if ($filesOnly) { $selected.Count } else { $matchedFiles.Count }
|
||||
[Console]::Error.WriteLine('UNIDESK_WINDOWS_RG_SUMMARY matched=' + $selected.Count + ' fileCount=' + $fileCount + ' elapsedMs=' + $stopwatch.ElapsedMilliseconds + ' timeout=' + $timedOut.ToString().ToLowerInvariant() + ' skipped=0 fileLimit=' + ($filesOnly -and $truncated).ToString().ToLowerInvariant() + ' matchLimit=' + ((-not $filesOnly) -and $truncated).ToString().ToLowerInvariant() + ' engine=native-rg config=config/unidesk-cli.yaml#trans.windowsFs.rg')
|
||||
if ($timedOut) { exit 124 }
|
||||
if ($process.ExitCode -ne 0) { exit $process.ExitCode }
|
||||
exit 0
|
||||
}
|
||||
Reference in New Issue
Block a user