mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Split commands on newlines (#6121)
This commit is contained in:
parent
714fafd328
commit
9d434c2db9
2 changed files with 140 additions and 1 deletions
|
|
@ -50,6 +50,121 @@ describe("Command Validation", () => {
|
|||
parseCommand('npm test | Select-String -NotMatch "node_modules" | Select-String "FAIL|Error"'),
|
||||
).toEqual(["npm test", 'Select-String -NotMatch "node_modules"', 'Select-String "FAIL|Error"'])
|
||||
})
|
||||
|
||||
describe("newline handling", () => {
|
||||
it("splits commands by Unix newlines (\\n)", () => {
|
||||
expect(parseCommand("echo hello\ngit status\nnpm install")).toEqual([
|
||||
"echo hello",
|
||||
"git status",
|
||||
"npm install",
|
||||
])
|
||||
})
|
||||
|
||||
it("splits commands by Windows newlines (\\r\\n)", () => {
|
||||
expect(parseCommand("echo hello\r\ngit status\r\nnpm install")).toEqual([
|
||||
"echo hello",
|
||||
"git status",
|
||||
"npm install",
|
||||
])
|
||||
})
|
||||
|
||||
it("splits commands by old Mac newlines (\\r)", () => {
|
||||
expect(parseCommand("echo hello\rgit status\rnpm install")).toEqual([
|
||||
"echo hello",
|
||||
"git status",
|
||||
"npm install",
|
||||
])
|
||||
})
|
||||
|
||||
it("handles mixed line endings", () => {
|
||||
expect(parseCommand("echo hello\ngit status\r\nnpm install\rls -la")).toEqual([
|
||||
"echo hello",
|
||||
"git status",
|
||||
"npm install",
|
||||
"ls -la",
|
||||
])
|
||||
})
|
||||
|
||||
it("ignores empty lines", () => {
|
||||
expect(parseCommand("echo hello\n\n\ngit status\r\n\r\nnpm install")).toEqual([
|
||||
"echo hello",
|
||||
"git status",
|
||||
"npm install",
|
||||
])
|
||||
})
|
||||
|
||||
it("handles newlines with chain operators", () => {
|
||||
expect(parseCommand('npm install && npm test\ngit add .\ngit commit -m "test"')).toEqual([
|
||||
"npm install",
|
||||
"npm test",
|
||||
"git add .",
|
||||
'git commit -m "test"',
|
||||
])
|
||||
})
|
||||
|
||||
it("splits on actual newlines even within quotes", () => {
|
||||
// Note: Since we split by newlines first, actual newlines in the input
|
||||
// will split the command, even if they appear to be within quotes
|
||||
// Using template literal to create actual newline
|
||||
const commandWithNewlineInQuotes = `echo "Hello
|
||||
World"
|
||||
git status`
|
||||
// The quotes get stripped because they're no longer properly paired after splitting
|
||||
expect(parseCommand(commandWithNewlineInQuotes)).toEqual(["echo Hello", "World", "git status"])
|
||||
})
|
||||
|
||||
it("handles quoted strings on single line", () => {
|
||||
// When quotes are on the same line, they are preserved
|
||||
expect(parseCommand('echo "Hello World"\ngit status')).toEqual(['echo "Hello World"', "git status"])
|
||||
})
|
||||
|
||||
it("handles complex multi-line commands", () => {
|
||||
const multiLineCommand = `npm install
|
||||
npm test && npm run build
|
||||
echo "Done" | tee output.log
|
||||
git status; git add .
|
||||
ls -la || echo "Failed"`
|
||||
|
||||
expect(parseCommand(multiLineCommand)).toEqual([
|
||||
"npm install",
|
||||
"npm test",
|
||||
"npm run build",
|
||||
'echo "Done"',
|
||||
"tee output.log",
|
||||
"git status",
|
||||
"git add .",
|
||||
"ls -la",
|
||||
'echo "Failed"',
|
||||
])
|
||||
})
|
||||
|
||||
it("handles newlines with subshells", () => {
|
||||
expect(parseCommand("echo $(date)\nnpm test\ngit status")).toEqual([
|
||||
"echo",
|
||||
"date",
|
||||
"npm test",
|
||||
"git status",
|
||||
])
|
||||
})
|
||||
|
||||
it("handles newlines with redirections", () => {
|
||||
expect(parseCommand("npm test 2>&1\necho done\nls -la > files.txt")).toEqual([
|
||||
"npm test 2>&1",
|
||||
"echo done",
|
||||
"ls -la > files.txt",
|
||||
])
|
||||
})
|
||||
|
||||
it("handles empty input with newlines", () => {
|
||||
expect(parseCommand("\n\n\n")).toEqual([])
|
||||
expect(parseCommand("\r\n\r\n")).toEqual([])
|
||||
expect(parseCommand("\r\r\r")).toEqual([])
|
||||
})
|
||||
|
||||
it("handles whitespace-only lines", () => {
|
||||
expect(parseCommand("echo hello\n \t \ngit status")).toEqual(["echo hello", "git status"])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("isAutoApprovedSingleCommand (legacy behavior)", () => {
|
||||
|
|
|
|||
|
|
@ -60,17 +60,41 @@ type ShellToken = string | { op: string } | { command: string }
|
|||
|
||||
/**
|
||||
* Split a command string into individual sub-commands by
|
||||
* chaining operators (&&, ||, ;, or |).
|
||||
* chaining operators (&&, ||, ;, or |) and newlines.
|
||||
*
|
||||
* Uses shell-quote to properly handle:
|
||||
* - Quoted strings (preserves quotes)
|
||||
* - Subshell commands ($(cmd) or `cmd`)
|
||||
* - PowerShell redirections (2>&1)
|
||||
* - Chain operators (&&, ||, ;, |)
|
||||
* - Newlines as command separators
|
||||
*/
|
||||
export function parseCommand(command: string): string[] {
|
||||
if (!command?.trim()) return []
|
||||
|
||||
// Split by newlines first (handle different line ending formats)
|
||||
// This regex splits on \r\n (Windows), \n (Unix), or \r (old Mac)
|
||||
const lines = command.split(/\r\n|\r|\n/)
|
||||
const allCommands: string[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
// Skip empty lines
|
||||
if (!line.trim()) continue
|
||||
|
||||
// Process each line through the existing parsing logic
|
||||
const lineCommands = parseCommandLine(line)
|
||||
allCommands.push(...lineCommands)
|
||||
}
|
||||
|
||||
return allCommands
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single line of commands (internal helper function)
|
||||
*/
|
||||
function parseCommandLine(command: string): string[] {
|
||||
if (!command?.trim()) return []
|
||||
|
||||
// Storage for replaced content
|
||||
const redirections: string[] = []
|
||||
const subshells: string[] = []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue