* Stream-count lines from `rg --files` without buffering stdout. * * On large repos (e.g. 247k files, 16MB of paths), calling `ripGrep()` just * to read `.length` materializes the full stdout string plus a 247k-element * array. This counts newline bytes per chunk instead; peak memory is one * s
( args: string[], target: string, abortSignal: AbortSignal, )
| 296 | * timeout (callers pass AbortSignal.timeout; spawn's signal option kills rg). |
| 297 | */ |
| 298 | async function ripGrepFileCount( |
| 299 | args: string[], |
| 300 | target: string, |
| 301 | abortSignal: AbortSignal, |
| 302 | ): Promise<number> { |
| 303 | await codesignRipgrepIfNecessary() |
| 304 | const { rgPath, rgArgs, argv0 } = ripgrepCommand() |
| 305 | |
| 306 | return new Promise<number>((resolve, reject) => { |
| 307 | const child = spawn(rgPath, [...rgArgs, ...args, target], { |
| 308 | argv0, |
| 309 | signal: abortSignal, |
| 310 | windowsHide: true, |
| 311 | stdio: ['ignore', 'pipe', 'ignore'], |
| 312 | }) |
| 313 | |
| 314 | let lines = 0 |
| 315 | child.stdout?.on('data', (chunk: Buffer) => { |
| 316 | lines += countCharInString(chunk, '\n') |
| 317 | }) |
| 318 | |
| 319 | // On Windows, both 'close' and 'error' can fire for the same process. |
| 320 | let settled = false |
| 321 | child.on('close', code => { |
| 322 | if (settled) return |
| 323 | settled = true |
| 324 | if (code === 0 || code === 1) resolve(lines) |
| 325 | else reject(new Error(`rg --files exited ${code}`)) |
| 326 | }) |
| 327 | child.on('error', err => { |
| 328 | if (settled) return |
| 329 | settled = true |
| 330 | reject(err) |
| 331 | }) |
| 332 | }) |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Stream lines from ripgrep as they arrive, calling `onLines` per stdout chunk. |
no test coverage detected