@hyperframes/lint 0.8.81 → 0.8.83

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/index.js CHANGED
@@ -5822,6 +5822,9 @@ import {
5822
5822
  topLevelElements,
5823
5823
  trackKindOf
5824
5824
  } from "@hyperframes/parsers/top-level-elements";
5825
+ import { readClipTiming as readClipTiming2 } from "@hyperframes/parsers/composition-contract";
5826
+ import { readDataDurationSeconds } from "@hyperframes/parsers/media-duration";
5827
+ import { TIMING_TOLERANCE_SECONDS } from "@hyperframes/parsers/composition-duration";
5825
5828
  var INLINE_TEXT_TAGS = /* @__PURE__ */ new Set([
5826
5829
  "br",
5827
5830
  "wbr",
@@ -5950,6 +5953,27 @@ function captionFindings(rows, severity) {
5950
5953
  }
5951
5954
  return findings;
5952
5955
  }
5956
+ var hundredths = (seconds) => Math.round(seconds * 100) / 100;
5957
+ function clipsPastRootFindings(root, rows) {
5958
+ if (root.attrs["data-composition-id"] === void 0) return [];
5959
+ const rootDuration = readDataDurationSeconds((name) => root.attrs[name]);
5960
+ if (rootDuration === null) return [];
5961
+ const limit = hundredths(rootDuration);
5962
+ return rows.flatMap((row) => {
5963
+ const { start, end } = readClipTiming2({ getAttribute: (name) => row.attrs[name] ?? null });
5964
+ if (start === null || end === null || end <= rootDuration + TIMING_TOLERANCE_SECONDS) return [];
5965
+ return [
5966
+ {
5967
+ code: "clip_ends_past_root_duration",
5968
+ severity: "warning",
5969
+ message: `${describe(row)} runs from ${hundredths(start)}s to ${hundredths(end)}s, past the root composition's data-duration of ${limit}s, so it is cut off in previews, posters and renders.`,
5970
+ elementId: row.attrs.id,
5971
+ fixHint: `Extend the root data-duration to ${hundredths(end)}, or make ${describe(row)} end at or before ${limit}s.`,
5972
+ snippet: truncateSnippet(row.open.raw)
5973
+ }
5974
+ ];
5975
+ });
5976
+ }
5953
5977
  var structureRules = [
5954
5978
  (ctx) => {
5955
5979
  if (ctx.options.isSubComposition) return [];
@@ -5960,7 +5984,8 @@ var structureRules = [
5960
5984
  return [
5961
5985
  ...nestedStructureFindings(rows, severity),
5962
5986
  ...missingDurationFindings(rows, severity),
5963
- ...captionFindings(rows, severity)
5987
+ ...captionFindings(rows, severity),
5988
+ ...clipsPastRootFindings(root, rows)
5964
5989
  ];
5965
5990
  }
5966
5991
  ];
@@ -6378,6 +6403,7 @@ async function lintHevcPreviewCodec(candidates) {
6378
6403
  function parseSubCompHtml(html) {
6379
6404
  return parseHTML3(html).document;
6380
6405
  }
6406
+ var sourceFile = (projectDir, compSrcPath) => resolve(projectDir, compSrcPath ?? "index.html");
6381
6407
  function querySelectorAllIncludingTemplates(root, selector) {
6382
6408
  const matches = [...root.querySelectorAll(selector)];
6383
6409
  for (const template of root.querySelectorAll("template")) {
@@ -6529,20 +6555,20 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
6529
6555
  ...await lintVideoMediaStartPastEof(projectDir, allHtmlSources),
6530
6556
  ...await lintHevcPreviewCodec(collectLocalVideoCandidates(projectDir, allHtmlSources))
6531
6557
  ];
6532
- if (projectFindings.length > 0) {
6533
- for (const finding of projectFindings) {
6534
- rootResult.findings.push(finding);
6535
- if (finding.severity === "error") {
6536
- rootResult.errorCount++;
6537
- rootResult.ok = false;
6538
- totalErrors++;
6539
- } else if (finding.severity === "warning") {
6540
- rootResult.warningCount++;
6541
- totalWarnings++;
6542
- } else {
6543
- rootResult.infoCount++;
6544
- totalInfos++;
6545
- }
6558
+ for (const finding of projectFindings) {
6559
+ const ownFile = finding.file && resolve(projectDir, finding.file);
6560
+ const owner = results.find((entry) => resolve(projectDir, entry.file) === ownFile)?.result ?? rootResult;
6561
+ owner.findings.push(finding);
6562
+ if (finding.severity === "error") {
6563
+ owner.errorCount++;
6564
+ owner.ok = false;
6565
+ totalErrors++;
6566
+ } else if (finding.severity === "warning") {
6567
+ owner.warningCount++;
6568
+ totalWarnings++;
6569
+ } else {
6570
+ owner.infoCount++;
6571
+ totalInfos++;
6546
6572
  }
6547
6573
  }
6548
6574
  return { results, totalErrors, totalWarnings, totalInfos };
@@ -6602,7 +6628,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
6602
6628
  function lintAudioSrcNotFound(projectDir, htmlSources) {
6603
6629
  const findings = [];
6604
6630
  const audioSrcRe = mediaSrcTagRe("audio");
6605
- const missingSrcs = [];
6631
+ const missingByFile = /* @__PURE__ */ new Map();
6606
6632
  for (const { html, compSrcPath } of htmlSources) {
6607
6633
  let match;
6608
6634
  while ((match = audioSrcRe.exec(html)) !== null) {
@@ -6611,15 +6637,17 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
6611
6637
  if (isUnresolvedAssetPlaceholder2(src)) continue;
6612
6638
  const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join2(projectDir, path))) : src;
6613
6639
  if (!existsSync2(resolveProjectRelativeSrc(rootRelative, projectDir))) {
6614
- missingSrcs.push(src);
6640
+ const file = sourceFile(projectDir, compSrcPath);
6641
+ missingByFile.set(file, (missingByFile.get(file) ?? /* @__PURE__ */ new Set()).add(src));
6615
6642
  }
6616
6643
  }
6617
6644
  }
6618
- if (missingSrcs.length > 0) {
6619
- const unique = [...new Set(missingSrcs)];
6645
+ for (const [file, srcs] of missingByFile) {
6646
+ const unique = [...srcs];
6620
6647
  findings.push({
6621
6648
  code: "audio_src_not_found",
6622
6649
  severity: "error",
6650
+ file,
6623
6651
  message: `<audio> element references file(s) not found in the project: ${unique.join(", ")}. The rendered video will be silent.`,
6624
6652
  fixHint: unique.length === 1 ? `Add the file "${unique[0]}" to the project directory, or update the src attribute to point to an existing file.` : `Add the missing files to the project directory, or update the src attributes to point to existing files.`
6625
6653
  });
@@ -6645,19 +6673,22 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
6645
6673
  const resolvedAsset = resolveExistingLocalAsset2(projectDir, rootRelative);
6646
6674
  if (resolvedAsset) continue;
6647
6675
  const resolvedKey = resolve(projectDir, rootRelative);
6648
- let bucket = missingByTag.get(tagName);
6676
+ const file = sourceFile(projectDir, compSrcPath);
6677
+ const bucketKey = `${file}\0${tagName}`;
6678
+ let bucket = missingByTag.get(bucketKey);
6649
6679
  if (!bucket) {
6650
- bucket = /* @__PURE__ */ new Map();
6651
- missingByTag.set(tagName, bucket);
6680
+ bucket = { file, tagName, byResolved: /* @__PURE__ */ new Map() };
6681
+ missingByTag.set(bucketKey, bucket);
6652
6682
  }
6653
- if (!bucket.has(resolvedKey)) bucket.set(resolvedKey, src);
6683
+ if (!bucket.byResolved.has(resolvedKey)) bucket.byResolved.set(resolvedKey, src);
6654
6684
  }
6655
6685
  }
6656
- for (const [tagName, byResolved] of missingByTag) {
6686
+ for (const { file, tagName, byResolved } of missingByTag.values()) {
6657
6687
  const unique = [...byResolved.values()];
6658
6688
  findings.push({
6659
6689
  code: "missing_local_asset",
6660
6690
  severity: "error",
6691
+ file,
6661
6692
  message: `<${tagName}> element references local file(s) not found in the project: ${unique.join(", ")}. The renderer will silently skip these and produce a video with missing visuals.`,
6662
6693
  fixHint: unique.length === 1 ? `Add "${unique[0]}" to the project directory, or update the src attribute to point to an existing file. Common cause: captured asset filenames are unreliable (heygen-logo.svg often contains Google, nvidia-logo.svg may contain Autodesk, etc.). Open the contact sheets and verify the file actually exists at this path before referencing it.` : "Add the missing files to the project directory, or update the src attributes to point to existing files. Captured asset filenames are unreliable \u2014 verify against capture/contact-sheets/ and capture/extracted/asset-descriptions.md."
6663
6694
  });
@@ -6665,8 +6696,9 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
6665
6696
  return findings;
6666
6697
  }
6667
6698
  function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
6668
- const missing = /* @__PURE__ */ new Map();
6699
+ const missingByFile = /* @__PURE__ */ new Map();
6669
6700
  for (const { html, compSrcPath } of htmlSources) {
6701
+ const file = sourceFile(projectDir, compSrcPath);
6670
6702
  for (const cssSource of collectCssSources(projectDir, html, compSrcPath)) {
6671
6703
  let match;
6672
6704
  const pattern = new RegExp(MASK_IMAGE_URL_RE.source, MASK_IMAGE_URL_RE.flags);
@@ -6682,20 +6714,20 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
6682
6714
  cssSource.rootRelativePath
6683
6715
  );
6684
6716
  if (candidates.some(existsSync2)) continue;
6685
- missing.set(url, candidates[0] ?? resolve(projectDir, url));
6717
+ missingByFile.set(file, (missingByFile.get(file) ?? /* @__PURE__ */ new Set()).add(url));
6686
6718
  }
6687
6719
  }
6688
6720
  }
6689
- if (missing.size === 0) return [];
6690
- const urls = [...missing.keys()];
6691
- return [
6692
- {
6721
+ return [...missingByFile].map(([file, found]) => {
6722
+ const urls = [...found];
6723
+ return {
6693
6724
  code: "texture_mask_asset_not_found",
6694
6725
  severity: "error",
6726
+ file,
6695
6727
  message: `CSS mask-image references file(s) not found in the project: ${urls.join(", ")}.`,
6696
6728
  fixHint: urls.length === 1 ? `Add "${urls[0]}" to the project, or update the mask-image URL to point to an existing texture mask.` : "Add the missing texture mask files to the project, or update the mask-image URLs to point to existing files."
6697
- }
6698
- ];
6729
+ };
6730
+ });
6699
6731
  }
6700
6732
  function lintMultipleRootCompositions(projectDir) {
6701
6733
  const findings = [];