@dev-pi2pie/word-counter 0.1.0 → 0.1.2
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/README.md +44 -5
- package/dist/esm/bin.mjs +142 -21
- package/dist/esm/bin.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -121,17 +121,25 @@ word-counter --path ./examples/test-case-multi-files-support --keep-progress
|
|
|
121
121
|
|
|
122
122
|
Progress is transient by default, auto-disabled for single-input runs, and suppressed in `--format raw` and `--format json`.
|
|
123
123
|
|
|
124
|
-
### Stable Path Resolution Contract
|
|
124
|
+
### Stable Path Resolution Contract
|
|
125
125
|
|
|
126
126
|
- Repeated `--path` values are accepted as mixed inputs (file + directory).
|
|
127
127
|
- In `--path-mode auto` (default), directory inputs are expanded to files (recursive unless `--no-recursive`).
|
|
128
|
-
- In `--path-mode manual`,
|
|
129
|
-
- Extension filters apply only to files discovered from directory expansion.
|
|
130
|
-
- Direct file inputs are always considered regardless of `--include-ext` / `--exclude-ext`.
|
|
128
|
+
- In `--path-mode manual`, `--path` values are treated as literal file inputs; `--path <dir>` is not supported and is skipped as `not a regular file`.
|
|
129
|
+
- Extension and regex filters apply only to files discovered from directory expansion.
|
|
130
|
+
- Direct file inputs are always considered regardless of `--include-ext` / `--exclude-ext` / `--regex`.
|
|
131
131
|
- Overlap dedupe is by resolved absolute file path.
|
|
132
132
|
- If the same file is discovered multiple ways (repeated roots, nested roots, explicit file + directory), it is counted once.
|
|
133
133
|
- Final processing order is deterministic: resolved files are sorted by absolute path ascending before load/count.
|
|
134
134
|
|
|
135
|
+
Path mode examples:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
word-counter --path ./examples/test-case-multi-files-support --path-mode auto
|
|
139
|
+
word-counter --path ./examples/test-case-multi-files-support --path-mode manual
|
|
140
|
+
word-counter --path ./examples/test-case-multi-files-support/a.md --path-mode manual
|
|
141
|
+
```
|
|
142
|
+
|
|
135
143
|
### Extension Filters
|
|
136
144
|
|
|
137
145
|
Use include/exclude filters for directory scans:
|
|
@@ -147,6 +155,28 @@ Direct file path example (filters do not block explicit file inputs):
|
|
|
147
155
|
word-counter --path ./examples/test-case-multi-files-support/ignored.js --include-ext .md --exclude-ext .md
|
|
148
156
|
```
|
|
149
157
|
|
|
158
|
+
### Regex Filter (`--regex`)
|
|
159
|
+
|
|
160
|
+
Use `--regex` to include only directory-scanned files whose root-relative path matches:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
word-counter --path ./examples/test-case-multi-files-support --regex '^a\\.md$'
|
|
164
|
+
word-counter --path ./examples/test-case-multi-files-support --regex '^nested/.*\\.md$'
|
|
165
|
+
word-counter --path ./examples/test-case-multi-files-support --path ./examples --regex '\\.md$'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Regex behavior contract:
|
|
169
|
+
|
|
170
|
+
- `--regex` applies only to files discovered from `--path <dir>` expansion.
|
|
171
|
+
- Matching is against each directory root-relative path.
|
|
172
|
+
- The same regex is applied across all provided directory roots.
|
|
173
|
+
- Direct file inputs are literal and are not blocked by regex filters.
|
|
174
|
+
- In `--path-mode manual`, directories are not expanded, so `--include-ext`, `--exclude-ext`, and `--regex` have no effect.
|
|
175
|
+
- `--regex` is single-use; repeated `--regex` flags fail fast with a misuse error.
|
|
176
|
+
- Empty regex values are treated as no regex restriction.
|
|
177
|
+
|
|
178
|
+
For additional usage details and troubleshooting, see [`docs/regex-usage-guide.md`](docs/regex-usage-guide.md).
|
|
179
|
+
|
|
150
180
|
### Debugging Diagnostics (`--debug`)
|
|
151
181
|
|
|
152
182
|
`--debug` remains the diagnostics gate and now defaults to `compact` event volume:
|
|
@@ -426,6 +456,9 @@ Select how results are printed with `--format`:
|
|
|
426
456
|
- `raw` – only the total count (single number).
|
|
427
457
|
- `json` – machine-readable output; add `--pretty` for indentation.
|
|
428
458
|
|
|
459
|
+
JSON contract reference:
|
|
460
|
+
- `docs/schemas/json-output-contract.md`
|
|
461
|
+
|
|
429
462
|
Examples:
|
|
430
463
|
|
|
431
464
|
```bash
|
|
@@ -465,7 +498,13 @@ Rules:
|
|
|
465
498
|
- `symbol` -> `symbols`
|
|
466
499
|
- `punction` -> `punctuation`
|
|
467
500
|
|
|
468
|
-
JSON output adds
|
|
501
|
+
JSON output adds override metadata when `--total-of` is provided:
|
|
502
|
+
|
|
503
|
+
- single input and merged batch: `meta.totalOf`, `meta.totalOfOverride`
|
|
504
|
+
- per-file batch (`--per-file`):
|
|
505
|
+
- top-level: `meta.totalOf`, `meta.aggregateTotalOfOverride`
|
|
506
|
+
- per entry: `files[i].meta.totalOf`, `files[i].meta.totalOfOverride`
|
|
507
|
+
- applies to both sectioned and non-sectioned per-file JSON results
|
|
469
508
|
|
|
470
509
|
Example JSON (trimmed):
|
|
471
510
|
|
package/dist/esm/bin.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
|
|
5
|
-
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
8
8
|
import { parseDocument } from "yaml";
|
|
@@ -55,8 +55,9 @@ function withCollisionSuffix(pathValue, sequence) {
|
|
|
55
55
|
function resolveReportPath(report, now, pid) {
|
|
56
56
|
const cwd = report.cwd ?? process.cwd();
|
|
57
57
|
const defaultName = `wc-debug-${formatDebugReportTimestamp(now)}-${pid}.jsonl`;
|
|
58
|
-
const
|
|
59
|
-
const
|
|
58
|
+
const explicitPathValue = typeof report.path === "string" ? report.path : void 0;
|
|
59
|
+
const explicitPath = explicitPathValue !== void 0;
|
|
60
|
+
const basePath = resolve(cwd, explicitPathValue ?? defaultName);
|
|
60
61
|
mkdirSync(dirname(basePath), { recursive: true });
|
|
61
62
|
if (explicitPath) {
|
|
62
63
|
if (existsSync(basePath) && statSync(basePath).isDirectory()) throw new Error(`debug report path must be a file: ${basePath}`);
|
|
@@ -191,6 +192,34 @@ function shouldIncludeFromDirectory(filePath, filter) {
|
|
|
191
192
|
const extension = extname(filePath).toLowerCase();
|
|
192
193
|
return filter.effectiveIncludeExtensions.has(extension);
|
|
193
194
|
}
|
|
195
|
+
function buildDirectoryRegexFilter(pattern) {
|
|
196
|
+
if (pattern === void 0) return {
|
|
197
|
+
sourcePattern: void 0,
|
|
198
|
+
regex: void 0
|
|
199
|
+
};
|
|
200
|
+
if (pattern.trim().length === 0) return {
|
|
201
|
+
sourcePattern: pattern,
|
|
202
|
+
regex: void 0
|
|
203
|
+
};
|
|
204
|
+
try {
|
|
205
|
+
return {
|
|
206
|
+
sourcePattern: pattern,
|
|
207
|
+
regex: new RegExp(pattern)
|
|
208
|
+
};
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
throw new Error(`Invalid --regex pattern: ${message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function toDirectoryRelativePath(rootPath, filePath) {
|
|
215
|
+
const relativePath = relative(rootPath, filePath);
|
|
216
|
+
if (sep === "/") return relativePath;
|
|
217
|
+
return relativePath.split(sep).join("/");
|
|
218
|
+
}
|
|
219
|
+
function shouldIncludeFromDirectoryRegex(relativePath, filter) {
|
|
220
|
+
if (!filter.regex) return true;
|
|
221
|
+
return filter.regex.test(relativePath);
|
|
222
|
+
}
|
|
194
223
|
|
|
195
224
|
//#endregion
|
|
196
225
|
//#region src/cli/total-of.ts
|
|
@@ -314,7 +343,7 @@ function collectPathValue(value, previous = []) {
|
|
|
314
343
|
return [...previous, value];
|
|
315
344
|
}
|
|
316
345
|
function configureProgramOptions(program, parseMode) {
|
|
317
|
-
program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("--path-mode <mode>", "path resolution mode").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies with --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-skips", "hide skip diagnostics (applies when --debug is enabled)").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("-p, --path <path>", "read input from file or directory", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
|
|
346
|
+
program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("--path-mode <mode>", "path resolution mode: auto (default) expands directories; manual treats --path values as literal files").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies with --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-skips", "hide skip diagnostics (applies when --debug is enabled)").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("--regex <pattern>", "regex filter for directory-scanned paths (applies to --path directories only)").option("-p, --path <path>", "read input from file or directory (directories expand in auto mode by default)", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
//#endregion
|
|
@@ -387,8 +416,12 @@ var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
387
416
|
}));
|
|
388
417
|
|
|
389
418
|
//#endregion
|
|
390
|
-
//#region src/cli/program/version.ts
|
|
419
|
+
//#region src/cli/program/version-embedded.ts
|
|
391
420
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
421
|
+
const EMBEDDED_PACKAGE_VERSION = "0.1.2";
|
|
422
|
+
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/cli/program/version.ts
|
|
392
425
|
function* candidateSearchRoots() {
|
|
393
426
|
yield dirname(fileURLToPath(import.meta.url));
|
|
394
427
|
const argvPath = process.argv[1];
|
|
@@ -412,13 +445,23 @@ function resolveVersionFromPath(start, maxLevels) {
|
|
|
412
445
|
} catch {}
|
|
413
446
|
return null;
|
|
414
447
|
}
|
|
415
|
-
function
|
|
416
|
-
|
|
448
|
+
function normalizeVersion(value) {
|
|
449
|
+
if (typeof value !== "string") return null;
|
|
450
|
+
const trimmed = value.trim();
|
|
451
|
+
if (!trimmed) return null;
|
|
452
|
+
return trimmed;
|
|
453
|
+
}
|
|
454
|
+
function resolvePackageVersion(options = {}) {
|
|
455
|
+
const embeddedVersion = normalizeVersion(options.embeddedVersion ?? EMBEDDED_PACKAGE_VERSION);
|
|
456
|
+
if (embeddedVersion) return embeddedVersion;
|
|
457
|
+
const maxLevels = options.maxLevels ?? 8;
|
|
458
|
+
const resolveFromPath = options.resolveFromPath ?? resolveVersionFromPath;
|
|
459
|
+
const roots = options.candidateRoots ?? [...candidateSearchRoots()];
|
|
417
460
|
const seen = /* @__PURE__ */ new Set();
|
|
418
|
-
for (const root of
|
|
461
|
+
for (const root of roots) {
|
|
419
462
|
if (seen.has(root)) continue;
|
|
420
463
|
seen.add(root);
|
|
421
|
-
const version =
|
|
464
|
+
const version = normalizeVersion(resolveFromPath(root, maxLevels));
|
|
422
465
|
if (version) return version;
|
|
423
466
|
}
|
|
424
467
|
return "0.0.0";
|
|
@@ -485,7 +528,7 @@ async function loadBatchInputs(filePaths) {
|
|
|
485
528
|
|
|
486
529
|
//#endregion
|
|
487
530
|
//#region src/cli/path/resolve.ts
|
|
488
|
-
async function expandDirectory(directoryPath, recursive,
|
|
531
|
+
async function expandDirectory(rootPath, directoryPath, recursive, extensionFilter, regexFilter, skipped, recordRegexExcluded, debug, stats) {
|
|
489
532
|
let entries;
|
|
490
533
|
try {
|
|
491
534
|
entries = await readdir(directoryPath, {
|
|
@@ -514,7 +557,7 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
|
|
|
514
557
|
for (const entry of sortedEntries) {
|
|
515
558
|
const entryPath = resolve(directoryPath, entry.name);
|
|
516
559
|
if (entry.isFile()) {
|
|
517
|
-
if (!shouldIncludeFromDirectory(entryPath,
|
|
560
|
+
if (!shouldIncludeFromDirectory(entryPath, extensionFilter)) {
|
|
518
561
|
skipped.push({
|
|
519
562
|
path: entryPath,
|
|
520
563
|
reason: "extension excluded"
|
|
@@ -526,6 +569,19 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
|
|
|
526
569
|
stats.filterExcluded += 1;
|
|
527
570
|
continue;
|
|
528
571
|
}
|
|
572
|
+
const relativePath = toDirectoryRelativePath(rootPath, entryPath);
|
|
573
|
+
if (!shouldIncludeFromDirectoryRegex(relativePath, regexFilter)) {
|
|
574
|
+
if (recordRegexExcluded(entryPath)) {
|
|
575
|
+
debug.emit("path.resolve.regex.excluded", {
|
|
576
|
+
path: entryPath,
|
|
577
|
+
relativePath,
|
|
578
|
+
pattern: regexFilter.sourcePattern,
|
|
579
|
+
reason: "regex excluded"
|
|
580
|
+
}, { verbosity: "verbose" });
|
|
581
|
+
stats.regexExcluded += 1;
|
|
582
|
+
}
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
529
585
|
files.push(entryPath);
|
|
530
586
|
stats.directoryIncluded += 1;
|
|
531
587
|
debug.emit("path.resolve.expand.include", {
|
|
@@ -535,7 +591,7 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
|
|
|
535
591
|
continue;
|
|
536
592
|
}
|
|
537
593
|
if (!entry.isDirectory() || !recursive) continue;
|
|
538
|
-
appendAll(files, await expandDirectory(entryPath, recursive,
|
|
594
|
+
appendAll(files, await expandDirectory(rootPath, entryPath, recursive, extensionFilter, regexFilter, skipped, recordRegexExcluded, debug, stats));
|
|
539
595
|
}
|
|
540
596
|
debug.emit("path.resolve.expand.complete", {
|
|
541
597
|
directory: directoryPath,
|
|
@@ -545,14 +601,17 @@ async function expandDirectory(directoryPath, recursive, filter, skipped, debug,
|
|
|
545
601
|
}
|
|
546
602
|
async function resolveBatchFilePaths(pathInputs, options) {
|
|
547
603
|
const skipped = [];
|
|
604
|
+
const regexExcludedPaths = /* @__PURE__ */ new Set();
|
|
548
605
|
const resolvedFiles = /* @__PURE__ */ new Set();
|
|
549
606
|
const stats = {
|
|
550
607
|
dedupeAccepted: 0,
|
|
551
608
|
dedupeDuplicates: 0,
|
|
552
609
|
filterExcluded: 0,
|
|
610
|
+
regexExcluded: 0,
|
|
553
611
|
directoryIncluded: 0
|
|
554
612
|
};
|
|
555
613
|
const extensionFilter = options.extensionFilter ?? buildDirectoryExtensionFilter(void 0, void 0);
|
|
614
|
+
let regexFilter;
|
|
556
615
|
const debug = options.debug ?? {
|
|
557
616
|
enabled: false,
|
|
558
617
|
verbosity: "compact",
|
|
@@ -562,9 +621,11 @@ async function resolveBatchFilePaths(pathInputs, options) {
|
|
|
562
621
|
debug.emit("path.resolve.inputs", {
|
|
563
622
|
inputs: pathInputs.length,
|
|
564
623
|
pathMode: options.pathMode,
|
|
565
|
-
recursive: options.recursive
|
|
624
|
+
recursive: options.recursive,
|
|
625
|
+
hasRegex: Boolean(options.directoryRegexPattern)
|
|
566
626
|
});
|
|
567
627
|
const addResolvedFile = (filePath, details) => {
|
|
628
|
+
regexExcludedPaths.delete(filePath);
|
|
568
629
|
if (resolvedFiles.has(filePath)) {
|
|
569
630
|
stats.dedupeDuplicates += 1;
|
|
570
631
|
debug.emit("path.resolve.dedupe.duplicate", {
|
|
@@ -582,6 +643,15 @@ async function resolveBatchFilePaths(pathInputs, options) {
|
|
|
582
643
|
input: details.input
|
|
583
644
|
}, { verbosity: "verbose" });
|
|
584
645
|
};
|
|
646
|
+
const getRegexFilter = () => {
|
|
647
|
+
if (!regexFilter) regexFilter = buildDirectoryRegexFilter(options.directoryRegexPattern);
|
|
648
|
+
return regexFilter;
|
|
649
|
+
};
|
|
650
|
+
const recordRegexExcluded = (filePath) => {
|
|
651
|
+
if (resolvedFiles.has(filePath)) return false;
|
|
652
|
+
regexExcludedPaths.add(filePath);
|
|
653
|
+
return true;
|
|
654
|
+
};
|
|
585
655
|
for (const rawPath of pathInputs) {
|
|
586
656
|
const targetPath = resolve(rawPath);
|
|
587
657
|
debug.emit("path.resolve.input", {
|
|
@@ -604,11 +674,13 @@ async function resolveBatchFilePaths(pathInputs, options) {
|
|
|
604
674
|
continue;
|
|
605
675
|
}
|
|
606
676
|
if (metadata.isDirectory() && options.pathMode === "auto") {
|
|
677
|
+
const effectiveRegexFilter = getRegexFilter();
|
|
607
678
|
debug.emit("path.resolve.root.expand", {
|
|
608
679
|
root: targetPath,
|
|
609
|
-
recursive: options.recursive
|
|
680
|
+
recursive: options.recursive,
|
|
681
|
+
regex: effectiveRegexFilter.sourcePattern ?? null
|
|
610
682
|
});
|
|
611
|
-
const files = await expandDirectory(targetPath, options.recursive, extensionFilter, skipped, debug, stats);
|
|
683
|
+
const files = await expandDirectory(targetPath, targetPath, options.recursive, extensionFilter, effectiveRegexFilter, skipped, recordRegexExcluded, debug, stats);
|
|
612
684
|
for (const file of files) addResolvedFile(file, {
|
|
613
685
|
source: "directory",
|
|
614
686
|
input: targetPath
|
|
@@ -631,9 +703,15 @@ async function resolveBatchFilePaths(pathInputs, options) {
|
|
|
631
703
|
input: targetPath
|
|
632
704
|
});
|
|
633
705
|
}
|
|
706
|
+
for (const path of regexExcludedPaths) skipped.push({
|
|
707
|
+
path,
|
|
708
|
+
reason: "regex excluded"
|
|
709
|
+
});
|
|
634
710
|
const files = [...resolvedFiles].sort((left, right) => left.localeCompare(right));
|
|
635
711
|
debug.emit("path.resolve.filter.summary", {
|
|
636
|
-
excluded: stats.filterExcluded,
|
|
712
|
+
excluded: stats.filterExcluded + stats.regexExcluded,
|
|
713
|
+
extensionExcluded: stats.filterExcluded,
|
|
714
|
+
regexExcluded: stats.regexExcluded,
|
|
637
715
|
included: stats.directoryIncluded
|
|
638
716
|
});
|
|
639
717
|
debug.emit("path.resolve.dedupe.summary", {
|
|
@@ -2086,6 +2164,7 @@ async function runBatchCount(options) {
|
|
|
2086
2164
|
pathMode: options.batchOptions.pathMode,
|
|
2087
2165
|
recursive: options.batchOptions.recursive,
|
|
2088
2166
|
extensionFilter: options.extensionFilter,
|
|
2167
|
+
directoryRegexPattern: options.batchOptions.directoryRegexPattern,
|
|
2089
2168
|
debug: options.debug
|
|
2090
2169
|
});
|
|
2091
2170
|
const resolveElapsedMs = Date.now() - resolveStartedAtMs;
|
|
@@ -2362,6 +2441,27 @@ function normalizeBatchSummaryBase(summary) {
|
|
|
2362
2441
|
function hasPathInput(pathValues) {
|
|
2363
2442
|
return Array.isArray(pathValues) && pathValues.length > 0;
|
|
2364
2443
|
}
|
|
2444
|
+
function countLongOptionOccurrences(argv, optionName) {
|
|
2445
|
+
let count = 0;
|
|
2446
|
+
for (let index = 2; index < argv.length; index += 1) {
|
|
2447
|
+
const token = argv[index];
|
|
2448
|
+
if (!token) continue;
|
|
2449
|
+
if (token === "--") break;
|
|
2450
|
+
if (token === optionName) {
|
|
2451
|
+
count += 1;
|
|
2452
|
+
index += 1;
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
if (token.startsWith(`${optionName}=`)) {
|
|
2456
|
+
count += 1;
|
|
2457
|
+
continue;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
return count;
|
|
2461
|
+
}
|
|
2462
|
+
function validateSingleRegexOptionUsage(argv) {
|
|
2463
|
+
if (countLongOptionOccurrences(argv, "--regex") > 1) throw new Error("`--regex` can only be provided once.");
|
|
2464
|
+
}
|
|
2365
2465
|
function resolveBatchScope(argv) {
|
|
2366
2466
|
let scope = "merged";
|
|
2367
2467
|
for (const token of argv) {
|
|
@@ -2413,7 +2513,8 @@ async function executeBatchCount({ argv, options, runtime, resolved, debug, teeE
|
|
|
2413
2513
|
scope: resolveBatchScope(argv),
|
|
2414
2514
|
pathMode: options.pathMode,
|
|
2415
2515
|
recursive: options.recursive,
|
|
2416
|
-
quietSkips: Boolean(options.quietSkips)
|
|
2516
|
+
quietSkips: Boolean(options.quietSkips),
|
|
2517
|
+
directoryRegexPattern: options.regex
|
|
2417
2518
|
};
|
|
2418
2519
|
const extensionFilter = buildDirectoryExtensionFilter(options.includeExt, options.excludeExt);
|
|
2419
2520
|
const debugEnabled = Boolean(options.debug);
|
|
@@ -2471,16 +2572,29 @@ async function executeBatchCount({ argv, options, runtime, resolved, debug, teeE
|
|
|
2471
2572
|
const spacing = options.pretty ? 2 : 0;
|
|
2472
2573
|
if (batchOptions.scope === "per-file") {
|
|
2473
2574
|
const skipped = showSkipDiagnostics ? summary.skipped : void 0;
|
|
2575
|
+
const fileEntries = summary.files.map((file) => {
|
|
2576
|
+
const base = {
|
|
2577
|
+
path: file.path,
|
|
2578
|
+
result: file.result
|
|
2579
|
+
};
|
|
2580
|
+
if (!resolved.totalOfParts || resolved.totalOfParts.length === 0) return base;
|
|
2581
|
+
const fileOverride = totalOfOverridesByResult?.get(file.result) ?? resolveTotalOfOverride(file.result, resolved.totalOfParts);
|
|
2582
|
+
if (!fileOverride) return base;
|
|
2583
|
+
return {
|
|
2584
|
+
...base,
|
|
2585
|
+
meta: {
|
|
2586
|
+
totalOf: fileOverride.parts,
|
|
2587
|
+
totalOfOverride: fileOverride.total
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
});
|
|
2474
2591
|
const meta = resolved.totalOfParts && resolved.totalOfParts.length > 0 ? {
|
|
2475
2592
|
totalOf: resolved.totalOfParts,
|
|
2476
2593
|
aggregateTotalOfOverride: aggregateTotalOfOverride?.total ?? summary.aggregate.total
|
|
2477
2594
|
} : void 0;
|
|
2478
2595
|
const payload = {
|
|
2479
2596
|
scope: "per-file",
|
|
2480
|
-
files:
|
|
2481
|
-
path: file.path,
|
|
2482
|
-
result: file.result
|
|
2483
|
-
})),
|
|
2597
|
+
files: fileEntries,
|
|
2484
2598
|
...skipped ? { skipped } : {},
|
|
2485
2599
|
aggregate: summary.aggregate,
|
|
2486
2600
|
...meta ? { meta } : {}
|
|
@@ -2601,6 +2715,13 @@ async function runCli(argv = process.argv, runtime = {}) {
|
|
|
2601
2715
|
program.error(import_picocolors.default.red("`--debug-report-tee` (alias: `--debug-tee`) requires `--debug-report`."));
|
|
2602
2716
|
return;
|
|
2603
2717
|
}
|
|
2718
|
+
try {
|
|
2719
|
+
validateSingleRegexOptionUsage(argv);
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2722
|
+
program.error(import_picocolors.default.red(message));
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2604
2725
|
let debug;
|
|
2605
2726
|
try {
|
|
2606
2727
|
debug = createDebugChannel({
|