@hyperframes/lint 0.8.50 → 0.8.53-desktop.0
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 +68 -13
- package/dist/browser.js.map +1 -1
- package/dist/index.js +71 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1541,6 +1541,7 @@ function findRuntimeMediaSrcMutationFindings(ctx) {
|
|
|
1541
1541
|
}
|
|
1542
1542
|
var mediaRules = [
|
|
1543
1543
|
findNestedMediaStartBasisFindings,
|
|
1544
|
+
findSpeedRampOnNonMediaFindings,
|
|
1544
1545
|
// duplicate_media_id + duplicate_media_discovery_risk
|
|
1545
1546
|
({ tags }) => {
|
|
1546
1547
|
const findings = [];
|
|
@@ -1962,6 +1963,18 @@ function findVolumeDoubleAutomationFindings(ctx) {
|
|
|
1962
1963
|
}
|
|
1963
1964
|
return findings;
|
|
1964
1965
|
}
|
|
1966
|
+
function findSpeedRampOnNonMediaFindings(ctx) {
|
|
1967
|
+
return ctx.tags.filter((tag) => tag.name !== "video" && tag.name !== "audio").filter(
|
|
1968
|
+
(tag) => /"target"\s*:\s*"rate"/.test(readDecodedAttr(tag.raw, "data-automation") ?? "")
|
|
1969
|
+
).map((tag) => ({
|
|
1970
|
+
code: "speed_ramp_on_non_media",
|
|
1971
|
+
severity: "warning",
|
|
1972
|
+
message: `<${tag.name}> has a speed-ramp lane, but only <video> and <audio> clips can be retimed. The lane does nothing here.`,
|
|
1973
|
+
elementId: readAttr(tag.raw, "id") || void 0,
|
|
1974
|
+
fixHint: "Move the rate lane onto the <video> or <audio> clip, or retime an animation with a GSAP timeline instead.",
|
|
1975
|
+
snippet: truncateSnippet(tag.raw)
|
|
1976
|
+
}));
|
|
1977
|
+
}
|
|
1965
1978
|
function findCarveUngroupedSourcesFindings(ctx) {
|
|
1966
1979
|
const groupIds = new Set(
|
|
1967
1980
|
ctx.tags.filter((tag) => tag.name === "hf-audio-group").map((tag) => readAttr(tag.raw, "id"))
|
|
@@ -2068,6 +2081,11 @@ function findAudioGroupCarveAttrFindings(ctx) {
|
|
|
2068
2081
|
// src/rules/composition.ts
|
|
2069
2082
|
import { COMPOSITION_VARIABLE_TYPES, isSafeMediaUrl } from "@hyperframes/parsers/composition";
|
|
2070
2083
|
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
|
2084
|
+
import { resolveCompositionDuration } from "@hyperframes/parsers/composition-duration";
|
|
2085
|
+
import {
|
|
2086
|
+
readAuthoredDurationSeconds,
|
|
2087
|
+
resolveMediaDuration
|
|
2088
|
+
} from "@hyperframes/parsers/media-duration";
|
|
2071
2089
|
var MAX_COMPOSITION_LINES = 300;
|
|
2072
2090
|
var MAX_TIMED_ELEMENTS_PER_TRACK = 3;
|
|
2073
2091
|
var TRACK_DENSITY_EXEMPT_TAGS = /* @__PURE__ */ new Set(["audio", "script", "style", "video"]);
|
|
@@ -2843,6 +2861,18 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
|
2843
2861
|
const hasInfiniteCssAnimation = /\banimation(?:-iteration-count)?\s*:[^;{}]*(?<![\w-])infinite(?![\w-])/.test(combinedCss);
|
|
2844
2862
|
const hasAnyNonGsapSignal = usesLottie || usesThree || usesWaapi || hasCssAnimationName;
|
|
2845
2863
|
if (!hasAnyNonGsapSignal) {
|
|
2864
|
+
const derived = deriveDurationFromClips(tags, rootTag);
|
|
2865
|
+
if (derived.source === "derived") {
|
|
2866
|
+
return [
|
|
2867
|
+
{
|
|
2868
|
+
code: "root_composition_duration_derived",
|
|
2869
|
+
severity: "warning",
|
|
2870
|
+
message: `Root composition has no data-duration and no GSAP timeline, so its length is taken from its clips: at least ${derived.seconds}s` + (derived.pendingClips > 0 ? `, and ${derived.pendingClips} clip(s) whose length is only known at runtime` : "") + ".",
|
|
2871
|
+
fixHint: 'Add data-duration="<seconds>" to the root element to set the length yourself.',
|
|
2872
|
+
snippet: truncateSnippet(rootTag.raw)
|
|
2873
|
+
}
|
|
2874
|
+
];
|
|
2875
|
+
}
|
|
2846
2876
|
return [
|
|
2847
2877
|
{
|
|
2848
2878
|
code: "root_composition_missing_duration_source",
|
|
@@ -2927,6 +2957,35 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
|
2927
2957
|
];
|
|
2928
2958
|
}
|
|
2929
2959
|
];
|
|
2960
|
+
var CLIP_MEDIA_TAGS = /* @__PURE__ */ new Set(["img", "video", "audio"]);
|
|
2961
|
+
function deriveDurationFromClips(tags, rootTag) {
|
|
2962
|
+
const clipEnds = [];
|
|
2963
|
+
for (const tag of tags) {
|
|
2964
|
+
if (tag === rootTag) continue;
|
|
2965
|
+
const startRaw = readAttr(tag.raw, "data-start");
|
|
2966
|
+
if (startRaw === null) continue;
|
|
2967
|
+
const start = Number(startRaw);
|
|
2968
|
+
if (!Number.isFinite(start)) {
|
|
2969
|
+
clipEnds.push(null);
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
const getAttr = (name) => readAttr(tag.raw, name);
|
|
2973
|
+
const authored = readAuthoredDurationSeconds(getAttr, start);
|
|
2974
|
+
if (CLIP_MEDIA_TAGS.has(tag.name)) {
|
|
2975
|
+
const { seconds } = resolveMediaDuration({
|
|
2976
|
+
tag: tag.name,
|
|
2977
|
+
authoredDurationSeconds: authored,
|
|
2978
|
+
sourceDurationSeconds: null,
|
|
2979
|
+
mediaStartSeconds: 0,
|
|
2980
|
+
playbackRate: 1
|
|
2981
|
+
});
|
|
2982
|
+
clipEnds.push(seconds === null ? null : start + seconds);
|
|
2983
|
+
} else if (authored !== null && authored > 0) {
|
|
2984
|
+
clipEnds.push(start + authored);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
return resolveCompositionDuration({ authoredDurationSeconds: null, clipEndsSeconds: clipEnds });
|
|
2988
|
+
}
|
|
2930
2989
|
|
|
2931
2990
|
// src/rules/gsap.ts
|
|
2932
2991
|
async function loadParseGsapScript() {
|
|
@@ -5358,7 +5417,7 @@ var OPAQUE_TAGS = /* @__PURE__ */ new Set([
|
|
|
5358
5417
|
"object"
|
|
5359
5418
|
]);
|
|
5360
5419
|
var NON_LAYOUT_TAGS = /* @__PURE__ */ new Set(["style", "script", "template", "noscript"]);
|
|
5361
|
-
var MEDIA_TAGS = /* @__PURE__ */ new Set(["
|
|
5420
|
+
var MEDIA_TAGS = /* @__PURE__ */ new Set(["video", "audio", "img"]);
|
|
5362
5421
|
var NODE_ATTRS = [
|
|
5363
5422
|
"id",
|
|
5364
5423
|
"class",
|
|
@@ -5424,19 +5483,15 @@ function nestedStructureFindings(rows, severity) {
|
|
|
5424
5483
|
});
|
|
5425
5484
|
}
|
|
5426
5485
|
function missingDurationFindings(rows, severity) {
|
|
5427
|
-
const intendedClip = (row) => !MEDIA_TAGS.has(row.tag) || row.attrs["data-start"] !== void 0 || row.attrs["data-track-index"] !== void 0;
|
|
5428
5486
|
return rows.filter(
|
|
5429
|
-
(row) => row.attrs["data-duration"] === void 0 && row.attrs["data-end"] === void 0 && !isSubCompositionHost(row)
|
|
5430
|
-
).map((row) => {
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
fixHint: `Add data-duration (in seconds) to ${describe(row)}.`
|
|
5438
|
-
};
|
|
5439
|
-
});
|
|
5487
|
+
(row) => !MEDIA_TAGS.has(row.tag) && row.attrs["data-duration"] === void 0 && row.attrs["data-end"] === void 0 && !isSubCompositionHost(row)
|
|
5488
|
+
).map((row) => ({
|
|
5489
|
+
code: "timeline_element_missing_timing",
|
|
5490
|
+
severity,
|
|
5491
|
+
message: `${describe(row)} is a timeline element without data-duration, so the timeline cannot draw where it ends.`,
|
|
5492
|
+
elementId: row.attrs.id,
|
|
5493
|
+
fixHint: `Add data-duration (in seconds) to ${describe(row)}.`
|
|
5494
|
+
}));
|
|
5440
5495
|
}
|
|
5441
5496
|
function captionFindings(rows, severity) {
|
|
5442
5497
|
const captionRows = rows.filter((row) => trackKindOf(row).kind === "captions");
|
|
@@ -5630,7 +5685,8 @@ import {
|
|
|
5630
5685
|
isWithinProjectRoot,
|
|
5631
5686
|
maskNonScannableRanges as maskNonScannableRanges2,
|
|
5632
5687
|
resolveExistingLocalAsset as resolveExistingLocalAsset2,
|
|
5633
|
-
resolveLocalAssetCandidates
|
|
5688
|
+
resolveLocalAssetCandidates,
|
|
5689
|
+
resolveProjectRelativeSrc
|
|
5634
5690
|
} from "@hyperframes/parsers/asset-resolution";
|
|
5635
5691
|
|
|
5636
5692
|
// src/hevcPreviewLint.ts
|
|
@@ -6101,7 +6157,7 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
|
|
|
6101
6157
|
if (/^(https?:|data:|blob:)/i.test(src)) continue;
|
|
6102
6158
|
if (isUnresolvedAssetPlaceholder2(src)) continue;
|
|
6103
6159
|
const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join2(projectDir, path))) : src;
|
|
6104
|
-
if (!
|
|
6160
|
+
if (!existsSync2(resolveProjectRelativeSrc(rootRelative, projectDir))) {
|
|
6105
6161
|
missingSrcs.push(src);
|
|
6106
6162
|
}
|
|
6107
6163
|
}
|