66 * This is now JS/Bun-only.
77 */
88
9- import { join } from "node:path" ;
9+ import { join , resolve } from "node:path" ;
1010import { scan } from "../src/scanner.ts" ;
1111
1212const args = Bun . argv . slice ( 2 ) ;
@@ -34,10 +34,51 @@ function shouldSkip(p: string): boolean {
3434 ) ;
3535}
3636
37- async function npkillScan ( dir : string ) : Promise < number > {
37+ function shellQuote ( value : string ) : string {
38+ return `'${ value . replaceAll ( "'" , "'\\''" ) } '` ;
39+ }
40+
41+ /** Convert a Windows absolute path to a WSL /mnt/... path. */
42+ function toWslPath ( winPath : string ) : string {
43+ const normalizedPath = resolve ( winPath ) . replaceAll ( "\\" , "/" ) ;
44+ const driveMatch = / ^ ( [ A - Z a - z ] ) : \/ ( .* ) $ / . exec ( normalizedPath ) ;
45+ if ( ! driveMatch ?. [ 1 ] ) {
46+ return normalizedPath ;
47+ }
48+
49+ const drive = driveMatch [ 1 ] . toLowerCase ( ) ;
50+ const rest = driveMatch [ 2 ] ?? "" ;
51+ return `/mnt/${ drive } /${ rest } ` ;
52+ }
53+
54+ async function hasWslNpkill ( ) : Promise < boolean > {
55+ const probe = Bun . spawn ( {
56+ cmd : [
57+ "wsl" ,
58+ "bash" ,
59+ "-lc" ,
60+ "command -v script >/dev/null && command -v npkill >/dev/null" ,
61+ ] ,
62+ stdout : "ignore" ,
63+ stderr : "ignore" ,
64+ } ) ;
65+
66+ return await probe . exited === 0 ;
67+ }
68+
69+ async function npkillScan ( dir : string , useWsl = false ) : Promise < number > {
70+ if ( useWsl && ! ( await hasWslNpkill ( ) ) ) {
71+ return - 1 ;
72+ }
73+
74+ const wslCommand = `npkill -d ${ shellQuote ( toWslPath ( dir ) ) } -nu --hide-errors` ;
75+ const scriptCmd = useWsl
76+ ? [ "wsl" , "bash" , "-lc" , `script -q /dev/null -c ${ shellQuote ( wslCommand ) } 2>/dev/null` ]
77+ : [ "script" , "-q" , "/dev/null" , "npkill" , "-d" , dir , "-nu" , "--hide-errors" ] ;
78+
3879 try {
3980 const proc = Bun . spawn ( {
40- cmd : [ "script" , "-q" , "/dev/null" , "npkill" , "-d" , dir , "-nu" , "--hide-errors" ] ,
81+ cmd : scriptCmd ,
4182 stdout : "pipe" ,
4283 stderr : "pipe" ,
4384 stdin : "pipe" ,
@@ -61,6 +102,10 @@ async function npkillScan(dir: string): Promise<number> {
61102 clearTimeout ( killTimer ) ;
62103 try { proc . kill ( ) ; } catch { }
63104
105+ if ( await proc . exited !== 0 ) {
106+ return - 1 ;
107+ }
108+
64109 const raw = Buffer . concat ( chunks ) . toString ( "utf8" ) ;
65110 const clean = raw . replace ( / \x1b \[ [ 0 - 9 ; ] * [ a - z A - Z ] / g, "" ) . replace ( / \r / g, "" ) ;
66111
@@ -84,11 +129,24 @@ async function bunGlobScan(dir: string): Promise<number> {
84129 onlyFiles : false ,
85130 followSymlinks : false ,
86131 } ) ) {
132+ const normalized = relative . replaceAll ( "\\" , "/" ) ;
87133 const fullPath = join ( dir , relative ) ;
88- if ( fullPath . split ( "/node_modules" ) . length > 2 ) continue ;
89- if ( shouldSkip ( fullPath ) ) continue ;
90- const depth = relative . split ( "/" ) . filter ( Boolean ) . length ;
134+ const fullNormalized = fullPath . replaceAll ( "\\" , "/" ) ;
135+
136+ const targetSegments = normalized . split ( "/" )
137+ . filter ( ( segment ) => segment === "node_modules" ) ;
138+ if ( targetSegments . length > 1 ) continue ;
139+ if ( shouldSkip ( fullNormalized ) ) continue ;
140+
141+ const depth = normalized . split ( "/" ) . filter ( Boolean ) . length ;
91142 if ( depth > MAX_DEPTH ) continue ;
143+
144+ const projectPath = join ( fullPath , ".." ) ;
145+ const packageJson = await Bun . file ( join ( projectPath , "package.json" ) )
146+ . json ( )
147+ . catch ( ( ) => null ) ;
148+ if ( ! packageJson ) continue ;
149+
92150 results . push ( fullPath ) ;
93151 }
94152
@@ -162,19 +220,20 @@ function fmtMs(ms: number): string {
162220}
163221
164222function printRow ( r : BenchResult , baseline : number ) : void {
223+ // \r\x1b[2K clears any partial line left by WSL/npkill carriage returns
165224 if ( ! r . available ) {
166- console . log ( ` ${ r . label . padEnd ( 24 ) } N/A (${ r . note ?? "unavailable" } )`) ;
225+ process . stdout . write ( `\r\x1b[2K ${ r . label . padEnd ( 24 ) } N/A (${ r . note ?? "unavailable" } )\n `) ;
167226 return ;
168227 }
169228
170229 const speedup = r . avgMs === baseline
171230 ? "baseline"
172231 : `${ ( baseline / r . avgMs ) . toFixed ( 1 ) } x faster` ;
173232
174- console . log (
175- ` ${ r . label . padEnd ( 24 ) } avg=${ fmtMs ( r . avgMs ) . padStart ( 8 ) } ` +
233+ process . stdout . write (
234+ `\r\x1b[2K ${ r . label . padEnd ( 24 ) } avg=${ fmtMs ( r . avgMs ) . padStart ( 8 ) } ` +
176235 `min=${ fmtMs ( r . minMs ) . padStart ( 8 ) } max=${ fmtMs ( r . maxMs ) . padStart ( 8 ) } ` +
177- `found=${ String ( r . found ) . padStart ( 4 ) } ${ speedup } ` ,
236+ `found=${ String ( r . found ) . padStart ( 4 ) } ${ speedup } \n ` ,
178237 ) ;
179238}
180239
@@ -184,9 +243,29 @@ console.log("╚═════════════════════
184243console . log ( ` Scan root : ${ SCAN_DIR } ` ) ;
185244console . log ( ` Runs : ${ RUNS } (+ 1 warm-up)\n` ) ;
186245
187- const npkillPath = await Bun . $ . nothrow ( ) `which npkill` . text ( ) . then ( ( s ) => s . trim ( ) ) . catch ( ( ) => "" ) ;
188-
189- console . log ( ` npkill : ${ npkillPath || "NOT FOUND (npm install -g npkill)" } ` ) ;
246+ const isWindows = process . platform === "win32" ;
247+
248+ // Detect npkill
249+ const npkillPath = isWindows
250+ ? await Bun . $ . nothrow ( ) `where npkill` . text ( ) . then ( ( s ) => s . trim ( ) . split ( "\n" ) [ 0 ] ?. trim ( ) ?? "" ) . catch ( ( ) => "" )
251+ : await Bun . $ . nothrow ( ) `which npkill` . text ( ) . then ( ( s ) => s . trim ( ) ) . catch ( ( ) => "" ) ;
252+
253+ const wslAvailable = isWindows
254+ ? await Bun . $ . nothrow ( ) `where wsl` . text ( ) . then ( ( s ) => s . trim ( ) . length > 0 ) . catch ( ( ) => false )
255+ : false ;
256+ const wslNpkillAvailable = isWindows && wslAvailable
257+ ? await hasWslNpkill ( )
258+ : false ;
259+
260+ const npkillStatus = ! npkillPath
261+ ? "NOT FOUND (npm install -g npkill)"
262+ : isWindows && wslNpkillAvailable
263+ ? `${ npkillPath } (via WSL)`
264+ : isWindows
265+ ? `${ npkillPath } (skipped: WSL npkill/script unavailable)`
266+ : npkillPath ;
267+
268+ console . log ( ` npkill : ${ npkillStatus } ` ) ;
190269console . log ( ` Bun.Glob : baseline glob walk` ) ;
191270console . log ( ` bunkill : current scanner.ts implementation\n` ) ;
192271
@@ -197,9 +276,15 @@ const [globResult, bunkillResult] = await Promise.all([
197276 bench ( "bunkill current" , bunKillScan , SCAN_DIR , RUNS ) ,
198277] ) ;
199278
200- const npkillResult = npkillPath
201- ? await bench ( "npkill" , npkillScan , SCAN_DIR , 1 )
202- : { label : "npkill" , avgMs : 0 , minMs : 0 , maxMs : 0 , found : - 1 , available : false , note : "not installed" } ;
279+ // On Windows: run npkill via WSL if available; on Unix: run directly
280+ const canRunNpkill = npkillPath && ( ! isWindows || wslNpkillAvailable ) ;
281+ const npkillResult = canRunNpkill
282+ ? await bench ( "npkill" , ( d ) => npkillScan ( d , isWindows && wslAvailable ) , SCAN_DIR , 1 )
283+ : {
284+ label : "npkill" ,
285+ avgMs : 0 , minMs : 0 , maxMs : 0 , found : - 1 , available : false ,
286+ note : ! npkillPath ? "not installed" : "WSL npkill/script unavailable" ,
287+ } ;
203288
204289const available = [ npkillResult , globResult , bunkillResult ] . filter ( ( r ) => r . available ) ;
205290
0 commit comments