@@ -30,6 +30,17 @@ function createBatchOrBashRunner($filename, $command)
3030 return $ runnerPath ;
3131}
3232
33+ /**
34+ * Quote an argument for Windows cmd.exe (double-quote and double internal quotes).
35+ */
36+ function quoteWindowsArg (string $ str ): string
37+ {
38+ // convert forward slashes to backslashes for Windows paths
39+ $ s = str_replace ('/ ' , '\\' , $ str );
40+ // double any internal double-quotes and wrap in double-quotes
41+ return '" ' . str_replace ('" ' , '"" ' , $ s ) . '" ' ;
42+ }
43+
3344/**
3445 * Executes a Bash or Batch script asynchronously with optional arguments.
3546 *
@@ -67,7 +78,11 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
6778 // Convert arguments to command line string
6879 $ commandArgsString = '' ;
6980 foreach ($ commandArgs as $ key => $ value ) {
70- $ escapedValue = escapeshellarg ($ value );
81+ if ($ isWin ) {
82+ $ escapedValue = quoteWindowsArg ((string )$ value );
83+ } else {
84+ $ escapedValue = escapeshellarg ((string )$ value );
85+ }
7186 $ commandArgsString .= "-- $ key= $ escapedValue " ;
7287 }
7388 $ commandArgsString = trim ($ commandArgsString );
@@ -95,30 +110,30 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
95110 $ cmdParts = [];
96111 if ($ venv ) {
97112 // Escape the venv path so it's safe for shells
98- $ cmdParts [] = $ isWin ? 'call ' . escapeshellarg ($ venv ) : 'source ' . escapeshellarg ($ venv );
113+ $ cmdParts [] = $ isWin ? 'call ' . quoteWindowsArg ($ venv ) : 'source ' . escapeshellarg ($ venv );
99114 }
100115
101116 // Ensure output file is escaped
102- $ escapedOutput = escapeshellarg ($ output_file );
117+ $ escapedOutput = $ isWin ? quoteWindowsArg ( $ output_file ) : escapeshellarg ($ output_file );
103118
104119 // Decide how to invoke the target runner script.
105120 // Always treat the provided $scriptPath as a runner script (bat/sh).
106121 // On Windows we use `call` to run the .bat; on Unix we use `bash`.
107122 if ($ isWin ) {
108- $ invoke = 'call ' . escapeshellarg ($ scriptPath );
123+ $ invoke = 'call ' . quoteWindowsArg ($ scriptPath );
109124 } else {
110125 $ invoke = 'bash ' . escapeshellarg ($ scriptPath );
111126 }
112127
113128 if ($ redirectOutput ) {
114129 $ cmdParts [] = $ invoke . ' > ' . $ escapedOutput . ' 2>&1 ' ;
115130 } else {
116- if ($ isWin ) {
117- $ redirect_cmd = ' > NUL 2>&1 ' ;
118- } else {
119- $ redirect_cmd = ' > /dev/null 2>&1 & ' ;
120- }
121- $ cmdParts [] = $ invoke . $ redirect_cmd ;
131+ // if ($isWin) {
132+ // $redirect_cmd = ' > NUL 2>&1';
133+ // } else {
134+ // $redirect_cmd = ' > /dev/null 2>&1 &';
135+ // }
136+ $ cmdParts [] = $ invoke; // . $redirect_cmd;
122137 }
123138
124139 $ cmd = trim (implode (' && ' , $ cmdParts ));
@@ -127,19 +142,25 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
127142 truncateFile ($ output_file );
128143 truncateFile ($ runner_file );
129144
130- // Write command to runner file
131- write_file ($ runner_file , $ cmd );
145+ // Write command to runner file. On Windows prefer CRLF endings for batch files.
146+ if ($ isWin ) {
147+ $ cmdToWrite = preg_replace ("/ \r? \n/ " , "\r\n" , $ cmd );
148+ } else {
149+ $ cmdToWrite = $ cmd ;
150+ }
151+ write_file ($ runner_file , $ cmdToWrite );
132152
133153 // Change current working directory
134154 chdir ($ cwd );
135155
136156 // Execute the runner script in background; runner already redirects output
137157 if ($ isWin ) {
138- $ window_name = 'window_name ' ;
139- $ startCmd = 'start /B " ' . $ window_name . '" ' . escapeshellarg (unixPath ($ runner_file ));
140- exec ($ startCmd );
158+ // Use empty title ("") so start doesn't treat the first quoted string as the title.
159+ $ startCmd = 'start "" /B ' . quoteWindowsArg (unixPath ($ runner_file ));
160+ // Use pclose popen to avoid waiting on exec on some PHP builds.
161+ pclose (popen ($ startCmd , 'r ' ));
141162 } else {
142- exec ('bash ' . escapeshellarg ($ runner_file ));
163+ exec ('bash ' . escapeshellarg ($ runner_file ) . ' > /dev/null 2>&1 & ' );
143164 }
144165
145166 return [
0 commit comments