@riddledc/riddle-proof 0.7.93 → 0.7.95

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 CHANGED
@@ -6913,6 +6913,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6913
6913
  "selector_count_equals",
6914
6914
  "selector_count_equal",
6915
6915
  "selector_count_eq",
6916
+ "selector_text_visible",
6917
+ "selector_text_absent",
6916
6918
  "selector_text_order",
6917
6919
  "frame_text_visible",
6918
6920
  "frame_url_equals",
@@ -7626,7 +7628,7 @@ function normalizeCheck(input, index) {
7626
7628
  if (!isSupportedCheckType(type)) {
7627
7629
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
7628
7630
  }
7629
- if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue2(input.selector)) {
7631
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue2(input.selector)) {
7630
7632
  throw new Error(`checks[${index}] ${type} requires selector.`);
7631
7633
  }
7632
7634
  if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
@@ -7642,7 +7644,7 @@ function normalizeCheck(input, index) {
7642
7644
  if (type === "frame_url_matches" && !stringValue2(input.pattern)) {
7643
7645
  throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
7644
7646
  }
7645
- if ((type === "text_visible" || type === "text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7647
+ if ((type === "text_visible" || type === "text_absent" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7646
7648
  throw new Error(`checks[${index}] ${type} requires text or pattern.`);
7647
7649
  }
7648
7650
  if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue2(input.param) && !stringValue2(input.search_param) && !stringValue2(input.searchParam) && !stringValue2(input.key)) {
@@ -7676,6 +7678,12 @@ function normalizeCheck(input, index) {
7676
7678
  `checks[${index}] allowed_statuses`
7677
7679
  ) : void 0;
7678
7680
  const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
7681
+ const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
7682
+ const expectedContentType = isLinkStatusCheck ? stringValue2(input.expected_content_type) || stringValue2(input.expectedContentType) || stringValue2(input.content_type) || stringValue2(input.contentType) : void 0;
7683
+ const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
7684
+ input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
7685
+ `checks[${index}] allowed_content_types`
7686
+ ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
7679
7687
  if (isLinkStatusCheck) {
7680
7688
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
7681
7689
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -7717,6 +7725,8 @@ function normalizeCheck(input, index) {
7717
7725
  same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
7718
7726
  dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
7719
7727
  require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
7728
+ min_bytes: minBytes,
7729
+ allowed_content_types: allowedContentTypes,
7720
7730
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
7721
7731
  max_overflow_px: numberValue(input.max_overflow_px),
7722
7732
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
@@ -7823,15 +7833,40 @@ function linkStatusIsAllowed(status, check) {
7823
7833
  const allowed = linkStatusAllowedStatuses(check);
7824
7834
  return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
7825
7835
  }
7836
+ function linkStatusObservedBytes(result) {
7837
+ const bytes = numberValue(result.bytes);
7838
+ const contentLength = numberValue(result.content_length);
7839
+ return Math.max(bytes ?? 0, contentLength ?? 0) || void 0;
7840
+ }
7841
+ function normalizeLinkStatusContentType(value) {
7842
+ const normalized = value?.split(";")[0]?.trim().toLowerCase();
7843
+ return normalized || void 0;
7844
+ }
7845
+ function linkStatusContentTypeOk(result, check) {
7846
+ const expected = check.allowed_content_types;
7847
+ if (!expected?.length) return true;
7848
+ const actual = normalizeLinkStatusContentType(stringValue2(result.content_type));
7849
+ if (!actual) return false;
7850
+ return expected.some((contentType) => {
7851
+ const normalized = normalizeLinkStatusContentType(contentType);
7852
+ if (!normalized) return false;
7853
+ if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
7854
+ return actual === normalized;
7855
+ });
7856
+ }
7826
7857
  function linkStatusResultOk(result, check) {
7827
7858
  const status = numberValue(result.status);
7828
7859
  if (!linkStatusIsAllowed(status, check)) return false;
7829
7860
  if (stringValue2(result.error)) return false;
7830
7861
  if (result.ok === false) return false;
7862
+ if (!linkStatusContentTypeOk(result, check)) return false;
7831
7863
  if (check.require_nonzero_bytes) {
7832
- const bytes = numberValue(result.bytes);
7833
- const contentLength = numberValue(result.content_length);
7834
- return bytes !== void 0 && bytes > 0 || contentLength !== void 0 && contentLength > 0;
7864
+ const observedBytes = linkStatusObservedBytes(result);
7865
+ if (observedBytes === void 0 || observedBytes <= 0) return false;
7866
+ }
7867
+ if (check.min_bytes !== void 0) {
7868
+ const observedBytes = linkStatusObservedBytes(result);
7869
+ if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
7835
7870
  }
7836
7871
  return true;
7837
7872
  }
@@ -7860,7 +7895,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
7860
7895
  status: numberValue(result.status) ?? null,
7861
7896
  method: stringValue2(result.method) ?? null,
7862
7897
  error: stringValue2(result.error) ?? null,
7863
- bytes: numberValue(result.bytes) ?? numberValue(result.content_length) ?? null
7898
+ content_type: stringValue2(result.content_type) ?? null,
7899
+ bytes: linkStatusObservedBytes(result) ?? null,
7900
+ min_bytes: check.min_bytes ?? null,
7901
+ allowed_content_types: check.allowed_content_types ?? null
7864
7902
  }));
7865
7903
  if (stringValue2(linkEvidence.error)) {
7866
7904
  failures.push({ code: "link_status_capture_failed", error: stringValue2(linkEvidence.error) ?? "" });
@@ -7897,6 +7935,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
7897
7935
  failed_count: failures.length,
7898
7936
  truncated: linkEvidence.truncated === true,
7899
7937
  max_links: numberValue(linkEvidence.max_links) ?? check.max_links ?? 100,
7938
+ min_bytes: check.min_bytes ?? null,
7939
+ allowed_content_types: check.allowed_content_types ?? null,
7900
7940
  status_counts: statusCounts,
7901
7941
  failures: failures.slice(0, 20)
7902
7942
  };
@@ -7908,9 +7948,11 @@ function textSequenceForCheck(viewport, check) {
7908
7948
  const key = selectorKey(check);
7909
7949
  const sequence = viewport.text_sequences?.[key];
7910
7950
  if (isRecord(sequence)) {
7951
+ const visibleMatchTexts = Array.isArray(sequence.visible_match_texts) ? sequence.visible_match_texts : [];
7952
+ const matchTexts = Array.isArray(sequence.match_texts) ? sequence.match_texts : [];
7911
7953
  const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
7912
7954
  const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
7913
- const candidates = visibleTexts.length ? visibleTexts : texts;
7955
+ const candidates = visibleMatchTexts.length ? visibleMatchTexts : matchTexts.length ? matchTexts : visibleTexts.length ? visibleTexts : texts;
7914
7956
  return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
7915
7957
  }
7916
7958
  return [];
@@ -8244,6 +8286,35 @@ function assessCheckFromEvidence(check, evidence) {
8244
8286
  message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
8245
8287
  };
8246
8288
  }
8289
+ if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
8290
+ const key = selectorKey(check);
8291
+ const expectedVisible = check.type === "selector_text_visible";
8292
+ const results = viewports.map((viewport) => {
8293
+ const texts = textSequenceForCheck(viewport, check);
8294
+ const matches = texts.filter((text) => matchText(text, check));
8295
+ return {
8296
+ viewport: viewport.name,
8297
+ selector_count: viewport.selectors?.[key]?.count || 0,
8298
+ visible_count: viewport.selectors?.[key]?.visible_count || 0,
8299
+ matched_count: matches.length,
8300
+ matched: matches.length > 0,
8301
+ samples: matches.slice(0, 3).map((text) => text.slice(0, 240))
8302
+ };
8303
+ });
8304
+ const failed = results.filter((result) => result.matched !== expectedVisible).length;
8305
+ return {
8306
+ type: check.type,
8307
+ label: checkLabel(check),
8308
+ status: failed ? "failed" : "passed",
8309
+ evidence: {
8310
+ selector: key,
8311
+ text: check.text || null,
8312
+ pattern: check.pattern || null,
8313
+ viewports: results.map((result) => toJsonValue(result))
8314
+ },
8315
+ message: failed ? `Selector ${key} text assertion failed in ${failed} viewport(s).` : void 0
8316
+ };
8317
+ }
8247
8318
  if (check.type === "selector_text_order") {
8248
8319
  const key = selectorKey(check);
8249
8320
  const expectedTexts = check.expected_texts || [];
@@ -8254,7 +8325,7 @@ function assessCheckFromEvidence(check, evidence) {
8254
8325
  viewport: viewport.name,
8255
8326
  matched: match.matched,
8256
8327
  matched_positions: match.positions,
8257
- visible_texts: texts.slice(0, 20)
8328
+ visible_texts: texts.slice(0, 20).map((text) => text.slice(0, 240))
8258
8329
  };
8259
8330
  });
8260
8331
  const failed = results.filter((result) => !result.matched).length;
@@ -8394,6 +8465,8 @@ function assessCheckFromEvidence(check, evidence) {
8394
8465
  min_count: check.min_count ?? null,
8395
8466
  allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
8396
8467
  require_nonzero_bytes: check.require_nonzero_bytes === true,
8468
+ min_bytes: check.min_bytes ?? null,
8469
+ allowed_content_types: check.allowed_content_types ?? null,
8397
8470
  viewports: summaries.map((summary) => toJsonValue(summary)),
8398
8471
  failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
8399
8472
  },
@@ -8948,9 +9021,11 @@ function textSequenceForCheck(viewport, check) {
8948
9021
  const key = check.selector || "";
8949
9022
  const sequence = viewport.text_sequences && viewport.text_sequences[key];
8950
9023
  if (sequence && typeof sequence === "object") {
9024
+ const visibleMatchTexts = Array.isArray(sequence.visible_match_texts) ? sequence.visible_match_texts : [];
9025
+ const matchTexts = Array.isArray(sequence.match_texts) ? sequence.match_texts : [];
8951
9026
  const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
8952
9027
  const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
8953
- const candidates = visibleTexts.length ? visibleTexts : texts;
9028
+ const candidates = visibleMatchTexts.length ? visibleMatchTexts : matchTexts.length ? matchTexts : visibleTexts.length ? visibleTexts : texts;
8954
9029
  return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
8955
9030
  }
8956
9031
  return [];
@@ -8979,15 +9054,41 @@ function linkStatusIsAllowed(status, check) {
8979
9054
  const allowed = linkStatusAllowedStatuses(check);
8980
9055
  return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
8981
9056
  }
9057
+ function linkStatusObservedBytes(result) {
9058
+ const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
9059
+ const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
9060
+ const observed = Math.max(bytes || 0, contentLength || 0);
9061
+ return observed > 0 ? observed : undefined;
9062
+ }
9063
+ function normalizeLinkStatusContentType(value) {
9064
+ if (typeof value !== "string") return undefined;
9065
+ const normalized = (value.split(";")[0] || "").trim().toLowerCase();
9066
+ return normalized || undefined;
9067
+ }
9068
+ function linkStatusContentTypeOk(result, check) {
9069
+ if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
9070
+ const actual = normalizeLinkStatusContentType(result.content_type);
9071
+ if (!actual) return false;
9072
+ return check.allowed_content_types.some((contentType) => {
9073
+ const normalized = normalizeLinkStatusContentType(contentType);
9074
+ if (!normalized) return false;
9075
+ if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
9076
+ return actual === normalized;
9077
+ });
9078
+ }
8982
9079
  function linkStatusResultOk(result, check) {
8983
9080
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
8984
9081
  if (!linkStatusIsAllowed(result.status, check)) return false;
8985
9082
  if (typeof result.error === "string" && result.error.trim()) return false;
8986
9083
  if (result.ok === false) return false;
9084
+ if (!linkStatusContentTypeOk(result, check)) return false;
8987
9085
  if (check.require_nonzero_bytes === true) {
8988
- const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
8989
- const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
8990
- return (bytes !== undefined && bytes > 0) || (contentLength !== undefined && contentLength > 0);
9086
+ const observedBytes = linkStatusObservedBytes(result);
9087
+ if (observedBytes === undefined || observedBytes <= 0) return false;
9088
+ }
9089
+ if (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) {
9090
+ const observedBytes = linkStatusObservedBytes(result);
9091
+ if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
8991
9092
  }
8992
9093
  return true;
8993
9094
  }
@@ -9015,11 +9116,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
9015
9116
  status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
9016
9117
  method: typeof result.method === "string" ? result.method : null,
9017
9118
  error: typeof result.error === "string" ? result.error : null,
9018
- bytes: typeof result.bytes === "number" && Number.isFinite(result.bytes)
9019
- ? result.bytes
9020
- : typeof result.content_length === "number" && Number.isFinite(result.content_length)
9021
- ? result.content_length
9022
- : null,
9119
+ content_type: typeof result.content_type === "string" ? result.content_type : null,
9120
+ bytes: linkStatusObservedBytes(result) ?? null,
9121
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
9122
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
9023
9123
  }));
