next-leak 0.4.0 → 0.4.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 +2 -1
- package/dist/{chunk-BDIPW6FU.js → chunk-2FZXZLZW.js} +29 -2
- package/dist/{chunk-NYWCWKS6.js → chunk-NNDGXIGQ.js} +1 -1
- package/dist/{chunk-USNLL625.js → chunk-YDMJVVWG.js} +60 -11
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli.js +3 -2
- package/dist/confidence.d.ts +8 -1
- package/dist/{html-report-VW7VQOCH.js → html-report-X2LUOZRH.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/runner.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,7 @@ Every report prints the gate it used.
|
|
|
104
104
|
| `--idle <seconds>` | 30 | **Maximum** wait before each sample; the run continues as soon as the heap settles |
|
|
105
105
|
| `--max-old-space <mb>` | 512 | Heap cap of each measured process. Raise it for apps whose legitimate working set is larger, or they die under measurement |
|
|
106
106
|
| `--quick` | off | Fast preset (2000 requests × 4 cycles, 8s idle) — the exact profile the real-app validation ran with. Same cycle count as the default; what it trades away is traffic per cycle, so it sits on the noise floor and is less sensitive to slow leaks. Explicit flags override it |
|
|
107
|
+
| `--no-resolve` | off | Skip the second pass on inconclusive routes |
|
|
107
108
|
| `--diff-all` | off | Diff snapshots for stable routes too |
|
|
108
109
|
| `--output <dir>` | `<app>/.next-leak` | Where runs are written |
|
|
109
110
|
|
|
@@ -168,7 +169,7 @@ separates them, because each one has a different fix:
|
|
|
168
169
|
flat but RSS keeps climbing, the report says so explicitly: that is an
|
|
169
170
|
allocator, external-buffer or fragmentation problem, not a JS-heap leak.
|
|
170
171
|
- **`leak`** — the report names the culprit when attribution resolves: your file (`culprit: src/app/x/page.tsx (your code)`), a dependency (package name), or framework internals. An `ISSUE-<route>.md` draft is generated; if the leak is app-owned, the draft tells you **not** to file it upstream.
|
|
171
|
-
- **`inconclusive`** —
|
|
172
|
+
- **`inconclusive`** — the evidence does not decide. The run does not stop there: any inconclusive route is **measured again automatically**, with twice the cycles, and the second pass is what you see (`resolved at 8 cycles` next to the verdict). On the reproduction for [#95094](https://github.com/vercel/next.js/issues/95094), `--quick` alone reports `inconclusive` on three deltas and then comes back with the leak. `--no-resolve` turns the second pass off; when even that is undecided, the re-run command is still printed.
|
|
172
173
|
- **`failed`** — the route errored under load (auth redirects, POST-only endpoints). >1% non-2xx aborts measurement instead of measuring garbage. That's by design.
|
|
173
174
|
|
|
174
175
|
## Peak pressure: `stable` is not the same as safe
|
|
@@ -68,12 +68,14 @@ function classifyMemoryTrend(heapSamples, externalSamples, options = {}) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// src/confidence.ts
|
|
71
|
+
var resolveCycles = (cycles) => Math.max(cycles * 2, 6);
|
|
71
72
|
function effectiveVerdict(report) {
|
|
72
73
|
return report.confidence.supersededVerdict ?? report.trend.verdict;
|
|
73
74
|
}
|
|
74
75
|
var VERDICT_WEAKENING = /* @__PURE__ */ new Set([
|
|
75
76
|
"near-threshold",
|
|
76
|
-
"spiky-growth"
|
|
77
|
+
"spiky-growth",
|
|
78
|
+
"thin-evidence"
|
|
77
79
|
]);
|
|
78
80
|
function warrantsIssueDraft(report) {
|
|
79
81
|
return effectiveVerdict(report) === "leak" && !report.confidence.warnings.some((warning) => VERDICT_WEAKENING.has(warning.code));
|
|
@@ -182,13 +184,36 @@ function heapCeilingWarnings(input) {
|
|
|
182
184
|
detail: `the heap peaked at ${mb(peak)} against a ${capMb} MB cap (${pct(peak, capBytes)}) \u2014 the curve may have been clipped by the ceiling rather than by the app; re-run with a larger --max-old-space`
|
|
183
185
|
}];
|
|
184
186
|
}
|
|
187
|
+
var THIN_EVIDENCE_MIN_DELTAS = 5;
|
|
188
|
+
var THIN_EVIDENCE_MIN_DELTA_RATIO = 4;
|
|
189
|
+
function isThinEvidence(trend, minGrowth) {
|
|
190
|
+
if (trend.deltas.length >= THIN_EVIDENCE_MIN_DELTAS || trend.deltas.length === 0) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const smallest = Math.min(...trend.deltas);
|
|
194
|
+
return smallest < minGrowth * THIN_EVIDENCE_MIN_DELTA_RATIO;
|
|
195
|
+
}
|
|
196
|
+
function thinEvidenceWarnings(trend, minGrowth) {
|
|
197
|
+
if (trend.verdict !== "leak" || !isThinEvidence(trend, minGrowth)) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const smallest = Math.min(...trend.deltas);
|
|
201
|
+
return [{
|
|
202
|
+
code: "thin-evidence",
|
|
203
|
+
detail: `judged on ${trend.deltas.length} cycles, and its weakest grew ${mb(smallest)} against a ${mb(minGrowth)} gate \u2014 on that few cycles ordinary oscillation reaches that, and a repeat run often disagrees; measure more cycles to resolve it`
|
|
204
|
+
}];
|
|
205
|
+
}
|
|
185
206
|
function isVerdictInvalid(input) {
|
|
186
207
|
if (input.trend.verdict !== "leak") {
|
|
187
208
|
return false;
|
|
188
209
|
}
|
|
189
210
|
const neverSettled = input.settleOutcomes.length > 0 && input.settleOutcomes.every((outcome) => outcome.status === "moving");
|
|
190
211
|
const abandonedNothing = input.abandonAfterMs !== void 0 && input.loadOutcomes.length > 0 && input.loadOutcomes.every((outcome) => (outcome.abandoned ?? 0) === 0);
|
|
191
|
-
|
|
212
|
+
const thinEvidence = isThinEvidence(
|
|
213
|
+
input.trend,
|
|
214
|
+
input.minGrowthPerCycle ?? MIN_GROWTH_NOISE_FLOOR
|
|
215
|
+
);
|
|
216
|
+
return neverSettled || abandonedNothing || thinEvidence;
|
|
192
217
|
}
|
|
193
218
|
function assessConfidence(input) {
|
|
194
219
|
const minGrowth = input.minGrowthPerCycle ?? MIN_GROWTH_NOISE_FLOOR;
|
|
@@ -197,6 +222,7 @@ function assessConfidence(input) {
|
|
|
197
222
|
...loadWarnings(input.loadOutcomes, input.abandonAfterMs),
|
|
198
223
|
...growthShapeWarnings(input.trend),
|
|
199
224
|
...noiseFloorWarnings(input.trend, minGrowth),
|
|
225
|
+
...thinEvidenceWarnings(input.trend, minGrowth),
|
|
200
226
|
...heapCeilingWarnings(input)
|
|
201
227
|
];
|
|
202
228
|
return {
|
|
@@ -210,6 +236,7 @@ export {
|
|
|
210
236
|
minGrowthFor,
|
|
211
237
|
classifyTrend,
|
|
212
238
|
classifyMemoryTrend,
|
|
239
|
+
resolveCycles,
|
|
213
240
|
effectiveVerdict,
|
|
214
241
|
warrantsIssueDraft,
|
|
215
242
|
assessConfidence
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
|
|
2
2
|
import {
|
|
3
3
|
effectiveVerdict
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2FZXZLZW.js";
|
|
5
5
|
import {
|
|
6
6
|
assessPeakPressure,
|
|
7
7
|
describePeakPressure
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
classifyTrend,
|
|
6
6
|
effectiveVerdict,
|
|
7
7
|
minGrowthFor,
|
|
8
|
+
resolveCycles,
|
|
8
9
|
warrantsIssueDraft
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2FZXZLZW.js";
|
|
10
11
|
import {
|
|
11
12
|
assessPeakPressure,
|
|
12
13
|
describePeakPressure
|
|
@@ -91992,6 +91993,11 @@ var FLAGS = [
|
|
|
91992
91993
|
help: "Fast preset: 2000 requests x 4 cycles, 8s idle \u2014 same cycle count as the default, less traffic per cycle"
|
|
91993
91994
|
},
|
|
91994
91995
|
{ flag: "--diff-all", value: "none", help: "Diff snapshots for stable routes too (slow)" },
|
|
91996
|
+
{
|
|
91997
|
+
flag: "--no-resolve",
|
|
91998
|
+
value: "none",
|
|
91999
|
+
help: "Do not re-measure inconclusive routes with more cycles (default: re-measure once)"
|
|
92000
|
+
},
|
|
91995
92001
|
{ flag: "--output", value: "string", argName: "<dir>", help: "Where to write runs (default <app-dir>/.next-leak)" },
|
|
91996
92002
|
{ flag: "--help", alias: "-h", value: "none", help: "Show this help" },
|
|
91997
92003
|
{ flag: "--version", alias: "-v", value: "none", help: "Print the version" }
|
|
@@ -92087,6 +92093,9 @@ function applyFlag(spec, value, options) {
|
|
|
92087
92093
|
case "--diff-all":
|
|
92088
92094
|
options.diffAll = true;
|
|
92089
92095
|
return FLAG_OK;
|
|
92096
|
+
case "--no-resolve":
|
|
92097
|
+
options.noResolve = true;
|
|
92098
|
+
return FLAG_OK;
|
|
92090
92099
|
case "--output":
|
|
92091
92100
|
options.output = value;
|
|
92092
92101
|
return FLAG_OK;
|
|
@@ -92137,6 +92146,7 @@ function parseCliArgs(argv) {
|
|
|
92137
92146
|
idleSeconds: null,
|
|
92138
92147
|
maxOldSpaceMb: null,
|
|
92139
92148
|
quick: false,
|
|
92149
|
+
noResolve: false,
|
|
92140
92150
|
diffAll: false,
|
|
92141
92151
|
output: null
|
|
92142
92152
|
};
|
|
@@ -92441,10 +92451,11 @@ function routeLines(route, parameters) {
|
|
|
92441
92451
|
}
|
|
92442
92452
|
const verdict = effectiveVerdict(route);
|
|
92443
92453
|
const curve = route.samples.map(formatMb).join(" \u2192 ");
|
|
92454
|
+
const resolved = route.resolvedWithCycles === void 0 ? "" : ` (resolved at ${route.resolvedWithCycles} cycles)`;
|
|
92444
92455
|
return [
|
|
92445
92456
|
` ${VERDICT_ICON[verdict]} ${route.route} ${verdict} (${formatGrowth(
|
|
92446
92457
|
route.growthPer1000Requests
|
|
92447
|
-
)}) heap ${curve}`,
|
|
92458
|
+
)}) heap ${curve}${resolved}`,
|
|
92448
92459
|
...confidenceLines(route),
|
|
92449
92460
|
...memorySourceLines(route, verdict),
|
|
92450
92461
|
...peakPressureLines(route, parameters),
|
|
@@ -92457,9 +92468,17 @@ function formatReport(report) {
|
|
|
92457
92468
|
lines.push(...routeLines(route, report.parameters));
|
|
92458
92469
|
}
|
|
92459
92470
|
const { minGrowthPerCycle, loadRequests, cycles, maxOldSpaceMb } = report.parameters;
|
|
92471
|
+
const resolvedCycles = [
|
|
92472
|
+
...new Set(
|
|
92473
|
+
report.routes.flatMap(
|
|
92474
|
+
(route) => route.status === "measured" && route.resolvedWithCycles !== void 0 ? [route.resolvedWithCycles] : []
|
|
92475
|
+
)
|
|
92476
|
+
)
|
|
92477
|
+
].sort((a, b) => a - b);
|
|
92478
|
+
const cyclesLabel = resolvedCycles.length === 0 ? `${cycles} cycles` : `${cycles} cycles (${resolvedCycles.join(", ")} where resolved)`;
|
|
92460
92479
|
lines.push(
|
|
92461
92480
|
"",
|
|
92462
|
-
`judged over ${
|
|
92481
|
+
`judged over ${cyclesLabel} \xD7 ${loadRequests} requests, growth gate ${(minGrowthPerCycle / 1024).toFixed(0)} KiB/cycle (${formatGrowth(minGrowthPerCycle / loadRequests * 1e3)}), heap cap ${maxOldSpaceMb} MB`,
|
|
92463
92482
|
`snapshots and run.json: ${report.workDir}`,
|
|
92464
92483
|
`report: ${report.bundle.htmlReport}`,
|
|
92465
92484
|
...report.bundle.issues.map((issue) => `issue draft (${issue.route}): ${issue.file}`)
|
|
@@ -92469,7 +92488,7 @@ function formatReport(report) {
|
|
|
92469
92488
|
);
|
|
92470
92489
|
if (inconclusive.length > 0) {
|
|
92471
92490
|
const routeList = inconclusive.map((route) => route.route).join(",");
|
|
92472
|
-
const moreCycles =
|
|
92491
|
+
const moreCycles = resolveCycles(report.parameters.cycles);
|
|
92473
92492
|
lines.push(
|
|
92474
92493
|
"",
|
|
92475
92494
|
"hint: inconclusive means sustained sub-threshold growth \u2014 measure longer to resolve it:",
|
|
@@ -93236,7 +93255,14 @@ var ControlError = class extends Error {
|
|
|
93236
93255
|
}
|
|
93237
93256
|
};
|
|
93238
93257
|
async function request(port, pathname) {
|
|
93239
|
-
|
|
93258
|
+
let response;
|
|
93259
|
+
try {
|
|
93260
|
+
response = await fetch(`http://127.0.0.1:${port}${pathname}`);
|
|
93261
|
+
} catch (cause) {
|
|
93262
|
+
throw new ControlError(
|
|
93263
|
+
`control channel ${pathname} on port ${port} did not answer (${cause instanceof Error ? cause.message : String(cause)}) \u2014 the measured process is gone or its event loop is blocked`
|
|
93264
|
+
);
|
|
93265
|
+
}
|
|
93240
93266
|
if (!response.ok) {
|
|
93241
93267
|
throw new ControlError(`control channel ${pathname} responded ${response.status}`);
|
|
93242
93268
|
}
|
|
@@ -93851,18 +93877,21 @@ function skipReason(route, requestPath) {
|
|
|
93851
93877
|
}
|
|
93852
93878
|
return null;
|
|
93853
93879
|
}
|
|
93854
|
-
async function measureRoute(context, route, requestPath, index) {
|
|
93880
|
+
async function measureRoute(context, route, requestPath, index, pass = void 0) {
|
|
93855
93881
|
const { deps, options, target, workDir, routeConfig, registry, nextVersion, progress } = context;
|
|
93856
93882
|
const result = await deps.ritual({
|
|
93857
93883
|
serverPath: target.standaloneServer,
|
|
93858
93884
|
route: requestPath,
|
|
93859
|
-
workDir: path6.join(
|
|
93885
|
+
workDir: path6.join(
|
|
93886
|
+
workDir,
|
|
93887
|
+
`${String(index + 1).padStart(2, "0")}-${routeSlug(route.path)}${pass?.dirSuffix ?? ""}`
|
|
93888
|
+
),
|
|
93860
93889
|
bootstrapPath: options.bootstrapPath,
|
|
93861
93890
|
appPort: await deps.freePort(),
|
|
93862
93891
|
...options.warmupRequests !== void 0 && { warmupRequests: options.warmupRequests },
|
|
93863
93892
|
...options.loadRequests !== void 0 && { loadRequests: options.loadRequests },
|
|
93864
93893
|
...options.connections !== void 0 && { connections: options.connections },
|
|
93865
|
-
...options.cycles !== void 0 && { cycles: options.cycles },
|
|
93894
|
+
...pass !== void 0 ? { cycles: pass.cycles } : options.cycles !== void 0 && { cycles: options.cycles },
|
|
93866
93895
|
...options.idleMs !== void 0 && { idleMs: options.idleMs },
|
|
93867
93896
|
...options.maxOldSpaceMb !== void 0 && { maxOldSpaceMb: options.maxOldSpaceMb },
|
|
93868
93897
|
...routeConfig.headers !== void 0 && { headers: routeConfig.headers },
|
|
@@ -93896,6 +93925,7 @@ async function measureRoute(context, route, requestPath, index) {
|
|
|
93896
93925
|
route: route.path,
|
|
93897
93926
|
status: "measured",
|
|
93898
93927
|
requestPath,
|
|
93928
|
+
...pass !== void 0 && { resolvedWithCycles: pass.cycles },
|
|
93899
93929
|
samples: result.samples,
|
|
93900
93930
|
memorySamples: result.memorySamples,
|
|
93901
93931
|
peaks: result.peaks,
|
|
@@ -93914,7 +93944,7 @@ async function measureRoute(context, route, requestPath, index) {
|
|
|
93914
93944
|
};
|
|
93915
93945
|
}
|
|
93916
93946
|
async function writeEvidenceBundle(report, workDir) {
|
|
93917
|
-
const { renderHtmlReport } = await import("./html-report-
|
|
93947
|
+
const { renderHtmlReport } = await import("./html-report-X2LUOZRH.js");
|
|
93918
93948
|
const { renderIssueMarkdown } = await import("./issue-report-HKA26WNV.js");
|
|
93919
93949
|
for (const route of report.routes) {
|
|
93920
93950
|
if (route.status === "measured" && warrantsIssueDraft(route)) {
|
|
@@ -93937,7 +93967,26 @@ async function routeReportFor(context, route, index, total) {
|
|
|
93937
93967
|
try {
|
|
93938
93968
|
const asPath = requestPath === route.path ? "" : ` as ${requestPath}`;
|
|
93939
93969
|
progress(`measuring ${label}${asPath}`);
|
|
93940
|
-
|
|
93970
|
+
const first = await measureRoute(context, route, requestPath, index);
|
|
93971
|
+
if (first.status !== "measured" || context.options.resolveInconclusive === false || effectiveVerdict(first) !== "inconclusive") {
|
|
93972
|
+
return first;
|
|
93973
|
+
}
|
|
93974
|
+
if (context.options.signal?.aborted === true) {
|
|
93975
|
+
return first;
|
|
93976
|
+
}
|
|
93977
|
+
const cycles = resolveCycles(context.options.cycles ?? RITUAL_DEFAULTS.cycles);
|
|
93978
|
+
progress(`re-measuring ${route.path} with ${cycles} cycles: the first pass could not call it`);
|
|
93979
|
+
try {
|
|
93980
|
+
return await measureRoute(context, route, requestPath, index, {
|
|
93981
|
+
cycles,
|
|
93982
|
+
dirSuffix: "-resolve"
|
|
93983
|
+
});
|
|
93984
|
+
} catch (cause) {
|
|
93985
|
+
progress(
|
|
93986
|
+
`re-measurement of ${route.path} failed (${cause instanceof Error ? cause.message : String(cause)}) \u2014 keeping the first pass`
|
|
93987
|
+
);
|
|
93988
|
+
return first;
|
|
93989
|
+
}
|
|
93941
93990
|
} catch (cause) {
|
|
93942
93991
|
const failure = cause instanceof Error ? cause.message : String(cause);
|
|
93943
93992
|
progress(`failed ${label}: ${failure}`);
|
|
@@ -93972,7 +94021,7 @@ async function planRun(options, target, deps, progress) {
|
|
|
93972
94021
|
).length;
|
|
93973
94022
|
const estimate = estimateRun(measurable, parameters);
|
|
93974
94023
|
progress(
|
|
93975
|
-
`${routes.length} routes discovered` + (measurable === routes.length ? "" : ` \xB7 ${measurable} measurable`) + ` \xB7 estimated ${formatEstimate(estimate)}` + // Long default runs are where first-time users give up; point at the two
|
|
94024
|
+
`${routes.length} routes discovered` + (measurable === routes.length ? "" : ` \xB7 ${measurable} measurable`) + ` \xB7 estimated ${formatEstimate(estimate)}` + (options.resolveInconclusive === false ? "" : " (inconclusive routes are measured again)") + // Long default runs are where first-time users give up; point at the two
|
|
93976
94025
|
// ways out. Suppressed once load parameters were tuned by hand (or by
|
|
93977
94026
|
// --quick, which arrives here as explicit loadRequests/idleMs).
|
|
93978
94027
|
(estimate.slowSeconds > 15 * 60 && options.loadRequests === void 0 && options.idleMs === void 0 ? " \u2014 use --quick for the fast validated preset, or narrow with --routes" : "")
|
package/dist/cli-args.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
killActiveChildren,
|
|
10
10
|
parseCliArgs,
|
|
11
11
|
runMeasurement
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-YDMJVVWG.js";
|
|
13
|
+
import "./chunk-2FZXZLZW.js";
|
|
14
14
|
import "./chunk-XHPUAMJG.js";
|
|
15
15
|
import "./chunk-6XYFBOL2.js";
|
|
16
16
|
|
|
@@ -100,6 +100,7 @@ async function main() {
|
|
|
100
100
|
...options.idleSeconds !== null && { idleMs: options.idleSeconds * 1e3 },
|
|
101
101
|
...options.maxOldSpaceMb !== null && { maxOldSpaceMb: options.maxOldSpaceMb },
|
|
102
102
|
...options.diffAll && { diffAll: true },
|
|
103
|
+
...options.noResolve && { resolveInconclusive: false },
|
|
103
104
|
...options.output !== null && { outputDir: options.output },
|
|
104
105
|
onProgress: (message) => console.error(`\xB7 ${message}`)
|
|
105
106
|
});
|
package/dist/confidence.d.ts
CHANGED
|
@@ -12,10 +12,11 @@ import { type TrendResult, type TrendVerdict } from "./trend.js";
|
|
|
12
12
|
* mid-stream teardown path was never reached
|
|
13
13
|
* `spiky-growth` — one cycle dominates, so the mean describes little
|
|
14
14
|
* `near-threshold` — growth barely clears the noise floor
|
|
15
|
+
* `thin-evidence` — a leak called on too few cycles for its size
|
|
15
16
|
* `near-heap-ceiling` — the heap approached the cap the process ran under,
|
|
16
17
|
* so the curve was measured against a ceiling instead of running free
|
|
17
18
|
*/
|
|
18
|
-
export type WarningCode = "unsettled" | "settle-unverified" | "load-incomplete" | "abandon-ineffective" | "abandon-before-response" | "spiky-growth" | "near-threshold" | "near-heap-ceiling";
|
|
19
|
+
export type WarningCode = "unsettled" | "settle-unverified" | "load-incomplete" | "abandon-ineffective" | "abandon-before-response" | "spiky-growth" | "near-threshold" | "thin-evidence" | "near-heap-ceiling";
|
|
19
20
|
export type MeasurementWarning = {
|
|
20
21
|
code: WarningCode;
|
|
21
22
|
detail: string;
|
|
@@ -44,6 +45,12 @@ export type ConfidenceInput = {
|
|
|
44
45
|
/** Old-space cap the measured process ran under (MB). */
|
|
45
46
|
maxOldSpaceMb?: number;
|
|
46
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Cycles a re-measurement uses when a verdict came back `inconclusive` — the
|
|
50
|
+
* same figure the report's manual re-run hint prints. One definition, imported
|
|
51
|
+
* by both, so the tool can never recommend one number and use another.
|
|
52
|
+
*/
|
|
53
|
+
export declare const resolveCycles: (cycles: number) => number;
|
|
47
54
|
/**
|
|
48
55
|
* The verdict a route's evidence actually supports.
|
|
49
56
|
*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire as __nextLeakCreateRequire } from 'node:module';import { fileURLToPath as __nextLeakFileURLToPath } from 'node:url';import { dirname as __nextLeakDirname } from 'node:path';const require = __nextLeakCreateRequire(import.meta.url);const __filename = __nextLeakFileURLToPath(import.meta.url);const __dirname = __nextLeakDirname(__filename);
|
|
2
2
|
import {
|
|
3
3
|
renderHtmlReport
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-NNDGXIGQ.js";
|
|
5
|
+
import "./chunk-2FZXZLZW.js";
|
|
6
6
|
import "./chunk-XHPUAMJG.js";
|
|
7
7
|
import "./chunk-6XYFBOL2.js";
|
|
8
8
|
export {
|
package/dist/index.js
CHANGED
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
sourceIndexAt,
|
|
40
40
|
summarizeBaseline,
|
|
41
41
|
validateTarget
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-YDMJVVWG.js";
|
|
43
43
|
import {
|
|
44
44
|
renderHtmlReport
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-NNDGXIGQ.js";
|
|
46
46
|
import {
|
|
47
47
|
classifyTrend
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-2FZXZLZW.js";
|
|
49
49
|
import {
|
|
50
50
|
renderIssueMarkdown
|
|
51
51
|
} from "./chunk-WXAFXVWS.js";
|
package/dist/runner.d.ts
CHANGED
|
@@ -56,6 +56,11 @@ export type RouteReport = {
|
|
|
56
56
|
/** Null when there is no diff or no module registry. */
|
|
57
57
|
attribution: AttributedDiff | null;
|
|
58
58
|
signatures: MatchedSignature[];
|
|
59
|
+
/**
|
|
60
|
+
* Cycles used by the second pass, when the first came back
|
|
61
|
+
* `inconclusive` and the run went back for more evidence.
|
|
62
|
+
*/
|
|
63
|
+
resolvedWithCycles?: number;
|
|
59
64
|
};
|
|
60
65
|
export type MeasuredRoute = Extract<RouteReport, {
|
|
61
66
|
status: "measured";
|
|
@@ -111,6 +116,11 @@ export type RunOptions = {
|
|
|
111
116
|
diffAll?: boolean;
|
|
112
117
|
/** Only measure routes matching these templates or prefixes. */
|
|
113
118
|
routeFilter?: string[];
|
|
119
|
+
/**
|
|
120
|
+
* Measure a route again, with more cycles, when the first pass could not
|
|
121
|
+
* call it. Default true: the run should answer the question it was asked.
|
|
122
|
+
*/
|
|
123
|
+
resolveInconclusive?: boolean;
|
|
114
124
|
/** Abort between phases; remaining routes are reported as interrupted. */
|
|
115
125
|
signal?: AbortSignal;
|
|
116
126
|
onProgress?: (message: string) => void;
|