muaddib-scanner 2.11.99 → 2.11.100
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/package.json
CHANGED
|
@@ -56,14 +56,18 @@ const _lane = { active: 0, queue: [] };
|
|
|
56
56
|
/**
|
|
57
57
|
* Pure classifier. `truncated` (the bounded measurement walk overflowed its
|
|
58
58
|
* depth/file caps) classifies heavy by default — defensive: an unmeasurable
|
|
59
|
-
* package is exactly the kind that blows a worker.
|
|
60
|
-
*
|
|
59
|
+
* package is exactly the kind that blows a worker. Compares weightedJsBytes
|
|
60
|
+
* (plain + ×12 minified — see measureJsWeight in queue.js: raw bytes alone
|
|
61
|
+
* missed the minified explosions, powerlines 517KB → 1151MB heap) and falls
|
|
62
|
+
* back to totalJsBytes for callers that don't weight.
|
|
63
|
+
* @param {{totalJsBytes: number, weightedJsBytes?: number, truncated: boolean}|null} weight
|
|
61
64
|
* @param {number} [thresholdBytes]
|
|
62
65
|
*/
|
|
63
66
|
function isHeavyScan(weight, thresholdBytes = heavyScanBytesThreshold()) {
|
|
64
67
|
if (!weight) return false;
|
|
65
68
|
if (weight.truncated) return true;
|
|
66
|
-
|
|
69
|
+
const effective = Number.isFinite(weight.weightedJsBytes) ? weight.weightedJsBytes : (weight.totalJsBytes || 0);
|
|
70
|
+
return effective >= thresholdBytes;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
/**
|
package/src/monitor/queue.js
CHANGED
|
@@ -311,6 +311,36 @@ function countPackageFiles(dir) {
|
|
|
311
311
|
const JS_WEIGHT_MAX_DEPTH = 8;
|
|
312
312
|
const JS_WEIGHT_MAX_FILES = 2000;
|
|
313
313
|
const JS_WEIGHT_FILE_PATTERN = /\.(?:[cm]?js|[jt]sx?)$/i;
|
|
314
|
+
// Minified JS expands SUPER-linearly in the worker (live counter-examples
|
|
315
|
+
// from the 16:18 rollout, 2026-06-11: powerlines = 517KB JS of which 449KB
|
|
316
|
+
// minified → 1151MB heap, ~2300×; @lethevimlet/sshift = ~1.9MB minified →
|
|
317
|
+
// 1.38GB — both sailed under the raw-bytes threshold as 'light'). Plain
|
|
318
|
+
// source stays roughly linear (the 12MB-heap mode of the bimodal
|
|
319
|
+
// distribution). So minified bytes count ×12 toward the heavy threshold —
|
|
320
|
+
// ≥~256KB of minified JS crosses the 3MiB default. Detection: average line
|
|
321
|
+
// length over the first 4KB; plain code sits at 40-120 chars, minified
|
|
322
|
+
// bundles at 800+ (often a single line). 250 splits cleanly even when a
|
|
323
|
+
// license header pads the probe window.
|
|
324
|
+
const JS_MINIFIED_WEIGHT = 12;
|
|
325
|
+
const JS_MINIFIED_AVG_LINE = 250;
|
|
326
|
+
const JS_MINIFIED_PROBE_BYTES = 4096;
|
|
327
|
+
|
|
328
|
+
/** Probe the first 4KB of a file (never loads the rest) for minification. */
|
|
329
|
+
function probeIsMinified(filePath) {
|
|
330
|
+
let fd = null;
|
|
331
|
+
try {
|
|
332
|
+
fd = fs.openSync(filePath, 'r');
|
|
333
|
+
const buf = Buffer.alloc(JS_MINIFIED_PROBE_BYTES);
|
|
334
|
+
const n = fs.readSync(fd, buf, 0, JS_MINIFIED_PROBE_BYTES, 0);
|
|
335
|
+
if (n <= 0) return false;
|
|
336
|
+
const head = buf.toString('utf8', 0, n);
|
|
337
|
+
return (head.length / head.split('\n').length) > JS_MINIFIED_AVG_LINE;
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
340
|
+
} finally {
|
|
341
|
+
if (fd !== null) { try { fs.closeSync(fd); } catch { /* best-effort */ } }
|
|
342
|
+
}
|
|
343
|
+
}
|
|
314
344
|
|
|
315
345
|
/**
|
|
316
346
|
* Measure how much parsable JS a package carries — the heavy-lane
|
|
@@ -325,11 +355,16 @@ const JS_WEIGHT_FILE_PATTERN = /\.(?:[cm]?js|[jt]sx?)$/i;
|
|
|
325
355
|
* Bounded walk; an overflow (depth/file caps) returns truncated:true, which
|
|
326
356
|
* isHeavyScan classifies heavy by default.
|
|
327
357
|
*
|
|
358
|
+
* weightedJsBytes = plain bytes + JS_MINIFIED_WEIGHT × minified bytes — the
|
|
359
|
+
* value isHeavyScan compares against the threshold (raw bytes alone missed
|
|
360
|
+
* the minified explosions, see JS_MINIFIED_WEIGHT above).
|
|
361
|
+
*
|
|
328
362
|
* @param {string} dir - extracted package directory
|
|
329
|
-
* @returns {{ totalJsBytes: number, maxJsFileBytes: number, truncated: boolean }}
|
|
363
|
+
* @returns {{ totalJsBytes: number, minifiedJsBytes: number, weightedJsBytes: number, maxJsFileBytes: number, truncated: boolean }}
|
|
330
364
|
*/
|
|
331
365
|
function measureJsWeight(dir) {
|
|
332
366
|
let totalJsBytes = 0;
|
|
367
|
+
let minifiedJsBytes = 0;
|
|
333
368
|
let maxJsFileBytes = 0;
|
|
334
369
|
let seen = 0;
|
|
335
370
|
let truncated = false;
|
|
@@ -347,17 +382,20 @@ function measureJsWeight(dir) {
|
|
|
347
382
|
walk(path.join(current, entry.name), depth + 1);
|
|
348
383
|
} else if (entry.isFile() && JS_WEIGHT_FILE_PATTERN.test(entry.name)) {
|
|
349
384
|
if (++seen > JS_WEIGHT_MAX_FILES) { truncated = true; return; }
|
|
385
|
+
const filePath = path.join(current, entry.name);
|
|
350
386
|
let size;
|
|
351
|
-
try { size = fs.statSync(
|
|
387
|
+
try { size = fs.statSync(filePath).size; } catch { continue; }
|
|
352
388
|
if (size > perFileCap) continue; // executor skips these — they never reach the AST
|
|
353
389
|
totalJsBytes += size;
|
|
390
|
+
if (probeIsMinified(filePath)) minifiedJsBytes += size;
|
|
354
391
|
if (size > maxJsFileBytes) maxJsFileBytes = size;
|
|
355
392
|
}
|
|
356
393
|
}
|
|
357
394
|
}
|
|
358
395
|
|
|
359
396
|
walk(dir, 0);
|
|
360
|
-
|
|
397
|
+
const weightedJsBytes = (totalJsBytes - minifiedJsBytes) + JS_MINIFIED_WEIGHT * minifiedJsBytes;
|
|
398
|
+
return { totalJsBytes, minifiedJsBytes, weightedJsBytes, maxJsFileBytes, truncated };
|
|
361
399
|
}
|
|
362
400
|
|
|
363
401
|
/**
|
|
@@ -490,7 +528,7 @@ function runScanInWorker(extractedDir, timeoutMs, scanContext = null, signal = n
|
|
|
490
528
|
appendWorkerMem({
|
|
491
529
|
ev: 'spawn', tid: _wmTid,
|
|
492
530
|
name: _sc.name, version: _sc.version, ecosystem: _sc.ecosystem,
|
|
493
|
-
lane: _sc._lane, jsBytes: _sc._jsBytes,
|
|
531
|
+
lane: _sc._lane, jsBytes: _sc._jsBytes, jsMin: _sc._jsMin,
|
|
494
532
|
rss: process.memoryUsage().rss
|
|
495
533
|
});
|
|
496
534
|
|
|
@@ -774,7 +812,8 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
|
|
|
774
812
|
// event (runScanInWorker) so lane×heap-peak cross-checks are possible
|
|
775
813
|
// post-rollout (hard criterion: zero 'light' scans peaking >512MB).
|
|
776
814
|
_lane: lane,
|
|
777
|
-
_jsBytes: jsWeight.totalJsBytes
|
|
815
|
+
_jsBytes: jsWeight.totalJsBytes,
|
|
816
|
+
_jsMin: jsWeight.minifiedJsBytes || 0
|
|
778
817
|
};
|
|
779
818
|
// Hand the main-thread-fetched metadata to the worker so its processor skips
|
|
780
819
|
// the per-worker getPackageMetadata fetch (429-storm fix). npm only; the key
|