* 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, )
| 244 | * timeout (callers pass AbortSignal.timeout; spawn's signal option kills rg). |
| 245 | */ |
| 246 | async function ripGrepFileCount( |
| 247 | args: string[], |
| 248 | target: string, |
| 249 | abortSignal: AbortSignal, |
| 250 | ): Promise<number> { |
| 251 | await codesignRipgrepIfNecessary() |
| 252 | const { rgPath, rgArgs, argv0 } = ripgrepCommand() |
| 253 | |
| 254 | return new Promise<number>((resolve, reject) => { |
| 255 | const child = spawn(rgPath, [...rgArgs, ...args, target], { |
| 256 | argv0, |
| 257 | signal: abortSignal, |
| 258 | windowsHide: true, |
| 259 | stdio: ['ignore', 'pipe', 'ignore'], |
| 260 | }) |
| 261 | |
| 262 | let lines = 0 |
| 263 | child.stdout?.on('data', (chunk: Buffer) => { |
| 264 | lines += countCharInString(chunk, '\n') |
| 265 | }) |
| 266 | |
| 267 | // On Windows, both 'close' and 'error' can fire for the same process. |
| 268 | let settled = false |
| 269 | child.on('close', code => { |
| 270 | if (settled) return |
| 271 | settled = true |
| 272 | if (code === 0 || code === 1) resolve(lines) |
| 273 | else reject(new Error(`rg --files exited ${code}`)) |
| 274 | }) |
| 275 | child.on('error', err => { |
| 276 | if (settled) return |
| 277 | settled = true |
| 278 | reject(err) |
| 279 | }) |
| 280 | }) |
| 281 | } |
| 282 | |
| 283 | /** |
| 284 | * Stream lines from ripgrep as they arrive, calling `onLines` per stdout chunk. |
no test coverage detected