9024
9124
  if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
9025
9125
  failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
@@ -9054,6 +9154,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
9054
9154
  failed_count: failures.length,
9055
9155
  truncated: linkEvidence.truncated === true,
9056
9156
  max_links: typeof linkEvidence.max_links === "number" ? linkEvidence.max_links : check.max_links || 100,
9157
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
9158
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
9057
9159
  status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
9058
9160
  failures: failures.slice(0, 20),
9059
9161
  };
@@ -9588,6 +9690,31 @@ function assessProfile(profile, evidence) {
9588
9690
  });
9589
9691
  continue;
9590
9692
  }
9693
+ if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
9694
+ const selector = check.selector || "";
9695
+ const expectedVisible = check.type === "selector_text_visible";
9696
+ const results = checkViewports.map((viewport) => {
9697
+ const texts = textSequenceForCheck(viewport, check);
9698
+ const matches = texts.filter((text) => textMatches(text, check));
9699
+ return {
9700
+ viewport: viewport.name,
9701
+ selector_count: viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0,
9702
+ visible_count: viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0,
9703
+ matched_count: matches.length,
9704
+ matched: matches.length > 0,
9705
+ samples: matches.slice(0, 3).map((text) => text.slice(0, 240)),
9706
+ };
9707
+ });
9708
+ const failed = results.filter((result) => result.matched !== expectedVisible).length;
9709
+ checks.push({
9710
+ type: check.type,
9711
+ label: check.label || check.type,
9712
+ status: failed ? "failed" : "passed",
9713
+ evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
9714
+ message: failed ? "Selector " + selector + " text assertion failed in " + failed + " viewport(s)." : undefined,
9715
+ });
9716
+ continue;
9717
+ }
9591
9718
  if (check.type === "selector_text_order") {
9592
9719
  const selector = check.selector || "";
9593
9720
  const expectedTexts = check.expected_texts || [];
@@ -9598,7 +9725,7 @@ function assessProfile(profile, evidence) {
9598
9725
  viewport: viewport.name,
9599
9726
  matched: match.matched,
9600
9727
  matched_positions: match.positions,
9601
- visible_texts: texts.slice(0, 20),
9728
+ visible_texts: texts.slice(0, 20).map((text) => text.slice(0, 240)),
9602
9729
  };
9603
9730
  });
