@riddledc/riddle-proof 0.8.35 → 0.8.37

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.
@@ -81,10 +81,68 @@ function previewDeployResultFromRecord(input) {
81
81
  function canRecoverPreviewPublish(error) {
82
82
  return error instanceof RiddleApiError && PREVIEW_PUBLISH_RECOVERY_STATUSES.has(error.status);
83
83
  }
84
+ function summarizePreviewDirectory(directory) {
85
+ const stack = [directory];
86
+ let fileCount = 0;
87
+ let totalBytes = 0;
88
+ while (stack.length) {
89
+ const current = stack.pop();
90
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
91
+ const fullPath = path.join(current, entry.name);
92
+ if (entry.isDirectory()) {
93
+ stack.push(fullPath);
94
+ continue;
95
+ }
96
+ if (!entry.isFile()) continue;
97
+ const stat = statSync(fullPath);
98
+ fileCount += 1;
99
+ totalBytes += stat.size;
100
+ }
101
+ }
102
+ return { file_count: fileCount, total_bytes: totalBytes };
103
+ }
104
+ function previewProgressEmitter(config, base) {
105
+ return async (snapshot) => {
106
+ if (!config.onPreviewProgress) return;
107
+ await config.onPreviewProgress({
108
+ label: base.label,
109
+ framework: base.framework,
110
+ directory: base.directory,
111
+ elapsed_ms: Math.max(0, Date.now() - base.startedAt),
112
+ warnings: base.warnings?.length ? base.warnings : void 0,
113
+ ...snapshot
114
+ });
115
+ };
116
+ }
84
117
  async function waitForPublishedPreview(config, input) {
118
+ await input.emitProgress?.({
119
+ stage: "publish_recovering",
120
+ id: input.id,
121
+ file_count: input.localSummary?.file_count,
122
+ total_bytes: input.localSummary?.total_bytes,
123
+ publish_error: input.publishError.message,
124
+ message: `publish returned ${input.publishError.status}; polling preview status`
125
+ });
85
126
  for (let attempt = 1; attempt <= PREVIEW_PUBLISH_RECOVERY_ATTEMPTS; attempt += 1) {
86
127
  const status = await riddleRequestJson(config, `/v1/preview/${input.id}`);
128
+ await input.emitProgress?.({
129
+ stage: "checking_status",
130
+ id: input.id,
131
+ attempt,
132
+ attempts: PREVIEW_PUBLISH_RECOVERY_ATTEMPTS,
133
+ status: typeof status.status === "string" ? status.status : null,
134
+ preview_url: typeof status.preview_url === "string" ? status.preview_url : void 0,
135
+ file_count: typeof status.file_count === "number" ? status.file_count : input.localSummary?.file_count,
136
+ total_bytes: typeof status.total_bytes === "number" ? status.total_bytes : input.localSummary?.total_bytes
137
+ });
87
138
  if (String(status.status || "") === "ready" && String(status.preview_url || "").trim()) {
139
+ await input.emitProgress?.({
140
+ stage: "ready",
141
+ id: input.id,
142
+ preview_url: String(status.preview_url),
143
+ file_count: typeof status.file_count === "number" ? status.file_count : input.localSummary?.file_count,
144
+ total_bytes: typeof status.total_bytes === "number" ? status.total_bytes : input.localSummary?.total_bytes
145
+ });
88
146
  return previewDeployResultFromRecord({
89
147
  record: status,
90
148
  id: input.id,
@@ -106,7 +164,17 @@ async function deployRiddlePreview(config, directory, label, framework = "static
106
164
  if (!directory?.trim()) throw new Error("directory is required");
107
165
  if (!label?.trim()) throw new Error("label is required");
108
166
  if (framework !== "spa" && framework !== "static") throw new Error("framework must be spa or static");
167
+ const startedAt = Date.now();
109
168
  const warnings = collectRiddlePreviewDeployWarnings(directory, framework);
169
+ const emitProgress = previewProgressEmitter(config, { label, framework, directory, startedAt, warnings });
170
+ await emitProgress({ stage: "validating", message: "checking preview input directory" });
171
+ const localSummary = summarizePreviewDirectory(directory);
172
+ await emitProgress({
173
+ stage: "creating",
174
+ file_count: localSummary.file_count,
175
+ total_bytes: localSummary.total_bytes,
176
+ message: "creating preview upload target"
177
+ });
110
178
  const created = await riddleRequestJson(config, "/v1/preview", {
111
179
  method: "POST",
112
180
  body: JSON.stringify({ framework, label })
@@ -114,10 +182,42 @@ async function deployRiddlePreview(config, directory, label, framework = "static
114
182
  const id = String(created.id || "");
115
183
  const uploadUrl = String(created.upload_url || "");
116
184
  if (!id || !uploadUrl) throw new Error("Riddle preview create response was missing id or upload_url.");
185
+ await emitProgress({
186
+ stage: "created",
187
+ id,
188
+ file_count: localSummary.file_count,
189
+ total_bytes: localSummary.total_bytes,
190
+ message: "preview upload target created"
191
+ });
117
192
  const scratch = mkdtempSync(path.join(tmpdir(), "riddle-preview-upload-"));
118
193
  const tarball = path.join(scratch, `${id}.tar.gz`);
194
+ let tarballBytes = 0;
119
195
  try {
196
+ await emitProgress({
197
+ stage: "archiving",
198
+ id,
199
+ file_count: localSummary.file_count,
200
+ total_bytes: localSummary.total_bytes,
201
+ message: "creating preview archive"
202
+ });
120
203
  execFileSync("tar", ["czf", tarball, "-C", directory, "."], { stdio: "pipe" });
204
+ tarballBytes = statSync(tarball).size;
205
+ await emitProgress({
206
+ stage: "archived",
207
+ id,
208
+ file_count: localSummary.file_count,
209
+ total_bytes: localSummary.total_bytes,
210
+ tarball_bytes: tarballBytes,
211
+ message: "preview archive created"
212
+ });
213
+ await emitProgress({
214
+ stage: "uploading",
215
+ id,
216
+ file_count: localSummary.file_count,
217
+ total_bytes: localSummary.total_bytes,
218
+ tarball_bytes: tarballBytes,
219
+ message: "uploading preview archive"
220
+ });
121
221
  const upload = await fetchFor(config)(uploadUrl, {
122
222
  method: "PUT",
123
223
  headers: { "Content-Type": "application/gzip" },
@@ -126,14 +226,37 @@ async function deployRiddlePreview(config, directory, label, framework = "static
126
226
  if (!upload.ok) {
127
227
  throw new RiddleApiError(uploadUrl, upload.status, await upload.text());
128
228
  }
229
+ await emitProgress({
230
+ stage: "uploaded",
231
+ id,
232
+ file_count: localSummary.file_count,
233
+ total_bytes: localSummary.total_bytes,
234
+ tarball_bytes: tarballBytes,
235
+ message: "preview archive uploaded"
236
+ });
129
237
  } finally {
130
238
  rmSync(scratch, { recursive: true, force: true });
131
239
  }
132
240
  const expiresAt = typeof created.expires_at === "string" ? created.expires_at : void 0;
133
241
  try {
242
+ await emitProgress({
243
+ stage: "publishing",
244
+ id,
245
+ file_count: localSummary.file_count,
246
+ total_bytes: localSummary.total_bytes,
247
+ tarball_bytes: tarballBytes || void 0,
248
+ message: "publishing preview"
249
+ });
134
250
  const published = await riddleRequestJson(config, `/v1/preview/${id}/publish`, {
135
251
  method: "POST"
136
252
  });
253
+ await emitProgress({
254
+ stage: "ready",
255
+ id,
256
+ preview_url: String(published.preview_url || ""),
257
+ file_count: typeof published.file_count === "number" ? published.file_count : localSummary.file_count,
258
+ total_bytes: typeof published.total_bytes === "number" ? published.total_bytes : localSummary.total_bytes
259
+ });
137
260
  return previewDeployResultFromRecord({ record: published, id, label, framework, expiresAt, warnings });
138
261
  } catch (error) {
139
262
  if (!canRecoverPreviewPublish(error)) throw error;
@@ -143,7 +266,9 @@ async function deployRiddlePreview(config, directory, label, framework = "static
143
266
  framework,
144
267
  expiresAt,
145
268
  publishError: error,
146
- warnings
269
+ warnings,
270
+ localSummary,
271
+ emitProgress
147
272
  });
148
273
  }
149
274
  }
@@ -12,12 +12,12 @@ import {
12
12
  profileStatusExitCode,
13
13
  resolveRiddleProofProfileTargetUrl,
14
14
  resolveRiddleProofProfileTimeoutSec
15
- } from "./chunk-PEWAIEER.js";
15
+ } from "./chunk-Z2LCVROU.js";
16
16
  import {
17
17
  createRiddleApiClient,
18
18
  isTerminalRiddleJobStatus,
19
19
  parseRiddleViewport
20
- } from "./chunk-TWTEUS7R.js";
20
+ } from "./chunk-DI2XNGEZ.js";
21
21
  import {
22
22
  createDisabledRiddleProofAgentAdapter,
23
23
  readRiddleProofRunStatus,
@@ -140,7 +140,7 @@ function usage() {
140
140
  " riddle-proof-loop regression-pack run [--pack oc-flow-regression|--pack-file <file>] [--local-core true|false; default true] [--hosted-riddle true|false; default false] [--format json|markdown|compact-json; default json] [--output <dir>|--output-dir <dir>]",
141
141
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
142
142
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
143
- " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
143
+ " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static] [--quiet]",
144
144
  " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
145
145
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720] [--strict true|false]",
146
146
  " riddle-proof-loop riddle-poll <job-id> [--wait] [--attempts n] [--quiet]",
@@ -909,6 +909,18 @@ function formatPollDuration(ms) {
909
909
  const remainder = seconds % 60;
910
910
  return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
911
911
  }
912
+ function formatByteCount(bytes) {
913
+ if (typeof bytes !== "number" || !Number.isFinite(bytes)) return "n/a";
914
+ if (bytes < 1024) return `${bytes}B`;
915
+ const units = ["KB", "MB", "GB", "TB"];
916
+ let value = bytes / 1024;
917
+ let unitIndex = 0;
918
+ while (value >= 1024 && unitIndex < units.length - 1) {
919
+ value /= 1024;
920
+ unitIndex += 1;
921
+ }
922
+ return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
923
+ }
912
924
  function riddlePollProgressLine(snapshot) {
913
925
  const submittedAt = snapshot.submitted_at || "not-submitted";
914
926
  const queuePart = snapshot.running_without_submission ? ` waiting_for_submit=${formatPollDuration(snapshot.pre_submission_elapsed_ms)}${snapshot.queue_elapsed_ms !== null ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : ""}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
@@ -922,6 +934,18 @@ function riddlePollProgressLine(snapshot) {
922
934
  `submitted_at=${submittedAt}${queuePart}${terminalPart}`
923
935
  ].join(" ");
924
936
  }
937
+ function riddlePreviewProgressLine(snapshot) {
938
+ const idPart = snapshot.id ? ` id=${snapshot.id}` : "";
939
+ const statusPart = snapshot.status ? ` status=${snapshot.status}` : "";
940
+ const attemptPart = snapshot.attempt && snapshot.attempts ? ` attempt=${snapshot.attempt}/${snapshot.attempts}` : "";
941
+ const previewPart = snapshot.preview_url ? ` url=${snapshot.preview_url}` : "";
942
+ const filePart = typeof snapshot.file_count === "number" ? ` files=${snapshot.file_count}` : "";
943
+ const totalPart = typeof snapshot.total_bytes === "number" ? ` bytes=${formatByteCount(snapshot.total_bytes)}` : "";
944
+ const archivePart = typeof snapshot.tarball_bytes === "number" ? ` archive=${formatByteCount(snapshot.tarball_bytes)}` : "";
945
+ const recoveryPart = snapshot.publish_error ? ` publish_error=${JSON.stringify(snapshot.publish_error)}` : "";
946
+ const messagePart = snapshot.message ? ` ${snapshot.message}` : "";
947
+ return `[riddle-preview] ${snapshot.stage}${idPart}${statusPart}${attemptPart} elapsed=${formatPollDuration(snapshot.elapsed_ms)} label=${snapshot.label} framework=${snapshot.framework}${filePart}${totalPart}${archivePart}${previewPart}${recoveryPart}${messagePart}`;
948
+ }
925
949
  function readJsonValue(value, label) {
926
950
  if (!value) throw new Error(`${label} is required.`);
927
951
  const raw = value === "-" ? readStdin() : existsSync(value) ? readFileSync(value, "utf-8") : value;
@@ -4597,7 +4621,14 @@ async function main() {
4597
4621
  if (command === "riddle-preview-deploy") {
4598
4622
  const buildDir = positional[1];
4599
4623
  const label = positional[2];
4600
- const result = await createRiddleApiClient(riddleClientConfig(options)).deployPreview(buildDir, label, previewFrameworkOption(options));
4624
+ const clientConfig = riddleClientConfig(options);
4625
+ if (options.quiet !== true) {
4626
+ clientConfig.onPreviewProgress = (snapshot) => {
4627
+ process.stderr.write(`${riddlePreviewProgressLine(snapshot)}
4628
+ `);
4629
+ };
4630
+ }
4631
+ const result = await createRiddleApiClient(clientConfig).deployPreview(buildDir, label, previewFrameworkOption(options));
4601
4632
  for (const warning of result.warnings ?? []) {
4602
4633
  process.stderr.write(`Warning: ${warning}
4603
4634
  `);
@@ -1331,6 +1331,8 @@ function normalizeSetupAction(input, index) {
1331
1331
  expect_changed: booleanValue(valueFromOwn(input, "expect_changed", "expectChanged", "should_change", "shouldChange", "changed")),
1332
1332
  until_path: untilPath,
1333
1333
  until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
1334
+ expected_path: stringFromOwn(input, "expected_path", "expectedPath", "expected_terminal_path", "expectedTerminalPath"),
1335
+ expected_url: stringFromOwn(input, "expected_url", "expectedUrl", "expected_terminal_url", "expectedTerminalUrl"),
1334
1336
  max_calls: maxCalls,
1335
1337
  tap_burst_size: tapBurstSize,
1336
1338
  settle_ms: settleMs,
@@ -3688,6 +3690,80 @@ function routePathMatches(observed, expected, targetUrl) {
3688
3690
  if (normalizedObserved === normalizedExpected) return true;
3689
3691
  return normalizedObserved === normalizeRoutePath(mountedExpectedRoutePath(targetUrl, expected));
3690
3692
  }
3693
+ function setupActionExpectedRoute(action) {
3694
+ const expectedUrl = typeof action.expected_url === "string" && action.expected_url.trim()
3695
+ ? action.expected_url.trim()
3696
+ : typeof action.expectedUrl === "string" && action.expectedUrl.trim()
3697
+ ? action.expectedUrl.trim()
3698
+ : "";
3699
+ const expectedPath = typeof action.expected_path === "string" && action.expected_path.trim()
3700
+ ? action.expected_path.trim()
3701
+ : typeof action.expectedPath === "string" && action.expectedPath.trim()
3702
+ ? action.expectedPath.trim()
3703
+ : "";
3704
+ if (!expectedUrl && !expectedPath) return null;
3705
+ return { expected_url: expectedUrl || undefined, expected_path: expectedPath || undefined };
3706
+ }
3707
+ function setupUrlMatchesExpectedRoute(href, expected) {
3708
+ if (!expected) return true;
3709
+ let observedUrl;
3710
+ try {
3711
+ observedUrl = new URL(href, targetUrl);
3712
+ } catch {
3713
+ return false;
3714
+ }
3715
+ if (expected.expected_url) {
3716
+ let expectedUrl;
3717
+ try {
3718
+ expectedUrl = new URL(expected.expected_url, targetUrl);
3719
+ } catch {
3720
+ return false;
3721
+ }
3722
+ return observedUrl.href === expectedUrl.href;
3723
+ }
3724
+ const expectedPath = expected.expected_path || "/";
3725
+ if (/[?#]/.test(expectedPath)) {
3726
+ const observedRoute = observedUrl.pathname + observedUrl.search + observedUrl.hash;
3727
+ const normalizedObservedRoute = observedRoute === "/" ? "/" : observedRoute.replace(/\/+(?=[?#]|$)/, "");
3728
+ const normalizedExpectedRoute = expectedPath === "/" ? "/" : expectedPath.replace(/\/+(?=[?#]|$)/, "");
3729
+ return normalizedObservedRoute === normalizedExpectedRoute;
3730
+ }
3731
+ return routePathMatches(observedUrl.pathname, expectedPath, targetUrl);
3732
+ }
3733
+ function setupObservedRouteEvidence(expected, waitError) {
3734
+ let observedUrl = page.url();
3735
+ let observedPath = "";
3736
+ let observedRoute = "";
3737
+ try {
3738
+ const url = new URL(observedUrl, targetUrl);
3739
+ observedUrl = url.href;
3740
+ observedPath = url.pathname;
3741
+ observedRoute = url.pathname + url.search + url.hash;
3742
+ } catch {
3743
+ observedPath = "";
3744
+ observedRoute = "";
3745
+ }
3746
+ return {
3747
+ expected_url: expected && expected.expected_url || undefined,
3748
+ expected_path: expected && expected.expected_path || undefined,
3749
+ observed_url: observedUrl,
3750
+ observed_path: observedPath,
3751
+ observed_route: observedRoute,
3752
+ route_matched: setupUrlMatchesExpectedRoute(observedUrl, expected),
3753
+ route_wait_error: waitError ? String(waitError && waitError.message ? waitError.message : waitError).slice(0, 1000) : undefined,
3754
+ };
3755
+ }
3756
+ async function waitForSetupActionRoute(action, timeout) {
3757
+ const expected = setupActionExpectedRoute(action);
3758
+ if (!expected) return null;
3759
+ let waitError;
3760
+ try {
3761
+ await page.waitForURL((url) => setupUrlMatchesExpectedRoute(url.href, expected), { timeout: Math.min(timeout, 20000) });
3762
+ } catch (error) {
3763
+ waitError = error;
3764
+ }
3765
+ return setupObservedRouteEvidence(expected, waitError);
3766
+ }
3691
3767
  function routeOk(route, targetUrl) {
3692
3768
  return Boolean(route && (route.matched || routePathMatches(route.observed, route.expected_path, targetUrl)) && !route.error && (route.http_status == null || route.http_status < 400));
3693
3769
  }
@@ -6581,11 +6657,22 @@ async function executeSetupAction(action, ordinal, viewport) {
6581
6657
  const prepared = await resolveSetupTapTarget(action, base, scope, timeout);
6582
6658
  if (prepared.result) return prepared.result;
6583
6659
  await dispatchSetupTapPoint(prepared.target.point, prepared.target.pointerType, prepared.target.durationMs);
6660
+ const routeEvidence = await waitForSetupActionRoute(action, timeout);
6661
+ if (routeEvidence && !routeEvidence.route_matched) {
6662
+ return {
6663
+ ...base,
6664
+ ...setupScopeEvidence(scope),
6665
+ ...setupTapTargetEvidence(prepared.target),
6666
+ ...routeEvidence,
6667
+ reason: "expected_route_not_reached",
6668
+ };
6669
+ }
6584
6670
  return {
6585
6671
  ...base,
6586
6672
  ...setupScopeEvidence(scope),
6587
6673
  ok: true,
6588
6674
  ...setupTapTargetEvidence(prepared.target),
6675
+ ...routeEvidence,
6589
6676
  };
6590
6677
  }
6591
6678
  if (type === "tap_until") {
@@ -7446,6 +7533,26 @@ async function executeSetupAction(action, ordinal, viewport) {
7446
7533
  : { x: box.x + box.width / 2, y: box.y + box.height / 2 };
7447
7534
  if (clickCount > 1) await page.mouse.click(fallbackPoint.x, fallbackPoint.y, { clickCount });
7448
7535
  else await page.mouse.click(fallbackPoint.x, fallbackPoint.y);
7536
+ const routeEvidence = await waitForSetupActionRoute(action, timeout);
7537
+ if (routeEvidence && !routeEvidence.route_matched) {
7538
+ return {
7539
+ ...base,
7540
+ ...setupScopeEvidence(scope),
7541
+ count,
7542
+ target_index: targetIndex,
7543
+ text: matchedText,
7544
+ force: action.force === true || undefined,
7545
+ fallback_to_tap: true,
7546
+ input_dispatch: "playwright_mouse",
7547
+ click_error: String(error && error.message ? error.message : error).slice(0, 1000),
7548
+ click_count: clickCount > 1 ? clickCount : undefined,
7549
+ coordinate_mode: mode,
7550
+ x: position ? fromX : undefined,
7551
+ y: position ? fromY : undefined,
7552
+ ...routeEvidence,
7553
+ reason: "expected_route_not_reached",
7554
+ };
7555
+ }
7449
7556
  return {
7450
7557
  ...base,
7451
7558
  ...setupScopeEvidence(scope),
@@ -7461,6 +7568,24 @@ async function executeSetupAction(action, ordinal, viewport) {
7461
7568
  coordinate_mode: mode,
7462
7569
  x: position ? fromX : undefined,
7463
7570
  y: position ? fromY : undefined,
7571
+ ...routeEvidence,
7572
+ };
7573
+ }
7574
+ const routeEvidence = await waitForSetupActionRoute(action, timeout);
7575
+ if (routeEvidence && !routeEvidence.route_matched) {
7576
+ return {
7577
+ ...base,
7578
+ ...setupScopeEvidence(scope),
7579
+ count,
7580
+ target_index: targetIndex,
7581
+ text: matchedText,
7582
+ force: action.force === true || undefined,
7583
+ click_count: clickCount > 1 ? clickCount : undefined,
7584
+ coordinate_mode: mode,
7585
+ x: position ? fromX : undefined,
7586
+ y: position ? fromY : undefined,
7587
+ ...routeEvidence,
7588
+ reason: "expected_route_not_reached",
7464
7589
  };
7465
7590
  }
7466
7591
  return {
@@ -7475,6 +7600,7 @@ async function executeSetupAction(action, ordinal, viewport) {
7475
7600
  coordinate_mode: mode,
7476
7601
  x: position ? fromX : undefined,
7477
7602
  y: position ? fromY : undefined,
7603
+ ...routeEvidence,
7478
7604
  };
7479
7605
  }
7480
7606
  if (type === "fill" || type === "set_input_value") {
package/dist/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import "../chunk-UGHN77PS.js";
2
- import "../chunk-PEWAIEER.js";
3
- import "../chunk-TWTEUS7R.js";
1
+ import "../chunk-F4HKK2YH.js";
2
+ import "../chunk-Z2LCVROU.js";
3
+ import "../chunk-DI2XNGEZ.js";
4
4
  import "../chunk-ZREWMTFA.js";
5
5
  import "../chunk-ZQWVXQKJ.js";
6
6
  import "../chunk-RDPG554T.js";