mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
fix(cli): 修复 Windows 路径反斜杠被吞掉的问题并添加跨平台路径支持
- 重写 escapeWindowsArg 函数,正确处理反斜杠和引号转义 - 添加 escapeUnixArg 函数支持 Linux/macOS shell 转义 - 添加 normalizePathSeparators 函数自动转换路径分隔符 - 修复 vscode-lsp.ts 中的 TypeScript 类型错误
This commit is contained in:
@@ -93,7 +93,7 @@ async function callVSCodeBridge(
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.json();
|
||||
const errorBody = await response.json() as any;
|
||||
return {
|
||||
success: false,
|
||||
error: errorBody.error || `HTTP ${response.status}: ${response.statusText}`,
|
||||
@@ -101,7 +101,7 @@ async function callVSCodeBridge(
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json() as any;
|
||||
return {
|
||||
success: data.success !== false,
|
||||
result: data.result,
|
||||
|
||||
@@ -21,25 +21,56 @@ export interface ValidatePathOptions {
|
||||
allowHome?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path separators to the native format for the current platform
|
||||
* - Windows: converts / to \
|
||||
* - Linux/macOS: converts \ to /
|
||||
* @param inputPath - Path with potentially mixed separators
|
||||
* @returns Path with native separators
|
||||
*/
|
||||
export function normalizePathSeparators(inputPath: string): string {
|
||||
if (!inputPath) return inputPath;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: convert forward slashes to backslashes
|
||||
return inputPath.replace(/\//g, '\\');
|
||||
} else {
|
||||
// Linux/macOS: convert backslashes to forward slashes
|
||||
// This handles Windows-style paths being used on Unix systems
|
||||
return inputPath.replace(/\\/g, '/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a path, handling ~ for home directory
|
||||
* Also handles Windows drive-relative paths (e.g., "D:path" -> "D:\path")
|
||||
* @param inputPath - Path to resolve
|
||||
* @returns Absolute path
|
||||
* and normalizes mixed slashes for cross-platform compatibility
|
||||
*
|
||||
* Cross-platform behavior:
|
||||
* - Windows: D:/path/to/file -> D:\path\to\file
|
||||
* - Linux/macOS: /path\to/file -> /path/to/file
|
||||
*
|
||||
* @param inputPath - Path to resolve (can use / or \ on any platform)
|
||||
* @returns Absolute path with native separators
|
||||
*/
|
||||
export function resolvePath(inputPath: string): string {
|
||||
if (!inputPath) return process.cwd();
|
||||
|
||||
// Handle ~ for home directory
|
||||
// Handle ~ for home directory (before normalizing separators)
|
||||
if (inputPath.startsWith('~')) {
|
||||
return join(homedir(), inputPath.slice(1));
|
||||
const remainder = inputPath.slice(1);
|
||||
return join(homedir(), normalizePathSeparators(remainder));
|
||||
}
|
||||
|
||||
// Normalize path separators to native format
|
||||
inputPath = normalizePathSeparators(inputPath);
|
||||
|
||||
// Handle Windows drive-relative paths (e.g., "D:path" without backslash)
|
||||
// Pattern: single letter followed by colon, then immediately a non-slash character
|
||||
// This converts "D:path" to "D:\path" to make it absolute
|
||||
if (process.platform === 'win32' || /^[a-zA-Z]:/.test(inputPath)) {
|
||||
const driveRelativeMatch = inputPath.match(/^([a-zA-Z]:)([^/\\].*)$/);
|
||||
// Only apply on Windows or when path looks like a Windows drive path
|
||||
if (process.platform === 'win32') {
|
||||
const driveRelativeMatch = inputPath.match(/^([a-zA-Z]:)([^\\].*)$/);
|
||||
if (driveRelativeMatch) {
|
||||
// Insert backslash after drive letter
|
||||
inputPath = driveRelativeMatch[1] + '\\' + driveRelativeMatch[2];
|
||||
|
||||
@@ -1,30 +1,117 @@
|
||||
/**
|
||||
* Windows cmd.exe argument escaping for spawn({ shell: true }).
|
||||
* Cross-platform shell argument escaping utilities.
|
||||
*
|
||||
* This utility escapes cmd.exe metacharacters using caret (^) so that user
|
||||
* controlled input cannot inject additional commands.
|
||||
* Provides proper escaping for command-line arguments when using spawn({ shell: true })
|
||||
* on different platforms:
|
||||
* - Windows: cmd.exe metacharacter handling with proper quote escaping
|
||||
* - Unix (Linux/macOS): POSIX shell escaping with single quotes
|
||||
*/
|
||||
|
||||
const WINDOWS_METACHARS = /[&|<>()%!"]/g;
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for Windows cmd.exe.
|
||||
* Follows Microsoft C/C++ runtime argv parsing rules.
|
||||
*
|
||||
* Rules:
|
||||
* 1. Arguments containing spaces, tabs, or quotes must be quoted
|
||||
* 2. Backslashes are literal unless followed by a quote
|
||||
* 3. To include a literal quote, use \"
|
||||
* 4. Backslashes before a quote must be doubled: \\" produces \"
|
||||
* 5. Trailing backslashes must be doubled when the arg is quoted
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument safe for Windows command line
|
||||
*/
|
||||
export function escapeWindowsArg(arg: string): string {
|
||||
if (arg === '') return '""';
|
||||
|
||||
// Normalize newlines to spaces to prevent cmd.exe from
|
||||
// misinterpreting multiline arguments (breaks argument parsing)
|
||||
let sanitizedArg = arg.replace(/\r?\n/g, ' ');
|
||||
// Normalize newlines to spaces to prevent cmd.exe issues
|
||||
arg = arg.replace(/\r?\n/g, ' ');
|
||||
|
||||
// Escape caret first to avoid double-escaping when prefixing other metachars.
|
||||
let escaped = sanitizedArg.replace(/\^/g, '^^');
|
||||
// Check if the argument needs quoting
|
||||
// Characters that require quoting: space, tab, double quote, or backslash
|
||||
const needsQuoting = /[ \t"\\]/.test(arg);
|
||||
|
||||
// Escape cmd.exe metacharacters with caret.
|
||||
escaped = escaped.replace(WINDOWS_METACHARS, '^$&');
|
||||
|
||||
// Wrap whitespace-containing args in double quotes.
|
||||
if (/\s/.test(escaped)) {
|
||||
escaped = `"${escaped}"`;
|
||||
if (!needsQuoting) {
|
||||
// No special characters - return as is
|
||||
return arg;
|
||||
}
|
||||
|
||||
return escaped;
|
||||
// Build the escaped string with proper quoting
|
||||
let result = '"';
|
||||
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
// Count consecutive backslashes
|
||||
let backslashes = 0;
|
||||
while (i < arg.length && arg[i] === '\\') {
|
||||
backslashes++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === arg.length) {
|
||||
// Trailing backslashes: double them all (they precede the closing quote)
|
||||
result += '\\'.repeat(backslashes * 2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (arg[i] === '"') {
|
||||
// Backslashes followed by a quote: double the backslashes and escape the quote
|
||||
result += '\\'.repeat(backslashes * 2 + 1) + '"';
|
||||
} else {
|
||||
// Backslashes not followed by a quote: they are literal
|
||||
result += '\\'.repeat(backslashes) + arg[i];
|
||||
}
|
||||
}
|
||||
|
||||
result += '"';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for Unix shells (bash, sh, zsh).
|
||||
* Uses single quotes which treat all characters literally except single quote itself.
|
||||
*
|
||||
* Strategy:
|
||||
* - Wrap argument in single quotes
|
||||
* - Replace any single quotes with: '\'' (end quote, escaped quote, start quote)
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument safe for Unix shell
|
||||
*/
|
||||
export function escapeUnixArg(arg: string): string {
|
||||
if (arg === '') return "''";
|
||||
|
||||
// If no special characters, return as-is
|
||||
// Safe characters: alphanumeric, hyphen, underscore, dot, forward slash, colon
|
||||
if (/^[a-zA-Z0-9_.\/:@=-]+$/.test(arg)) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
// Use single quotes and escape any single quotes within
|
||||
// 'arg' -> 'arg'\''s' for "arg's"
|
||||
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for the current platform.
|
||||
* Automatically selects the appropriate escaping method based on OS.
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument for the current platform's shell
|
||||
*/
|
||||
export function escapeShellArg(arg: string): string {
|
||||
if (process.platform === 'win32') {
|
||||
return escapeWindowsArg(arg);
|
||||
}
|
||||
return escapeUnixArg(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need shell escaping on the current platform.
|
||||
* Windows always needs escaping when shell: true due to cmd.exe.
|
||||
* Unix typically doesn't need escaping when shell: false.
|
||||
*
|
||||
* @returns true if shell escaping should be applied
|
||||
*/
|
||||
export function needsShellEscaping(): boolean {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user