init claude-code
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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')
|
||||
}
|
||||
Reference in New Issue
Block a user