@riddledc/riddle-proof 0.7.94 → 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/README.md +8 -3
- package/dist/{chunk-FQ4QBY2N.js → chunk-5OCJKEHE.js} +116 -17
- package/dist/cli.cjs +126 -21
- package/dist/cli.js +11 -5
- package/dist/index.cjs +116 -17
- package/dist/index.js +1 -1
- package/dist/profile.cjs +116 -17
- package/dist/profile.d.cts +2 -0
- package/dist/profile.d.ts +2 -0
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -479,6 +479,8 @@ artifact links should fail the profile:
|
|
|
479
479
|
"expected_count": 88,
|
|
480
480
|
"same_origin_only": true,
|
|
481
481
|
"require_nonzero_bytes": true,
|
|
482
|
+
"min_bytes": 32,
|
|
483
|
+
"allowed_content_types": ["image/*", "application/json"],
|
|
482
484
|
"max_links": 150
|
|
483
485
|
}
|
|
484
486
|
```
|
|
@@ -486,9 +488,12 @@ artifact links should fail the profile:
|
|
|
486
488
|
The check defaults to `a[href]`, deduplicates URLs, probes up to 100 selected
|
|
487
489
|
URLs, and treats HTTP `2xx` / `3xx` responses as healthy. Use
|
|
488
490
|
`allowed_statuses` or `expected_status` when a narrower status contract is
|
|
489
|
-
intentional, `min_count` for lower-bound audits,
|
|
490
|
-
|
|
491
|
-
|
|
491
|
+
intentional, `min_count` for lower-bound audits, `min_bytes` when a one-byte
|
|
492
|
+
range response is too weak, `allowed_content_types` for MIME checks, and
|
|
493
|
+
`max_links` when the selected set is intentionally larger than 100.
|
|
494
|
+
`allowed_content_types` accepts exact types and family wildcards such as
|
|
495
|
+
`image/*`. `artifact_link_status` is an alias with the same behavior for
|
|
496
|
+
profiles that want artifact-specific wording.
|
|
492
497
|
|
|
493
498
|
Use the `route_inventory` check for source-page route coverage audits where a
|
|
494
499
|
navigation surface must expose a known set of routes and each route must load
|
|
@@ -785,6 +785,12 @@ function normalizeCheck(input, index) {
|
|
|
785
785
|
`checks[${index}] allowed_statuses`
|
|
786
786
|
) : void 0;
|
|
787
787
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
788
|
+
const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
789
|
+
const expectedContentType = isLinkStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
|
|
790
|
+
const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
|
|
791
|
+
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
792
|
+
`checks[${index}] allowed_content_types`
|
|
793
|
+
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
788
794
|
if (isLinkStatusCheck) {
|
|
789
795
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
790
796
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -826,6 +832,8 @@ function normalizeCheck(input, index) {
|
|
|
826
832
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
827
833
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
828
834
|
require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
835
|
+
min_bytes: minBytes,
|
|
836
|
+
allowed_content_types: allowedContentTypes,
|
|
829
837
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
830
838
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
831
839
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -932,15 +940,40 @@ function linkStatusIsAllowed(status, check) {
|
|
|
932
940
|
const allowed = linkStatusAllowedStatuses(check);
|
|
933
941
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
934
942
|
}
|
|
943
|
+
function linkStatusObservedBytes(result) {
|
|
944
|
+
const bytes = numberValue(result.bytes);
|
|
945
|
+
const contentLength = numberValue(result.content_length);
|
|
946
|
+
return Math.max(bytes ?? 0, contentLength ?? 0) || void 0;
|
|
947
|
+
}
|
|
948
|
+
function normalizeLinkStatusContentType(value) {
|
|
949
|
+
const normalized = value?.split(";")[0]?.trim().toLowerCase();
|
|
950
|
+
return normalized || void 0;
|
|
951
|
+
}
|
|
952
|
+
function linkStatusContentTypeOk(result, check) {
|
|
953
|
+
const expected = check.allowed_content_types;
|
|
954
|
+
if (!expected?.length) return true;
|
|
955
|
+
const actual = normalizeLinkStatusContentType(stringValue(result.content_type));
|
|
956
|
+
if (!actual) return false;
|
|
957
|
+
return expected.some((contentType) => {
|
|
958
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
959
|
+
if (!normalized) return false;
|
|
960
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
961
|
+
return actual === normalized;
|
|
962
|
+
});
|
|
963
|
+
}
|
|
935
964
|
function linkStatusResultOk(result, check) {
|
|
936
965
|
const status = numberValue(result.status);
|
|
937
966
|
if (!linkStatusIsAllowed(status, check)) return false;
|
|
938
967
|
if (stringValue(result.error)) return false;
|
|
939
968
|
if (result.ok === false) return false;
|
|
969
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
940
970
|
if (check.require_nonzero_bytes) {
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
971
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
972
|
+
if (observedBytes === void 0 || observedBytes <= 0) return false;
|
|
973
|
+
}
|
|
974
|
+
if (check.min_bytes !== void 0) {
|
|
975
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
976
|
+
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
944
977
|
}
|
|
945
978
|
return true;
|
|
946
979
|
}
|
|
@@ -969,7 +1002,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
969
1002
|
status: numberValue(result.status) ?? null,
|
|
970
1003
|
method: stringValue(result.method) ?? null,
|
|
971
1004
|
error: stringValue(result.error) ?? null,
|
|
972
|
-
|
|
1005
|
+
content_type: stringValue(result.content_type) ?? null,
|
|
1006
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
1007
|
+
min_bytes: check.min_bytes ?? null,
|
|
1008
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
973
1009
|
}));
|
|
974
1010
|
if (stringValue(linkEvidence.error)) {
|
|
975
1011
|
failures.push({ code: "link_status_capture_failed", error: stringValue(linkEvidence.error) ?? "" });
|
|
@@ -1006,6 +1042,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1006
1042
|
failed_count: failures.length,
|
|
1007
1043
|
truncated: linkEvidence.truncated === true,
|
|
1008
1044
|
max_links: numberValue(linkEvidence.max_links) ?? check.max_links ?? 100,
|
|
1045
|
+
min_bytes: check.min_bytes ?? null,
|
|
1046
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1009
1047
|
status_counts: statusCounts,
|
|
1010
1048
|
failures: failures.slice(0, 20)
|
|
1011
1049
|
};
|
|
@@ -1534,6 +1572,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1534
1572
|
min_count: check.min_count ?? null,
|
|
1535
1573
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1536
1574
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1575
|
+
min_bytes: check.min_bytes ?? null,
|
|
1576
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1537
1577
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1538
1578
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1539
1579
|
},
|
|
@@ -2137,15 +2177,41 @@ function linkStatusIsAllowed(status, check) {
|
|
|
2137
2177
|
const allowed = linkStatusAllowedStatuses(check);
|
|
2138
2178
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2139
2179
|
}
|
|
2180
|
+
function linkStatusObservedBytes(result) {
|
|
2181
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2182
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
2183
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
2184
|
+
return observed > 0 ? observed : undefined;
|
|
2185
|
+
}
|
|
2186
|
+
function normalizeLinkStatusContentType(value) {
|
|
2187
|
+
if (typeof value !== "string") return undefined;
|
|
2188
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
2189
|
+
return normalized || undefined;
|
|
2190
|
+
}
|
|
2191
|
+
function linkStatusContentTypeOk(result, check) {
|
|
2192
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
2193
|
+
const actual = normalizeLinkStatusContentType(result.content_type);
|
|
2194
|
+
if (!actual) return false;
|
|
2195
|
+
return check.allowed_content_types.some((contentType) => {
|
|
2196
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
2197
|
+
if (!normalized) return false;
|
|
2198
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
2199
|
+
return actual === normalized;
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2140
2202
|
function linkStatusResultOk(result, check) {
|
|
2141
2203
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2142
2204
|
if (!linkStatusIsAllowed(result.status, check)) return false;
|
|
2143
2205
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2144
2206
|
if (result.ok === false) return false;
|
|
2207
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
2145
2208
|
if (check.require_nonzero_bytes === true) {
|
|
2146
|
-
const
|
|
2147
|
-
|
|
2148
|
-
|
|
2209
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2210
|
+
if (observedBytes === undefined || observedBytes <= 0) return false;
|
|
2211
|
+
}
|
|
2212
|
+
if (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) {
|
|
2213
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2214
|
+
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
2149
2215
|
}
|
|
2150
2216
|
return true;
|
|
2151
2217
|
}
|
|
@@ -2173,11 +2239,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2173
2239
|
status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
|
|
2174
2240
|
method: typeof result.method === "string" ? result.method : null,
|
|
2175
2241
|
error: typeof result.error === "string" ? result.error : null,
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
: null,
|
|
2242
|
+
content_type: typeof result.content_type === "string" ? result.content_type : null,
|
|
2243
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
2244
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2245
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2181
2246
|
}));
|
|
2182
2247
|
if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
|
|
2183
2248
|
failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
|
|
@@ -2212,6 +2277,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2212
2277
|
failed_count: failures.length,
|
|
2213
2278
|
truncated: linkEvidence.truncated === true,
|
|
2214
2279
|
max_links: typeof linkEvidence.max_links === "number" ? linkEvidence.max_links : check.max_links || 100,
|
|
2280
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2281
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2215
2282
|
status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
|
|
2216
2283
|
failures: failures.slice(0, 20),
|
|
2217
2284
|
};
|
|
@@ -2908,6 +2975,8 @@ function assessProfile(profile, evidence) {
|
|
|
2908
2975
|
min_count: check.min_count ?? null,
|
|
2909
2976
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2910
2977
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
2978
|
+
min_bytes: check.min_bytes ?? null,
|
|
2979
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
2911
2980
|
viewports: summaries,
|
|
2912
2981
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
2913
2982
|
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
@@ -4055,6 +4124,28 @@ function linkProbeAllowed(status, check) {
|
|
|
4055
4124
|
: null;
|
|
4056
4125
|
return allowed ? allowed.includes(status) : status >= 200 && status < 400;
|
|
4057
4126
|
}
|
|
4127
|
+
function linkProbeObservedBytes(result) {
|
|
4128
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
4129
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
4130
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
4131
|
+
return observed > 0 ? observed : undefined;
|
|
4132
|
+
}
|
|
4133
|
+
function normalizeLinkProbeContentType(value) {
|
|
4134
|
+
if (typeof value !== "string") return undefined;
|
|
4135
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
4136
|
+
return normalized || undefined;
|
|
4137
|
+
}
|
|
4138
|
+
function linkProbeContentTypeAllowed(result, check) {
|
|
4139
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
4140
|
+
const actual = normalizeLinkProbeContentType(result.content_type);
|
|
4141
|
+
if (!actual) return false;
|
|
4142
|
+
return check.allowed_content_types.some((contentType) => {
|
|
4143
|
+
const normalized = normalizeLinkProbeContentType(contentType);
|
|
4144
|
+
if (!normalized) return false;
|
|
4145
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
4146
|
+
return actual === normalized;
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4058
4149
|
function linkProbeResponseFields(response, method) {
|
|
4059
4150
|
const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
|
|
4060
4151
|
const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
|
|
@@ -4069,6 +4160,7 @@ function linkProbeResponseFields(response, method) {
|
|
|
4069
4160
|
}
|
|
4070
4161
|
async function probeLinkStatus(candidate, check) {
|
|
4071
4162
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4163
|
+
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
4072
4164
|
const allowGetFallback = check.allow_get_fallback !== false;
|
|
4073
4165
|
const result = {
|
|
4074
4166
|
url: candidate.url,
|
|
@@ -4086,7 +4178,7 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4086
4178
|
};
|
|
4087
4179
|
const applyResponse = async (response, method, readBytes) => {
|
|
4088
4180
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4089
|
-
if (readBytes
|
|
4181
|
+
if (readBytes) {
|
|
4090
4182
|
try {
|
|
4091
4183
|
const buffer = await response.arrayBuffer();
|
|
4092
4184
|
result.bytes = buffer.byteLength;
|
|
@@ -4095,7 +4187,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4095
4187
|
}
|
|
4096
4188
|
}
|
|
4097
4189
|
result.ok = linkProbeAllowed(result.status, check)
|
|
4098
|
-
&& (
|
|
4190
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4191
|
+
&& (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4192
|
+
&& (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
|
|
4099
4193
|
&& !result.error;
|
|
4100
4194
|
};
|
|
4101
4195
|
try {
|
|
@@ -4112,9 +4206,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4112
4206
|
method: "GET",
|
|
4113
4207
|
redirect: "follow",
|
|
4114
4208
|
cache: "no-store",
|
|
4115
|
-
headers: { Range: "bytes=0-
|
|
4209
|
+
headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
|
|
4116
4210
|
});
|
|
4117
|
-
await applyResponse(response, "GET", requireNonzeroBytes);
|
|
4211
|
+
await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
|
|
4118
4212
|
return result;
|
|
4119
4213
|
} catch (error) {
|
|
4120
4214
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -4168,7 +4262,10 @@ async function collectLinkStatus(check) {
|
|
|
4168
4262
|
status: result.status,
|
|
4169
4263
|
method: result.method,
|
|
4170
4264
|
error: result.error,
|
|
4171
|
-
|
|
4265
|
+
content_type: result.content_type,
|
|
4266
|
+
bytes: linkProbeObservedBytes(result) || null,
|
|
4267
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4268
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4172
4269
|
}));
|
|
4173
4270
|
const statusCounts = {};
|
|
4174
4271
|
for (const result of results) {
|
|
@@ -4182,6 +4279,8 @@ async function collectLinkStatus(check) {
|
|
|
4182
4279
|
same_origin_only: linkProbeSameOriginOnly(check),
|
|
4183
4280
|
dedupe: linkProbeDedupe(check),
|
|
4184
4281
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4282
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4283
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4185
4284
|
allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
|
|
4186
4285
|
? check.allowed_statuses
|
|
4187
4286
|
: typeof check.expected_status === "number"
|
package/dist/cli.cjs
CHANGED
|
@@ -7678,6 +7678,12 @@ function normalizeCheck(input, index) {
|
|
|
7678
7678
|
`checks[${index}] allowed_statuses`
|
|
7679
7679
|
) : void 0;
|
|
7680
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;
|
|
7681
7687
|
if (isLinkStatusCheck) {
|
|
7682
7688
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
7683
7689
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -7719,6 +7725,8 @@ function normalizeCheck(input, index) {
|
|
|
7719
7725
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
7720
7726
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
7721
7727
|
require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
7728
|
+
min_bytes: minBytes,
|
|
7729
|
+
allowed_content_types: allowedContentTypes,
|
|
7722
7730
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
7723
7731
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
7724
7732
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -7825,15 +7833,40 @@ function linkStatusIsAllowed(status, check) {
|
|
|
7825
7833
|
const allowed = linkStatusAllowedStatuses(check);
|
|
7826
7834
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
7827
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
|
+
}
|
|
7828
7857
|
function linkStatusResultOk(result, check) {
|
|
7829
7858
|
const status = numberValue(result.status);
|
|
7830
7859
|
if (!linkStatusIsAllowed(status, check)) return false;
|
|
7831
7860
|
if (stringValue2(result.error)) return false;
|
|
7832
7861
|
if (result.ok === false) return false;
|
|
7862
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
7833
7863
|
if (check.require_nonzero_bytes) {
|
|
7834
|
-
const
|
|
7835
|
-
|
|
7836
|
-
|
|
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;
|
|
7837
7870
|
}
|
|
7838
7871
|
return true;
|
|
7839
7872
|
}
|
|
@@ -7862,7 +7895,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
7862
7895
|
status: numberValue(result.status) ?? null,
|
|
7863
7896
|
method: stringValue2(result.method) ?? null,
|
|
7864
7897
|
error: stringValue2(result.error) ?? null,
|
|
7865
|
-
|
|
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
|
|
7866
7902
|
}));
|
|
7867
7903
|
if (stringValue2(linkEvidence.error)) {
|
|
7868
7904
|
failures.push({ code: "link_status_capture_failed", error: stringValue2(linkEvidence.error) ?? "" });
|
|
@@ -7899,6 +7935,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
7899
7935
|
failed_count: failures.length,
|
|
7900
7936
|
truncated: linkEvidence.truncated === true,
|
|
7901
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,
|
|
7902
7940
|
status_counts: statusCounts,
|
|
7903
7941
|
failures: failures.slice(0, 20)
|
|
7904
7942
|
};
|
|
@@ -8427,6 +8465,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
8427
8465
|
min_count: check.min_count ?? null,
|
|
8428
8466
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
8429
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,
|
|
8430
8470
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
8431
8471
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
|
|
8432
8472
|
},
|
|
@@ -9014,15 +9054,41 @@ function linkStatusIsAllowed(status, check) {
|
|
|
9014
9054
|
const allowed = linkStatusAllowedStatuses(check);
|
|
9015
9055
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
9016
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
|
+
}
|
|
9017
9079
|
function linkStatusResultOk(result, check) {
|
|
9018
9080
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
9019
9081
|
if (!linkStatusIsAllowed(result.status, check)) return false;
|
|
9020
9082
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
9021
9083
|
if (result.ok === false) return false;
|
|
9084
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
9022
9085
|
if (check.require_nonzero_bytes === true) {
|
|
9023
|
-
const
|
|
9024
|
-
|
|
9025
|
-
|
|
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;
|
|
9026
9092
|
}
|
|
9027
9093
|
return true;
|
|
9028
9094
|
}
|
|
@@ -9050,11 +9116,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
9050
9116
|
status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
|
|
9051
9117
|
method: typeof result.method === "string" ? result.method : null,
|
|
9052
9118
|
error: typeof result.error === "string" ? result.error : null,
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
: 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,
|
|
9058
9123
|
}));
|
|
9059
9124
|
if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
|
|
9060
9125
|
failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
|
|
@@ -9089,6 +9154,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
9089
9154
|
failed_count: failures.length,
|
|
9090
9155
|
truncated: linkEvidence.truncated === true,
|
|
9091
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,
|
|
9092
9159
|
status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
|
|
9093
9160
|
failures: failures.slice(0, 20),
|
|
9094
9161
|
};
|
|
@@ -9785,6 +9852,8 @@ function assessProfile(profile, evidence) {
|
|
|
9785
9852
|
min_count: check.min_count ?? null,
|
|
9786
9853
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
9787
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,
|
|
9788
9857
|
viewports: summaries,
|
|
9789
9858
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
9790
9859
|
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
@@ -10932,6 +11001,28 @@ function linkProbeAllowed(status, check) {
|
|
|
10932
11001
|
: null;
|
|
10933
11002
|
return allowed ? allowed.includes(status) : status >= 200 && status < 400;
|
|
10934
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
|
+
}
|
|
10935
11026
|
function linkProbeResponseFields(response, method) {
|
|
10936
11027
|
const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
|
|
10937
11028
|
const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
|
|
@@ -10946,6 +11037,7 @@ function linkProbeResponseFields(response, method) {
|
|
|
10946
11037
|
}
|
|
10947
11038
|
async function probeLinkStatus(candidate, check) {
|
|
10948
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;
|
|
10949
11041
|
const allowGetFallback = check.allow_get_fallback !== false;
|
|
10950
11042
|
const result = {
|
|
10951
11043
|
url: candidate.url,
|
|
@@ -10963,7 +11055,7 @@ async function probeLinkStatus(candidate, check) {
|
|
|
10963
11055
|
};
|
|
10964
11056
|
const applyResponse = async (response, method, readBytes) => {
|
|
10965
11057
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
10966
|
-
if (readBytes
|
|
11058
|
+
if (readBytes) {
|
|
10967
11059
|
try {
|
|
10968
11060
|
const buffer = await response.arrayBuffer();
|
|
10969
11061
|
result.bytes = buffer.byteLength;
|
|
@@ -10972,7 +11064,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
10972
11064
|
}
|
|
10973
11065
|
}
|
|
10974
11066
|
result.ok = linkProbeAllowed(result.status, check)
|
|
10975
|
-
&& (
|
|
11067
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
11068
|
+
&& (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
11069
|
+
&& (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
|
|
10976
11070
|
&& !result.error;
|
|
10977
11071
|
};
|
|
10978
11072
|
try {
|
|
@@ -10989,9 +11083,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
10989
11083
|
method: "GET",
|
|
10990
11084
|
redirect: "follow",
|
|
10991
11085
|
cache: "no-store",
|
|
10992
|
-
headers: { Range: "bytes=0-
|
|
11086
|
+
headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
|
|
10993
11087
|
});
|
|
10994
|
-
await applyResponse(response, "GET", requireNonzeroBytes);
|
|
11088
|
+
await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
|
|
10995
11089
|
return result;
|
|
10996
11090
|
} catch (error) {
|
|
10997
11091
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -11045,7 +11139,10 @@ async function collectLinkStatus(check) {
|
|
|
11045
11139
|
status: result.status,
|
|
11046
11140
|
method: result.method,
|
|
11047
11141
|
error: result.error,
|
|
11048
|
-
|
|
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,
|
|
11049
11146
|
}));
|
|
11050
11147
|
const statusCounts = {};
|
|
11051
11148
|
for (const result of results) {
|
|
@@ -11059,6 +11156,8 @@ async function collectLinkStatus(check) {
|
|
|
11059
11156
|
same_origin_only: linkProbeSameOriginOnly(check),
|
|
11060
11157
|
dedupe: linkProbeDedupe(check),
|
|
11061
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,
|
|
11062
11161
|
allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
|
|
11063
11162
|
? check.allowed_statuses
|
|
11064
11163
|
: typeof check.expected_status === "number"
|
|
@@ -12279,9 +12378,13 @@ function profileCheckMarkdownTarget(check) {
|
|
|
12279
12378
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
12280
12379
|
const expectedCount = cliFiniteNumber(evidence.expected_count);
|
|
12281
12380
|
const minCount = cliFiniteNumber(evidence.min_count);
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
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;
|
|
12285
12388
|
}
|
|
12286
12389
|
if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
|
|
12287
12390
|
const maxOverflow = cliFiniteNumber(evidence.max_overflow_px);
|
|
@@ -12489,9 +12592,11 @@ function profileLinkStatusSummaryMarkdown(result) {
|
|
|
12489
12592
|
const okTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.ok_count) || 0), 0);
|
|
12490
12593
|
const failedTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.failed_count) || 0), 0);
|
|
12491
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)) : [];
|
|
12492
12597
|
const countText = totals.length ? totals.join("/") : "unknown";
|
|
12493
12598
|
lines.push(
|
|
12494
|
-
`- ${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(", ")}` : ""}`
|
|
12495
12600
|
);
|
|
12496
12601
|
}
|
|
12497
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-
|
|
13
|
+
} from "./chunk-5OCJKEHE.js";
|
|
14
14
|
import {
|
|
15
15
|
createRiddleApiClient,
|
|
16
16
|
parseRiddleViewport
|
|
@@ -423,9 +423,13 @@ function profileCheckMarkdownTarget(check) {
|
|
|
423
423
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
424
424
|
const expectedCount = cliFiniteNumber(evidence.expected_count);
|
|
425
425
|
const minCount = cliFiniteNumber(evidence.min_count);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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;
|
|
429
433
|
}
|
|
430
434
|
if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
|
|
431
435
|
const maxOverflow = cliFiniteNumber(evidence.max_overflow_px);
|
|
@@ -633,9 +637,11 @@ function profileLinkStatusSummaryMarkdown(result) {
|
|
|
633
637
|
const okTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.ok_count) || 0), 0);
|
|
634
638
|
const failedTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.failed_count) || 0), 0);
|
|
635
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)) : [];
|
|
636
642
|
const countText = totals.length ? totals.join("/") : "unknown";
|
|
637
643
|
lines.push(
|
|
638
|
-
`- ${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(", ")}` : ""}`
|
|
639
645
|
);
|
|
640
646
|
}
|
|
641
647
|
return lines;
|
package/dist/index.cjs
CHANGED
|
@@ -9514,6 +9514,12 @@ function normalizeCheck(input, index) {
|
|
|
9514
9514
|
`checks[${index}] allowed_statuses`
|
|
9515
9515
|
) : void 0;
|
|
9516
9516
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
9517
|
+
const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
9518
|
+
const expectedContentType = isLinkStatusCheck ? stringValue5(input.expected_content_type) || stringValue5(input.expectedContentType) || stringValue5(input.content_type) || stringValue5(input.contentType) : void 0;
|
|
9519
|
+
const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
|
|
9520
|
+
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
9521
|
+
`checks[${index}] allowed_content_types`
|
|
9522
|
+
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
9517
9523
|
if (isLinkStatusCheck) {
|
|
9518
9524
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
9519
9525
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -9555,6 +9561,8 @@ function normalizeCheck(input, index) {
|
|
|
9555
9561
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
9556
9562
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
9557
9563
|
require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
9564
|
+
min_bytes: minBytes,
|
|
9565
|
+
allowed_content_types: allowedContentTypes,
|
|
9558
9566
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
9559
9567
|
max_overflow_px: numberValue3(input.max_overflow_px),
|
|
9560
9568
|
timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
|
|
@@ -9661,15 +9669,40 @@ function linkStatusIsAllowed(status, check) {
|
|
|
9661
9669
|
const allowed = linkStatusAllowedStatuses(check);
|
|
9662
9670
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
9663
9671
|
}
|
|
9672
|
+
function linkStatusObservedBytes(result) {
|
|
9673
|
+
const bytes = numberValue3(result.bytes);
|
|
9674
|
+
const contentLength = numberValue3(result.content_length);
|
|
9675
|
+
return Math.max(bytes ?? 0, contentLength ?? 0) || void 0;
|
|
9676
|
+
}
|
|
9677
|
+
function normalizeLinkStatusContentType(value) {
|
|
9678
|
+
const normalized = value?.split(";")[0]?.trim().toLowerCase();
|
|
9679
|
+
return normalized || void 0;
|
|
9680
|
+
}
|
|
9681
|
+
function linkStatusContentTypeOk(result, check) {
|
|
9682
|
+
const expected = check.allowed_content_types;
|
|
9683
|
+
if (!expected?.length) return true;
|
|
9684
|
+
const actual = normalizeLinkStatusContentType(stringValue5(result.content_type));
|
|
9685
|
+
if (!actual) return false;
|
|
9686
|
+
return expected.some((contentType) => {
|
|
9687
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
9688
|
+
if (!normalized) return false;
|
|
9689
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
9690
|
+
return actual === normalized;
|
|
9691
|
+
});
|
|
9692
|
+
}
|
|
9664
9693
|
function linkStatusResultOk(result, check) {
|
|
9665
9694
|
const status = numberValue3(result.status);
|
|
9666
9695
|
if (!linkStatusIsAllowed(status, check)) return false;
|
|
9667
9696
|
if (stringValue5(result.error)) return false;
|
|
9668
9697
|
if (result.ok === false) return false;
|
|
9698
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
9669
9699
|
if (check.require_nonzero_bytes) {
|
|
9670
|
-
const
|
|
9671
|
-
|
|
9672
|
-
|
|
9700
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
9701
|
+
if (observedBytes === void 0 || observedBytes <= 0) return false;
|
|
9702
|
+
}
|
|
9703
|
+
if (check.min_bytes !== void 0) {
|
|
9704
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
9705
|
+
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
9673
9706
|
}
|
|
9674
9707
|
return true;
|
|
9675
9708
|
}
|
|
@@ -9698,7 +9731,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
9698
9731
|
status: numberValue3(result.status) ?? null,
|
|
9699
9732
|
method: stringValue5(result.method) ?? null,
|
|
9700
9733
|
error: stringValue5(result.error) ?? null,
|
|
9701
|
-
|
|
9734
|
+
content_type: stringValue5(result.content_type) ?? null,
|
|
9735
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
9736
|
+
min_bytes: check.min_bytes ?? null,
|
|
9737
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
9702
9738
|
}));
|
|
9703
9739
|
if (stringValue5(linkEvidence.error)) {
|
|
9704
9740
|
failures.push({ code: "link_status_capture_failed", error: stringValue5(linkEvidence.error) ?? "" });
|
|
@@ -9735,6 +9771,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
9735
9771
|
failed_count: failures.length,
|
|
9736
9772
|
truncated: linkEvidence.truncated === true,
|
|
9737
9773
|
max_links: numberValue3(linkEvidence.max_links) ?? check.max_links ?? 100,
|
|
9774
|
+
min_bytes: check.min_bytes ?? null,
|
|
9775
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
9738
9776
|
status_counts: statusCounts,
|
|
9739
9777
|
failures: failures.slice(0, 20)
|
|
9740
9778
|
};
|
|
@@ -10263,6 +10301,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
10263
10301
|
min_count: check.min_count ?? null,
|
|
10264
10302
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
10265
10303
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
10304
|
+
min_bytes: check.min_bytes ?? null,
|
|
10305
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
10266
10306
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
10267
10307
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue5(summary.viewport) ?? null, failure })) : [])
|
|
10268
10308
|
},
|
|
@@ -10866,15 +10906,41 @@ function linkStatusIsAllowed(status, check) {
|
|
|
10866
10906
|
const allowed = linkStatusAllowedStatuses(check);
|
|
10867
10907
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
10868
10908
|
}
|
|
10909
|
+
function linkStatusObservedBytes(result) {
|
|
10910
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
10911
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
10912
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
10913
|
+
return observed > 0 ? observed : undefined;
|
|
10914
|
+
}
|
|
10915
|
+
function normalizeLinkStatusContentType(value) {
|
|
10916
|
+
if (typeof value !== "string") return undefined;
|
|
10917
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
10918
|
+
return normalized || undefined;
|
|
10919
|
+
}
|
|
10920
|
+
function linkStatusContentTypeOk(result, check) {
|
|
10921
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
10922
|
+
const actual = normalizeLinkStatusContentType(result.content_type);
|
|
10923
|
+
if (!actual) return false;
|
|
10924
|
+
return check.allowed_content_types.some((contentType) => {
|
|
10925
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
10926
|
+
if (!normalized) return false;
|
|
10927
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
10928
|
+
return actual === normalized;
|
|
10929
|
+
});
|
|
10930
|
+
}
|
|
10869
10931
|
function linkStatusResultOk(result, check) {
|
|
10870
10932
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
10871
10933
|
if (!linkStatusIsAllowed(result.status, check)) return false;
|
|
10872
10934
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
10873
10935
|
if (result.ok === false) return false;
|
|
10936
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
10874
10937
|
if (check.require_nonzero_bytes === true) {
|
|
10875
|
-
const
|
|
10876
|
-
|
|
10877
|
-
|
|
10938
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
10939
|
+
if (observedBytes === undefined || observedBytes <= 0) return false;
|
|
10940
|
+
}
|
|
10941
|
+
if (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) {
|
|
10942
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
10943
|
+
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
10878
10944
|
}
|
|
10879
10945
|
return true;
|
|
10880
10946
|
}
|
|
@@ -10902,11 +10968,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
10902
10968
|
status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
|
|
10903
10969
|
method: typeof result.method === "string" ? result.method : null,
|
|
10904
10970
|
error: typeof result.error === "string" ? result.error : null,
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
: null,
|
|
10971
|
+
content_type: typeof result.content_type === "string" ? result.content_type : null,
|
|
10972
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
10973
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
10974
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
10910
10975
|
}));
|
|
10911
10976
|
if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
|
|
10912
10977
|
failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
|
|
@@ -10941,6 +11006,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
10941
11006
|
failed_count: failures.length,
|
|
10942
11007
|
truncated: linkEvidence.truncated === true,
|
|
10943
11008
|
max_links: typeof linkEvidence.max_links === "number" ? linkEvidence.max_links : check.max_links || 100,
|
|
11009
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
11010
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
10944
11011
|
status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
|
|
10945
11012
|
failures: failures.slice(0, 20),
|
|
10946
11013
|
};
|
|
@@ -11637,6 +11704,8 @@ function assessProfile(profile, evidence) {
|
|
|
11637
11704
|
min_count: check.min_count ?? null,
|
|
11638
11705
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
11639
11706
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
11707
|
+
min_bytes: check.min_bytes ?? null,
|
|
11708
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
11640
11709
|
viewports: summaries,
|
|
11641
11710
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
11642
11711
|
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
@@ -12784,6 +12853,28 @@ function linkProbeAllowed(status, check) {
|
|
|
12784
12853
|
: null;
|
|
12785
12854
|
return allowed ? allowed.includes(status) : status >= 200 && status < 400;
|
|
12786
12855
|
}
|
|
12856
|
+
function linkProbeObservedBytes(result) {
|
|
12857
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
12858
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
12859
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
12860
|
+
return observed > 0 ? observed : undefined;
|
|
12861
|
+
}
|
|
12862
|
+
function normalizeLinkProbeContentType(value) {
|
|
12863
|
+
if (typeof value !== "string") return undefined;
|
|
12864
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
12865
|
+
return normalized || undefined;
|
|
12866
|
+
}
|
|
12867
|
+
function linkProbeContentTypeAllowed(result, check) {
|
|
12868
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
12869
|
+
const actual = normalizeLinkProbeContentType(result.content_type);
|
|
12870
|
+
if (!actual) return false;
|
|
12871
|
+
return check.allowed_content_types.some((contentType) => {
|
|
12872
|
+
const normalized = normalizeLinkProbeContentType(contentType);
|
|
12873
|
+
if (!normalized) return false;
|
|
12874
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
12875
|
+
return actual === normalized;
|
|
12876
|
+
});
|
|
12877
|
+
}
|
|
12787
12878
|
function linkProbeResponseFields(response, method) {
|
|
12788
12879
|
const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
|
|
12789
12880
|
const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
|
|
@@ -12798,6 +12889,7 @@ function linkProbeResponseFields(response, method) {
|
|
|
12798
12889
|
}
|
|
12799
12890
|
async function probeLinkStatus(candidate, check) {
|
|
12800
12891
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
12892
|
+
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
12801
12893
|
const allowGetFallback = check.allow_get_fallback !== false;
|
|
12802
12894
|
const result = {
|
|
12803
12895
|
url: candidate.url,
|
|
@@ -12815,7 +12907,7 @@ async function probeLinkStatus(candidate, check) {
|
|
|
12815
12907
|
};
|
|
12816
12908
|
const applyResponse = async (response, method, readBytes) => {
|
|
12817
12909
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
12818
|
-
if (readBytes
|
|
12910
|
+
if (readBytes) {
|
|
12819
12911
|
try {
|
|
12820
12912
|
const buffer = await response.arrayBuffer();
|
|
12821
12913
|
result.bytes = buffer.byteLength;
|
|
@@ -12824,7 +12916,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
12824
12916
|
}
|
|
12825
12917
|
}
|
|
12826
12918
|
result.ok = linkProbeAllowed(result.status, check)
|
|
12827
|
-
&& (
|
|
12919
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
12920
|
+
&& (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
12921
|
+
&& (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
|
|
12828
12922
|
&& !result.error;
|
|
12829
12923
|
};
|
|
12830
12924
|
try {
|
|
@@ -12841,9 +12935,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
12841
12935
|
method: "GET",
|
|
12842
12936
|
redirect: "follow",
|
|
12843
12937
|
cache: "no-store",
|
|
12844
|
-
headers: { Range: "bytes=0-
|
|
12938
|
+
headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
|
|
12845
12939
|
});
|
|
12846
|
-
await applyResponse(response, "GET", requireNonzeroBytes);
|
|
12940
|
+
await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
|
|
12847
12941
|
return result;
|
|
12848
12942
|
} catch (error) {
|
|
12849
12943
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -12897,7 +12991,10 @@ async function collectLinkStatus(check) {
|
|
|
12897
12991
|
status: result.status,
|
|
12898
12992
|
method: result.method,
|
|
12899
12993
|
error: result.error,
|
|
12900
|
-
|
|
12994
|
+
content_type: result.content_type,
|
|
12995
|
+
bytes: linkProbeObservedBytes(result) || null,
|
|
12996
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
12997
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
12901
12998
|
}));
|
|
12902
12999
|
const statusCounts = {};
|
|
12903
13000
|
for (const result of results) {
|
|
@@ -12911,6 +13008,8 @@ async function collectLinkStatus(check) {
|
|
|
12911
13008
|
same_origin_only: linkProbeSameOriginOnly(check),
|
|
12912
13009
|
dedupe: linkProbeDedupe(check),
|
|
12913
13010
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
13011
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
13012
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
12914
13013
|
allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
|
|
12915
13014
|
? check.allowed_statuses
|
|
12916
13015
|
: typeof check.expected_status === "number"
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
resolveRiddleProofProfileTimeoutSec,
|
|
59
59
|
slugifyRiddleProofProfileName,
|
|
60
60
|
summarizeRiddleProofProfileResult
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-5OCJKEHE.js";
|
|
62
62
|
import {
|
|
63
63
|
DEFAULT_RIDDLE_API_BASE_URL,
|
|
64
64
|
DEFAULT_RIDDLE_API_KEY_FILE,
|
package/dist/profile.cjs
CHANGED
|
@@ -828,6 +828,12 @@ function normalizeCheck(input, index) {
|
|
|
828
828
|
`checks[${index}] allowed_statuses`
|
|
829
829
|
) : void 0;
|
|
830
830
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
831
|
+
const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
832
|
+
const expectedContentType = isLinkStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
|
|
833
|
+
const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
|
|
834
|
+
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
835
|
+
`checks[${index}] allowed_content_types`
|
|
836
|
+
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
831
837
|
if (isLinkStatusCheck) {
|
|
832
838
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
833
839
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -869,6 +875,8 @@ function normalizeCheck(input, index) {
|
|
|
869
875
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
870
876
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
871
877
|
require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
878
|
+
min_bytes: minBytes,
|
|
879
|
+
allowed_content_types: allowedContentTypes,
|
|
872
880
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
873
881
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
874
882
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -975,15 +983,40 @@ function linkStatusIsAllowed(status, check) {
|
|
|
975
983
|
const allowed = linkStatusAllowedStatuses(check);
|
|
976
984
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
977
985
|
}
|
|
986
|
+
function linkStatusObservedBytes(result) {
|
|
987
|
+
const bytes = numberValue(result.bytes);
|
|
988
|
+
const contentLength = numberValue(result.content_length);
|
|
989
|
+
return Math.max(bytes ?? 0, contentLength ?? 0) || void 0;
|
|
990
|
+
}
|
|
991
|
+
function normalizeLinkStatusContentType(value) {
|
|
992
|
+
const normalized = value?.split(";")[0]?.trim().toLowerCase();
|
|
993
|
+
return normalized || void 0;
|
|
994
|
+
}
|
|
995
|
+
function linkStatusContentTypeOk(result, check) {
|
|
996
|
+
const expected = check.allowed_content_types;
|
|
997
|
+
if (!expected?.length) return true;
|
|
998
|
+
const actual = normalizeLinkStatusContentType(stringValue(result.content_type));
|
|
999
|
+
if (!actual) return false;
|
|
1000
|
+
return expected.some((contentType) => {
|
|
1001
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
1002
|
+
if (!normalized) return false;
|
|
1003
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
1004
|
+
return actual === normalized;
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
978
1007
|
function linkStatusResultOk(result, check) {
|
|
979
1008
|
const status = numberValue(result.status);
|
|
980
1009
|
if (!linkStatusIsAllowed(status, check)) return false;
|
|
981
1010
|
if (stringValue(result.error)) return false;
|
|
982
1011
|
if (result.ok === false) return false;
|
|
1012
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
983
1013
|
if (check.require_nonzero_bytes) {
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
1014
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
1015
|
+
if (observedBytes === void 0 || observedBytes <= 0) return false;
|
|
1016
|
+
}
|
|
1017
|
+
if (check.min_bytes !== void 0) {
|
|
1018
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
1019
|
+
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
987
1020
|
}
|
|
988
1021
|
return true;
|
|
989
1022
|
}
|
|
@@ -1012,7 +1045,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1012
1045
|
status: numberValue(result.status) ?? null,
|
|
1013
1046
|
method: stringValue(result.method) ?? null,
|
|
1014
1047
|
error: stringValue(result.error) ?? null,
|
|
1015
|
-
|
|
1048
|
+
content_type: stringValue(result.content_type) ?? null,
|
|
1049
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
1050
|
+
min_bytes: check.min_bytes ?? null,
|
|
1051
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
1016
1052
|
}));
|
|
1017
1053
|
if (stringValue(linkEvidence.error)) {
|
|
1018
1054
|
failures.push({ code: "link_status_capture_failed", error: stringValue(linkEvidence.error) ?? "" });
|
|
@@ -1049,6 +1085,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1049
1085
|
failed_count: failures.length,
|
|
1050
1086
|
truncated: linkEvidence.truncated === true,
|
|
1051
1087
|
max_links: numberValue(linkEvidence.max_links) ?? check.max_links ?? 100,
|
|
1088
|
+
min_bytes: check.min_bytes ?? null,
|
|
1089
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1052
1090
|
status_counts: statusCounts,
|
|
1053
1091
|
failures: failures.slice(0, 20)
|
|
1054
1092
|
};
|
|
@@ -1577,6 +1615,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1577
1615
|
min_count: check.min_count ?? null,
|
|
1578
1616
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1579
1617
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1618
|
+
min_bytes: check.min_bytes ?? null,
|
|
1619
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1580
1620
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1581
1621
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1582
1622
|
},
|
|
@@ -2180,15 +2220,41 @@ function linkStatusIsAllowed(status, check) {
|
|
|
2180
2220
|
const allowed = linkStatusAllowedStatuses(check);
|
|
2181
2221
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2182
2222
|
}
|
|
2223
|
+
function linkStatusObservedBytes(result) {
|
|
2224
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2225
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
2226
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
2227
|
+
return observed > 0 ? observed : undefined;
|
|
2228
|
+
}
|
|
2229
|
+
function normalizeLinkStatusContentType(value) {
|
|
2230
|
+
if (typeof value !== "string") return undefined;
|
|
2231
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
2232
|
+
return normalized || undefined;
|
|
2233
|
+
}
|
|
2234
|
+
function linkStatusContentTypeOk(result, check) {
|
|
2235
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
2236
|
+
const actual = normalizeLinkStatusContentType(result.content_type);
|
|
2237
|
+
if (!actual) return false;
|
|
2238
|
+
return check.allowed_content_types.some((contentType) => {
|
|
2239
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
2240
|
+
if (!normalized) return false;
|
|
2241
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
2242
|
+
return actual === normalized;
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2183
2245
|
function linkStatusResultOk(result, check) {
|
|
2184
2246
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2185
2247
|
if (!linkStatusIsAllowed(result.status, check)) return false;
|
|
2186
2248
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2187
2249
|
if (result.ok === false) return false;
|
|
2250
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
2188
2251
|
if (check.require_nonzero_bytes === true) {
|
|
2189
|
-
const
|
|
2190
|
-
|
|
2191
|
-
|
|
2252
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2253
|
+
if (observedBytes === undefined || observedBytes <= 0) return false;
|
|
2254
|
+
}
|
|
2255
|
+
if (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) {
|
|
2256
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2257
|
+
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
2192
2258
|
}
|
|
2193
2259
|
return true;
|
|
2194
2260
|
}
|
|
@@ -2216,11 +2282,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2216
2282
|
status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
|
|
2217
2283
|
method: typeof result.method === "string" ? result.method : null,
|
|
2218
2284
|
error: typeof result.error === "string" ? result.error : null,
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
: null,
|
|
2285
|
+
content_type: typeof result.content_type === "string" ? result.content_type : null,
|
|
2286
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
2287
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2288
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2224
2289
|
}));
|
|
2225
2290
|
if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
|
|
2226
2291
|
failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
|
|
@@ -2255,6 +2320,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2255
2320
|
failed_count: failures.length,
|
|
2256
2321
|
truncated: linkEvidence.truncated === true,
|
|
2257
2322
|
max_links: typeof linkEvidence.max_links === "number" ? linkEvidence.max_links : check.max_links || 100,
|
|
2323
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2324
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2258
2325
|
status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
|
|
2259
2326
|
failures: failures.slice(0, 20),
|
|
2260
2327
|
};
|
|
@@ -2951,6 +3018,8 @@ function assessProfile(profile, evidence) {
|
|
|
2951
3018
|
min_count: check.min_count ?? null,
|
|
2952
3019
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2953
3020
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
3021
|
+
min_bytes: check.min_bytes ?? null,
|
|
3022
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
2954
3023
|
viewports: summaries,
|
|
2955
3024
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
2956
3025
|
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
@@ -4098,6 +4167,28 @@ function linkProbeAllowed(status, check) {
|
|
|
4098
4167
|
: null;
|
|
4099
4168
|
return allowed ? allowed.includes(status) : status >= 200 && status < 400;
|
|
4100
4169
|
}
|
|
4170
|
+
function linkProbeObservedBytes(result) {
|
|
4171
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
4172
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
4173
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
4174
|
+
return observed > 0 ? observed : undefined;
|
|
4175
|
+
}
|
|
4176
|
+
function normalizeLinkProbeContentType(value) {
|
|
4177
|
+
if (typeof value !== "string") return undefined;
|
|
4178
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
4179
|
+
return normalized || undefined;
|
|
4180
|
+
}
|
|
4181
|
+
function linkProbeContentTypeAllowed(result, check) {
|
|
4182
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
4183
|
+
const actual = normalizeLinkProbeContentType(result.content_type);
|
|
4184
|
+
if (!actual) return false;
|
|
4185
|
+
return check.allowed_content_types.some((contentType) => {
|
|
4186
|
+
const normalized = normalizeLinkProbeContentType(contentType);
|
|
4187
|
+
if (!normalized) return false;
|
|
4188
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
4189
|
+
return actual === normalized;
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4101
4192
|
function linkProbeResponseFields(response, method) {
|
|
4102
4193
|
const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
|
|
4103
4194
|
const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
|
|
@@ -4112,6 +4203,7 @@ function linkProbeResponseFields(response, method) {
|
|
|
4112
4203
|
}
|
|
4113
4204
|
async function probeLinkStatus(candidate, check) {
|
|
4114
4205
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4206
|
+
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
4115
4207
|
const allowGetFallback = check.allow_get_fallback !== false;
|
|
4116
4208
|
const result = {
|
|
4117
4209
|
url: candidate.url,
|
|
@@ -4129,7 +4221,7 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4129
4221
|
};
|
|
4130
4222
|
const applyResponse = async (response, method, readBytes) => {
|
|
4131
4223
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4132
|
-
if (readBytes
|
|
4224
|
+
if (readBytes) {
|
|
4133
4225
|
try {
|
|
4134
4226
|
const buffer = await response.arrayBuffer();
|
|
4135
4227
|
result.bytes = buffer.byteLength;
|
|
@@ -4138,7 +4230,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4138
4230
|
}
|
|
4139
4231
|
}
|
|
4140
4232
|
result.ok = linkProbeAllowed(result.status, check)
|
|
4141
|
-
&& (
|
|
4233
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4234
|
+
&& (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4235
|
+
&& (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
|
|
4142
4236
|
&& !result.error;
|
|
4143
4237
|
};
|
|
4144
4238
|
try {
|
|
@@ -4155,9 +4249,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4155
4249
|
method: "GET",
|
|
4156
4250
|
redirect: "follow",
|
|
4157
4251
|
cache: "no-store",
|
|
4158
|
-
headers: { Range: "bytes=0-
|
|
4252
|
+
headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
|
|
4159
4253
|
});
|
|
4160
|
-
await applyResponse(response, "GET", requireNonzeroBytes);
|
|
4254
|
+
await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
|
|
4161
4255
|
return result;
|
|
4162
4256
|
} catch (error) {
|
|
4163
4257
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -4211,7 +4305,10 @@ async function collectLinkStatus(check) {
|
|
|
4211
4305
|
status: result.status,
|
|
4212
4306
|
method: result.method,
|
|
4213
4307
|
error: result.error,
|
|
4214
|
-
|
|
4308
|
+
content_type: result.content_type,
|
|
4309
|
+
bytes: linkProbeObservedBytes(result) || null,
|
|
4310
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4311
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4215
4312
|
}));
|
|
4216
4313
|
const statusCounts = {};
|
|
4217
4314
|
for (const result of results) {
|
|
@@ -4225,6 +4322,8 @@ async function collectLinkStatus(check) {
|
|
|
4225
4322
|
same_origin_only: linkProbeSameOriginOnly(check),
|
|
4226
4323
|
dedupe: linkProbeDedupe(check),
|
|
4227
4324
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4325
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4326
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4228
4327
|
allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
|
|
4229
4328
|
? check.allowed_statuses
|
|
4230
4329
|
: typeof check.expected_status === "number"
|
package/dist/profile.d.cts
CHANGED
|
@@ -138,6 +138,8 @@ interface RiddleProofProfileCheck {
|
|
|
138
138
|
same_origin_only?: boolean;
|
|
139
139
|
dedupe?: boolean;
|
|
140
140
|
require_nonzero_bytes?: boolean;
|
|
141
|
+
min_bytes?: number;
|
|
142
|
+
allowed_content_types?: string[];
|
|
141
143
|
allow_get_fallback?: boolean;
|
|
142
144
|
max_overflow_px?: number;
|
|
143
145
|
timeout_ms?: number;
|
package/dist/profile.d.ts
CHANGED
|
@@ -138,6 +138,8 @@ interface RiddleProofProfileCheck {
|
|
|
138
138
|
same_origin_only?: boolean;
|
|
139
139
|
dedupe?: boolean;
|
|
140
140
|
require_nonzero_bytes?: boolean;
|
|
141
|
+
min_bytes?: number;
|
|
142
|
+
allowed_content_types?: string[];
|
|
141
143
|
allow_get_fallback?: boolean;
|
|
142
144
|
max_overflow_px?: number;
|
|
143
145
|
timeout_ms?: number;
|
package/dist/profile.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveRiddleProofProfileTimeoutSec,
|
|
20
20
|
slugifyRiddleProofProfileName,
|
|
21
21
|
summarizeRiddleProofProfileResult
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-5OCJKEHE.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|