@augment-vir/node 31.73.1 → 31.73.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/augments/fs/grep.d.ts +2 -2
- package/dist/augments/fs/grep.js +575 -103
- package/dist/augments/fs/read-dir.js +9 -3
- package/dist/augments/path/contains.d.ts +11 -7
- package/dist/augments/path/contains.js +1 -1
- package/dist/augments/path/resolve-import.d.ts +4 -1
- package/dist/augments/path/resolve-import.js +8 -3
- package/dist/augments/terminal/run-cli-script.d.ts +5 -8
- package/dist/augments/terminal/run-cli-script.js +1 -8
- package/dist/augments/terminal/shell.d.ts +20 -11
- package/dist/augments/terminal/shell.js +10 -4
- package/package.json +10 -10
- package/src/augments/fs/grep.ts +830 -131
- package/src/augments/fs/read-dir.ts +12 -3
- package/src/augments/path/contains.ts +10 -6
- package/src/augments/path/resolve-import.ts +15 -10
- package/src/augments/terminal/run-cli-script.ts +5 -10
- package/src/augments/terminal/shell.ts +47 -27
package/src/augments/fs/grep.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {assert, check} from '@augment-vir/assert';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
awaitedBlockingMap,
|
|
4
|
+
getObjectTypedKeys,
|
|
5
5
|
log,
|
|
6
|
-
|
|
6
|
+
typedObjectFromEntries,
|
|
7
7
|
type PartialWithUndefined,
|
|
8
8
|
type SelectFrom,
|
|
9
9
|
} from '@augment-vir/common';
|
|
10
|
-
import {
|
|
10
|
+
import {spawn} from 'node:child_process';
|
|
11
|
+
import {lstat, readdir, stat} from 'node:fs/promises';
|
|
12
|
+
import {isAbsolute, join, resolve} from 'node:path';
|
|
11
13
|
import {type IsEqual, type RequireExactlyOne} from 'type-fest';
|
|
12
|
-
import {
|
|
14
|
+
import {isOperatingSystem, OperatingSystem} from '../os/operating-system.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Optional options for {@link grep}.
|
|
@@ -19,6 +21,7 @@ import {runShellCommand} from '../terminal/shell.js';
|
|
|
19
21
|
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
20
22
|
*/
|
|
21
23
|
export type GrepOptions<CountOnly extends boolean = false> = PartialWithUndefined<{
|
|
24
|
+
/* node:coverage ignore next */
|
|
22
25
|
patternSyntax: RequireExactlyOne<{
|
|
23
26
|
/**
|
|
24
27
|
* -E, --extended-regexp: Interpret PATTERNS as extended regular expressions (EREs, see
|
|
@@ -174,13 +177,750 @@ export type GrepSearchPattern = RequireExactlyOne<{
|
|
|
174
177
|
patterns: string[];
|
|
175
178
|
}>;
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
const grepBinPath = '/usr/bin/grep';
|
|
181
|
+
|
|
182
|
+
function shellQuote(input: string) {
|
|
183
|
+
return [
|
|
184
|
+
"'",
|
|
185
|
+
input.replaceAll("'", String.raw`'\''`),
|
|
186
|
+
"'",
|
|
187
|
+
].join('');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function recursiveFlag({
|
|
191
|
+
recursive,
|
|
192
|
+
followSymLinks,
|
|
193
|
+
}: Readonly<Pick<GrepOptions, 'recursive' | 'followSymLinks'>>): string {
|
|
194
|
+
if (!recursive) {
|
|
195
|
+
return '';
|
|
196
|
+
} else if (!followSymLinks) {
|
|
197
|
+
return '--recursive';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* BSD `grep` (macOS) requires `-S` to follow symlinks while recursing, but GNU `grep` (Linux)
|
|
202
|
+
* has no `-S` flag and instead follows all symlinks with `-R`. Only one of these branches can
|
|
203
|
+
* run on a given operating system.
|
|
204
|
+
*/
|
|
205
|
+
/* node:coverage ignore next */
|
|
206
|
+
return isOperatingSystem(OperatingSystem.Mac) ? '-RS' : '-R';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function isValidMaxCount(maxCount: unknown) {
|
|
210
|
+
return (
|
|
211
|
+
maxCount == undefined ||
|
|
212
|
+
(check.isNumber(maxCount) && Number.isInteger(maxCount) && maxCount >= -1)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isOptionalBoolean(input: unknown) {
|
|
217
|
+
return input == undefined || check.isBoolean(input);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isValidTrueOnlyOptionGroup({
|
|
221
|
+
input,
|
|
222
|
+
values,
|
|
223
|
+
}: Readonly<{
|
|
224
|
+
input: unknown;
|
|
225
|
+
values: ReadonlyArray<unknown>;
|
|
226
|
+
}>) {
|
|
227
|
+
return (
|
|
228
|
+
input == undefined ||
|
|
229
|
+
(check.isObject(input) &&
|
|
230
|
+
values.every((value) => value == undefined || value === true) &&
|
|
231
|
+
values.filter((value) => value === true).length === 1)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isValidPatternSyntax(input: unknown) {
|
|
236
|
+
return isValidTrueOnlyOptionGroup({
|
|
237
|
+
input,
|
|
238
|
+
values: check.isObject(input)
|
|
239
|
+
? [
|
|
240
|
+
input.basicRegExp,
|
|
241
|
+
input.extendedRegExp,
|
|
242
|
+
input.fixedStrings,
|
|
243
|
+
]
|
|
244
|
+
: [],
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isValidMatchType(input: unknown) {
|
|
249
|
+
return isValidTrueOnlyOptionGroup({
|
|
250
|
+
input,
|
|
251
|
+
values: check.isObject(input)
|
|
252
|
+
? [
|
|
253
|
+
input.lineRegExp,
|
|
254
|
+
input.wordRegExp,
|
|
255
|
+
]
|
|
256
|
+
: [],
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isValidOutput(input: unknown) {
|
|
261
|
+
return isValidTrueOnlyOptionGroup({
|
|
262
|
+
input,
|
|
263
|
+
values: check.isObject(input)
|
|
264
|
+
? [
|
|
265
|
+
input.countOnly,
|
|
266
|
+
input.filesOnly,
|
|
267
|
+
]
|
|
268
|
+
: [],
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
type GrepCommandOutput = {
|
|
273
|
+
exitCode: number | undefined;
|
|
274
|
+
stdout: string;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
function didGrepFail({exitCode}: Readonly<GrepCommandOutput>) {
|
|
278
|
+
return exitCode == undefined || exitCode > 1;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function spawnGrepProcess({
|
|
282
|
+
args,
|
|
283
|
+
cwd,
|
|
284
|
+
}: Readonly<{
|
|
285
|
+
args: string[];
|
|
286
|
+
cwd: string | undefined;
|
|
287
|
+
}>) {
|
|
288
|
+
try {
|
|
289
|
+
return spawn(grepBinPath, args, {
|
|
290
|
+
cwd,
|
|
291
|
+
stdio: [
|
|
292
|
+
'ignore',
|
|
293
|
+
'pipe',
|
|
294
|
+
'pipe',
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
/* node:coverage ignore next 3 */
|
|
298
|
+
} catch {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function runGrepCommand({
|
|
304
|
+
args,
|
|
305
|
+
cwd,
|
|
306
|
+
}: Readonly<{
|
|
307
|
+
args: string[];
|
|
308
|
+
cwd: string | undefined;
|
|
309
|
+
}>): Promise<GrepCommandOutput> {
|
|
310
|
+
return new Promise<GrepCommandOutput>((resolveOutput) => {
|
|
311
|
+
const stdoutChunks: Buffer[] = [];
|
|
312
|
+
const grepProcess = spawnGrepProcess({
|
|
313
|
+
args,
|
|
314
|
+
cwd,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (!grepProcess) {
|
|
318
|
+
resolveOutput({
|
|
319
|
+
exitCode: undefined,
|
|
320
|
+
stdout: '',
|
|
321
|
+
});
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
assert.isDefined(grepProcess.stdout, 'stdout emitter was not created for grep.');
|
|
326
|
+
assert.isDefined(grepProcess.stderr, 'stderr emitter was not created for grep.');
|
|
327
|
+
|
|
328
|
+
grepProcess.stdout.on('data', (chunk) => {
|
|
329
|
+
stdoutChunks.push(Buffer.from(chunk));
|
|
330
|
+
});
|
|
331
|
+
grepProcess.stderr.on('data', () => {});
|
|
332
|
+
/* node:coverage ignore next 5 */
|
|
333
|
+
grepProcess.on('error', () => {
|
|
334
|
+
resolveOutput({
|
|
335
|
+
exitCode: undefined,
|
|
336
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
grepProcess.on('close', (rawExitCode) => {
|
|
340
|
+
resolveOutput({
|
|
341
|
+
/* node:coverage ignore next */
|
|
342
|
+
exitCode: rawExitCode ?? undefined,
|
|
343
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function redactGrepArgForLogging({
|
|
350
|
+
arg,
|
|
351
|
+
index,
|
|
352
|
+
operandDelimiterIndex,
|
|
353
|
+
previousArg,
|
|
354
|
+
}: Readonly<{
|
|
355
|
+
arg: string;
|
|
356
|
+
index: number;
|
|
357
|
+
operandDelimiterIndex: number;
|
|
358
|
+
previousArg: string | undefined;
|
|
359
|
+
}>) {
|
|
360
|
+
if (previousArg === '-e') {
|
|
361
|
+
return '<pattern>';
|
|
362
|
+
} else if (operandDelimiterIndex >= 0 && index > operandDelimiterIndex) {
|
|
363
|
+
return '<path>';
|
|
364
|
+
} else if (arg.startsWith('--exclude-dir=')) {
|
|
365
|
+
return '--exclude-dir=<glob>';
|
|
366
|
+
} else if (arg.startsWith('--exclude=')) {
|
|
367
|
+
return '--exclude=<glob>';
|
|
368
|
+
} else if (arg.startsWith('--include=')) {
|
|
369
|
+
return '--include=<glob>';
|
|
370
|
+
} else if (arg.startsWith('--max-count=')) {
|
|
371
|
+
return '--max-count=<count>';
|
|
372
|
+
} else {
|
|
373
|
+
return arg;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function formatGrepCommand(args: ReadonlyArray<string>) {
|
|
378
|
+
const operandDelimiterIndex = args.indexOf('--');
|
|
379
|
+
|
|
380
|
+
return [
|
|
381
|
+
'grep',
|
|
382
|
+
...args.map((arg, index) =>
|
|
383
|
+
shellQuote(
|
|
384
|
+
redactGrepArgForLogging({
|
|
385
|
+
arg,
|
|
386
|
+
index,
|
|
387
|
+
operandDelimiterIndex,
|
|
388
|
+
previousArg: args[index - 1],
|
|
389
|
+
}),
|
|
390
|
+
),
|
|
391
|
+
),
|
|
392
|
+
].join(' ');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function replaceGrepCountOutputArg({
|
|
396
|
+
args,
|
|
397
|
+
replacement,
|
|
398
|
+
}: Readonly<{
|
|
399
|
+
args: ReadonlyArray<string>;
|
|
400
|
+
replacement: string;
|
|
401
|
+
}>) {
|
|
402
|
+
const countArgIndex = args.indexOf('--count');
|
|
403
|
+
|
|
404
|
+
/* node:coverage ignore next */
|
|
405
|
+
return countArgIndex < 0 ? [...args] : args.toSpliced(countArgIndex, 1, replacement);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function replaceGrepSearchOperands({
|
|
409
|
+
args,
|
|
410
|
+
searchParts,
|
|
411
|
+
}: Readonly<{
|
|
412
|
+
args: ReadonlyArray<string>;
|
|
413
|
+
searchParts: ReadonlyArray<string>;
|
|
414
|
+
}>) {
|
|
415
|
+
const operandDelimiterIndex = args.indexOf('--');
|
|
416
|
+
|
|
417
|
+
return [
|
|
418
|
+
...args.slice(0, operandDelimiterIndex + 1),
|
|
419
|
+
...searchParts,
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function extractStringArray(input: unknown) {
|
|
424
|
+
return check.isArray(input) ? input.filter(check.isString).filter(check.isTruthy) : [];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function extractOptionalStringArray(input: unknown) {
|
|
428
|
+
if (input == undefined) {
|
|
429
|
+
return [];
|
|
430
|
+
} else if (!check.isArray(input) || !input.every(check.isString)) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return input.filter(check.isTruthy);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function extractString(input: unknown) {
|
|
438
|
+
return check.isString(input) && input ? input : undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function extractGrepOptionArrays({
|
|
442
|
+
excludeDirs,
|
|
443
|
+
excludePatterns,
|
|
444
|
+
includeFiles,
|
|
445
|
+
}: Readonly<
|
|
446
|
+
PartialWithUndefined<{
|
|
447
|
+
excludeDirs: unknown;
|
|
448
|
+
excludePatterns: unknown;
|
|
449
|
+
includeFiles: unknown;
|
|
450
|
+
}>
|
|
451
|
+
>) {
|
|
452
|
+
const extractedExcludeDirs = extractOptionalStringArray(excludeDirs);
|
|
453
|
+
const extractedExcludePatterns = extractOptionalStringArray(excludePatterns);
|
|
454
|
+
const extractedIncludeFiles = extractOptionalStringArray(includeFiles);
|
|
455
|
+
|
|
456
|
+
return extractedExcludeDirs && extractedExcludePatterns && extractedIncludeFiles
|
|
457
|
+
? {
|
|
458
|
+
excludeDirs: extractedExcludeDirs,
|
|
459
|
+
excludePatterns: extractedExcludePatterns,
|
|
460
|
+
includeFiles: extractedIncludeFiles,
|
|
461
|
+
}
|
|
462
|
+
: undefined;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function areGrepOptionsValid<const CountOnly extends boolean>({
|
|
466
|
+
grepOptions,
|
|
467
|
+
}: Readonly<{
|
|
468
|
+
grepOptions: Readonly<Partial<GrepOptions<CountOnly>>>;
|
|
469
|
+
}>) {
|
|
470
|
+
return (
|
|
471
|
+
isValidMaxCount(grepOptions.maxCount) &&
|
|
472
|
+
(grepOptions.cwd == undefined || !!extractString(grepOptions.cwd)) &&
|
|
473
|
+
[
|
|
474
|
+
grepOptions.binary,
|
|
475
|
+
grepOptions.followSymLinks,
|
|
476
|
+
grepOptions.ignoreCase,
|
|
477
|
+
grepOptions.invertMatch,
|
|
478
|
+
grepOptions.printCommand,
|
|
479
|
+
grepOptions.recursive,
|
|
480
|
+
].every(isOptionalBoolean) &&
|
|
481
|
+
isValidPatternSyntax(grepOptions.patternSyntax) &&
|
|
482
|
+
isValidMatchType(grepOptions.matchType) &&
|
|
483
|
+
isValidOutput(grepOptions.output)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function createSearchPatterns(grepSearchPattern: Readonly<GrepSearchPattern> | undefined) {
|
|
488
|
+
if (!check.isObject(grepSearchPattern)) {
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const rawPatterns: unknown[] = check.isArray(grepSearchPattern.patterns)
|
|
493
|
+
? grepSearchPattern.patterns
|
|
494
|
+
: [
|
|
495
|
+
grepSearchPattern.pattern,
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
return extractStringArray(rawPatterns);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function createSearchLocation(
|
|
502
|
+
grepSearchLocation: Readonly<GrepSearchLocation> | undefined,
|
|
503
|
+
): SelectFrom<GrepSearchLocation, {files: true; dirs: true}> | undefined {
|
|
504
|
+
if (!check.isObject(grepSearchLocation)) {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const file = extractString(grepSearchLocation.file);
|
|
509
|
+
const dir = extractString(grepSearchLocation.dir);
|
|
510
|
+
|
|
511
|
+
if (grepSearchLocation.files != undefined) {
|
|
512
|
+
return {
|
|
513
|
+
files: extractStringArray(grepSearchLocation.files),
|
|
514
|
+
};
|
|
515
|
+
} else if (file) {
|
|
516
|
+
return {
|
|
517
|
+
files: [
|
|
518
|
+
file,
|
|
519
|
+
],
|
|
520
|
+
};
|
|
521
|
+
} else if (grepSearchLocation.dirs != undefined) {
|
|
522
|
+
return {
|
|
523
|
+
dirs: extractStringArray(grepSearchLocation.dirs),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return dir
|
|
528
|
+
? {
|
|
529
|
+
dirs: [
|
|
530
|
+
dir,
|
|
531
|
+
],
|
|
532
|
+
}
|
|
533
|
+
: undefined;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function resolveSearchPart({
|
|
537
|
+
cwd,
|
|
538
|
+
searchPart,
|
|
539
|
+
}: Readonly<{
|
|
540
|
+
cwd: string | undefined;
|
|
541
|
+
searchPart: string;
|
|
542
|
+
}>) {
|
|
543
|
+
return cwd && !isAbsolute(searchPart) ? resolve(cwd, searchPart) : searchPart;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function shouldSearchPart({
|
|
547
|
+
cwd,
|
|
548
|
+
followSymLinks,
|
|
549
|
+
includeDirectories,
|
|
550
|
+
searchPart,
|
|
551
|
+
}: Readonly<{
|
|
552
|
+
cwd: string | undefined;
|
|
553
|
+
followSymLinks: boolean | undefined;
|
|
554
|
+
includeDirectories: boolean;
|
|
555
|
+
searchPart: string;
|
|
556
|
+
}>) {
|
|
557
|
+
try {
|
|
558
|
+
const fileStats = await (followSymLinks ? stat : lstat)(
|
|
559
|
+
resolveSearchPart({
|
|
560
|
+
cwd,
|
|
561
|
+
searchPart,
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
return fileStats.isFile() || (includeDirectories && fileStats.isDirectory());
|
|
566
|
+
} catch {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function filterSearchParts({
|
|
572
|
+
cwd,
|
|
573
|
+
followSymLinks,
|
|
574
|
+
includeDirectories,
|
|
575
|
+
searchParts,
|
|
576
|
+
}: Readonly<{
|
|
577
|
+
cwd: string | undefined;
|
|
578
|
+
followSymLinks: boolean | undefined;
|
|
579
|
+
includeDirectories: boolean;
|
|
580
|
+
searchParts: ReadonlyArray<string>;
|
|
581
|
+
}>) {
|
|
582
|
+
return (
|
|
583
|
+
await awaitedBlockingMap(searchParts, async (searchPart) => {
|
|
584
|
+
return (await shouldSearchPart({
|
|
585
|
+
cwd,
|
|
586
|
+
followSymLinks,
|
|
587
|
+
includeDirectories,
|
|
588
|
+
searchPart,
|
|
589
|
+
}))
|
|
590
|
+
? searchPart
|
|
591
|
+
: undefined;
|
|
592
|
+
})
|
|
593
|
+
).filter(check.isTruthy);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function readDirectDirSearchParts({
|
|
597
|
+
cwd,
|
|
598
|
+
dir,
|
|
599
|
+
followSymLinks,
|
|
600
|
+
}: Readonly<{
|
|
601
|
+
cwd: string | undefined;
|
|
602
|
+
dir: string;
|
|
603
|
+
followSymLinks: boolean | undefined;
|
|
604
|
+
}>) {
|
|
605
|
+
try {
|
|
606
|
+
const readDirPath = resolveSearchPart({
|
|
607
|
+
cwd,
|
|
608
|
+
searchPart: dir,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
await awaitedBlockingMap(
|
|
613
|
+
(await readdir(readDirPath)).toSorted().filter((entry) => !entry.startsWith('.')),
|
|
614
|
+
async (entry) => {
|
|
615
|
+
const searchPart = join(dir, entry);
|
|
616
|
+
|
|
617
|
+
return (await shouldSearchPart({
|
|
618
|
+
cwd: readDirPath,
|
|
619
|
+
followSymLinks,
|
|
620
|
+
includeDirectories: false,
|
|
621
|
+
searchPart: entry,
|
|
622
|
+
}))
|
|
623
|
+
? searchPart
|
|
624
|
+
: undefined;
|
|
625
|
+
},
|
|
626
|
+
)
|
|
627
|
+
).filter(check.isTruthy);
|
|
628
|
+
/* node:coverage ignore next 3 */
|
|
629
|
+
} catch {
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function createSearchParts({
|
|
635
|
+
cwd,
|
|
636
|
+
followSymLinks,
|
|
637
|
+
recursive,
|
|
638
|
+
searchLocation,
|
|
639
|
+
}: Readonly<{
|
|
640
|
+
cwd: string | undefined;
|
|
641
|
+
followSymLinks: boolean | undefined;
|
|
642
|
+
recursive: boolean | undefined;
|
|
643
|
+
searchLocation: SelectFrom<GrepSearchLocation, {files: true; dirs: true}>;
|
|
644
|
+
}>) {
|
|
645
|
+
const searchParts = searchLocation.dirs || searchLocation.files;
|
|
646
|
+
assert.isDefined(searchParts, 'Grep search location was not resolved.');
|
|
647
|
+
|
|
648
|
+
const filteredSearchParts = await filterSearchParts({
|
|
649
|
+
cwd,
|
|
650
|
+
followSymLinks,
|
|
651
|
+
includeDirectories: !!searchLocation.dirs,
|
|
652
|
+
searchParts,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
return searchLocation.dirs
|
|
656
|
+
? recursive
|
|
657
|
+
? filteredSearchParts
|
|
658
|
+
: (
|
|
659
|
+
await awaitedBlockingMap(filteredSearchParts, (dir) =>
|
|
660
|
+
readDirectDirSearchParts({
|
|
661
|
+
cwd,
|
|
662
|
+
dir,
|
|
663
|
+
followSymLinks,
|
|
664
|
+
}),
|
|
665
|
+
)
|
|
666
|
+
).flat()
|
|
667
|
+
: filteredSearchParts;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
type GrepCountEntryParams = {
|
|
671
|
+
countString: string | undefined;
|
|
672
|
+
fileName: string | undefined;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
type NullDelimitedGrepRecord = {
|
|
676
|
+
fileName: string;
|
|
677
|
+
value: string;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
function createGrepCountEntry({countString, fileName}: Readonly<GrepCountEntryParams>) {
|
|
681
|
+
assert.isDefined(fileName, 'Failed parse grep file name.');
|
|
682
|
+
|
|
683
|
+
const count = Number(countString);
|
|
684
|
+
|
|
685
|
+
assert.isNumber(count, `Failed to parse grep number from: '${countString}'`);
|
|
686
|
+
/* node:coverage ignore next 3 */
|
|
687
|
+
if (!count) {
|
|
688
|
+
return undefined;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
key: fileName,
|
|
693
|
+
value: count,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function parseNullDelimitedGrepRecords({stdout}: Readonly<{stdout: string}>) {
|
|
698
|
+
const records: NullDelimitedGrepRecord[] = [];
|
|
699
|
+
let recordStartIndex = 0;
|
|
700
|
+
|
|
701
|
+
while (recordStartIndex < stdout.length) {
|
|
702
|
+
const delimiterIndex = stdout.indexOf('\0', recordStartIndex);
|
|
703
|
+
|
|
704
|
+
/* node:coverage ignore next 3 */
|
|
705
|
+
if (delimiterIndex < 0) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const valueStartIndex = delimiterIndex + 1;
|
|
710
|
+
const newlineIndex = stdout.indexOf('\n', valueStartIndex);
|
|
711
|
+
/* node:coverage ignore next */
|
|
712
|
+
const valueEndIndex = newlineIndex < 0 ? stdout.length : newlineIndex;
|
|
713
|
+
|
|
714
|
+
records.push({
|
|
715
|
+
fileName: stdout.slice(recordStartIndex, delimiterIndex),
|
|
716
|
+
value: stdout.slice(valueStartIndex, valueEndIndex),
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
/* node:coverage ignore next */
|
|
720
|
+
recordStartIndex = newlineIndex < 0 ? stdout.length : newlineIndex + 1;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return records;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* node:coverage ignore next 26 */
|
|
727
|
+
function parseColonDelimitedGrepCountOutput(stdout: string) {
|
|
728
|
+
return typedObjectFromEntries(
|
|
729
|
+
stdout
|
|
730
|
+
.trimEnd()
|
|
731
|
+
.split('\n')
|
|
732
|
+
.map((entry) => {
|
|
733
|
+
if (!entry) {
|
|
734
|
+
return undefined;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const countDelimiterIndex = entry.lastIndexOf(':');
|
|
738
|
+
|
|
739
|
+
return createGrepCountEntry({
|
|
740
|
+
countString:
|
|
741
|
+
countDelimiterIndex < 0 ? undefined : entry.slice(countDelimiterIndex + 1),
|
|
742
|
+
fileName:
|
|
743
|
+
countDelimiterIndex < 0 ? undefined : entry.slice(0, countDelimiterIndex),
|
|
744
|
+
});
|
|
745
|
+
})
|
|
746
|
+
.filter(check.isTruthy)
|
|
747
|
+
.map((entry) => [
|
|
748
|
+
entry.key,
|
|
749
|
+
entry.value,
|
|
750
|
+
]),
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/* node:coverage ignore next 20 */
|
|
755
|
+
function parseGrepCountOutput(stdout: string) {
|
|
756
|
+
return stdout.includes('\0')
|
|
757
|
+
? typedObjectFromEntries(
|
|
758
|
+
parseNullDelimitedGrepRecords({
|
|
759
|
+
stdout,
|
|
760
|
+
})
|
|
761
|
+
.map((record) => {
|
|
762
|
+
return createGrepCountEntry({
|
|
763
|
+
countString: record.value,
|
|
764
|
+
fileName: record.fileName,
|
|
765
|
+
});
|
|
766
|
+
})
|
|
767
|
+
.filter(check.isTruthy)
|
|
768
|
+
.map((entry) => [
|
|
769
|
+
entry.key,
|
|
770
|
+
entry.value,
|
|
771
|
+
]),
|
|
772
|
+
)
|
|
773
|
+
: parseColonDelimitedGrepCountOutput(stdout);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/* node:coverage ignore next 7 */
|
|
777
|
+
function tryParseGrepCountOutput(stdout: string) {
|
|
778
|
+
try {
|
|
779
|
+
return parseGrepCountOutput(stdout);
|
|
780
|
+
} catch {
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function parseKnownFileCountOutput({
|
|
786
|
+
filePath,
|
|
787
|
+
stdout,
|
|
788
|
+
}: Readonly<{
|
|
789
|
+
filePath: string;
|
|
790
|
+
stdout: string;
|
|
791
|
+
}>) {
|
|
792
|
+
/* node:coverage ignore next 3 */
|
|
793
|
+
if (stdout.includes('\0')) {
|
|
794
|
+
return parseGrepCountOutput(stdout)[filePath];
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const countPrefix = `${filePath}:`;
|
|
798
|
+
/* node:coverage ignore next */
|
|
799
|
+
const outputLine = stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout;
|
|
800
|
+
|
|
801
|
+
/* node:coverage ignore next 3 */
|
|
802
|
+
if (!outputLine.startsWith(countPrefix)) {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/* node:coverage ignore next */
|
|
807
|
+
return createGrepCountEntry({
|
|
808
|
+
countString: outputLine.slice(countPrefix.length),
|
|
809
|
+
fileName: filePath,
|
|
810
|
+
})?.value;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function runKnownFileGrepCount({
|
|
814
|
+
cwd,
|
|
815
|
+
filePath,
|
|
816
|
+
grepArgs,
|
|
817
|
+
}: Readonly<{
|
|
818
|
+
cwd: string | undefined;
|
|
819
|
+
filePath: string;
|
|
820
|
+
grepArgs: ReadonlyArray<string>;
|
|
821
|
+
}>) {
|
|
822
|
+
const result = await runGrepCommand({
|
|
823
|
+
args: replaceGrepSearchOperands({
|
|
824
|
+
args: grepArgs,
|
|
825
|
+
searchParts: [
|
|
826
|
+
filePath,
|
|
827
|
+
],
|
|
828
|
+
}),
|
|
829
|
+
cwd,
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
/* node:coverage ignore next 3 */
|
|
833
|
+
if (didGrepFail(result)) {
|
|
834
|
+
return undefined;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const count = parseKnownFileCountOutput({
|
|
838
|
+
filePath,
|
|
839
|
+
stdout: result.stdout,
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
/* node:coverage ignore next 3 */
|
|
843
|
+
if (!count) {
|
|
844
|
+
return undefined;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
key: filePath,
|
|
849
|
+
value: count,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function runGrepCountFallback({
|
|
854
|
+
cwd,
|
|
855
|
+
grepArgs,
|
|
856
|
+
}: Readonly<{
|
|
857
|
+
cwd: string | undefined;
|
|
858
|
+
grepArgs: ReadonlyArray<string>;
|
|
859
|
+
}>) {
|
|
860
|
+
const filesOnlyResult = await runGrepCommand({
|
|
861
|
+
args: replaceGrepCountOutputArg({
|
|
862
|
+
args: grepArgs,
|
|
863
|
+
replacement: '--files-with-matches',
|
|
864
|
+
}),
|
|
865
|
+
cwd,
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
/* node:coverage ignore next 3 */
|
|
869
|
+
if (didGrepFail(filesOnlyResult) || filesOnlyResult.exitCode === 1 || !filesOnlyResult.stdout) {
|
|
870
|
+
return {};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return typedObjectFromEntries(
|
|
874
|
+
(
|
|
875
|
+
await awaitedBlockingMap(
|
|
876
|
+
getObjectTypedKeys(parseGrepFilesOnlyOutput(filesOnlyResult.stdout)),
|
|
877
|
+
(filePath) => {
|
|
878
|
+
return runKnownFileGrepCount({
|
|
879
|
+
cwd,
|
|
880
|
+
filePath,
|
|
881
|
+
grepArgs,
|
|
882
|
+
});
|
|
883
|
+
},
|
|
884
|
+
)
|
|
885
|
+
)
|
|
886
|
+
.filter(check.isTruthy)
|
|
887
|
+
.map((entry) => [
|
|
888
|
+
entry.key,
|
|
889
|
+
entry.value,
|
|
890
|
+
]),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function parseGrepFilesOnlyOutput(stdout: string) {
|
|
895
|
+
return typedObjectFromEntries(
|
|
896
|
+
/* node:coverage ignore next */
|
|
897
|
+
(stdout.includes('\0') ? stdout.split('\0') : stdout.trimEnd().split('\n'))
|
|
898
|
+
.filter(check.isTruthy)
|
|
899
|
+
.map((entry) => [
|
|
900
|
+
entry,
|
|
901
|
+
[],
|
|
902
|
+
]),
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function parseGrepNormalOutput(stdout: string) {
|
|
907
|
+
const fileMatches = new Map<string, string[]>();
|
|
908
|
+
|
|
909
|
+
parseNullDelimitedGrepRecords({
|
|
910
|
+
stdout,
|
|
911
|
+
}).forEach((record) => {
|
|
912
|
+
fileMatches.set(record.fileName, [
|
|
913
|
+
...(fileMatches.get(record.fileName) || []),
|
|
914
|
+
record.value,
|
|
915
|
+
]);
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
return typedObjectFromEntries([...fileMatches.entries()]);
|
|
179
919
|
}
|
|
180
920
|
|
|
181
921
|
/**
|
|
182
|
-
* Output of {@link grep}. Each key is
|
|
183
|
-
* that file.
|
|
922
|
+
* Output of {@link grep}. Each key is a file path returned by `grep`. Values are arrays of matched
|
|
923
|
+
* lines for that file.
|
|
184
924
|
*
|
|
185
925
|
* @category Internal
|
|
186
926
|
* @category Package : @augment-vir/node
|
|
@@ -205,32 +945,24 @@ export async function grep<const CountOnly extends boolean = false>(
|
|
|
205
945
|
grepSearchLocation: Readonly<GrepSearchLocation>,
|
|
206
946
|
options: Readonly<GrepOptions<CountOnly>> = {},
|
|
207
947
|
): Promise<GrepMatches<CountOnly>> {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
948
|
+
const grepOptions: Readonly<Partial<GrepOptions<CountOnly>>> = check.isObject(options)
|
|
949
|
+
? options
|
|
950
|
+
: {};
|
|
951
|
+
const searchPatterns = createSearchPatterns(grepSearchPattern);
|
|
952
|
+
const grepOptionArrays = extractGrepOptionArrays(grepOptions);
|
|
953
|
+
const cwd = extractString(grepOptions.cwd);
|
|
211
954
|
|
|
212
|
-
if (
|
|
955
|
+
if (
|
|
956
|
+
!searchPatterns.length ||
|
|
957
|
+
!grepOptionArrays ||
|
|
958
|
+
!areGrepOptionsValid({
|
|
959
|
+
grepOptions,
|
|
960
|
+
})
|
|
961
|
+
) {
|
|
213
962
|
return {};
|
|
214
963
|
}
|
|
215
964
|
|
|
216
|
-
const searchLocation
|
|
217
|
-
grepSearchLocation.files
|
|
218
|
-
? {
|
|
219
|
-
files: grepSearchLocation.files,
|
|
220
|
-
}
|
|
221
|
-
: grepSearchLocation.file
|
|
222
|
-
? {
|
|
223
|
-
files: [grepSearchLocation.file],
|
|
224
|
-
}
|
|
225
|
-
: grepSearchLocation.dirs
|
|
226
|
-
? {
|
|
227
|
-
dirs: grepSearchLocation.dirs,
|
|
228
|
-
}
|
|
229
|
-
: grepSearchLocation.dir
|
|
230
|
-
? {
|
|
231
|
-
dirs: [grepSearchLocation.dir],
|
|
232
|
-
}
|
|
233
|
-
: undefined;
|
|
965
|
+
const searchLocation = createSearchLocation(grepSearchLocation);
|
|
234
966
|
|
|
235
967
|
if (
|
|
236
968
|
!searchLocation ||
|
|
@@ -240,136 +972,103 @@ export async function grep<const CountOnly extends boolean = false>(
|
|
|
240
972
|
return {};
|
|
241
973
|
}
|
|
242
974
|
|
|
243
|
-
const searchParts =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
975
|
+
const searchParts = await createSearchParts({
|
|
976
|
+
cwd,
|
|
977
|
+
followSymLinks: grepOptions.followSymLinks,
|
|
978
|
+
recursive: grepOptions.recursive,
|
|
979
|
+
searchLocation,
|
|
980
|
+
});
|
|
248
981
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
982
|
+
if (!searchParts.length) {
|
|
983
|
+
return {};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const grepArgs = [
|
|
987
|
+
grepOptions.patternSyntax?.basicRegExp
|
|
252
988
|
? '--basic-regexp'
|
|
253
|
-
:
|
|
989
|
+
: grepOptions.patternSyntax?.extendedRegExp
|
|
254
990
|
? '--extended-regexp'
|
|
255
|
-
:
|
|
991
|
+
: grepOptions.patternSyntax?.fixedStrings
|
|
256
992
|
? '--fixed-strings'
|
|
257
993
|
: '',
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
994
|
+
grepOptions.ignoreCase ? '--ignore-case' : '',
|
|
995
|
+
grepOptions.invertMatch && !grepOptions.output?.filesOnly ? '--invert-match' : '',
|
|
996
|
+
grepOptions.matchType?.wordRegExp
|
|
261
997
|
? '--word-regexp'
|
|
262
|
-
:
|
|
998
|
+
: grepOptions.matchType?.lineRegExp
|
|
263
999
|
? '--line-regexp'
|
|
264
1000
|
: '',
|
|
265
|
-
|
|
1001
|
+
grepOptions.output?.countOnly
|
|
266
1002
|
? '--count'
|
|
267
|
-
:
|
|
268
|
-
?
|
|
1003
|
+
: grepOptions.output?.filesOnly
|
|
1004
|
+
? grepOptions.invertMatch
|
|
269
1005
|
? '--files-without-match'
|
|
270
1006
|
: '--files-with-matches'
|
|
271
1007
|
: '',
|
|
272
1008
|
'--color=never',
|
|
273
|
-
|
|
1009
|
+
grepOptions.maxCount == undefined ? '' : `--max-count=${grepOptions.maxCount}`,
|
|
274
1010
|
'--no-messages',
|
|
1011
|
+
'--devices=skip',
|
|
275
1012
|
'--with-filename',
|
|
276
1013
|
'--null',
|
|
277
|
-
...(
|
|
278
|
-
?
|
|
279
|
-
(excludePattern) => `--exclude
|
|
1014
|
+
...(grepOptionArrays.excludePatterns.length
|
|
1015
|
+
? grepOptionArrays.excludePatterns.map(
|
|
1016
|
+
(excludePattern) => `--exclude=${excludePattern}`,
|
|
280
1017
|
)
|
|
281
1018
|
: []),
|
|
282
|
-
|
|
283
|
-
...(
|
|
284
|
-
?
|
|
1019
|
+
recursiveFlag(grepOptions),
|
|
1020
|
+
...(grepOptionArrays.excludeDirs.length
|
|
1021
|
+
? grepOptionArrays.excludeDirs.map((excludeDir) => `--exclude-dir=${excludeDir}`)
|
|
285
1022
|
: []),
|
|
286
|
-
...(
|
|
287
|
-
?
|
|
1023
|
+
...(grepOptionArrays.includeFiles.length
|
|
1024
|
+
? grepOptionArrays.includeFiles.map((includeFile) => `--include=${includeFile}`)
|
|
288
1025
|
: []),
|
|
289
|
-
|
|
290
|
-
...searchPatterns.
|
|
1026
|
+
grepOptions.binary ? '--binary' : '',
|
|
1027
|
+
...searchPatterns.flatMap((searchPattern) => [
|
|
1028
|
+
'-e',
|
|
1029
|
+
searchPattern,
|
|
1030
|
+
]),
|
|
1031
|
+
'--',
|
|
291
1032
|
...searchParts,
|
|
292
|
-
]
|
|
293
|
-
.filter(check.isTruthy)
|
|
294
|
-
.join(' ');
|
|
1033
|
+
].filter(check.isTruthy);
|
|
295
1034
|
|
|
296
|
-
if (
|
|
297
|
-
log.faint(`> ${
|
|
1035
|
+
if (grepOptions.printCommand) {
|
|
1036
|
+
log.faint(`> ${formatGrepCommand(grepArgs)}`);
|
|
298
1037
|
}
|
|
299
1038
|
|
|
300
|
-
const result = await
|
|
301
|
-
|
|
1039
|
+
const result = await runGrepCommand({
|
|
1040
|
+
args: grepArgs,
|
|
1041
|
+
cwd,
|
|
302
1042
|
});
|
|
303
1043
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (result.exitCode === 1 || !trimmedOutput) {
|
|
1044
|
+
if (didGrepFail(result) || result.exitCode === 1 || !result.stdout) {
|
|
307
1045
|
/** No matches. */
|
|
308
1046
|
return {};
|
|
309
|
-
} else if (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (!entry) {
|
|
316
|
-
return undefined;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const [
|
|
320
|
-
,
|
|
321
|
-
fileName,
|
|
322
|
-
countString,
|
|
323
|
-
] = safeMatch(entry, /(^.+):(\d+)$/);
|
|
324
|
-
|
|
325
|
-
assert.isDefined(fileName, `Failed parse grep file name from: '${entry}'`);
|
|
1047
|
+
} else if (grepOptions.output?.countOnly) {
|
|
1048
|
+
/* node:coverage ignore next 4 */
|
|
1049
|
+
const parsedCountOutput =
|
|
1050
|
+
isOperatingSystem(OperatingSystem.Mac) && !result.stdout.includes('\0')
|
|
1051
|
+
? undefined
|
|
1052
|
+
: tryParseGrepCountOutput(result.stdout);
|
|
326
1053
|
|
|
327
|
-
|
|
1054
|
+
/* node:coverage ignore next 3 */
|
|
1055
|
+
if (parsedCountOutput) {
|
|
1056
|
+
return parsedCountOutput satisfies Record<string, number> as GrepMatches<CountOnly>;
|
|
1057
|
+
}
|
|
328
1058
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
useRequired: true,
|
|
341
|
-
},
|
|
342
|
-
) satisfies Record<string, number> as GrepMatches<CountOnly>;
|
|
343
|
-
} else if (options.output?.filesOnly) {
|
|
344
|
-
return arrayToObject(
|
|
345
|
-
trimmedOutput.split(/[\0\n]/),
|
|
346
|
-
(entry) => {
|
|
347
|
-
/** Ignore empty strings. */
|
|
348
|
-
if (!entry) {
|
|
349
|
-
return undefined;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
key: entry,
|
|
354
|
-
value: [],
|
|
355
|
-
};
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
useRequired: true,
|
|
359
|
-
},
|
|
360
|
-
) satisfies Record<string, string[]> as GrepMatches as GrepMatches<CountOnly>;
|
|
1059
|
+
return (await runGrepCountFallback({
|
|
1060
|
+
cwd,
|
|
1061
|
+
grepArgs,
|
|
1062
|
+
})) satisfies Record<string, number> as GrepMatches<CountOnly>;
|
|
1063
|
+
} else if (grepOptions.output?.filesOnly) {
|
|
1064
|
+
return parseGrepFilesOnlyOutput(result.stdout) satisfies Record<
|
|
1065
|
+
string,
|
|
1066
|
+
string[]
|
|
1067
|
+
> as GrepMatches as GrepMatches<CountOnly>;
|
|
361
1068
|
} else {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
outputLines.forEach((line, index) => {
|
|
367
|
-
if (!(index % 2)) {
|
|
368
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
369
|
-
getOrSet(fileMatches, line, () => []).push(outputLines[index + 1]!);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
return fileMatches as GrepMatches<CountOnly>;
|
|
1069
|
+
return parseGrepNormalOutput(result.stdout) satisfies Record<
|
|
1070
|
+
string,
|
|
1071
|
+
string[]
|
|
1072
|
+
> as GrepMatches<CountOnly>;
|
|
374
1073
|
}
|
|
375
1074
|
}
|