ccqa 1.44.0 → 1.45.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/bin/ccqa.mjs +92 -42
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -12337,7 +12337,7 @@ var StreamResolution = class {
|
|
|
12337
12337
|
asOf = 0;
|
|
12338
12338
|
lastSeq = 0;
|
|
12339
12339
|
pushesDuringRun = 0;
|
|
12340
|
-
/** `markers` needs to hold every marker
|
|
12340
|
+
/** `markers` needs to hold every marker `runId` issued; other runs' markers and pushes are ignored. */
|
|
12341
12341
|
constructor(markers, runId) {
|
|
12342
12342
|
this.runId = runId;
|
|
12343
12343
|
const specIds = /* @__PURE__ */ new Set();
|
|
@@ -12460,16 +12460,22 @@ var StreamResolution = class {
|
|
|
12460
12460
|
/**
|
|
12461
12461
|
* Every run that opened a spec in this stream, most recently heard-from
|
|
12462
12462
|
* first — recency by the arrival position of each run's latest spec-open,
|
|
12463
|
-
* the one order the hub's stamps establish.
|
|
12463
|
+
* the one order the hub's stamps establish. Fed one event at a time, so the
|
|
12464
|
+
* question can be asked of a stream too large to hold.
|
|
12464
12465
|
*/
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12466
|
+
var RunIdIndex = class {
|
|
12467
|
+
/** Re-inserted on every `spec-open`, so insertion order is order of latest open. */
|
|
12468
|
+
opened = /* @__PURE__ */ new Set();
|
|
12469
|
+
accept(event) {
|
|
12468
12470
|
const body = event.body;
|
|
12469
|
-
if ("kind" in body
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
}
|
|
12471
|
+
if (!("kind" in body) || body.kind !== "spec-open") return;
|
|
12472
|
+
this.opened.delete(body.runId);
|
|
12473
|
+
this.opened.add(body.runId);
|
|
12474
|
+
}
|
|
12475
|
+
newestFirst() {
|
|
12476
|
+
return [...this.opened].reverse();
|
|
12477
|
+
}
|
|
12478
|
+
};
|
|
12473
12479
|
//#endregion
|
|
12474
12480
|
//#region src/cli/session.ts
|
|
12475
12481
|
const AB = resolveAgentBrowserBin$1();
|
|
@@ -17128,7 +17134,8 @@ async function executeRun(targets, opts) {
|
|
|
17128
17134
|
const liveSpecs = withMode.filter((s) => s.mode === "live");
|
|
17129
17135
|
meta("modes", `${detSpecs.length} deterministic / ${liveSpecs.length} live`);
|
|
17130
17136
|
if (dispatch.external.length > 0) meta("external", dispatch.external.map((g) => `${g.targetId} ${g.specs.length}`).join(" / "));
|
|
17131
|
-
const
|
|
17137
|
+
const storedMaps = hubCtx !== null && deployedSha !== null ? createStoredSourceMaps(hubCtx, deployedSha) : void 0;
|
|
17138
|
+
const coverage = opts.coverage && forExecution ? await startCoverage(cwd, projectConfig.coverage, actors, dispatch, opts.teardown, coverageInbox, storedMaps?.read, hubCtx !== null) : void 0;
|
|
17132
17139
|
if (liveSpecs.length === 0) {
|
|
17133
17140
|
const why = "it only applies to agent-browser 'mode: live' specs, and this run has none";
|
|
17134
17141
|
if (typeof opts.liveStepRetry === "number" && opts.liveStepRetry > 0) warn(`--live-step-retry is ignored: ${why}`);
|
|
@@ -17241,8 +17248,11 @@ async function executeRun(targets, opts) {
|
|
|
17241
17248
|
};
|
|
17242
17249
|
const live = await runLiveSpecs(liveSpecs, liveOpts);
|
|
17243
17250
|
let streamedEdges = {};
|
|
17244
|
-
if (coverage && !coverage.streamsToHub) reportCoverageHealth(coverage, [...externalRows, ...live.reportResults]);
|
|
17245
|
-
else if (coverage && hubCtx != null)
|
|
17251
|
+
if (coverage && !coverage.streamsToHub) reportCoverageHealth(coverage, [...externalRows, ...live.reportResults], storedMaps);
|
|
17252
|
+
else if (coverage && hubCtx != null) {
|
|
17253
|
+
streamedEdges = await reportStreamedCoverageHealth(coverage, hubCtx);
|
|
17254
|
+
storedMaps?.warnIfNothingAnswered();
|
|
17255
|
+
}
|
|
17246
17256
|
let overallExitCode = det.exitCode !== 0 ? 1 : 0;
|
|
17247
17257
|
if (live.failedCount > 0) overallExitCode = 1;
|
|
17248
17258
|
if (externalRows.some((r) => r.status === "failed")) overallExitCode = 1;
|
|
@@ -17336,6 +17346,12 @@ async function executeRun(targets, opts) {
|
|
|
17336
17346
|
reportDir
|
|
17337
17347
|
};
|
|
17338
17348
|
}
|
|
17349
|
+
/**
|
|
17350
|
+
* Starts the run's coverage measurement before any spec runs: the sink has to
|
|
17351
|
+
* be listening before the first request reaches the application, and the set
|
|
17352
|
+
* of spec ids it will accept is only known once dispatch has resolved. With
|
|
17353
|
+
* an `inbox`, nothing binds — the run streams its events to the hub instead.
|
|
17354
|
+
*/
|
|
17339
17355
|
async function startCoverage(cwd, config, actors, dispatch, teardown, inbox, fetchStoredSourceMap, hubConfigured) {
|
|
17340
17356
|
if (config === void 0) throw new RunUsageError("--coverage needs a `coverage:` block in .ccqa/config.yaml whose `instrumentedOrigins` names the origins the spec cookie may be attached to");
|
|
17341
17357
|
let session;
|
|
@@ -17361,25 +17377,25 @@ async function startCoverage(cwd, config, actors, dispatch, teardown, inbox, fet
|
|
|
17361
17377
|
return session;
|
|
17362
17378
|
}
|
|
17363
17379
|
/**
|
|
17364
|
-
*
|
|
17365
|
-
*
|
|
17366
|
-
*
|
|
17367
|
-
* an `inbox`, nothing binds — the run streams its events to the hub instead.
|
|
17380
|
+
* The maps a deploy pushed for one commit. One answer per asset for the run:
|
|
17381
|
+
* the commit is fixed, so a miss stays a miss, and every spec would otherwise
|
|
17382
|
+
* re-ask for the same scripts.
|
|
17368
17383
|
*/
|
|
17369
|
-
|
|
17370
|
-
* Needs both a hub and the commit under test: without either there is nothing
|
|
17371
|
-
* to address a stored map by, and reading some other commit's maps would name
|
|
17372
|
-
* the wrong files. See `SourceMapStore`.
|
|
17373
|
-
*/
|
|
17374
|
-
function storedSourceMapReader(hubCtx, deployedSha) {
|
|
17375
|
-
if (hubCtx === null || deployedSha === null) return void 0;
|
|
17384
|
+
function createStoredSourceMaps(hubCtx, deployedSha) {
|
|
17376
17385
|
const seen = /* @__PURE__ */ new Map();
|
|
17377
|
-
return
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17386
|
+
return {
|
|
17387
|
+
read: async (assetPath) => {
|
|
17388
|
+
const cached = seen.get(assetPath);
|
|
17389
|
+
if (cached !== void 0 || seen.has(assetPath)) return cached;
|
|
17390
|
+
const map = await hubCtx.hub.getSourceMap(hubCtx.project, deployedSha, assetPath) ?? void 0;
|
|
17391
|
+
seen.set(assetPath, map);
|
|
17392
|
+
return map;
|
|
17393
|
+
},
|
|
17394
|
+
warnIfNothingAnswered() {
|
|
17395
|
+
const answered = [...seen.values()].some((map) => map !== void 0);
|
|
17396
|
+
if (seen.size === 0 || answered) return;
|
|
17397
|
+
warn(`coverage: no stored source map answered for any of the ${seen.size} script(s) this run asked about, under commit ${deployedSha.slice(0, 12)}. Either that deploy pushed none, or the hub's deploy log names a commit the environment is no longer serving`);
|
|
17398
|
+
}
|
|
17383
17399
|
};
|
|
17384
17400
|
}
|
|
17385
17401
|
/**
|
|
@@ -17460,8 +17476,17 @@ async function upsertMeasuredEdges(hubCtx, specs) {
|
|
|
17460
17476
|
warn(`coverage: could not record measured edges on the hub (${errMessage(error)})`);
|
|
17461
17477
|
}
|
|
17462
17478
|
}
|
|
17479
|
+
/**
|
|
17480
|
+
* Whether the browser half came back with nothing anywhere. `every` rather
|
|
17481
|
+
* than `some`: a run that resolved some chunks from the copies the server
|
|
17482
|
+
* served has no gap the stored maps were needed for, and warning there would
|
|
17483
|
+
* fire on every build that strips maps from a few chunks and not the rest.
|
|
17484
|
+
*/
|
|
17485
|
+
function noFrontendResolved(rows) {
|
|
17486
|
+
return rows.every((row) => (row.coverage?.frontendFiles ?? 0) === 0);
|
|
17487
|
+
}
|
|
17463
17488
|
/** Everything the measurement could not place; silence here reads as "never reached". */
|
|
17464
|
-
function reportCoverageHealth(coverage, rows) {
|
|
17489
|
+
function reportCoverageHealth(coverage, rows, storedMaps) {
|
|
17465
17490
|
if (!coverage.heardFromApplication()) warn("no instrumented application process reported — only the browser half was measured. The server needs ccqa-tools and CCQA_COVERAGE_ENDPOINT pointed at this run's sink");
|
|
17466
17491
|
if (rows.filter((row) => row.coverage !== void 0).length > 0 && coverage.heardFromApplication() && coverage.attributedSpecs() === 0) warn("instrumented application processes reported, but no spec was attributed to them — the spec cookie is not reaching the application, so every spec's server-side reach is zero for that reason rather than because the server ran nothing. Check coverage.instrumentedOrigins covers every origin the spec's requests go to, and that a server which is not node:http-based installs the middleware itself");
|
|
17467
17492
|
const boot = coverage.boot();
|
|
@@ -17475,6 +17500,7 @@ function reportCoverageHealth(coverage, rows) {
|
|
|
17475
17500
|
if (rejected > 0) warn(`${rejected} coverage push(es) named a spec id this run never issued — dropped`);
|
|
17476
17501
|
const malformed = coverage.malformedPushes();
|
|
17477
17502
|
if (malformed > 0) warn(`${malformed} coverage push(es) could not be read — the application's ccqa-tools and this CLI disagree on the wire format`);
|
|
17503
|
+
if (noFrontendResolved(rows)) storedMaps?.warnIfNothingAnswered();
|
|
17478
17504
|
}
|
|
17479
17505
|
function buildLiveRunSummary(results) {
|
|
17480
17506
|
const sections = [];
|
|
@@ -23587,6 +23613,36 @@ function createResolveMemo(limit) {
|
|
|
23587
23613
|
};
|
|
23588
23614
|
}
|
|
23589
23615
|
/**
|
|
23616
|
+
* `runId`'s answer, in two more scans: one keeping its markers, one replaying
|
|
23617
|
+
* the stream through the resolver they seed. Both stop at `head`, where the
|
|
23618
|
+
* scan that chose the run stopped — the resolver's markers are frozen there,
|
|
23619
|
+
* so a later push would credit a spec it was never told had opened and count
|
|
23620
|
+
* as rejected: a fault in the read, reading as a fault in the run.
|
|
23621
|
+
*/
|
|
23622
|
+
/** A marker `runId` issued — what `StreamResolution` needs before it is fed the stream. */
|
|
23623
|
+
function isMarkerOf(event, runId) {
|
|
23624
|
+
const body = event.body;
|
|
23625
|
+
return "kind" in body && body.runId === runId;
|
|
23626
|
+
}
|
|
23627
|
+
async function resolveRun(config, key, project, runId, head, collected) {
|
|
23628
|
+
async function scanToHead(visit) {
|
|
23629
|
+
await scanStream(config, key, project, 0, (event) => {
|
|
23630
|
+
if (event.seq <= head) visit(event);
|
|
23631
|
+
});
|
|
23632
|
+
}
|
|
23633
|
+
let markers = collected;
|
|
23634
|
+
if (markers === null) {
|
|
23635
|
+
const found = [];
|
|
23636
|
+
await scanToHead((event) => {
|
|
23637
|
+
if (isMarkerOf(event, runId)) found.push(event);
|
|
23638
|
+
});
|
|
23639
|
+
markers = found;
|
|
23640
|
+
}
|
|
23641
|
+
const resolution = new StreamResolution(markers, runId);
|
|
23642
|
+
await scanToHead((event) => resolution.accept(event));
|
|
23643
|
+
return resolution.finish();
|
|
23644
|
+
}
|
|
23645
|
+
/**
|
|
23590
23646
|
* GET /api/v1/coverage?project=[&runId=] — the stream, interpreted for one
|
|
23591
23647
|
* run by the shared resolver (resolve-stream.ts); this handler only reads,
|
|
23592
23648
|
* memoizes and serves. `runId` omitted means the run the stream most
|
|
@@ -23607,24 +23663,18 @@ function createResolveCoverageHandler(config) {
|
|
|
23607
23663
|
sendJson(ctx.res, 200, hit);
|
|
23608
23664
|
return;
|
|
23609
23665
|
}
|
|
23610
|
-
const
|
|
23666
|
+
const runIndex = new RunIdIndex();
|
|
23667
|
+
const named = runKey !== "" ? [] : null;
|
|
23611
23668
|
let seen = 0;
|
|
23612
23669
|
const first = await scanStream(config, key, project, 0, (event) => {
|
|
23613
23670
|
seen += 1;
|
|
23614
|
-
|
|
23671
|
+
runIndex.accept(event);
|
|
23672
|
+
if (named !== null && isMarkerOf(event, runKey)) named.push(event);
|
|
23615
23673
|
});
|
|
23616
|
-
const runIds =
|
|
23674
|
+
const runIds = runIndex.newestFirst().slice(0, RUN_IDS_LIMIT);
|
|
23617
23675
|
const runId = runKey !== "" ? runKey : runIds[0];
|
|
23618
|
-
let resolved = null;
|
|
23619
|
-
if (runId !== void 0 && seen > 0) {
|
|
23620
|
-
const resolution = new StreamResolution(markers, runId);
|
|
23621
|
-
await scanStream(config, key, project, 0, (event) => {
|
|
23622
|
-
if (event.seq <= first.lastSeq) resolution.accept(event);
|
|
23623
|
-
});
|
|
23624
|
-
resolved = resolution.finish();
|
|
23625
|
-
}
|
|
23626
23676
|
const answer = {
|
|
23627
|
-
resolved,
|
|
23677
|
+
resolved: runId !== void 0 && seen > 0 ? await resolveRun(config, key, project, runId, first.lastSeq, named) : null,
|
|
23628
23678
|
runIds
|
|
23629
23679
|
};
|
|
23630
23680
|
memo.put(project, runKey, first.lastSeq, answer);
|
package/dist/package.json
CHANGED