/
larinv
/
claude-code
Обзор
Документация
Войти
/
larinv
/
claude-code
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
utils/bash/shellQuoting.ts
128 строк
5 KB
kuberwastaken
new initial commit (history rewritten)
01 апр 2026, 14:57
01 апр 2026, 14:57
c1996f2
Код
Авторство
О чём код?
import { quote } from './shellQuote.js' /** * Detects if a command contains a heredoc pattern * Matches patterns like: <<EOF, <<'EOF', <<"EOF", <<-EOF, <<-'EOF', <<\EOF, etc. */ function containsHeredoc(command: string): boolean { // Match heredoc patterns: << followed by optional -, then optional quotes or backslash, then word // Matches: <<EOF, <<'EOF', <<"EOF", <<-EOF, <<-'EOF', <<\EOF // Check for bit-shift operators first and exclude them if ( /\d\s*<<\s*\d/.test(command) || /\[\[\s*\d+\s*<<\s*\d+\s*\]\]/.test(command) || /\$\(\(.*<<.*\)\)/.test(command) ) { return false } // Now check for heredoc patterns const heredocRegex = /<<-?\s*(?:(['"]?)(\w+)\1|\\(\w+))/ return heredocRegex.test(command) } /** * Detects if a command contains multiline strings in quotes */ function containsMultilineString(command: string): boolean { // Check for strings with actual newlines in them // Handle escaped quotes by using a more sophisticated pattern // Match single quotes: '...\n...' where content can include escaped quotes \' // Match double quotes: "...\n..." where content can include escaped quotes \" const singleQuoteMultiline = /'(?:[^'\\]|\\.)*\n(?:[^'\\]|\\.)*'/ const doubleQuoteMultiline = /"(?:[^"\\]|\\.)*\n(?:[^"\\]|\\.)*"/ return ( singleQuoteMultiline.test(command) || doubleQuoteMultiline.test(command) ) } /** * Quotes a shell command appropriately, preserving heredocs and multiline strings * @param command The command to quote * @param addStdinRedirect Whether to add < /dev/null * @returns The properly quoted command */ export function quoteShellCommand( command: string, addStdinRedirect: boolean = true, ): string { // If command contains heredoc or multiline strings, handle specially // The shell-quote library incorrectly escapes ! to \! in these cases if (containsHeredoc(command) || containsMultilineString(command)) { // For heredocs and multiline strings, we need to quote for eval // but avoid shell-quote's aggressive escaping // We'll use single quotes and escape only single quotes in the command const escaped = command.replace(/'/g, "'\"'\"'") const quoted = `'${escaped}'` // Don't add stdin redirect for heredocs as they provide their own input if (containsHeredoc(command)) { return quoted } // For multiline strings without heredocs, add stdin redirect if needed return addStdinRedirect ? `${quoted} < /dev/null` : quoted } // For regular commands, use shell-quote if (addStdinRedirect) { return quote([command, '<', '/dev/null']) } return quote([command]) } /** * Detects if a command already has a stdin redirect * Match patterns like: < file, </path/to/file, < /dev/null, etc. * But not <<EOF (heredoc), << (bit shift), or <(process substitution) */ export function hasStdinRedirect(command: string): boolean { // Look for < followed by whitespace and a filename/path // Negative lookahead to exclude: <<, <( // Must be preceded by whitespace or command separator or start of string return /(?:^|[\s;&|])<(?![<(])\s*\S+/.test(command) } /** * Checks if stdin redirect should be added to a command * @param command The command to check * @returns true if stdin redirect can be safely added */ export function shouldAddStdinRedirect(command: string): boolean { // Don't add stdin redirect for heredocs as it interferes with the heredoc terminator if (containsHeredoc(command)) { return false } // Don't add stdin redirect if command already has one if (hasStdinRedirect(command)) { return false } // For other commands, stdin redirect is generally safe return true } /** * Rewrites Windows CMD-style `>nul` redirects to POSIX `/dev/null`. * * The model occasionally hallucinates Windows CMD syntax (e.g., `ls 2>nul`) * even though our bash shell is always POSIX (Git Bash / WSL on Windows). * When Git Bash sees `2>nul`, it creates a literal file named `nul` — a * Windows reserved device name that is extremely hard to delete and breaks * `git add .` and `git clone`. See anthropics/claude-code#4928. * * Matches: `>nul`, `> NUL`, `2>nul`, `&>nul`, `>>nul` (case-insensitive) * Does NOT match: `>null`, `>nullable`, `>nul.txt`, `cat nul.txt` * * Limitation: this regex does not parse shell quoting, so `echo ">nul"` * will also be rewritten. This is acceptable collateral — it's extremely * rare and rewriting to `/dev/null` inside a string is harmless. */ const NUL_REDIRECT_REGEX = /(\d?&?>+\s*)[Nn][Uu][Ll](?=\s|$|[|&;)\n])/g export function rewriteWindowsNullRedirect(command: string): string { return command.replace(NUL_REDIRECT_REGEX, '$1/dev/null') }