@@ -159,62 +159,116 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
159159 public override abort ( ) {
160160 this . aborted = true
161161
162+ // Function to kill entire process tree
163+ const killProcessTree = ( pid : number , signal : NodeJS . Signals = "SIGKILL" ) => {
164+ return new Promise < void > ( ( resolve ) => {
165+ psTree ( pid , ( err , children ) => {
166+ if ( ! err && children . length > 0 ) {
167+ const pids = children . map ( ( p ) => parseInt ( p . PID ) )
168+ console . log (
169+ `[ExecaTerminalProcess#abort] Killing process tree for PID ${ pid } : ${ pids . join ( ", " ) } ` ,
170+ )
171+
172+ // Kill children first (bottom-up approach)
173+ for ( const childPid of pids . reverse ( ) ) {
174+ try {
175+ process . kill ( childPid , signal )
176+ } catch ( e ) {
177+ // Process might already be dead
178+ console . debug (
179+ `[ExecaTerminalProcess#abort] Failed to send ${ signal } to child PID ${ childPid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
180+ )
181+ }
182+ }
183+ }
184+
185+ // Then kill the parent
186+ try {
187+ process . kill ( pid , signal )
188+ } catch ( e ) {
189+ console . debug (
190+ `[ExecaTerminalProcess#abort] Failed to send ${ signal } to parent PID ${ pid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
191+ )
192+ }
193+
194+ resolve ( )
195+ } )
196+ } )
197+ }
198+
162199 // Function to perform the kill operations
163- const performKill = ( ) => {
164- // Try to kill using the subprocess object
165- if ( this . subprocess ) {
166- try {
167- this . subprocess . kill ( "SIGKILL" )
168- } catch ( e ) {
169- console . warn (
170- `[ExecaTerminalProcess#abort] Failed to kill subprocess: ${ e instanceof Error ? e . message : String ( e ) } ` ,
171- )
200+ const performKill = async ( ) => {
201+ const command = this . command ?. toLowerCase ( ) || ""
202+ const needsAggressiveTermination =
203+ command . includes ( "pnpm" ) ||
204+ command . includes ( "npm" ) ||
205+ command . includes ( "yarn" ) ||
206+ command . includes ( "bun" )
207+
208+ if ( needsAggressiveTermination ) {
209+ console . log (
210+ `[ExecaTerminalProcess#abort] Detected package manager command, using aggressive termination` ,
211+ )
212+
213+ // First try SIGTERM to allow graceful shutdown
214+ if ( this . subprocess ) {
215+ try {
216+ this . subprocess . kill ( "SIGTERM" )
217+ } catch ( e ) {
218+ console . debug (
219+ `[ExecaTerminalProcess#abort] Failed to send SIGTERM to subprocess: ${ e instanceof Error ? e . message : String ( e ) } ` ,
220+ )
221+ }
172222 }
173- }
174223
175- // Kill the stored PID (which should be the actual command after our update)
176- if ( this . pid ) {
177- try {
178- process . kill ( this . pid , "SIGKILL" )
179- } catch ( e ) {
180- console . warn (
181- `[ExecaTerminalProcess#abort] Failed to kill process ${ this . pid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
182- )
224+ // Kill the entire process tree with SIGTERM first
225+ if ( this . pid ) {
226+ await killProcessTree ( this . pid , "SIGTERM" )
227+ }
228+
229+ // Wait a bit for graceful shutdown
230+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) )
231+
232+ // Then force kill with SIGKILL if still running
233+ if ( this . subprocess && ! this . subprocess . killed ) {
234+ try {
235+ this . subprocess . kill ( "SIGKILL" )
236+ } catch ( e ) {
237+ console . debug (
238+ `[ExecaTerminalProcess#abort] Failed to send SIGKILL to subprocess: ${ e instanceof Error ? e . message : String ( e ) } ` ,
239+ )
240+ }
241+ }
242+
243+ // Force kill the entire process tree
244+ if ( this . pid ) {
245+ await killProcessTree ( this . pid , "SIGKILL" )
246+ }
247+ } else {
248+ // For regular commands, use the standard approach
249+ if ( this . subprocess ) {
250+ try {
251+ this . subprocess . kill ( "SIGKILL" )
252+ } catch ( e ) {
253+ console . warn (
254+ `[ExecaTerminalProcess#abort] Failed to kill subprocess: ${ e instanceof Error ? e . message : String ( e ) } ` ,
255+ )
256+ }
257+ }
258+
259+ // Kill the stored PID and its children
260+ if ( this . pid ) {
261+ await killProcessTree ( this . pid , "SIGKILL" )
183262 }
184263 }
185264 }
186265
187266 // If PID update is in progress, wait for it before killing
188267 if ( this . pidUpdatePromise ) {
189- this . pidUpdatePromise . then ( performKill ) . catch ( ( ) => performKill ( ) )
268+ this . pidUpdatePromise . then ( ( ) => performKill ( ) ) . catch ( ( ) => performKill ( ) )
190269 } else {
191270 performKill ( )
192271 }
193-
194- // Continue with the rest of the abort logic
195- if ( this . pid ) {
196- // Also check for any child processes
197- psTree ( this . pid , async ( err , children ) => {
198- if ( ! err ) {
199- const pids = children . map ( ( p ) => parseInt ( p . PID ) )
200- console . error ( `[ExecaTerminalProcess#abort] SIGKILL children -> ${ pids . join ( ", " ) } ` )
201-
202- for ( const pid of pids ) {
203- try {
204- process . kill ( pid , "SIGKILL" )
205- } catch ( e ) {
206- console . warn (
207- `[ExecaTerminalProcess#abort] Failed to send SIGKILL to child PID ${ pid } : ${ e instanceof Error ? e . message : String ( e ) } ` ,
208- )
209- }
210- }
211- } else {
212- console . error (
213- `[ExecaTerminalProcess#abort] Failed to get process tree for PID ${ this . pid } : ${ err . message } ` ,
214- )
215- }
216- } )
217- }
218272 }
219273
220274 public override hasUnretrievedOutput ( ) {
0 commit comments