1- import { parseCommand } from "./command-validation "
1+ import { parse } from "shell-quote "
22
33export interface CommandPattern {
44 pattern : string
@@ -10,60 +10,63 @@ export interface SecurityWarning {
1010 message : string
1111}
1212
13- export function extractCommandPatterns ( command : string ) : string [ ] {
14- if ( ! command ?. trim ( ) ) return [ ]
13+ function processCommand ( cmd : string [ ] , patterns : Set < string > ) : void {
14+ if ( ! cmd . length || typeof cmd [ 0 ] !== "string" ) return
1515
16- const patterns = new Set < string > ( )
16+ const mainCmd = cmd [ 0 ]
1717
18- // First, check if the command contains subshells and remove them
19- // This is important for security - we don't want to extract patterns from subshell contents
20- const cleanedCommand = command
21- . replace ( / \$ \( [ ^ ) ] * \) / g, "" ) // Remove $() subshells
22- . replace ( / ` [ ^ ` ] * ` / g, "" ) // Remove backtick subshells
18+ // Skip if it's just a number (like "0" from "0 total")
19+ if ( / ^ \d + $ / . test ( mainCmd ) ) return
20+
21+ // Skip common output patterns that aren't commands
22+ const skipWords = [ "total" , "error" , "warning" , "failed" , "success" , "done" ]
23+ if ( skipWords . includes ( mainCmd . toLowerCase ( ) ) ) return
24+
25+ patterns . add ( mainCmd )
2326
24- // Use parseCommand to split the cleaned command into sub-commands
25- // This ensures consistent parsing behavior with command-validation
26- const subCommands = parseCommand ( cleanedCommand )
27+ const breakingExps = [ / ^ - / , / [ \\ / . ~ ] / ]
2728
28- // Process each sub-command to extract patterns
29- for ( const subCommand of subCommands ) {
30- // Skip empty commands
31- if ( ! subCommand . trim ( ) ) continue
29+ for ( let i = 1 ; i < cmd . length ; i ++ ) {
30+ const arg = cmd [ i ]
3231
33- // Split the command into tokens
34- const tokens = subCommand . trim ( ) . split ( / \s + / )
32+ if ( typeof arg !== "string" || breakingExps . some ( ( re ) => re . test ( arg ) ) ) break
3533
36- if ( tokens . length === 0 ) continue
34+ const pattern = cmd . slice ( 0 , i + 1 ) . join ( " " )
35+ patterns . add ( pattern )
36+ }
37+ }
3738
38- const mainCmd = tokens [ 0 ]
39+ function extractPatterns ( cmdStr : string ) : Set < string > {
40+ const patterns = new Set < string > ( )
3941
40- // Skip if it's just a number (like "0" from "0 total")
41- if ( / ^ \d + $ / . test ( mainCmd ) ) continue
42+ const parsed = parse ( cmdStr )
4243
43- // Skip common output patterns that aren't commands
44- const skipWords = [ "total" , "error" , "warning" , "failed" , "success" , "done" ]
45- if ( skipWords . includes ( mainCmd . toLowerCase ( ) ) ) continue
44+ const commandSeparators = new Set ( [ "|" , "&&" , "||" , ";" ] )
45+ let current : string [ ] = [ ]
46+ for ( const token of parsed ) {
47+ if ( typeof token === "object" && "op" in token && commandSeparators . has ( token . op ) ) {
48+ if ( current . length ) processCommand ( current , patterns )
49+ current = [ ]
50+ } else {
51+ current . push ( String ( token ) )
52+ }
53+ }
4654
47- // Only add if it contains at least one letter or is a valid path
48- if ( / [ a - z A - Z ] / . test ( mainCmd ) || mainCmd . includes ( "/" ) ) {
49- patterns . add ( mainCmd )
55+ if ( current . length ) processCommand ( current , patterns )
5056
51- // Build up patterns progressively (e.g., "npm", "npm install", "npm install express")
52- // Stop at flags or special characters
53- const stopPatterns = [ / ^ - / , / [ \\ / . ~ ] / ]
57+ return patterns
58+ }
5459
55- for ( let i = 1 ; i < tokens . length ; i ++ ) {
56- const token = tokens [ i ]
60+ export function extractCommandPatterns ( command : string ) : string [ ] {
61+ if ( ! command ?. trim ( ) ) return [ ]
5762
58- // Stop if we hit a flag or special character
59- if ( stopPatterns . some ( ( re ) => re . test ( token ) ) ) break
63+ // First, check if the command contains subshells and remove them
64+ // This is important for security - we don't want to extract patterns from subshell contents
65+ const cleanedCommand = command
66+ . replace ( / \$ \( [ ^ ) ] * \) / g, "" ) // Remove $() subshells
67+ . replace ( / ` [ ^ ` ] * ` / g, "" ) // Remove backtick subshells
6068
61- // Build the pattern up to this point
62- const pattern = tokens . slice ( 0 , i + 1 ) . join ( " " )
63- patterns . add ( pattern )
64- }
65- }
66- }
69+ const patterns = extractPatterns ( cleanedCommand )
6770
6871 return Array . from ( patterns ) . sort ( )
6972}
0 commit comments