@@ -19,13 +19,69 @@ export function extractCommandPattern(command: string): string {
1919 const trimmedCommand = command . trim ( )
2020
2121 // Check if this is a chained command
22- const chainMatch = trimmedCommand . match ( / ^ ( .+ ?) \s * ( & & | \| \| | ; | \| ) \s * ( .+ ) $ / )
23- if ( chainMatch ) {
24- // Handle chained commands by processing each part
25- const [ , firstPart , operator , restPart ] = chainMatch
26- const firstPattern = extractSingleCommandPattern ( firstPart . trim ( ) )
27- const restPattern = extractCommandPattern ( restPart . trim ( ) )
28- return `${ firstPattern } ${ operator } ${ restPattern } `
22+ // Use a more robust regex that handles nested quotes properly
23+ const operators = [ "&&" , "||" , ";" , "|" ]
24+ let chainOperator : string | null = null
25+ let splitIndex = - 1
26+
27+ // Find the first unquoted operator
28+ let inSingleQuote = false
29+ let inDoubleQuote = false
30+ let escapeNext = false
31+
32+ for ( let i = 0 ; i < trimmedCommand . length ; i ++ ) {
33+ const char = trimmedCommand [ i ]
34+
35+ if ( escapeNext ) {
36+ escapeNext = false
37+ continue
38+ }
39+
40+ if ( char === "\\" ) {
41+ escapeNext = true
42+ continue
43+ }
44+
45+ if ( char === "'" && ! inDoubleQuote ) {
46+ inSingleQuote = ! inSingleQuote
47+ continue
48+ }
49+
50+ if ( char === '"' && ! inSingleQuote ) {
51+ inDoubleQuote = ! inDoubleQuote
52+ continue
53+ }
54+
55+ // Only look for operators outside of quotes
56+ if ( ! inSingleQuote && ! inDoubleQuote ) {
57+ for ( const op of operators ) {
58+ if ( trimmedCommand . substring ( i , i + op . length ) === op ) {
59+ chainOperator = op
60+ splitIndex = i
61+ break
62+ }
63+ }
64+ if ( chainOperator ) break
65+ }
66+ }
67+
68+ if ( chainOperator && splitIndex > 0 ) {
69+ const firstPart = trimmedCommand . substring ( 0 , splitIndex ) . trim ( )
70+ const restPart = trimmedCommand . substring ( splitIndex + chainOperator . length ) . trim ( )
71+
72+ // Process each part separately
73+ const firstPattern = extractSingleCommandPattern ( firstPart )
74+ const restPattern = extractCommandPattern ( restPart )
75+
76+ // For security, limit the depth of chained commands
77+ // Count existing operators in the pattern to prevent deeply nested chains
78+ const operatorCount = ( restPattern . match ( / & & | \| \| | ; | \| / g) || [ ] ) . length
79+ if ( operatorCount >= 3 ) {
80+ // Too many chained commands, return a more restrictive pattern
81+ return firstPattern
82+ }
83+
84+ return `${ firstPattern } ${ chainOperator } ${ restPattern } `
2985 }
3086
3187 // Not a chained command, process normally
@@ -95,14 +151,24 @@ function extractSingleCommandPattern(command: string): string {
95151 // 1. npm/yarn/pnpm commands - include subcommand with wildcards for scripts
96152 if ( [ "npm" , "yarn" , "pnpm" , "bun" ] . includes ( baseCommand ) && tokens . length > 1 ) {
97153 const subCommand = tokens [ 1 ]
98- // For "run" commands, include "run" with wildcard for script names
154+ // For "run" commands, be more specific about patterns
99155 if ( subCommand === "run" && tokens . length > 2 ) {
100- // Check if the script name contains special characters like colons
101156 const scriptName = tokens [ 2 ]
157+ // Check if there are additional arguments after the script name
158+ const hasDoubleDash = tokens . includes ( "--" )
159+ const doubleDashIndex = hasDoubleDash ? tokens . indexOf ( "--" ) : - 1
160+
161+ // For scripts with colons or complex names
102162 if ( scriptName && ( scriptName . includes ( ":" ) || scriptName . includes ( "-" ) ) ) {
163+ // If there's a double dash with additional args, include the full script name
164+ if ( hasDoubleDash && doubleDashIndex > 2 ) {
165+ return `${ baseCommand } run ${ scriptName } `
166+ }
167+ // Otherwise use wildcard for flexibility
103168 return `${ baseCommand } run *`
104169 }
105- return `${ baseCommand } run`
170+ // For simple script names, include the script name itself
171+ return `${ baseCommand } run ${ scriptName } `
106172 }
107173 // For direct scripts like "npm test", "npm build", include the script name
108174 if ( ! subCommand . startsWith ( "-" ) ) {
@@ -139,7 +205,7 @@ function extractSingleCommandPattern(command: string): string {
139205 return baseCommand
140206 }
141207
142- // 6. cd command - include wildcard for paths
208+ // 6. cd command - use wildcard for flexibility
143209 if ( baseCommand === "cd" ) {
144210 return "cd *"
145211 }
@@ -160,6 +226,23 @@ function extractSingleCommandPattern(command: string): string {
160226 }
161227 }
162228
229+ // 9. Environment variables - handle with care
230+ if ( baseCommand . includes ( "=" ) ) {
231+ // This might be an environment variable like NODE_ENV=production
232+ const envMatch = baseCommand . match ( / ^ ( [ A - Z _ ] + ) = / )
233+ if ( envMatch ) {
234+ // Don't include the value, just the variable name pattern
235+ return `${ envMatch [ 1 ] } =*`
236+ }
237+ }
238+
239+ // 10. Commands with suspicious patterns - be restrictive
240+ // Check for potential command injection patterns
241+ if ( baseCommand . includes ( "$" ) || baseCommand . includes ( "`" ) || baseCommand . includes ( "(" ) ) {
242+ // These could be command substitutions or variables, be very restrictive
243+ return baseCommand . split ( / [ $ ` ( ] / ) [ 0 ] . trim ( ) || "echo"
244+ }
245+
163246 // Default: just return the base command
164247 return baseCommand
165248}
0 commit comments