@quantakrypto/core 0.5.0 → 0.6.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/dist/crypto-agility.d.ts +158 -0
- package/dist/crypto-agility.d.ts.map +1 -0
- package/dist/crypto-agility.js +285 -0
- package/dist/crypto-agility.js.map +1 -0
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/dependencies.js +180 -18
- package/dist/dependencies.js.map +1 -1
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +52 -0
- package/dist/detectors/source.js.map +1 -1
- package/dist/hndl.d.ts +241 -0
- package/dist/hndl.d.ts.map +1 -0
- package/dist/hndl.js +752 -0
- package/dist/hndl.js.map +1 -0
- package/dist/index.d.ts +4 -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 +22 -23
- package/dist/parallel.js.map +1 -1
- package/dist/report.d.ts +8 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +75 -19
- package/dist/report.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/walk.d.ts +31 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +30 -9
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto-agility.ts +407 -0
- package/src/dependencies.ts +182 -16
- package/src/detectors/source.ts +62 -0
- package/src/hndl.ts +1012 -0
- package/src/index.ts +52 -0
- package/src/parallel.ts +21 -20
- package/src/report.ts +84 -19
- package/src/types.ts +9 -1
- package/src/version.ts +1 -1
- package/src/walk.ts +47 -10
package/src/index.ts
CHANGED
|
@@ -83,6 +83,42 @@ export { changedFiles } from "./changed.js";
|
|
|
83
83
|
export { loadConfig, ConfigError, CONFIG_FILENAME } from "./config.js";
|
|
84
84
|
export type { QuantakryptoFileConfig, LoadConfigResult } from "./config.js";
|
|
85
85
|
|
|
86
|
+
// HNDL (harvest-now-decrypt-later) data-risk quantifier (see docs/HNDL.md).
|
|
87
|
+
export {
|
|
88
|
+
HNDL_MODEL_VERSION,
|
|
89
|
+
HNDL_FILENAME,
|
|
90
|
+
SEVERITY_VULNERABILITY,
|
|
91
|
+
CONFIDENCE_WEIGHT,
|
|
92
|
+
CLASSIFICATION_SENSITIVITY,
|
|
93
|
+
NON_HNDL_DISCOUNT,
|
|
94
|
+
DEFAULT_QUANTUM_THREAT_YEARS,
|
|
95
|
+
DEFAULT_MIGRATION_HORIZON_YEARS,
|
|
96
|
+
DEFAULT_UNBOUND_CLASSIFICATION,
|
|
97
|
+
HndlError,
|
|
98
|
+
computeHndl,
|
|
99
|
+
vulnerabilityFactor,
|
|
100
|
+
moscaFactor,
|
|
101
|
+
globMatch,
|
|
102
|
+
findingFingerprint,
|
|
103
|
+
findingScope,
|
|
104
|
+
parseHndlMap,
|
|
105
|
+
loadHndlMap,
|
|
106
|
+
scaffoldHndlYaml,
|
|
107
|
+
} from "./hndl.js";
|
|
108
|
+
export type {
|
|
109
|
+
DataClassification,
|
|
110
|
+
HndlScope,
|
|
111
|
+
HndlDataAsset,
|
|
112
|
+
HndlHorizon,
|
|
113
|
+
HndlDefaults,
|
|
114
|
+
HndlMap,
|
|
115
|
+
ExposureRationale,
|
|
116
|
+
FindingExposure,
|
|
117
|
+
AssetExposure,
|
|
118
|
+
HndlSummary,
|
|
119
|
+
HndlReport,
|
|
120
|
+
} from "./hndl.js";
|
|
121
|
+
|
|
86
122
|
// Filesystem walker (relative POSIX paths, default ignores, size/binary filters).
|
|
87
123
|
export { walkFiles, isBinaryPath, looksMinified } from "./walk.js";
|
|
88
124
|
|
|
@@ -122,6 +158,22 @@ export { mergeCboms } from "./cbom-merge.js";
|
|
|
122
158
|
// OpenVEX 0.2.0 export — quantum-readiness posture as VEX statements.
|
|
123
159
|
export { toOpenVex } from "./vex.js";
|
|
124
160
|
export type { OpenVexDocument, OpenVexStatement, OpenVexOptions } from "./vex.js";
|
|
161
|
+
// Crypto-agility manifest: agent-consumable crypto-posture document.
|
|
162
|
+
export {
|
|
163
|
+
buildCryptoAgilityManifest,
|
|
164
|
+
validateCryptoAgilityManifest,
|
|
165
|
+
CRYPTO_AGILITY_MANIFEST_VERSION,
|
|
166
|
+
CRYPTO_AGILITY_WELL_KNOWN_PATH,
|
|
167
|
+
} from "./crypto-agility.js";
|
|
168
|
+
export type {
|
|
169
|
+
CryptoAgilityManifest,
|
|
170
|
+
CryptoAgilityManifestOptions,
|
|
171
|
+
CryptoAgilityPosture,
|
|
172
|
+
CryptoAgilityCbomSummary,
|
|
173
|
+
CryptoAgilityFamily,
|
|
174
|
+
CryptoAgilityPolicy,
|
|
175
|
+
ManifestValidation,
|
|
176
|
+
} from "./crypto-agility.js";
|
|
125
177
|
// ISO/IEC 27001 A.8.24 evidence-chain readiness report.
|
|
126
178
|
export { buildReadinessReport, signReadinessReport, verifyReadinessReport } from "./evidence.js";
|
|
127
179
|
export type {
|
package/src/parallel.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { fileURLToPath } from "node:url";
|
|
|
17
17
|
import type { Worker as NodeWorker } from "node:worker_threads";
|
|
18
18
|
|
|
19
19
|
import type { Finding, ParallelScanOptions, ScanResult } from "./types.js";
|
|
20
|
-
import {
|
|
20
|
+
import { walkFilesSized } from "./walk.js";
|
|
21
21
|
import { isAnalyzableSource } from "./detect-utils.js";
|
|
22
22
|
import { buildInventory } from "./inventory.js";
|
|
23
23
|
import { compareFindings, filterExplicitFileList, scan } from "./scan.js";
|
|
@@ -115,30 +115,31 @@ function shouldParallelize(options: ParallelScanOptions, files: SizedFile[]): bo
|
|
|
115
115
|
* explicit `files` list or the walker. Sizes power byte-balanced chunking.
|
|
116
116
|
*/
|
|
117
117
|
async function enumerateFiles(options: ParallelScanOptions, baseDir: string): Promise<SizedFile[]> {
|
|
118
|
-
const
|
|
118
|
+
const sized: SizedFile[] = [];
|
|
119
119
|
if (options.files) {
|
|
120
|
+
// Explicit file list: the walker isn't involved, so stat each once here.
|
|
120
121
|
// Apply the SAME include/exclude/binary filtering the serial path uses via
|
|
121
|
-
// `
|
|
122
|
-
for (const rel of filterExplicitFileList(options.files, options))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
rels.push(rel);
|
|
122
|
+
// `filterExplicitFileList`, so `--parallel` is byte-for-byte identical to serial.
|
|
123
|
+
for (const rel of filterExplicitFileList(options.files, options)) {
|
|
124
|
+
let size = 0;
|
|
125
|
+
try {
|
|
126
|
+
size = (await stat(path.join(baseDir, ...rel.split("/")))).size;
|
|
127
|
+
} catch {
|
|
128
|
+
// Unreadable now; keep with size 0 (the worker read will skip if it's gone).
|
|
129
|
+
}
|
|
130
|
+
sized.push({ rel, size });
|
|
131
131
|
}
|
|
132
|
+
return sized;
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
// Directory walk: reuse the size the walker already stat'd while enforcing the
|
|
136
|
+
// size limit, so files are not stat'd a second time here (double-stat fix).
|
|
137
|
+
for await (const { rel, size } of walkFilesSized(options.root, {
|
|
138
|
+
include: options.include,
|
|
139
|
+
exclude: options.exclude,
|
|
140
|
+
noDefaultIgnores: options.noDefaultIgnores,
|
|
141
|
+
maxFileSize: options.maxFileSize,
|
|
142
|
+
})) {
|
|
142
143
|
sized.push({ rel, size });
|
|
143
144
|
}
|
|
144
145
|
return sized;
|
package/src/report.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { remediationFor, remediationForTier, remediationForProfile } from "./rem
|
|
|
11
11
|
import type { SecurityTier } from "./remediation.js";
|
|
12
12
|
import type { StandardsProfile } from "./standards-profiles.js";
|
|
13
13
|
import { fingerprintFinding } from "./baseline.js";
|
|
14
|
+
import { findingFingerprint } from "./hndl.js";
|
|
15
|
+
import type { FindingExposure, HndlReport } from "./hndl.js";
|
|
14
16
|
|
|
15
17
|
/** Minimal SARIF 2.1.0 log shape (kept permissive on purpose). */
|
|
16
18
|
export interface SarifLog {
|
|
@@ -36,6 +38,29 @@ export interface ReportOptions {
|
|
|
36
38
|
* {@link toJson}.
|
|
37
39
|
*/
|
|
38
40
|
catalog?: RuleMeta[];
|
|
41
|
+
/**
|
|
42
|
+
* Optional HNDL exposure analysis ({@link computeHndl}). When supplied, each
|
|
43
|
+
* finding gains its `exposure` fields (score, bound data asset, rationale)
|
|
44
|
+
* keyed by fingerprint, and the report carries the repo-level HNDL summary.
|
|
45
|
+
* Purely additive: it never changes finding identity, ordering, or exit codes.
|
|
46
|
+
*/
|
|
47
|
+
hndl?: HndlReport;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The per-finding exposure block emitted in JSON / SARIF, or undefined. */
|
|
51
|
+
function exposureFor(f: Finding, hndl: HndlReport | undefined): FindingExposure | undefined {
|
|
52
|
+
if (!hndl) return undefined;
|
|
53
|
+
return hndl.byFingerprint.get(findingFingerprint(f));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The repo HNDL summary block shared by JSON output and the SARIF run. */
|
|
57
|
+
function hndlSummaryBlock(hndl: HndlReport): Record<string, unknown> {
|
|
58
|
+
return {
|
|
59
|
+
modelVersion: hndl.modelVersion,
|
|
60
|
+
horizon: hndl.horizon,
|
|
61
|
+
summary: hndl.summary,
|
|
62
|
+
assets: hndl.assets,
|
|
63
|
+
};
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
const SARIF_SCHEMA =
|
|
@@ -105,6 +130,16 @@ function sarifRule(spec: {
|
|
|
105
130
|
};
|
|
106
131
|
}
|
|
107
132
|
|
|
133
|
+
/** SARIF result.properties fragment for a finding's HNDL exposure, or empty. */
|
|
134
|
+
function exposureProperties(exposure: FindingExposure | undefined): Record<string, unknown> {
|
|
135
|
+
if (!exposure) return {};
|
|
136
|
+
return {
|
|
137
|
+
exposureScore: exposure.exposureScore,
|
|
138
|
+
dataAsset: exposure.dataAsset,
|
|
139
|
+
exposureRationale: exposure.rationale,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
108
143
|
/** Serialize a scan result as SARIF 2.1.0. */
|
|
109
144
|
export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
|
|
110
145
|
const redactSnippets = opts?.redactSnippets ?? false;
|
|
@@ -171,6 +206,10 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
|
|
|
171
206
|
// edit above it. `quantakrypto/v1` names our scheme.
|
|
172
207
|
partialFingerprints: { "quantakrypto/v1": fingerprintFinding(f) },
|
|
173
208
|
properties: {
|
|
209
|
+
// Same stable identity mirrored into properties so non-GitHub SARIF
|
|
210
|
+
// consumers (our platform ingest) can read one uniform `fingerprint`
|
|
211
|
+
// field across JSON and SARIF without reaching into partialFingerprints.
|
|
212
|
+
fingerprint: fingerprintFinding(f),
|
|
174
213
|
category: f.category,
|
|
175
214
|
severity: f.severity,
|
|
176
215
|
confidence: f.confidence,
|
|
@@ -178,6 +217,7 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
|
|
|
178
217
|
...(f.algorithm ? { algorithm: f.algorithm } : {}),
|
|
179
218
|
...(f.remediation ? { remediation: f.remediation } : {}),
|
|
180
219
|
...(f.cwe ? { cwe: f.cwe } : {}),
|
|
220
|
+
...exposureProperties(exposureFor(f, opts?.hndl)),
|
|
181
221
|
},
|
|
182
222
|
...(f.cwe
|
|
183
223
|
? {
|
|
@@ -233,6 +273,7 @@ export function toSarif(result: ScanResult, opts?: ReportOptions): SarifLog {
|
|
|
233
273
|
},
|
|
234
274
|
},
|
|
235
275
|
...(taxonomies.length > 0 ? { taxonomies } : {}),
|
|
276
|
+
...(opts?.hndl ? { properties: { hndl: hndlSummaryBlock(opts.hndl) } } : {}),
|
|
236
277
|
results,
|
|
237
278
|
},
|
|
238
279
|
],
|
|
@@ -258,6 +299,7 @@ function securitySeverity(severity: Severity): string {
|
|
|
258
299
|
/** Serialize a scan result as a plain JSON-friendly object. */
|
|
259
300
|
export function toJson(result: ScanResult, opts?: ReportOptions): Record<string, unknown> {
|
|
260
301
|
const redactSnippets = opts?.redactSnippets ?? false;
|
|
302
|
+
const hndl = opts?.hndl;
|
|
261
303
|
return {
|
|
262
304
|
toolVersion: result.toolVersion,
|
|
263
305
|
root: result.root,
|
|
@@ -273,25 +315,48 @@ export function toJson(result: ScanResult, opts?: ReportOptions): Record<string,
|
|
|
273
315
|
byCategory: result.inventory.byCategory,
|
|
274
316
|
byAlgorithm: result.inventory.byAlgorithm,
|
|
275
317
|
},
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
318
|
+
...(hndl ? { hndl: hndlSummaryBlock(hndl) } : {}),
|
|
319
|
+
findings: result.findings.map((f) => {
|
|
320
|
+
const exposure = exposureFor(f, hndl);
|
|
321
|
+
return {
|
|
322
|
+
// Stable, line-INSENSITIVE identity of the finding: sha256 of
|
|
323
|
+
// ruleId | normalized-POSIX-repo-relative-path | normalized-snippet
|
|
324
|
+
// (the SARIF partialFingerprints trick, line number deliberately
|
|
325
|
+
// excluded). Reused verbatim from the baseline module so JSON identity,
|
|
326
|
+
// SARIF partialFingerprints, and the baseline suppression set are one and
|
|
327
|
+
// the same value. A line move does NOT change it; when no snippet context
|
|
328
|
+
// exists it falls back to ruleId|path. This is the cross-run identity the
|
|
329
|
+
// platform keys posture drift on.
|
|
330
|
+
fingerprint: fingerprintFinding(f),
|
|
331
|
+
ruleId: f.ruleId,
|
|
332
|
+
title: f.title,
|
|
333
|
+
category: f.category,
|
|
334
|
+
severity: f.severity,
|
|
335
|
+
confidence: f.confidence,
|
|
336
|
+
algorithm: f.algorithm,
|
|
337
|
+
hndl: f.hndl,
|
|
338
|
+
message: f.message,
|
|
339
|
+
remediation: f.remediation,
|
|
340
|
+
cwe: f.cwe,
|
|
341
|
+
location: {
|
|
342
|
+
file: f.location.file,
|
|
343
|
+
line: f.location.line,
|
|
344
|
+
column: f.location.column,
|
|
345
|
+
endLine: f.location.endLine,
|
|
346
|
+
snippet: emittedSnippet(f, redactSnippets),
|
|
347
|
+
},
|
|
348
|
+
...(exposure
|
|
349
|
+
? {
|
|
350
|
+
exposure: {
|
|
351
|
+
fingerprint: exposure.fingerprint,
|
|
352
|
+
exposureScore: exposure.exposureScore,
|
|
353
|
+
dataAsset: exposure.dataAsset,
|
|
354
|
+
rationale: exposure.rationale,
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
: {}),
|
|
358
|
+
};
|
|
359
|
+
}),
|
|
295
360
|
};
|
|
296
361
|
}
|
|
297
362
|
|
package/src/types.ts
CHANGED
|
@@ -95,7 +95,15 @@ export interface Finding {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/** Package ecosystems the dependency scanner understands. */
|
|
98
|
-
export type DependencyEcosystem =
|
|
98
|
+
export type DependencyEcosystem =
|
|
99
|
+
| "npm"
|
|
100
|
+
| "pypi"
|
|
101
|
+
| "cargo"
|
|
102
|
+
| "go"
|
|
103
|
+
| "maven"
|
|
104
|
+
| "rubygems"
|
|
105
|
+
| "nuget"
|
|
106
|
+
| "composer";
|
|
99
107
|
|
|
100
108
|
/** A known quantum-vulnerable dependency entry. */
|
|
101
109
|
export interface VulnerableDependency {
|
package/src/version.ts
CHANGED
package/src/walk.ts
CHANGED
|
@@ -291,12 +291,34 @@ export function looksMinified(content: string): boolean {
|
|
|
291
291
|
return avgLine > 1_000;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/** A scannable file plus the byte size the walker already stat'd for it. */
|
|
295
|
+
export interface WalkedFile {
|
|
296
|
+
/** Relative POSIX path from the walk root. */
|
|
297
|
+
rel: string;
|
|
298
|
+
/** File size in bytes (from the walker's own stat; never re-stat downstream). */
|
|
299
|
+
size: number;
|
|
300
|
+
}
|
|
301
|
+
|
|
294
302
|
/**
|
|
295
303
|
* Recursively yield scannable file paths (relative to `root`, POSIX) under a
|
|
296
304
|
* directory. If `root` points at a single file, yields just that file's
|
|
297
|
-
* basename (subject to the size / binary filters).
|
|
305
|
+
* basename (subject to the size / binary filters). Thin wrapper over
|
|
306
|
+
* {@link walkFilesSized} that drops the size (the historical string-yielding API).
|
|
298
307
|
*/
|
|
299
308
|
export async function* walkFiles(root: string, options: WalkOptions = {}): AsyncGenerator<string> {
|
|
309
|
+
for await (const f of walkFilesSized(root, options)) yield f.rel;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Like {@link walkFiles} but yields each file WITH the byte size the walker
|
|
314
|
+
* already obtained while applying the size limit. Callers that need sizes (the
|
|
315
|
+
* parallel scanner's byte-balanced chunking) consume this directly so they do
|
|
316
|
+
* not stat every file a second time (P2 double-stat elimination).
|
|
317
|
+
*/
|
|
318
|
+
export async function* walkFilesSized(
|
|
319
|
+
root: string,
|
|
320
|
+
options: WalkOptions = {},
|
|
321
|
+
): AsyncGenerator<WalkedFile> {
|
|
300
322
|
const include = options.include ?? [];
|
|
301
323
|
const exclude = options.exclude ?? [];
|
|
302
324
|
const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
@@ -312,7 +334,7 @@ export async function* walkFiles(root: string, options: WalkOptions = {}): Async
|
|
|
312
334
|
isIncluded(name, include) &&
|
|
313
335
|
passesSizeLimit(name, rootStat.size, maxFileSize)
|
|
314
336
|
) {
|
|
315
|
-
yield name;
|
|
337
|
+
yield { rel: name, size: rootStat.size };
|
|
316
338
|
}
|
|
317
339
|
return;
|
|
318
340
|
}
|
|
@@ -327,20 +349,33 @@ interface WalkContext {
|
|
|
327
349
|
ignores: readonly string[];
|
|
328
350
|
}
|
|
329
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Manifests bypass the normal file-size cap (they carry the whole dependency
|
|
354
|
+
* tree and must not be dropped), but still get a generous hard ceiling so a
|
|
355
|
+
* pathological or hostile lockfile can't be read unbounded and OOM the scan.
|
|
356
|
+
* 16 MiB comfortably covers real monorepo lockfiles (npm's own is well under 5).
|
|
357
|
+
*/
|
|
358
|
+
export const MANIFEST_MAX_BYTES = 16 * 1024 * 1024;
|
|
359
|
+
|
|
330
360
|
/**
|
|
331
361
|
* True if a file passes the size limit. Dependency manifests (package.json /
|
|
332
|
-
* package-lock.json
|
|
333
|
-
*
|
|
362
|
+
* package-lock.json / yarn.lock / …) get the larger {@link MANIFEST_MAX_BYTES}
|
|
363
|
+
* ceiling instead of the ordinary cap, so large lockfiles still get scanned for
|
|
364
|
+
* vulnerable dependencies without letting an enormous one exhaust memory.
|
|
365
|
+
* Exported for unit testing.
|
|
334
366
|
*/
|
|
335
|
-
function passesSizeLimit(rel: string, size: number, maxFileSize: number): boolean {
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
if (isManifestFile(rel)) return true;
|
|
367
|
+
export function passesSizeLimit(rel: string, size: number, maxFileSize: number): boolean {
|
|
368
|
+
// Uses the single {@link isManifestFile} definition.
|
|
369
|
+
if (isManifestFile(rel)) return size <= MANIFEST_MAX_BYTES;
|
|
339
370
|
return size <= maxFileSize;
|
|
340
371
|
}
|
|
341
372
|
|
|
342
373
|
/** Internal recursive directory walker. `relDir` is POSIX-relative to the root. */
|
|
343
|
-
async function* walkDir(
|
|
374
|
+
async function* walkDir(
|
|
375
|
+
absDir: string,
|
|
376
|
+
relDir: string,
|
|
377
|
+
ctx: WalkContext,
|
|
378
|
+
): AsyncGenerator<WalkedFile> {
|
|
344
379
|
let entries: Dirent[];
|
|
345
380
|
try {
|
|
346
381
|
entries = await readdir(absDir, { withFileTypes: true });
|
|
@@ -377,13 +412,15 @@ async function* walkDir(absDir: string, relDir: string, ctx: WalkContext): Async
|
|
|
377
412
|
if (!manifest && isBinaryPath(rel)) continue;
|
|
378
413
|
if (!manifest && isGeneratedPath(rel)) continue;
|
|
379
414
|
|
|
415
|
+
let size: number;
|
|
380
416
|
try {
|
|
381
417
|
const s = await stat(abs);
|
|
382
418
|
if (!passesSizeLimit(rel, s.size, ctx.maxFileSize)) continue;
|
|
419
|
+
size = s.size;
|
|
383
420
|
} catch {
|
|
384
421
|
continue;
|
|
385
422
|
}
|
|
386
423
|
|
|
387
|
-
yield rel;
|
|
424
|
+
yield { rel, size };
|
|
388
425
|
}
|
|
389
426
|
}
|