9604
9731
  const failed = results.filter((result) => !result.matched).length;
@@ -9725,6 +9852,8 @@ function assessProfile(profile, evidence) {
9725
9852
  min_count: check.min_count ?? null,
9726
9853
  allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
9727
9854
  require_nonzero_bytes: check.require_nonzero_bytes === true,
9855
+ min_bytes: check.min_bytes ?? null,
9856
+ allowed_content_types: check.allowed_content_types ?? null,
9728
9857
  viewports: summaries,
9729
9858
  failures: failed.flatMap((summary) => Array.isArray(summary.failures)
9730
9859
  ? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
@@ -10802,6 +10931,7 @@ async function selectorStats(selector) {
10802
10931
  async function selectorTextSequence(selector) {
10803
10932
  return page.locator(selector).evaluateAll((elements) => {
10804
10933
  const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
10934
+ const matchText = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 8000);
10805
10935
  const isVisible = (element) => {
10806
10936
  const style = window.getComputedStyle(element);
10807
10937
  const rect = element.getBoundingClientRect();
@@ -10810,6 +10940,7 @@ async function selectorTextSequence(selector) {
10810
10940
  const rows = elements.map((element, index) => ({
10811
10941
  index,
10812
10942
  text: compact(element.innerText || element.textContent || ""),
10943
+ match_text: matchText(element.innerText || element.textContent || ""),
10813
10944
  visible: isVisible(element),
10814
10945
  }));
10815
10946
  return {
@@ -10817,8 +10948,10 @@ async function selectorTextSequence(selector) {
10817
10948
  visible_count: rows.filter((row) => row.visible).length,
10818
10949
  texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
10819
10950
  visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
10951
+ match_texts: rows.map((row) => row.match_text).filter(Boolean).slice(0, 40),
10952
+ visible_match_texts: rows.filter((row) => row.visible).map((row) => row.match_text).filter(Boolean).slice(0, 40),
10820
10953
  };
10821
- }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
10954
+ }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
10822
10955
  }
10823
10956
  function linkProbeMaxLinks(check) {
10824
10957
  const value = Number(check.max_links || check.maxLinks || check.limit || 100);
@@ -10868,6 +11001,28 @@ function linkProbeAllowed(status, check) {
10868
11001
  : null;
10869
11002
  return allowed ? allowed.includes(status) : status >= 200 && status < 400;
10870
11003
  }
11004
+ function linkProbeObservedBytes(result) {
11005
+ const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
11006
+ const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
11007
+ const observed = Math.max(bytes || 0, contentLength || 0);
11008
+ return observed > 0 ? observed : undefined;
11009
+ }
11010
+ function normalizeLinkProbeContentType(value) {
11011
+ if (typeof value !== "string") return undefined;
11012
+ const normalized = (value.split(";")[0] || "").trim().toLowerCase();
11013
+ return normalized || undefined;
11014
+ }
11015
+ function linkProbeContentTypeAllowed(result, check) {
11016
+ if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
11017
+ const actual = normalizeLinkProbeContentType(result.content_type);
11018
+ if (!actual) return false;
11019
+ return check.allowed_content_types.some((contentType) => {
11020
+ const normalized = normalizeLinkProbeContentType(contentType);
11021
+ if (!normalized) return false;
11022
+ if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
11023
+ return actual === normalized;
11024
+ });
11025
+ }
10871
11026
  function linkProbeResponseFields(response, method) {
10872
11027
  const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
10873
11028
  const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
@@ -10882,6 +11037,7 @@ function linkProbeResponseFields(response, method) {
10882
11037
  }
10883
11038
  async function probeLinkStatus(candidate, check) {
10884
11039
  const requireNonzeroBytes = check.require_nonzero_bytes === true;
11040
+ const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
10885
11041
  const allowGetFallback = check.allow_get_fallback !== false;
10886
11042
  const result = {
10887
11043
  url: candidate.url,
@@ -10899,7 +11055,7 @@ async function probeLinkStatus(candidate, check) {
10899
11055
  };
10900
11056
  const applyResponse = async (response, method, readBytes) => {
10901
11057
  Object.assign(result, linkProbeResponseFields(response, method));
10902
- if (readBytes || requireNonzeroBytes) {
11058
+ if (readBytes) {
10903
11059
  try {
10904
11060
  const buffer = await response.arrayBuffer();
10905
11061
  result.bytes = buffer.byteLength;
@@ -10908,7 +11064,9 @@ async function probeLinkStatus(candidate, check) {
10908
11064
  }
10909
11065
  }
10910
11066
  result.ok = linkProbeAllowed(result.status, check)
10911
- && (!requireNonzeroBytes || (typeof result.bytes === "number" ? result.bytes > 0 : typeof result.content_length === "number" && result.content_length > 0))
11067
+ && linkProbeContentTypeAllowed(result, check)
11068
+ && (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
11069
+ && (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
10912
11070
  && !result.error;
10913
11071
  };
10914
11072
  try {
@@ -10925,9 +11083,9 @@ async function probeLinkStatus(candidate, check) {
10925
11083
  method: "GET",
10926
11084
  redirect: "follow",
10927
11085
  cache: "no-store",
10928
- headers: { Range: "bytes=0-0" },
11086
+ headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
10929
11087
  });
10930
- await applyResponse(response, "GET", requireNonzeroBytes);
11088
+ await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
10931
11089
  return result;
10932
11090
  } catch (error) {
10933
11091
  result.error = String(error && error.message ? error.message : error).slice(0, 500);
@@ -10981,7 +11139,10 @@ async function collectLinkStatus(check) {
10981
11139
  status: result.status,
10982
11140
  method: result.method,
10983
11141
  error: result.error,
10984
- bytes: result.bytes == null ? result.content_length : result.bytes,
11142
+ content_type: result.content_type,
11143
+ bytes: linkProbeObservedBytes(result) || null,
11144
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
11145
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
10985
11146
  }));
10986
11147
  const statusCounts = {};
10987
11148
  for (const result of results) {
@@ -10995,6 +11156,8 @@ async function collectLinkStatus(check) {
10995
11156
  same_origin_only: linkProbeSameOriginOnly(check),
10996
11157
  dedupe: linkProbeDedupe(check),
10997
11158
  require_nonzero_bytes: check.require_nonzero_bytes === true,
11159
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
11160
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
10998
11161
  allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
10999
11162
  ? check.allowed_statuses
11000
11163
  : typeof check.expected_status === "number"
@@ -11554,7 +11717,7 @@ async function captureViewport(viewport) {
11554
11717
  ) {
11555
11718
  selectors[check.selector] = await selectorStats(check.selector);
11556
11719
  }
11557
- if (check.type === "selector_text_order" && check.selector) {
11720
+ if ((check.type === "selector_text_order" || check.type === "selector_text_visible" || check.type === "selector_text_absent") && check.selector) {
11558
11721
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
11559
11722
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
11560
11723
  }
@@ -12181,6 +12344,11 @@ function profileCheckMarkdownTarget(check) {
12181
12344
  const expectedCount = cliFiniteNumber(evidence.expected_count);
12182
12345
  return selector && expectedCount !== void 0 ? `${markdownInlineCode(selector)} = ${expectedCount}` : void 0;
12183
12346
  }
12347
+ if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
12348
+ const textTarget = profileCheckTextTarget(evidence);
12349
+ if (selector && textTarget) return `${markdownInlineCode(selector)} contains ${textTarget}`;
12350
+ return selector ? markdownInlineCode(selector) : textTarget;
12351
+ }
12184
12352
  if (check.type === "selector_text_order") {
12185
12353
  return selector ? `${markdownInlineCode(selector)} text order` : void 0;
12186
12354
  }
@@ -12210,9 +12378,13 @@ function profileCheckMarkdownTarget(check) {
12210
12378
  if (check.type === "link_status" || check.type === "artifact_link_status") {
12211
12379
  const expectedCount = cliFiniteNumber(evidence.expected_count);
12212
12380
  const minCount = cliFiniteNumber(evidence.min_count);
12213
- if (selector && expectedCount !== void 0) return `${markdownInlineCode(selector)} links = ${expectedCount}`;
12214
- if (selector && minCount !== void 0) return `${markdownInlineCode(selector)} links >= ${minCount}`;
12215
- return selector ? markdownInlineCode(selector) : void 0;
12381
+ const minBytes = cliFiniteNumber(evidence.min_bytes);
12382
+ const parts = [];
12383
+ if (expectedCount !== void 0) parts.push(`links = ${expectedCount}`);
12384
+ else if (minCount !== void 0) parts.push(`links >= ${minCount}`);
12385
+ if (minBytes !== void 0) parts.push(`bytes >= ${minBytes}`);
12386
+ if (selector && parts.length) return `${markdownInlineCode(selector)} ${parts.join(", ")}`;
12387
+ return selector ? markdownInlineCode(selector) : parts.join(", ") || void 0;
12216
12388
  }
12217
12389
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
12218
12390
  const maxOverflow = cliFiniteNumber(evidence.max_overflow_px);
@@ -12420,9 +12592,11 @@ function profileLinkStatusSummaryMarkdown(result) {
12420
12592
  const okTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.ok_count) || 0), 0);
12421
12593
  const failedTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.failed_count) || 0), 0);
12422
12594
  const truncatedTotal = viewports.filter((viewport) => viewport.truncated === true).length;
12595
+ const minBytes = cliFiniteNumber(evidence.min_bytes);
12596
+ const allowedContentTypes = Array.isArray(evidence.allowed_content_types) ? evidence.allowed_content_types.map(cliString).filter((value) => Boolean(value)) : [];
12423
12597
  const countText = totals.length ? totals.join("/") : "unknown";
12424
12598
  lines.push(
12425
- `- ${label}${selector ? ` ${markdownInlineCode(selector)}` : ""}: links ${countText}, ok ${okTotal}, failures ${failedTotal}${truncatedTotal ? `, truncated viewports ${truncatedTotal}` : ""}`
12599
+ `- ${label}${selector ? ` ${markdownInlineCode(selector)}` : ""}: links ${countText}, ok ${okTotal}, failures ${failedTotal}${truncatedTotal ? `, truncated viewports ${truncatedTotal}` : ""}${minBytes !== void 0 ? `, min bytes ${minBytes}` : ""}${allowedContentTypes.length ? `, content types ${allowedContentTypes.map((value) => markdownInlineCode(value)).join(", ")}` : ""}`
12426
12600
  );
12427
12601
  }
12428
12602
  return lines;
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-QLBOFUON.js";
13
+ } from "./chunk-5OCJKEHE.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
@@ -389,6 +389,11 @@ function profileCheckMarkdownTarget(check) {
389
389
  const expectedCount = cliFiniteNumber(evidence.expected_count);
390
390
  return selector && expectedCount !== void 0 ? `${markdownInlineCode(selector)} = ${expectedCount}` : void 0;
391
391
  }
392
+ if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
393
+ const textTarget = profileCheckTextTarget(evidence);
394
+ if (selector && textTarget) return `${markdownInlineCode(selector)} contains ${textTarget}`;
395
+ return selector ? markdownInlineCode(selector) : textTarget;
396
+ }
392
397
  if (check.type === "selector_text_order") {
393
398
  return selector ? `${markdownInlineCode(selector)} text order` : void 0;
394
399
  }
@@ -418,9 +423,13 @@ function profileCheckMarkdownTarget(check) {
418
423
  if (check.type === "link_status" || check.type === "artifact_link_status") {
419
424
  const expectedCount = cliFiniteNumber(evidence.expected_count);
420
425
  const minCount = cliFiniteNumber(evidence.min_count);
421
- if (selector && expectedCount !== void 0) return `${markdownInlineCode(selector)} links = ${expectedCount}`;
422
- if (selector && minCount !== void 0) return `${markdownInlineCode(selector)} links >= ${minCount}`;
423
- return selector ? markdownInlineCode(selector) : void 0;
426
+ const minBytes = cliFiniteNumber(evidence.min_bytes);
427
+ const parts = [];
428
+ if (expectedCount !== void 0) parts.push(`links = ${expectedCount}`);
429
+ else if (minCount !== void 0) parts.push(`links >= ${minCount}`);
430
+ if (minBytes !== void 0) parts.push(`bytes >= ${minBytes}`);
431
+ if (selector && parts.length) return `${markdownInlineCode(selector)} ${parts.join(", ")}`;
432
+ return selector ? markdownInlineCode(selector) : parts.join(", ") || void 0;
424
433
  }
425
434
  if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
426
435
  const maxOverflow = cliFiniteNumber(evidence.max_overflow_px);
@@ -628,9 +637,11 @@ function profileLinkStatusSummaryMarkdown(result) {
628
637
  const okTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.ok_count) || 0), 0);
629
638
  const failedTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.failed_count) || 0), 0);
630
639
  const truncatedTotal = viewports.filter((viewport) => viewport.truncated === true).length;
640
+ const minBytes = cliFiniteNumber(evidence.min_bytes);
641
+ const allowedContentTypes = Array.isArray(evidence.allowed_content_types) ? evidence.allowed_content_types.map(cliString).filter((value) => Boolean(value)) : [];
631
642
  const countText = totals.length ? totals.join("/") : "unknown";
632
643
  lines.push(
633
- `- ${label}${selector ? ` ${markdownInlineCode(selector)}` : ""}: links ${countText}, ok ${okTotal}, failures ${failedTotal}${truncatedTotal ? `, truncated viewports ${truncatedTotal}` : ""}`
644
+ `- ${label}${selector ? ` ${markdownInlineCode(selector)}` : ""}: links ${countText}, ok ${okTotal}, failures ${failedTotal}${truncatedTotal ? `, truncated viewports ${truncatedTotal}` : ""}${minBytes !== void 0 ? `, min bytes ${minBytes}` : ""}${allowedContentTypes.length ? `, content types ${allowedContentTypes.map((value) => markdownInlineCode(value)).join(", ")}` : ""}`
634
645
  );
635
646
  }
636
647
  return lines;