diff --git a/packages/cli/lib/testing.ts b/packages/cli/lib/testing.ts index 52e2e432..66e889c8 100644 --- a/packages/cli/lib/testing.ts +++ b/packages/cli/lib/testing.ts @@ -4,6 +4,7 @@ import process from 'node:process'; import degit from 'degit'; import { x, exec } from 'tinyexec'; import { create } from '@sveltejs/create'; +import pstree, { type PS } from 'ps-tree'; export { addPnpmBuildDependencies } from '../utils/package-manager.ts'; export type ProjectVariant = 'kit-js' | 'kit-ts' | 'vite-js' | 'vite-ts'; @@ -121,17 +122,36 @@ export async function startPreview({ }); } +async function getProcessTree(pid: number) { + return new Promise((res, rej) => { + pstree(pid, (err, children) => { + if (err) rej(err); + res(children); + }); + }); +} + async function terminate(pid: number) { + if (process.platform === 'win32') { + // on windows, use taskkill to terminate the process tree + await x('taskkill', ['/PID', `${pid}`, '/T', '/F']); + return; + } + const children = await getProcessTree(pid); + // the process tree is ordered from parents -> children, + // so we'll iterate in the reverse order to terminate the children first + for (let i = children.length - 1; i >= 0; i--) { + const child = children[i]; + const pid = Number(child.PID); + kill(pid); + } + kill(pid); +} + +function kill(pid: number) { try { - if (process.platform === 'win32') { - // on windows, use taskkill to terminate the process tree - await x('taskkill', ['/PID', `${pid}`, '/T', '/F']); - } else { - process.kill(-pid, 'SIGTERM'); // Kill the process group - } + process.kill(pid); } catch { - try { - process.kill(pid, 'SIGTERM'); // Kill just the process - } catch {} + // this can happen if a process has been automatically terminated. } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 02199413..a3effe99 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,11 +35,13 @@ "@sveltejs/cli-core": "workspace:*", "@sveltejs/create": "workspace:*", "@types/degit": "^2.8.6", + "@types/ps-tree": "^1.1.6", "commander": "^13.1.0", "degit": "^2.8.4", "empathic": "^1.1.0", "package-manager-detector": "^0.2.11", "picocolors": "^1.1.1", + "ps-tree": "^1.2.0", "tinyexec": "^0.3.2", "valibot": "^0.41.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30594fb7..93c9ad4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,6 +112,9 @@ importers: '@types/degit': specifier: ^2.8.6 version: 2.8.6 + '@types/ps-tree': + specifier: ^1.1.6 + version: 1.1.6 commander: specifier: ^13.1.0 version: 13.1.0 @@ -127,6 +130,9 @@ importers: picocolors: specifier: ^1.1.1 version: 1.1.1 + ps-tree: + specifier: ^1.2.0 + version: 1.2.0 tinyexec: specifier: ^0.3.2 version: 0.3.2 @@ -883,6 +889,9 @@ packages: '@types/prompts@2.4.9': resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/ps-tree@1.1.6': + resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} + '@types/semver@7.7.0': resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} @@ -1180,6 +1189,9 @@ packages: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1310,6 +1322,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + expect-type@1.2.2: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} @@ -1375,6 +1390,9 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -1584,6 +1602,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1716,6 +1737,9 @@ packages: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1809,6 +1833,11 @@ packages: engines: {node: '>=14'} hasBin: true + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1906,6 +1935,9 @@ packages: spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -1915,6 +1947,9 @@ packages: std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1983,6 +2018,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} @@ -2802,6 +2840,8 @@ snapshots: '@types/node': 18.19.112 kleur: 3.0.3 + '@types/ps-tree@1.1.6': {} + '@types/semver@7.7.0': {} '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0)(typescript@5.8.3))(eslint@9.29.0)(typescript@5.8.3)': @@ -3098,6 +3138,8 @@ snapshots: dotenv@16.5.0: {} + duplexer@0.1.2: {} + eastasianwidth@0.2.0: {} emoji-regex@8.0.0: {} @@ -3278,6 +3320,16 @@ snapshots: esutils@2.0.3: {} + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + expect-type@1.2.2: {} extendable-error@0.1.7: {} @@ -3346,6 +3398,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + from@0.1.7: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -3530,6 +3584,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + map-stream@0.1.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3646,6 +3702,10 @@ snapshots: pathval@2.0.1: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3710,6 +3770,10 @@ snapshots: prettier@3.5.3: {} + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + punycode@2.3.1: {} quansync@0.2.10: {} @@ -3824,12 +3888,20 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + split@0.3.3: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} stackback@0.0.2: {} std-env@3.9.0: {} + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -3916,6 +3988,8 @@ snapshots: dependencies: any-promise: 1.3.0 + through@2.3.8: {} + tiny-glob@0.2.9: dependencies: globalyzer: 0.1.0