@hyperframes/lint 0.7.20 → 0.7.22
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/browser.js +42 -4
- package/dist/browser.js.map +1 -1
- package/dist/index.js +42 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1058,6 +1058,31 @@ var mediaRules = [
|
|
|
1058
1058
|
}
|
|
1059
1059
|
return findings;
|
|
1060
1060
|
},
|
|
1061
|
+
// media_crossorigin_breaks_preview — `crossorigin` on <video>/<audio> forces a
|
|
1062
|
+
// CORS-checked fetch. The server-side renderer downloads media directly (no CORS),
|
|
1063
|
+
// so it always works there; but Studio preview runs in the browser, where a media
|
|
1064
|
+
// host that omits Access-Control-Allow-Origin silently fails the load — the media
|
|
1065
|
+
// shows BLANK/black in preview while renders look fine, hiding the bug. Plain
|
|
1066
|
+
// displayed media never needs crossorigin; it's only required to read pixels/samples
|
|
1067
|
+
// back (canvas/WebGL texture, WebAudio createMediaElementSource) AND only when the
|
|
1068
|
+
// host is known CORS-enabled.
|
|
1069
|
+
({ tags }) => {
|
|
1070
|
+
const findings = [];
|
|
1071
|
+
for (const tag of tags) {
|
|
1072
|
+
if (tag.name !== "video" && tag.name !== "audio") continue;
|
|
1073
|
+
if (!hasAttrName(tag.raw, "crossorigin")) continue;
|
|
1074
|
+
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
1075
|
+
findings.push({
|
|
1076
|
+
code: "media_crossorigin_breaks_preview",
|
|
1077
|
+
severity: "error",
|
|
1078
|
+
message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has crossorigin, which forces a CORS-checked fetch. If the media host omits Access-Control-Allow-Origin, the load silently fails in Studio preview (media shows BLANK/black) while server-side renders still work \u2014 hiding the bug.`,
|
|
1079
|
+
elementId,
|
|
1080
|
+
fixHint: "Remove the crossorigin attribute unless you read the media back via canvas/WebGL/WebAudio AND the host is known to send CORS headers. Plain displayed media never needs it.",
|
|
1081
|
+
snippet: truncateSnippet(tag.raw)
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return findings;
|
|
1085
|
+
},
|
|
1061
1086
|
// video_audio_double_source — catches audible <video> paired with a separate
|
|
1062
1087
|
// <audio> pointing to the same file, which causes double playback at runtime
|
|
1063
1088
|
({ tags }) => {
|
|
@@ -1102,6 +1127,7 @@ async function loadParseGsapScript() {
|
|
|
1102
1127
|
return mod.parseGsapScriptAcorn;
|
|
1103
1128
|
}
|
|
1104
1129
|
var SCENE_BOUNDARY_EPSILON_SECONDS = 0.05;
|
|
1130
|
+
var UNRESOLVED_TARGET = "__unresolved__";
|
|
1105
1131
|
function countClassUsage(tags) {
|
|
1106
1132
|
const counts = /* @__PURE__ */ new Map();
|
|
1107
1133
|
for (const tag of tags) {
|
|
@@ -1464,6 +1490,7 @@ var gsapRules = [
|
|
|
1464
1490
|
const left = gsapWindows[i];
|
|
1465
1491
|
if (!left) continue;
|
|
1466
1492
|
if (left.end <= left.position) continue;
|
|
1493
|
+
if (left.targetSelector === UNRESOLVED_TARGET) continue;
|
|
1467
1494
|
for (let j = i + 1; j < gsapWindows.length; j++) {
|
|
1468
1495
|
const right = gsapWindows[j];
|
|
1469
1496
|
if (!right) continue;
|
|
@@ -1490,6 +1517,7 @@ ${right.raw}`)
|
|
|
1490
1517
|
}
|
|
1491
1518
|
if (clipStartBoundaries.length > 0) {
|
|
1492
1519
|
for (const win of gsapWindows) {
|
|
1520
|
+
if (win.targetSelector === UNRESOLVED_TARGET) continue;
|
|
1493
1521
|
if (!isSceneBoundaryExit(win)) continue;
|
|
1494
1522
|
const boundary = findMatchingSceneBoundary(win.end, clipStartBoundaries);
|
|
1495
1523
|
if (boundary == null) continue;
|
|
@@ -2726,7 +2754,7 @@ var adapterRules = [
|
|
|
2726
2754
|
(t) => /["']three["']/.test(t) && /importmap/.test(scripts.find((s) => s.content === t)?.attrs || "")
|
|
2727
2755
|
);
|
|
2728
2756
|
const hasThreeModuleImport = texts.some(
|
|
2729
|
-
(t) => /\
|
|
2757
|
+
(t) => /\b(?:import|from)\s*[^;\n]*['"][^'"]*three[^'"]*['"]/i.test(t)
|
|
2730
2758
|
);
|
|
2731
2759
|
if (!usesThree || hasThreeScript || hasThreeImportMap || hasThreeModuleImport) return [];
|
|
2732
2760
|
return [
|
|
@@ -2933,6 +2961,12 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
2933
2961
|
"math",
|
|
2934
2962
|
"emoji",
|
|
2935
2963
|
"fangsong",
|
|
2964
|
+
// Vendor-prefixed system-font keywords. Like `system-ui`, the engine resolves
|
|
2965
|
+
// these to the OS UI font — they are never installable files and must not be
|
|
2966
|
+
// flagged as a missing @font-face, even when a generic fallback follows them
|
|
2967
|
+
// (e.g. `-apple-system, system-ui, sans-serif`).
|
|
2968
|
+
"-apple-system",
|
|
2969
|
+
"blinkmacsystemfont",
|
|
2936
2970
|
"inherit",
|
|
2937
2971
|
"initial",
|
|
2938
2972
|
"unset",
|
|
@@ -2957,6 +2991,11 @@ function extractFontFaceFamilies(styles) {
|
|
|
2957
2991
|
}
|
|
2958
2992
|
return families;
|
|
2959
2993
|
}
|
|
2994
|
+
function normalizeUsedFontName(part) {
|
|
2995
|
+
const name = part.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
2996
|
+
if (!name || name.includes("(") || name.includes(")")) return null;
|
|
2997
|
+
return name;
|
|
2998
|
+
}
|
|
2960
2999
|
function extractUsedFontFamilies(styles) {
|
|
2961
3000
|
const used = [];
|
|
2962
3001
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2965,9 +3004,8 @@ function extractUsedFontFamilies(styles) {
|
|
|
2965
3004
|
const withoutFontFace = stripCssComments(style.content).replace(/@font-face\s*\{[^}]*\}/gi, "");
|
|
2966
3005
|
let match;
|
|
2967
3006
|
while ((match = propRe.exec(withoutFontFace)) !== null) {
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
const name = part.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
3007
|
+
for (const part of match[1].split(",")) {
|
|
3008
|
+
const name = normalizeUsedFontName(part);
|
|
2971
3009
|
if (name && !GENERIC_FAMILIES.has(name) && !seen.has(name)) {
|
|
2972
3010
|
seen.add(name);
|
|
2973
3011
|
used.push(name);
|