@quantakrypto/core 0.1.0 → 0.2.0
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 +6 -0
- package/dist/cbom.js +1 -1
- package/dist/cbom.js.map +1 -1
- package/dist/changed.d.ts.map +1 -1
- package/dist/changed.js +7 -4
- package/dist/changed.js.map +1 -1
- package/dist/detect-utils.d.ts +2 -0
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +2 -0
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +7 -0
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +5 -2
- package/dist/detectors/source.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/parallel.d.ts.map +1 -1
- package/dist/parallel.js +30 -12
- package/dist/parallel.js.map +1 -1
- package/dist/report.d.ts +12 -2
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +18 -18
- package/dist/report.js.map +1 -1
- package/dist/scan.d.ts +11 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +34 -7
- package/dist/scan.js.map +1 -1
- package/dist/severity.d.ts +22 -0
- package/dist/severity.d.ts.map +1 -0
- package/dist/severity.js +30 -0
- package/dist/severity.js.map +1 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/cbom.ts +1 -1
- package/src/changed.ts +11 -4
- package/src/detect-utils.ts +3 -0
- package/src/detectors/pem.ts +9 -0
- package/src/detectors/source.ts +5 -2
- package/src/errors.ts +25 -0
- package/src/index.ts +7 -0
- package/src/parallel.ts +27 -9
- package/src/report.ts +29 -20
- package/src/scan.ts +41 -8
- package/src/severity.ts +40 -0
- package/src/types.ts +23 -0
- package/src/version.ts +1 -1
package/src/parallel.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* (small file counts / small total bytes) and whenever workers are unavailable.
|
|
10
10
|
*/
|
|
11
11
|
import { stat } from "node:fs/promises";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
12
13
|
import * as os from "node:os";
|
|
13
14
|
import * as path from "node:path";
|
|
14
15
|
import { fileURLToPath } from "node:url";
|
|
@@ -16,9 +17,9 @@ import { fileURLToPath } from "node:url";
|
|
|
16
17
|
import type { Worker as NodeWorker } from "node:worker_threads";
|
|
17
18
|
|
|
18
19
|
import type { Finding, ParallelScanOptions, ScanResult } from "./types.js";
|
|
19
|
-
import { walkFiles
|
|
20
|
+
import { walkFiles } from "./walk.js";
|
|
20
21
|
import { buildInventory } from "./inventory.js";
|
|
21
|
-
import { compareFindings, scan } from "./scan.js";
|
|
22
|
+
import { compareFindings, filterExplicitFileList, scan } from "./scan.js";
|
|
22
23
|
import { VERSION } from "./version.js";
|
|
23
24
|
|
|
24
25
|
/** One unit of work dispatched to a worker. */
|
|
@@ -106,7 +107,9 @@ function shouldParallelize(options: ParallelScanOptions, files: SizedFile[]): bo
|
|
|
106
107
|
async function enumerateFiles(options: ParallelScanOptions, baseDir: string): Promise<SizedFile[]> {
|
|
107
108
|
const rels: string[] = [];
|
|
108
109
|
if (options.files) {
|
|
109
|
-
|
|
110
|
+
// Apply the SAME include/exclude/binary filtering the serial path uses via
|
|
111
|
+
// `filterExplicitFiles`, so `--parallel` is byte-for-byte identical to serial.
|
|
112
|
+
for (const rel of filterExplicitFileList(options.files, options)) rels.push(rel);
|
|
110
113
|
} else {
|
|
111
114
|
for await (const rel of walkFiles(options.root, {
|
|
112
115
|
include: options.include,
|
|
@@ -131,11 +134,21 @@ async function enumerateFiles(options: ParallelScanOptions, baseDir: string): Pr
|
|
|
131
134
|
return sized;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the worker entry next to this module. In a normal build it's
|
|
139
|
+
* `dist/scan-worker.js`. When running from source under a TypeScript loader
|
|
140
|
+
* (tsx) the built JS doesn't exist, so fall back to `scan-worker.ts` and tell
|
|
141
|
+
* the worker to load tsx as well — so the parallel scanner works in dev/source
|
|
142
|
+
* mode (and is exercisable by tests), not only after a build.
|
|
143
|
+
*/
|
|
144
|
+
function workerEntry(): { entry: string; execArgv?: string[] } {
|
|
137
145
|
const here = fileURLToPath(import.meta.url);
|
|
138
|
-
|
|
146
|
+
const dir = path.dirname(here);
|
|
147
|
+
const js = path.join(dir, "scan-worker.js");
|
|
148
|
+
if (existsSync(js)) return { entry: js };
|
|
149
|
+
const ts = path.join(dir, "scan-worker.ts");
|
|
150
|
+
if (existsSync(ts)) return { entry: ts, execArgv: ["--import", "tsx"] };
|
|
151
|
+
return { entry: js }; // neither present: let Worker surface a clear ENOENT
|
|
139
152
|
}
|
|
140
153
|
|
|
141
154
|
/**
|
|
@@ -171,7 +184,7 @@ export async function scanParallel(options: ParallelScanOptions): Promise<ScanRe
|
|
|
171
184
|
|
|
172
185
|
const chunks = chunkByBytes(files, options.chunkBytes ?? DEFAULT_CHUNK_BYTES);
|
|
173
186
|
const concurrency = Math.min(resolveConcurrency(options), chunks.length);
|
|
174
|
-
const entry =
|
|
187
|
+
const { entry, execArgv } = workerEntry();
|
|
175
188
|
|
|
176
189
|
const toggles = {
|
|
177
190
|
source: options.source !== false,
|
|
@@ -185,6 +198,7 @@ export async function scanParallel(options: ParallelScanOptions): Promise<ScanRe
|
|
|
185
198
|
results = await runPool(
|
|
186
199
|
WorkerCtor,
|
|
187
200
|
entry,
|
|
201
|
+
execArgv,
|
|
188
202
|
baseDir,
|
|
189
203
|
toggles,
|
|
190
204
|
chunks,
|
|
@@ -215,6 +229,7 @@ export async function scanParallel(options: ParallelScanOptions): Promise<ScanRe
|
|
|
215
229
|
function runPool(
|
|
216
230
|
WorkerCtor: typeof import("node:worker_threads").Worker,
|
|
217
231
|
entry: string,
|
|
232
|
+
execArgv: string[] | undefined,
|
|
218
233
|
baseDir: string,
|
|
219
234
|
toggles: { source: boolean; config: boolean; deps: boolean; scanMinified: boolean },
|
|
220
235
|
chunks: ScanChunk[],
|
|
@@ -243,7 +258,10 @@ function runPool(
|
|
|
243
258
|
};
|
|
244
259
|
|
|
245
260
|
const spawn = (): NodeWorker => {
|
|
246
|
-
const w = new WorkerCtor(entry, {
|
|
261
|
+
const w = new WorkerCtor(entry, {
|
|
262
|
+
workerData: { baseDir, toggles },
|
|
263
|
+
...(execArgv ? { execArgv } : {}),
|
|
264
|
+
});
|
|
247
265
|
w.on(
|
|
248
266
|
"message",
|
|
249
267
|
(msg: { index: number; result?: ChunkResult; files?: string[]; error?: string }) => {
|
package/src/report.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* or a human-readable text summary. No third-party dependencies — ANSI colour
|
|
4
4
|
* is emitted with raw escape codes and is off by default.
|
|
5
5
|
*/
|
|
6
|
-
import type { ScanResult, Severity } from "./types.js";
|
|
6
|
+
import type { Finding, ScanResult, Severity } from "./types.js";
|
|
7
7
|
import { VERSION } from "./version.js";
|
|
8
|
+
import { SEVERITY_ORDER, sarifLevel } from "./severity.js";
|
|
8
9
|
|
|
9
10
|
/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */
|
|
10
11
|
export interface SarifLog {
|
|
@@ -13,22 +14,30 @@ export interface SarifLog {
|
|
|
13
14
|
runs: unknown[];
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/** Options shared by the structured reporters ({@link toSarif} / {@link toJson}). */
|
|
18
|
+
export interface ReportOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Omit `location.snippet` from every finding in the output. Defaults to false
|
|
21
|
+
* (snippets are included). Snippets of `sensitive` findings (e.g. PEM key
|
|
22
|
+
* blocks, SSH public keys) are ALWAYS omitted regardless of this flag — the
|
|
23
|
+
* snippet there IS the sensitive value.
|
|
24
|
+
*/
|
|
25
|
+
redactSnippets?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
const SARIF_SCHEMA =
|
|
17
29
|
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
|
|
18
30
|
|
|
19
|
-
const INFORMATION_URI = "https://github.com/
|
|
31
|
+
const INFORMATION_URI = "https://github.com/quantakrypto/tools";
|
|
20
32
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
default:
|
|
30
|
-
return "note";
|
|
31
|
-
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the snippet to emit for a finding, honouring redaction. Sensitive
|
|
35
|
+
* findings (key material) never expose their snippet; otherwise the snippet is
|
|
36
|
+
* dropped only when `redactSnippets` is set.
|
|
37
|
+
*/
|
|
38
|
+
function emittedSnippet(f: Finding, redactSnippets: boolean): string | undefined {
|
|
39
|
+
if (redactSnippets || f.sensitive) return undefined;
|
|
40
|
+
return f.location.snippet;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/** Map our severity to a SARIF rule-level default (used in rules[].defaultConfiguration). */
|
|
@@ -48,7 +57,8 @@ function sarifRank(severity: Severity): number {
|
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/** Serialize a scan result as SARIF 2.1.0. */
|
|
51
|
-
export function toSarif(result: ScanResult): SarifLog {
|
|
60
|
+
export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
|
|
61
|
+
const redactSnippets = opts?.redactSnippets ?? false;
|
|
52
62
|
// Build the unique rule set (one rule per ruleId encountered) and collect the
|
|
53
63
|
// set of CWE taxa referenced by any rule.
|
|
54
64
|
const ruleIndex = new Map<string, number>();
|
|
@@ -86,6 +96,7 @@ export function toSarif(result: ScanResult): SarifLog {
|
|
|
86
96
|
const region: Record<string, number> = { startLine: f.location.line };
|
|
87
97
|
if (typeof f.location.column === "number") region.startColumn = f.location.column;
|
|
88
98
|
if (typeof f.location.endLine === "number") region.endLine = f.location.endLine;
|
|
99
|
+
const snippet = emittedSnippet(f, redactSnippets);
|
|
89
100
|
|
|
90
101
|
return {
|
|
91
102
|
ruleId: f.ruleId,
|
|
@@ -116,7 +127,7 @@ export function toSarif(result: ScanResult): SarifLog {
|
|
|
116
127
|
artifactLocation: { uri: f.location.file },
|
|
117
128
|
region: {
|
|
118
129
|
...region,
|
|
119
|
-
...(
|
|
130
|
+
...(snippet ? { snippet: { text: snippet } } : {}),
|
|
120
131
|
},
|
|
121
132
|
},
|
|
122
133
|
},
|
|
@@ -178,7 +189,8 @@ function securitySeverity(severity: Severity): string {
|
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
/** Serialize a scan result as a plain JSON-friendly object. */
|
|
181
|
-
export function toJson(result: ScanResult): Record<string, unknown> {
|
|
192
|
+
export function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {
|
|
193
|
+
const redactSnippets = opts?.redactSnippets ?? false;
|
|
182
194
|
return {
|
|
183
195
|
toolVersion: result.toolVersion,
|
|
184
196
|
root: result.root,
|
|
@@ -208,7 +220,7 @@ export function toJson(result: ScanResult): Record<string, unknown> {
|
|
|
208
220
|
line: f.location.line,
|
|
209
221
|
column: f.location.column,
|
|
210
222
|
endLine: f.location.endLine,
|
|
211
|
-
snippet: f
|
|
223
|
+
snippet: emittedSnippet(f, redactSnippets),
|
|
212
224
|
},
|
|
213
225
|
})),
|
|
214
226
|
};
|
|
@@ -231,9 +243,6 @@ const ANSI = {
|
|
|
231
243
|
cyan: "\x1b[36m",
|
|
232
244
|
} as const;
|
|
233
245
|
|
|
234
|
-
/** Severity ordering for grouping / sorting, most → least severe. */
|
|
235
|
-
const SEVERITY_ORDER: Severity[] = ["critical", "high", "medium", "low", "info"];
|
|
236
|
-
|
|
237
246
|
function severityColor(sev: Severity): string {
|
|
238
247
|
switch (sev) {
|
|
239
248
|
case "critical":
|
package/src/scan.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { pemDetector } from "./detectors/pem.js";
|
|
|
19
19
|
import { defaultRegistry, detectorScope } from "./registry.js";
|
|
20
20
|
import { isManifestFile, scanManifest } from "./dependencies.js";
|
|
21
21
|
import { buildInventory } from "./inventory.js";
|
|
22
|
+
import { AbortError, BudgetExceededError } from "./errors.js";
|
|
22
23
|
import { VERSION } from "./version.js";
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -90,6 +91,12 @@ export async function scan(options: ScanOptions): Promise<ScanResult> {
|
|
|
90
91
|
|
|
91
92
|
const findings: Finding[] = [];
|
|
92
93
|
let filesScanned = 0;
|
|
94
|
+
let bytesScanned = 0;
|
|
95
|
+
|
|
96
|
+
// Work-budget / cancellation controls (all optional, unlimited when omitted).
|
|
97
|
+
const signal = options.signal;
|
|
98
|
+
const maxFiles = options.maxFiles;
|
|
99
|
+
const maxBytes = options.maxBytes;
|
|
93
100
|
|
|
94
101
|
// Source of relative paths: an explicit file list (incremental) or the walker.
|
|
95
102
|
const relPaths: AsyncIterable<string> = options.files
|
|
@@ -102,6 +109,14 @@ export async function scan(options: ScanOptions): Promise<ScanResult> {
|
|
|
102
109
|
});
|
|
103
110
|
|
|
104
111
|
for await (const rel of relPaths) {
|
|
112
|
+
// Cooperative cancellation: check the signal at the top of every iteration.
|
|
113
|
+
if (signal?.aborted) throw new AbortError();
|
|
114
|
+
|
|
115
|
+
// File-count budget: enforced before reading the next file.
|
|
116
|
+
if (typeof maxFiles === "number" && filesScanned >= maxFiles) {
|
|
117
|
+
throw new BudgetExceededError(`maxFiles budget exceeded (limit: ${maxFiles}).`);
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
// In single-file mode, walkFiles yields the basename; map back to the file.
|
|
106
121
|
const absPath = singleFileName ? options.root : path.join(baseDir, ...rel.split("/"));
|
|
107
122
|
const reportedPath = singleFileName ? toPosix(rel) : rel;
|
|
@@ -121,6 +136,12 @@ export async function scan(options: ScanOptions): Promise<ScanResult> {
|
|
|
121
136
|
continue;
|
|
122
137
|
}
|
|
123
138
|
|
|
139
|
+
// Cumulative-byte budget: enforced once this file's bytes are accounted for.
|
|
140
|
+
bytesScanned += Buffer.byteLength(content, "utf8");
|
|
141
|
+
if (typeof maxBytes === "number" && bytesScanned > maxBytes) {
|
|
142
|
+
throw new BudgetExceededError(`maxBytes budget exceeded (limit: ${maxBytes}).`);
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
filesScanned += 1;
|
|
125
146
|
findings.push(
|
|
126
147
|
...detectFile(reportedPath, content, dets, {
|
|
@@ -150,11 +171,18 @@ export async function scan(options: ScanOptions): Promise<ScanResult> {
|
|
|
150
171
|
|
|
151
172
|
/**
|
|
152
173
|
* Filter an explicit relative file list down to the paths that pass the binary
|
|
153
|
-
* filter and the include/exclude patterns,
|
|
174
|
+
* filter and the include/exclude patterns, returning them deduped and sorted for
|
|
154
175
|
* deterministic output. Size limits are enforced later (we still read manifests
|
|
155
176
|
* over the cap). Non-existent files are simply skipped at read time.
|
|
177
|
+
*
|
|
178
|
+
* Pure + synchronous, and exported so the parallel enumerator applies the EXACT
|
|
179
|
+
* same filtering for explicit file lists as the serial path (byte-for-byte
|
|
180
|
+
* identical results under `--parallel`).
|
|
156
181
|
*/
|
|
157
|
-
|
|
182
|
+
export function filterExplicitFileList(
|
|
183
|
+
files: readonly string[],
|
|
184
|
+
options: Pick<ScanOptions, "include" | "exclude">,
|
|
185
|
+
): string[] {
|
|
158
186
|
const include = options.include ?? [];
|
|
159
187
|
const exclude = options.exclude ?? [];
|
|
160
188
|
const seen = new Set<string>();
|
|
@@ -167,12 +195,17 @@ async function* filterExplicitFiles(files: string[], options: ScanOptions): Asyn
|
|
|
167
195
|
})
|
|
168
196
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
169
197
|
|
|
170
|
-
|
|
171
|
-
if (isBinaryPath(rel))
|
|
172
|
-
if (include.length > 0 && !matchesAny(rel, include))
|
|
173
|
-
if (matchesAny(rel, exclude))
|
|
174
|
-
|
|
175
|
-
}
|
|
198
|
+
return list.filter((rel) => {
|
|
199
|
+
if (isBinaryPath(rel)) return false;
|
|
200
|
+
if (include.length > 0 && !matchesAny(rel, include)) return false;
|
|
201
|
+
if (matchesAny(rel, exclude)) return false;
|
|
202
|
+
return true;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Async-generator wrapper around {@link filterExplicitFileList} for `scan()`. */
|
|
207
|
+
async function* filterExplicitFiles(files: string[], options: ScanOptions): AsyncGenerator<string> {
|
|
208
|
+
for (const rel of filterExplicitFileList(files, options)) yield rel;
|
|
176
209
|
}
|
|
177
210
|
|
|
178
211
|
/** Local substring/prefix matcher (mirrors the walker's pattern semantics). */
|
package/src/severity.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Severity utilities shared across the monorepo (qScan, the MCP server, the
|
|
3
|
+
* GitHub Action). Lifts the previously-duplicated ordering / threshold / SARIF
|
|
4
|
+
* mapping logic into one place so every consumer agrees on what "at or above a
|
|
5
|
+
* threshold" means and how a severity maps to a SARIF level.
|
|
6
|
+
*/
|
|
7
|
+
import type { Severity } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/** Severity ordering, most → least severe. Index 0 is the most severe. */
|
|
10
|
+
export const SEVERITY_ORDER: readonly Severity[] = ["critical", "high", "medium", "low", "info"];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rank of a severity within {@link SEVERITY_ORDER} (0 = most severe). Lower
|
|
14
|
+
* ranks are more severe; unknown values fall to the end.
|
|
15
|
+
*/
|
|
16
|
+
export function severityRank(s: Severity): number {
|
|
17
|
+
const i = SEVERITY_ORDER.indexOf(s);
|
|
18
|
+
return i < 0 ? SEVERITY_ORDER.length : i;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* True when `severity` is at or above `threshold` (i.e. at least as severe).
|
|
23
|
+
* Because lower ranks are more severe, "at or above" is `rank <= threshold`.
|
|
24
|
+
*/
|
|
25
|
+
export function meetsThreshold(severity: Severity, threshold: Severity): boolean {
|
|
26
|
+
return severityRank(severity) <= severityRank(threshold);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Map our severity to a SARIF 2.1.0 result level. */
|
|
30
|
+
export function sarifLevel(severity: Severity): "error" | "warning" | "note" {
|
|
31
|
+
switch (severity) {
|
|
32
|
+
case "critical":
|
|
33
|
+
case "high":
|
|
34
|
+
return "error";
|
|
35
|
+
case "medium":
|
|
36
|
+
return "warning";
|
|
37
|
+
default:
|
|
38
|
+
return "note";
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -68,6 +68,12 @@ export interface Finding {
|
|
|
68
68
|
remediation?: string;
|
|
69
69
|
/** Associated CWE identifier, e.g. "CWE-327" (broken crypto), "CWE-326" (weak strength). */
|
|
70
70
|
cwe?: string;
|
|
71
|
+
/**
|
|
72
|
+
* True when the matched snippet IS the sensitive value (e.g. a PEM private/
|
|
73
|
+
* public key block, an `ssh-rsa AAAA…` public key). Reporters ALWAYS drop the
|
|
74
|
+
* snippet for such findings, regardless of any redaction flag.
|
|
75
|
+
*/
|
|
76
|
+
sensitive?: boolean;
|
|
71
77
|
location: SourceLocation;
|
|
72
78
|
}
|
|
73
79
|
|
|
@@ -166,6 +172,23 @@ export interface ScanOptions {
|
|
|
166
172
|
detectors?: Detector[];
|
|
167
173
|
/** Optional progress callback. */
|
|
168
174
|
onFile?: (file: string) => void;
|
|
175
|
+
/**
|
|
176
|
+
* Optional abort signal. When it fires mid-walk the scan stops cooperatively
|
|
177
|
+
* and rejects with an `AbortError` (a `DOMException`-like error with
|
|
178
|
+
* `name === "AbortError"`).
|
|
179
|
+
*/
|
|
180
|
+
signal?: AbortSignal;
|
|
181
|
+
/**
|
|
182
|
+
* Work budget: maximum number of files to scan. When exceeded mid-walk the
|
|
183
|
+
* scan stops and throws a `BudgetExceededError`. Unlimited when omitted.
|
|
184
|
+
*/
|
|
185
|
+
maxFiles?: number;
|
|
186
|
+
/**
|
|
187
|
+
* Work budget: maximum cumulative bytes of scanned file content. When
|
|
188
|
+
* exceeded mid-walk the scan stops and throws a `BudgetExceededError`.
|
|
189
|
+
* Unlimited when omitted.
|
|
190
|
+
*/
|
|
191
|
+
maxBytes?: number;
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
/** Extra options for {@link scanParallel}, layered onto {@link ScanOptions}. */
|
package/src/version.ts
CHANGED