@riddledc/riddle-proof 0.7.145 → 0.7.146
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/cli.cjs +204 -6
- package/dist/cli.js +204 -6
- package/dist/profile.d.cts +13 -0
- package/dist/profile.d.ts +13 -0
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -6938,6 +6938,7 @@ function createRiddleApiClient(config = {}) {
|
|
|
6938
6938
|
|
|
6939
6939
|
// src/profile.ts
|
|
6940
6940
|
var RIDDLE_PROOF_PROFILE_VERSION = "riddle-proof.profile.v1";
|
|
6941
|
+
var RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION = "riddle-proof.profile-evidence.v1";
|
|
6941
6942
|
var RIDDLE_PROOF_PROFILE_RESULT_VERSION = "riddle-proof.profile-result.v1";
|
|
6942
6943
|
var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
6943
6944
|
"passed",
|
|
@@ -14380,7 +14381,7 @@ function usage() {
|
|
|
14380
14381
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
14381
14382
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
14382
14383
|
" riddle-proof-loop status --state-path <path>",
|
|
14383
|
-
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
|
|
14384
|
+
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
|
|
14384
14385
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
14385
14386
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
14386
14387
|
" riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
|
|
@@ -14437,6 +14438,9 @@ function optionBoolean(options, key) {
|
|
|
14437
14438
|
function runProfileStrictOption(options) {
|
|
14438
14439
|
return optionBoolean(options, "strict") ?? false;
|
|
14439
14440
|
}
|
|
14441
|
+
function runProfileSplitViewportsOption(options) {
|
|
14442
|
+
return optionBoolean(options, "splitViewports") ?? false;
|
|
14443
|
+
}
|
|
14440
14444
|
function optionNumber(options, ...keys) {
|
|
14441
14445
|
for (const key of keys) {
|
|
14442
14446
|
const value = optionString(options, key);
|
|
@@ -14749,6 +14753,8 @@ function profileRiddleJobMarkdown(result) {
|
|
|
14749
14753
|
const riddle = cliRecord(result.riddle);
|
|
14750
14754
|
if (!riddle) return [];
|
|
14751
14755
|
const jobId = cliString(riddle.job_id);
|
|
14756
|
+
const mode = cliString(riddle.mode);
|
|
14757
|
+
const jobCount = cliFiniteNumber(riddle.job_count);
|
|
14752
14758
|
const status = cliString(riddle.status);
|
|
14753
14759
|
const terminal = typeof riddle.terminal === "boolean" ? riddle.terminal : void 0;
|
|
14754
14760
|
const queueElapsedMs = cliFiniteNumber(riddle.queue_elapsed_ms);
|
|
@@ -14758,6 +14764,8 @@ function profileRiddleJobMarkdown(result) {
|
|
|
14758
14764
|
const submittedAt = cliString(riddle.submitted_at);
|
|
14759
14765
|
const completedAt = cliString(riddle.completed_at);
|
|
14760
14766
|
const parts = [
|
|
14767
|
+
mode ? `mode ${markdownInlineCode(mode)}` : "",
|
|
14768
|
+
jobCount === void 0 ? "" : `jobs ${jobCount}`,
|
|
14761
14769
|
jobId ? `job ${markdownInlineCode(jobId)}` : "",
|
|
14762
14770
|
status ? `status ${markdownInlineCode(status)}` : "",
|
|
14763
14771
|
terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
|
|
@@ -14771,6 +14779,21 @@ function profileRiddleJobMarkdown(result) {
|
|
|
14771
14779
|
if (submittedAt || completedAt) {
|
|
14772
14780
|
lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
|
|
14773
14781
|
}
|
|
14782
|
+
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
14783
|
+
for (const job of splitJobs.slice(0, 12)) {
|
|
14784
|
+
const viewport = cliString(job.viewport) || "viewport";
|
|
14785
|
+
const splitJobId = cliString(job.job_id);
|
|
14786
|
+
const splitStatus = cliString(job.status);
|
|
14787
|
+
const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
|
|
14788
|
+
lines.push(
|
|
14789
|
+
`- ${viewport}: ${[
|
|
14790
|
+
splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
|
|
14791
|
+
splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
|
|
14792
|
+
splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`
|
|
14793
|
+
].filter(Boolean).join(", ") || "job metadata unavailable"}`
|
|
14794
|
+
);
|
|
14795
|
+
}
|
|
14796
|
+
if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
|
|
14774
14797
|
return lines;
|
|
14775
14798
|
}
|
|
14776
14799
|
function markdownInlineCode(value, maxLength = 80) {
|
|
@@ -15396,13 +15419,146 @@ function riddleMetadataFromPoll(jobId, poll) {
|
|
|
15396
15419
|
timed_out: poll.poll?.timed_out
|
|
15397
15420
|
};
|
|
15398
15421
|
}
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15422
|
+
function profileForSplitViewport(profile, viewport) {
|
|
15423
|
+
return {
|
|
15424
|
+
...profile,
|
|
15425
|
+
name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
|
|
15426
|
+
target: {
|
|
15427
|
+
...profile.target,
|
|
15428
|
+
viewports: [viewport]
|
|
15429
|
+
},
|
|
15430
|
+
metadata: {
|
|
15431
|
+
...profile.metadata || {},
|
|
15432
|
+
split_parent_profile: profile.name,
|
|
15433
|
+
split_viewport: viewport.name
|
|
15434
|
+
}
|
|
15435
|
+
};
|
|
15436
|
+
}
|
|
15437
|
+
function safeProfileOutputSegment(value) {
|
|
15438
|
+
const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15439
|
+
return safe || "viewport";
|
|
15440
|
+
}
|
|
15441
|
+
function splitViewportOutputDir(outputDir, viewportName, seen) {
|
|
15442
|
+
const base = safeProfileOutputSegment(viewportName);
|
|
15443
|
+
const count = seen.get(base) || 0;
|
|
15444
|
+
seen.set(base, count + 1);
|
|
15445
|
+
return import_node_path6.default.join(outputDir, count ? `${base}-${count + 1}` : base);
|
|
15446
|
+
}
|
|
15447
|
+
function splitViewportArtifactRefs(input) {
|
|
15448
|
+
return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
|
|
15449
|
+
...artifact,
|
|
15450
|
+
name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
|
|
15451
|
+
}));
|
|
15452
|
+
}
|
|
15453
|
+
function sumDefinedNumbers(values) {
|
|
15454
|
+
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
15455
|
+
return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
|
|
15456
|
+
}
|
|
15457
|
+
function splitViewportRiddleMetadata(childRuns) {
|
|
15458
|
+
const splitJobs = childRuns.map(({ viewport, result }) => ({
|
|
15459
|
+
viewport: viewport.name,
|
|
15460
|
+
job_id: result.riddle?.job_id,
|
|
15461
|
+
status: result.riddle?.status,
|
|
15462
|
+
terminal: result.riddle?.terminal,
|
|
15463
|
+
queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
|
|
15464
|
+
elapsed_ms: result.riddle?.elapsed_ms,
|
|
15465
|
+
attempt: result.riddle?.attempt,
|
|
15466
|
+
attempts: result.riddle?.attempts,
|
|
15467
|
+
timed_out: result.riddle?.timed_out
|
|
15468
|
+
}));
|
|
15469
|
+
return {
|
|
15470
|
+
mode: "split-viewports",
|
|
15471
|
+
job_count: childRuns.length,
|
|
15472
|
+
status: "split-viewports",
|
|
15473
|
+
terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
|
|
15474
|
+
queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
|
|
15475
|
+
elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
|
|
15476
|
+
split_jobs: splitJobs
|
|
15477
|
+
};
|
|
15478
|
+
}
|
|
15479
|
+
function latestCapturedAt(evidences) {
|
|
15480
|
+
let latest = "";
|
|
15481
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
15482
|
+
for (const evidence of evidences) {
|
|
15483
|
+
const parsed = Date.parse(evidence.captured_at);
|
|
15484
|
+
if (Number.isFinite(parsed) && parsed >= latestMs) {
|
|
15485
|
+
latest = evidence.captured_at;
|
|
15486
|
+
latestMs = parsed;
|
|
15487
|
+
}
|
|
15403
15488
|
}
|
|
15489
|
+
return latest || (/* @__PURE__ */ new Date()).toISOString();
|
|
15490
|
+
}
|
|
15491
|
+
function splitViewportDomSummary(profile, childRuns, evidence) {
|
|
15492
|
+
return {
|
|
15493
|
+
split_viewports: true,
|
|
15494
|
+
expected_viewport_count: profile.target.viewports.length,
|
|
15495
|
+
viewport_count: evidence.viewports.length,
|
|
15496
|
+
child_result_count: childRuns.length,
|
|
15497
|
+
child_statuses: childRuns.map(({ viewport, result }) => ({
|
|
15498
|
+
viewport: viewport.name,
|
|
15499
|
+
profile_name: result.profile_name,
|
|
15500
|
+
status: result.status,
|
|
15501
|
+
job_id: result.riddle?.job_id || null
|
|
15502
|
+
})),
|
|
15503
|
+
routes: evidence.viewports.map((viewport) => ({
|
|
15504
|
+
viewport: viewport.name,
|
|
15505
|
+
requested: viewport.route.requested,
|
|
15506
|
+
observed: viewport.route.observed,
|
|
15507
|
+
matched: viewport.route.matched,
|
|
15508
|
+
http_status: viewport.route.http_status ?? null
|
|
15509
|
+
})),
|
|
15510
|
+
titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
|
|
15511
|
+
overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
|
|
15512
|
+
bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
|
|
15513
|
+
overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
|
|
15514
|
+
console_event_count: evidence.console.events.length,
|
|
15515
|
+
console_fatal_count: evidence.console.fatal_count,
|
|
15516
|
+
page_error_count: evidence.page_errors.length,
|
|
15517
|
+
network_mock_count: (profile.target.network_mocks || []).length,
|
|
15518
|
+
network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
|
|
15519
|
+
};
|
|
15520
|
+
}
|
|
15521
|
+
function aggregateSplitViewportEvidence(profile, childRuns) {
|
|
15522
|
+
const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
|
|
15523
|
+
const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
|
|
15524
|
+
const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
|
|
15525
|
+
const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
|
|
15526
|
+
const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
|
|
15527
|
+
const evidence = {
|
|
15528
|
+
version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
15529
|
+
profile_name: profile.name,
|
|
15530
|
+
target_url: resolveRiddleProofProfileTargetUrl(profile),
|
|
15531
|
+
baseline_policy: profile.baseline_policy,
|
|
15532
|
+
captured_at: latestCapturedAt(evidences),
|
|
15533
|
+
viewports,
|
|
15534
|
+
console: {
|
|
15535
|
+
events: consoleEvents,
|
|
15536
|
+
fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
|
|
15537
|
+
},
|
|
15538
|
+
page_errors: pageErrors,
|
|
15539
|
+
network_mocks: networkMocks.length ? networkMocks : void 0
|
|
15540
|
+
};
|
|
15541
|
+
evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
|
|
15542
|
+
return evidence;
|
|
15543
|
+
}
|
|
15544
|
+
function splitViewportBlockedMessage(childRuns) {
|
|
15545
|
+
const blocked = childRuns.filter(({ result }) => !result.evidence || result.status === "environment_blocked" || result.status === "configuration_error").map(({ viewport, result }) => `${viewport.name}: ${result.status}${result.error ? ` (${result.error})` : ""}`);
|
|
15546
|
+
return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
|
|
15547
|
+
}
|
|
15548
|
+
function withSplitViewportWarnings(profile, result) {
|
|
15549
|
+
const warnings = [];
|
|
15550
|
+
if (profile.target.network_mocks?.length) {
|
|
15551
|
+
warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
|
|
15552
|
+
}
|
|
15553
|
+
if (!warnings.length) return result;
|
|
15554
|
+
return {
|
|
15555
|
+
...result,
|
|
15556
|
+
warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
|
|
15557
|
+
};
|
|
15558
|
+
}
|
|
15559
|
+
async function runSingleRiddleProfileForCli(profile, options, input) {
|
|
15560
|
+
const { client, runner } = input;
|
|
15404
15561
|
const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
|
|
15405
|
-
const client = createRiddleApiClient(riddleClientConfig(options));
|
|
15406
15562
|
let created;
|
|
15407
15563
|
try {
|
|
15408
15564
|
created = await client.runScript({
|
|
@@ -15461,6 +15617,48 @@ async function runProfileForCli(profile, options) {
|
|
|
15461
15617
|
artifacts
|
|
15462
15618
|
});
|
|
15463
15619
|
}
|
|
15620
|
+
async function runSplitViewportProfileForCli(profile, options, input) {
|
|
15621
|
+
const outputDir = profileOutputDirOption(options);
|
|
15622
|
+
const seenOutputNames = /* @__PURE__ */ new Map();
|
|
15623
|
+
const childRuns = [];
|
|
15624
|
+
for (const viewport of profile.target.viewports) {
|
|
15625
|
+
const childProfile = profileForSplitViewport(profile, viewport);
|
|
15626
|
+
const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
|
|
15627
|
+
if (outputDir) {
|
|
15628
|
+
writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
|
|
15629
|
+
}
|
|
15630
|
+
childRuns.push({ viewport, profile: childProfile, result: result2 });
|
|
15631
|
+
}
|
|
15632
|
+
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
15633
|
+
const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
|
|
15634
|
+
if (blocked.length) {
|
|
15635
|
+
return createRiddleProofProfileEnvironmentBlockedResult({
|
|
15636
|
+
profile,
|
|
15637
|
+
runner: input.runner,
|
|
15638
|
+
error: splitViewportBlockedMessage(childRuns),
|
|
15639
|
+
riddle: splitViewportRiddleMetadata(childRuns),
|
|
15640
|
+
artifacts
|
|
15641
|
+
});
|
|
15642
|
+
}
|
|
15643
|
+
const evidence = aggregateSplitViewportEvidence(profile, childRuns);
|
|
15644
|
+
const result = assessRiddleProofProfileEvidence(profile, evidence, {
|
|
15645
|
+
runner: input.runner,
|
|
15646
|
+
riddle: splitViewportRiddleMetadata(childRuns),
|
|
15647
|
+
artifacts
|
|
15648
|
+
});
|
|
15649
|
+
return withSplitViewportWarnings(profile, result);
|
|
15650
|
+
}
|
|
15651
|
+
async function runProfileForCli(profile, options) {
|
|
15652
|
+
const runner = optionString(options, "runner") || "riddle";
|
|
15653
|
+
if (runner !== "riddle") {
|
|
15654
|
+
throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
|
|
15655
|
+
}
|
|
15656
|
+
const client = createRiddleApiClient(riddleClientConfig(options));
|
|
15657
|
+
if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
|
|
15658
|
+
return runSplitViewportProfileForCli(profile, options, { client, runner });
|
|
15659
|
+
}
|
|
15660
|
+
return runSingleRiddleProfileForCli(profile, options, { client, runner });
|
|
15661
|
+
}
|
|
15464
15662
|
function requestForRun(options) {
|
|
15465
15663
|
const statePath = optionString(options, "statePath");
|
|
15466
15664
|
const withEngineModuleUrl = (request) => {
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
3
4
|
assessRiddleProofProfileEvidence,
|
|
4
5
|
buildRiddleProofProfileScript,
|
|
5
6
|
collectRiddleProfileArtifactRefs,
|
|
@@ -46,7 +47,7 @@ function usage() {
|
|
|
46
47
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
47
48
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
48
49
|
" riddle-proof-loop status --state-path <path>",
|
|
49
|
-
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
|
|
50
|
+
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
|
|
50
51
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
51
52
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
52
53
|
" riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
|
|
@@ -103,6 +104,9 @@ function optionBoolean(options, key) {
|
|
|
103
104
|
function runProfileStrictOption(options) {
|
|
104
105
|
return optionBoolean(options, "strict") ?? false;
|
|
105
106
|
}
|
|
107
|
+
function runProfileSplitViewportsOption(options) {
|
|
108
|
+
return optionBoolean(options, "splitViewports") ?? false;
|
|
109
|
+
}
|
|
106
110
|
function optionNumber(options, ...keys) {
|
|
107
111
|
for (const key of keys) {
|
|
108
112
|
const value = optionString(options, key);
|
|
@@ -415,6 +419,8 @@ function profileRiddleJobMarkdown(result) {
|
|
|
415
419
|
const riddle = cliRecord(result.riddle);
|
|
416
420
|
if (!riddle) return [];
|
|
417
421
|
const jobId = cliString(riddle.job_id);
|
|
422
|
+
const mode = cliString(riddle.mode);
|
|
423
|
+
const jobCount = cliFiniteNumber(riddle.job_count);
|
|
418
424
|
const status = cliString(riddle.status);
|
|
419
425
|
const terminal = typeof riddle.terminal === "boolean" ? riddle.terminal : void 0;
|
|
420
426
|
const queueElapsedMs = cliFiniteNumber(riddle.queue_elapsed_ms);
|
|
@@ -424,6 +430,8 @@ function profileRiddleJobMarkdown(result) {
|
|
|
424
430
|
const submittedAt = cliString(riddle.submitted_at);
|
|
425
431
|
const completedAt = cliString(riddle.completed_at);
|
|
426
432
|
const parts = [
|
|
433
|
+
mode ? `mode ${markdownInlineCode(mode)}` : "",
|
|
434
|
+
jobCount === void 0 ? "" : `jobs ${jobCount}`,
|
|
427
435
|
jobId ? `job ${markdownInlineCode(jobId)}` : "",
|
|
428
436
|
status ? `status ${markdownInlineCode(status)}` : "",
|
|
429
437
|
terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
|
|
@@ -437,6 +445,21 @@ function profileRiddleJobMarkdown(result) {
|
|
|
437
445
|
if (submittedAt || completedAt) {
|
|
438
446
|
lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
|
|
439
447
|
}
|
|
448
|
+
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
449
|
+
for (const job of splitJobs.slice(0, 12)) {
|
|
450
|
+
const viewport = cliString(job.viewport) || "viewport";
|
|
451
|
+
const splitJobId = cliString(job.job_id);
|
|
452
|
+
const splitStatus = cliString(job.status);
|
|
453
|
+
const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
|
|
454
|
+
lines.push(
|
|
455
|
+
`- ${viewport}: ${[
|
|
456
|
+
splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
|
|
457
|
+
splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
|
|
458
|
+
splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`
|
|
459
|
+
].filter(Boolean).join(", ") || "job metadata unavailable"}`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
|
|
440
463
|
return lines;
|
|
441
464
|
}
|
|
442
465
|
function markdownInlineCode(value, maxLength = 80) {
|
|
@@ -1062,13 +1085,146 @@ function riddleMetadataFromPoll(jobId, poll) {
|
|
|
1062
1085
|
timed_out: poll.poll?.timed_out
|
|
1063
1086
|
};
|
|
1064
1087
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1088
|
+
function profileForSplitViewport(profile, viewport) {
|
|
1089
|
+
return {
|
|
1090
|
+
...profile,
|
|
1091
|
+
name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
|
|
1092
|
+
target: {
|
|
1093
|
+
...profile.target,
|
|
1094
|
+
viewports: [viewport]
|
|
1095
|
+
},
|
|
1096
|
+
metadata: {
|
|
1097
|
+
...profile.metadata || {},
|
|
1098
|
+
split_parent_profile: profile.name,
|
|
1099
|
+
split_viewport: viewport.name
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function safeProfileOutputSegment(value) {
|
|
1104
|
+
const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1105
|
+
return safe || "viewport";
|
|
1106
|
+
}
|
|
1107
|
+
function splitViewportOutputDir(outputDir, viewportName, seen) {
|
|
1108
|
+
const base = safeProfileOutputSegment(viewportName);
|
|
1109
|
+
const count = seen.get(base) || 0;
|
|
1110
|
+
seen.set(base, count + 1);
|
|
1111
|
+
return path.join(outputDir, count ? `${base}-${count + 1}` : base);
|
|
1112
|
+
}
|
|
1113
|
+
function splitViewportArtifactRefs(input) {
|
|
1114
|
+
return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
|
|
1115
|
+
...artifact,
|
|
1116
|
+
name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
function sumDefinedNumbers(values) {
|
|
1120
|
+
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
1121
|
+
return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
|
|
1122
|
+
}
|
|
1123
|
+
function splitViewportRiddleMetadata(childRuns) {
|
|
1124
|
+
const splitJobs = childRuns.map(({ viewport, result }) => ({
|
|
1125
|
+
viewport: viewport.name,
|
|
1126
|
+
job_id: result.riddle?.job_id,
|
|
1127
|
+
status: result.riddle?.status,
|
|
1128
|
+
terminal: result.riddle?.terminal,
|
|
1129
|
+
queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
|
|
1130
|
+
elapsed_ms: result.riddle?.elapsed_ms,
|
|
1131
|
+
attempt: result.riddle?.attempt,
|
|
1132
|
+
attempts: result.riddle?.attempts,
|
|
1133
|
+
timed_out: result.riddle?.timed_out
|
|
1134
|
+
}));
|
|
1135
|
+
return {
|
|
1136
|
+
mode: "split-viewports",
|
|
1137
|
+
job_count: childRuns.length,
|
|
1138
|
+
status: "split-viewports",
|
|
1139
|
+
terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
|
|
1140
|
+
queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
|
|
1141
|
+
elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
|
|
1142
|
+
split_jobs: splitJobs
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function latestCapturedAt(evidences) {
|
|
1146
|
+
let latest = "";
|
|
1147
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
1148
|
+
for (const evidence of evidences) {
|
|
1149
|
+
const parsed = Date.parse(evidence.captured_at);
|
|
1150
|
+
if (Number.isFinite(parsed) && parsed >= latestMs) {
|
|
1151
|
+
latest = evidence.captured_at;
|
|
1152
|
+
latestMs = parsed;
|
|
1153
|
+
}
|
|
1069
1154
|
}
|
|
1155
|
+
return latest || (/* @__PURE__ */ new Date()).toISOString();
|
|
1156
|
+
}
|
|
1157
|
+
function splitViewportDomSummary(profile, childRuns, evidence) {
|
|
1158
|
+
return {
|
|
1159
|
+
split_viewports: true,
|
|
1160
|
+
expected_viewport_count: profile.target.viewports.length,
|
|
1161
|
+
viewport_count: evidence.viewports.length,
|
|
1162
|
+
child_result_count: childRuns.length,
|
|
1163
|
+
child_statuses: childRuns.map(({ viewport, result }) => ({
|
|
1164
|
+
viewport: viewport.name,
|
|
1165
|
+
profile_name: result.profile_name,
|
|
1166
|
+
status: result.status,
|
|
1167
|
+
job_id: result.riddle?.job_id || null
|
|
1168
|
+
})),
|
|
1169
|
+
routes: evidence.viewports.map((viewport) => ({
|
|
1170
|
+
viewport: viewport.name,
|
|
1171
|
+
requested: viewport.route.requested,
|
|
1172
|
+
observed: viewport.route.observed,
|
|
1173
|
+
matched: viewport.route.matched,
|
|
1174
|
+
http_status: viewport.route.http_status ?? null
|
|
1175
|
+
})),
|
|
1176
|
+
titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
|
|
1177
|
+
overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
|
|
1178
|
+
bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
|
|
1179
|
+
overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
|
|
1180
|
+
console_event_count: evidence.console.events.length,
|
|
1181
|
+
console_fatal_count: evidence.console.fatal_count,
|
|
1182
|
+
page_error_count: evidence.page_errors.length,
|
|
1183
|
+
network_mock_count: (profile.target.network_mocks || []).length,
|
|
1184
|
+
network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function aggregateSplitViewportEvidence(profile, childRuns) {
|
|
1188
|
+
const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
|
|
1189
|
+
const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
|
|
1190
|
+
const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
|
|
1191
|
+
const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
|
|
1192
|
+
const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
|
|
1193
|
+
const evidence = {
|
|
1194
|
+
version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
1195
|
+
profile_name: profile.name,
|
|
1196
|
+
target_url: resolveRiddleProofProfileTargetUrl(profile),
|
|
1197
|
+
baseline_policy: profile.baseline_policy,
|
|
1198
|
+
captured_at: latestCapturedAt(evidences),
|
|
1199
|
+
viewports,
|
|
1200
|
+
console: {
|
|
1201
|
+
events: consoleEvents,
|
|
1202
|
+
fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
|
|
1203
|
+
},
|
|
1204
|
+
page_errors: pageErrors,
|
|
1205
|
+
network_mocks: networkMocks.length ? networkMocks : void 0
|
|
1206
|
+
};
|
|
1207
|
+
evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
|
|
1208
|
+
return evidence;
|
|
1209
|
+
}
|
|
1210
|
+
function splitViewportBlockedMessage(childRuns) {
|
|
1211
|
+
const blocked = childRuns.filter(({ result }) => !result.evidence || result.status === "environment_blocked" || result.status === "configuration_error").map(({ viewport, result }) => `${viewport.name}: ${result.status}${result.error ? ` (${result.error})` : ""}`);
|
|
1212
|
+
return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
|
|
1213
|
+
}
|
|
1214
|
+
function withSplitViewportWarnings(profile, result) {
|
|
1215
|
+
const warnings = [];
|
|
1216
|
+
if (profile.target.network_mocks?.length) {
|
|
1217
|
+
warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
|
|
1218
|
+
}
|
|
1219
|
+
if (!warnings.length) return result;
|
|
1220
|
+
return {
|
|
1221
|
+
...result,
|
|
1222
|
+
warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
async function runSingleRiddleProfileForCli(profile, options, input) {
|
|
1226
|
+
const { client, runner } = input;
|
|
1070
1227
|
const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
|
|
1071
|
-
const client = createRiddleApiClient(riddleClientConfig(options));
|
|
1072
1228
|
let created;
|
|
1073
1229
|
try {
|
|
1074
1230
|
created = await client.runScript({
|
|
@@ -1127,6 +1283,48 @@ async function runProfileForCli(profile, options) {
|
|
|
1127
1283
|
artifacts
|
|
1128
1284
|
});
|
|
1129
1285
|
}
|
|
1286
|
+
async function runSplitViewportProfileForCli(profile, options, input) {
|
|
1287
|
+
const outputDir = profileOutputDirOption(options);
|
|
1288
|
+
const seenOutputNames = /* @__PURE__ */ new Map();
|
|
1289
|
+
const childRuns = [];
|
|
1290
|
+
for (const viewport of profile.target.viewports) {
|
|
1291
|
+
const childProfile = profileForSplitViewport(profile, viewport);
|
|
1292
|
+
const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
|
|
1293
|
+
if (outputDir) {
|
|
1294
|
+
writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
|
|
1295
|
+
}
|
|
1296
|
+
childRuns.push({ viewport, profile: childProfile, result: result2 });
|
|
1297
|
+
}
|
|
1298
|
+
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
1299
|
+
const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
|
|
1300
|
+
if (blocked.length) {
|
|
1301
|
+
return createRiddleProofProfileEnvironmentBlockedResult({
|
|
1302
|
+
profile,
|
|
1303
|
+
runner: input.runner,
|
|
1304
|
+
error: splitViewportBlockedMessage(childRuns),
|
|
1305
|
+
riddle: splitViewportRiddleMetadata(childRuns),
|
|
1306
|
+
artifacts
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
const evidence = aggregateSplitViewportEvidence(profile, childRuns);
|
|
1310
|
+
const result = assessRiddleProofProfileEvidence(profile, evidence, {
|
|
1311
|
+
runner: input.runner,
|
|
1312
|
+
riddle: splitViewportRiddleMetadata(childRuns),
|
|
1313
|
+
artifacts
|
|
1314
|
+
});
|
|
1315
|
+
return withSplitViewportWarnings(profile, result);
|
|
1316
|
+
}
|
|
1317
|
+
async function runProfileForCli(profile, options) {
|
|
1318
|
+
const runner = optionString(options, "runner") || "riddle";
|
|
1319
|
+
if (runner !== "riddle") {
|
|
1320
|
+
throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
|
|
1321
|
+
}
|
|
1322
|
+
const client = createRiddleApiClient(riddleClientConfig(options));
|
|
1323
|
+
if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
|
|
1324
|
+
return runSplitViewportProfileForCli(profile, options, { client, runner });
|
|
1325
|
+
}
|
|
1326
|
+
return runSingleRiddleProfileForCli(profile, options, { client, runner });
|
|
1327
|
+
}
|
|
1130
1328
|
function requestForRun(options) {
|
|
1131
1329
|
const statePath = optionString(options, "statePath");
|
|
1132
1330
|
const withEngineModuleUrl = (request) => {
|
package/dist/profile.d.cts
CHANGED
|
@@ -376,7 +376,9 @@ interface RiddleProofProfileResult {
|
|
|
376
376
|
warnings?: string[];
|
|
377
377
|
evidence?: RiddleProofProfileEvidence;
|
|
378
378
|
riddle?: {
|
|
379
|
+
mode?: "split-viewports" | (string & {});
|
|
379
380
|
job_id?: string;
|
|
381
|
+
job_count?: number;
|
|
380
382
|
status?: string | null;
|
|
381
383
|
terminal?: boolean;
|
|
382
384
|
created_at?: string | null;
|
|
@@ -387,6 +389,17 @@ interface RiddleProofProfileResult {
|
|
|
387
389
|
attempt?: number;
|
|
388
390
|
attempts?: number;
|
|
389
391
|
timed_out?: boolean;
|
|
392
|
+
split_jobs?: Array<{
|
|
393
|
+
viewport: string;
|
|
394
|
+
job_id?: string;
|
|
395
|
+
status?: string | null;
|
|
396
|
+
terminal?: boolean;
|
|
397
|
+
queue_elapsed_ms?: number | null;
|
|
398
|
+
elapsed_ms?: number;
|
|
399
|
+
attempt?: number;
|
|
400
|
+
attempts?: number;
|
|
401
|
+
timed_out?: boolean;
|
|
402
|
+
}>;
|
|
390
403
|
};
|
|
391
404
|
environment_blocker?: Record<string, JsonValue>;
|
|
392
405
|
error?: string;
|
package/dist/profile.d.ts
CHANGED
|
@@ -376,7 +376,9 @@ interface RiddleProofProfileResult {
|
|
|
376
376
|
warnings?: string[];
|
|
377
377
|
evidence?: RiddleProofProfileEvidence;
|
|
378
378
|
riddle?: {
|
|
379
|
+
mode?: "split-viewports" | (string & {});
|
|
379
380
|
job_id?: string;
|
|
381
|
+
job_count?: number;
|
|
380
382
|
status?: string | null;
|
|
381
383
|
terminal?: boolean;
|
|
382
384
|
created_at?: string | null;
|
|
@@ -387,6 +389,17 @@ interface RiddleProofProfileResult {
|
|
|
387
389
|
attempt?: number;
|
|
388
390
|
attempts?: number;
|
|
389
391
|
timed_out?: boolean;
|
|
392
|
+
split_jobs?: Array<{
|
|
393
|
+
viewport: string;
|
|
394
|
+
job_id?: string;
|
|
395
|
+
status?: string | null;
|
|
396
|
+
terminal?: boolean;
|
|
397
|
+
queue_elapsed_ms?: number | null;
|
|
398
|
+
elapsed_ms?: number;
|
|
399
|
+
attempt?: number;
|
|
400
|
+
attempts?: number;
|
|
401
|
+
timed_out?: boolean;
|
|
402
|
+
}>;
|
|
390
403
|
};
|
|
391
404
|
environment_blocker?: Record<string, JsonValue>;
|
|
392
405
|
error?: string;
|