@hyperframes/lint 0.8.206 → 0.8.208

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 CHANGED
@@ -4117,6 +4117,23 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
4117
4117
  snippet: truncateSnippet(rootTag.raw)
4118
4118
  }
4119
4119
  ];
4120
+ },
4121
+ // subcomposition_linked_style_names_root_id
4122
+ // The compiler flattens a sub-composition root into its host and rewrites inline <style>
4123
+ // selectors to match; a linked stylesheet is not rewritten, so `#root-id` in it matches nothing.
4124
+ ({ styles, rootTag, options }) => {
4125
+ if (!options.isSubComposition || !rootTag) return [];
4126
+ const rootId = readAttr(rootTag.raw, "id");
4127
+ if (!rootId) return [];
4128
+ const escaped = rootId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4129
+ const names = new RegExp(`#${escaped}(?![\\w-])`);
4130
+ return styles.filter((style) => style.index === -1 && names.test(stripCssComments(style.content))).map((style) => ({
4131
+ code: "subcomposition_linked_style_names_root_id",
4132
+ severity: "warning",
4133
+ message: `The linked stylesheet "${style.rootRelativePath ?? style.file}" has a rule on #${rootId}, the id of this sub-composition's root. The root is merged into its host when the composition is compiled, so that selector matches nothing (the same rule in an inline <style> is rewritten and works).`,
4134
+ fixHint: `Move the #${rootId} rules into a <style> inside the <template>, where they are rewritten for the merged root, or select a child element of the root in the linked file instead of the root itself.`,
4135
+ snippet: truncateSnippet(style.content.slice(0, 120))
4136
+ }));
4120
4137
  }
4121
4138
  ];
4122
4139
  var CLIP_MEDIA_TAGS = /* @__PURE__ */ new Set(["img", "video", "audio"]);
@@ -6872,6 +6889,68 @@ var shaderRules = [
6872
6889
  })
6873
6890
  ];
6874
6891
 
6892
+ // src/rules/rootPaths.ts
6893
+ var URL_ATTRS = ["src", "href", "poster", "xlink:href"];
6894
+ var ROOT_PATH = /^\/(?!\/)/;
6895
+ var CSS_ROOT_URL = /\burl\(\s*["']?(\/(?!\/)[^"')\s]*)/g;
6896
+ var SCRIPT_ROOT_URL = /\b(?:fetch|new\s+URL)\(\s*(["'`])(\/(?!\/)[^"'`$]*)\1/g;
6897
+ function relativeForm(path, cssFile) {
6898
+ const depth = (cssFile ?? "").split("/").filter(Boolean).length - 1;
6899
+ return `${"../".repeat(Math.max(depth, 0))}${path.slice(1)}`;
6900
+ }
6901
+ function rootPathFinding(path, cssFile, snippet) {
6902
+ return {
6903
+ code: "root_path_missing_in_preview",
6904
+ severity: "warning",
6905
+ message: `"${path}" starts at the server's root. Render and snapshot serve the project there, but Studio's preview serves it under the project's own path, so the preview cannot find this file.`,
6906
+ fixHint: `Use "${relativeForm(path, cssFile)}" instead.`,
6907
+ snippet: truncateSnippet(snippet)
6908
+ };
6909
+ }
6910
+ function cssRootPaths(css, cssFile) {
6911
+ return [...css.matchAll(CSS_ROOT_URL)].map(
6912
+ (match) => rootPathFinding(match[1] ?? "", cssFile, match[0])
6913
+ );
6914
+ }
6915
+ function tagRootPaths(tag) {
6916
+ if (tag.name === "a" || tag.name === "base") return [];
6917
+ const paths = URL_ATTRS.map((attr) => readAttr(tag.raw, attr)?.trim() ?? "").filter(
6918
+ (value) => ROOT_PATH.test(value)
6919
+ );
6920
+ return [
6921
+ ...paths.map((path) => rootPathFinding(path, void 0, tag.raw)),
6922
+ ...cssRootPaths(readAttr(tag.raw, "style") ?? "")
6923
+ ];
6924
+ }
6925
+ var scriptRootPaths = (script) => [...script.matchAll(SCRIPT_ROOT_URL)].map(
6926
+ (match) => rootPathFinding(match[2] ?? "", void 0, match[0])
6927
+ );
6928
+ function invalidImportMap(script) {
6929
+ if (!/\btype\s*=\s*["']?importmap\b/i.test(script.attrs)) return [];
6930
+ try {
6931
+ JSON.parse(script.content);
6932
+ return [];
6933
+ } catch (error) {
6934
+ return [
6935
+ {
6936
+ code: "import_map_invalid",
6937
+ severity: "warning",
6938
+ message: `This import map is not valid JSON (${error instanceof Error ? error.message : String(error)}). The browser ignores it, and preview cannot map root module paths ("/assets/x.js") to the project.`,
6939
+ fixHint: "Make the import map valid JSON: double-quoted keys and values, no trailing commas.",
6940
+ snippet: truncateSnippet(script.content)
6941
+ }
6942
+ ];
6943
+ }
6944
+ }
6945
+ var rootPathRules = [
6946
+ (ctx) => [
6947
+ ...ctx.tags.flatMap(tagRootPaths),
6948
+ ...ctx.styles.flatMap((style) => cssRootPaths(style.content, style.rootRelativePath)),
6949
+ ...ctx.scripts.flatMap((script) => scriptRootPaths(script.content)),
6950
+ ...ctx.scripts.flatMap(invalidImportMap)
6951
+ ]
6952
+ ];
6953
+
6875
6954
  // src/hyperframeLinter.ts
6876
6955
  var RULE_GROUPS = [
6877
6956
  { group: "core", rules: coreRules },
@@ -6884,7 +6963,8 @@ var RULE_GROUPS = [
6884
6963
  { group: "fonts", rules: fontRules },
6885
6964
  { group: "slideshow", rules: slideshowRules },
6886
6965
  { group: "structure", rules: structureRules },
6887
- { group: "shaders", rules: shaderRules }
6966
+ { group: "shaders", rules: shaderRules },
6967
+ { group: "rootPaths", rules: rootPathRules }
6888
6968
  ];
6889
6969
  var LINT_RULE_COUNT = RULE_GROUPS.reduce((n, g) => n + g.rules.length, 0);
6890
6970
  var LINT_RULE_GROUP_COUNTS = Object.fromEntries(
package/dist/index.js CHANGED
@@ -4123,6 +4123,23 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
4123
4123
  snippet: truncateSnippet(rootTag.raw)
4124
4124
  }
4125
4125
  ];
4126
+ },
4127
+ // subcomposition_linked_style_names_root_id
4128
+ // The compiler flattens a sub-composition root into its host and rewrites inline <style>
4129
+ // selectors to match; a linked stylesheet is not rewritten, so `#root-id` in it matches nothing.
4130
+ ({ styles, rootTag, options }) => {
4131
+ if (!options.isSubComposition || !rootTag) return [];
4132
+ const rootId = readAttr(rootTag.raw, "id");
4133
+ if (!rootId) return [];
4134
+ const escaped = rootId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4135
+ const names = new RegExp(`#${escaped}(?![\\w-])`);
4136
+ return styles.filter((style) => style.index === -1 && names.test(stripCssComments(style.content))).map((style) => ({
4137
+ code: "subcomposition_linked_style_names_root_id",
4138
+ severity: "warning",
4139
+ message: `The linked stylesheet "${style.rootRelativePath ?? style.file}" has a rule on #${rootId}, the id of this sub-composition's root. The root is merged into its host when the composition is compiled, so that selector matches nothing (the same rule in an inline <style> is rewritten and works).`,
4140
+ fixHint: `Move the #${rootId} rules into a <style> inside the <template>, where they are rewritten for the merged root, or select a child element of the root in the linked file instead of the root itself.`,
4141
+ snippet: truncateSnippet(style.content.slice(0, 120))
4142
+ }));
4126
4143
  }
4127
4144
  ];
4128
4145
  var CLIP_MEDIA_TAGS = /* @__PURE__ */ new Set(["img", "video", "audio"]);
@@ -6878,6 +6895,68 @@ var shaderRules = [
6878
6895
  })
6879
6896
  ];
6880
6897
 
6898
+ // src/rules/rootPaths.ts
6899
+ var URL_ATTRS = ["src", "href", "poster", "xlink:href"];
6900
+ var ROOT_PATH = /^\/(?!\/)/;
6901
+ var CSS_ROOT_URL = /\burl\(\s*["']?(\/(?!\/)[^"')\s]*)/g;
6902
+ var SCRIPT_ROOT_URL = /\b(?:fetch|new\s+URL)\(\s*(["'`])(\/(?!\/)[^"'`$]*)\1/g;
6903
+ function relativeForm(path, cssFile) {
6904
+ const depth = (cssFile ?? "").split("/").filter(Boolean).length - 1;
6905
+ return `${"../".repeat(Math.max(depth, 0))}${path.slice(1)}`;
6906
+ }
6907
+ function rootPathFinding(path, cssFile, snippet) {
6908
+ return {
6909
+ code: "root_path_missing_in_preview",
6910
+ severity: "warning",
6911
+ message: `"${path}" starts at the server's root. Render and snapshot serve the project there, but Studio's preview serves it under the project's own path, so the preview cannot find this file.`,
6912
+ fixHint: `Use "${relativeForm(path, cssFile)}" instead.`,
6913
+ snippet: truncateSnippet(snippet)
6914
+ };
6915
+ }
6916
+ function cssRootPaths(css, cssFile) {
6917
+ return [...css.matchAll(CSS_ROOT_URL)].map(
6918
+ (match) => rootPathFinding(match[1] ?? "", cssFile, match[0])
6919
+ );
6920
+ }
6921
+ function tagRootPaths(tag) {
6922
+ if (tag.name === "a" || tag.name === "base") return [];
6923
+ const paths = URL_ATTRS.map((attr) => readAttr(tag.raw, attr)?.trim() ?? "").filter(
6924
+ (value) => ROOT_PATH.test(value)
6925
+ );
6926
+ return [
6927
+ ...paths.map((path) => rootPathFinding(path, void 0, tag.raw)),
6928
+ ...cssRootPaths(readAttr(tag.raw, "style") ?? "")
6929
+ ];
6930
+ }
6931
+ var scriptRootPaths = (script) => [...script.matchAll(SCRIPT_ROOT_URL)].map(
6932
+ (match) => rootPathFinding(match[2] ?? "", void 0, match[0])
6933
+ );
6934
+ function invalidImportMap(script) {
6935
+ if (!/\btype\s*=\s*["']?importmap\b/i.test(script.attrs)) return [];
6936
+ try {
6937
+ JSON.parse(script.content);
6938
+ return [];
6939
+ } catch (error) {
6940
+ return [
6941
+ {
6942
+ code: "import_map_invalid",
6943
+ severity: "warning",
6944
+ message: `This import map is not valid JSON (${error instanceof Error ? error.message : String(error)}). The browser ignores it, and preview cannot map root module paths ("/assets/x.js") to the project.`,
6945
+ fixHint: "Make the import map valid JSON: double-quoted keys and values, no trailing commas.",
6946
+ snippet: truncateSnippet(script.content)
6947
+ }
6948
+ ];
6949
+ }
6950
+ }
6951
+ var rootPathRules = [
6952
+ (ctx) => [
6953
+ ...ctx.tags.flatMap(tagRootPaths),
6954
+ ...ctx.styles.flatMap((style) => cssRootPaths(style.content, style.rootRelativePath)),
6955
+ ...ctx.scripts.flatMap((script) => scriptRootPaths(script.content)),
6956
+ ...ctx.scripts.flatMap(invalidImportMap)
6957
+ ]
6958
+ ];
6959
+
6881
6960
  // src/hyperframeLinter.ts
6882
6961
  var RULE_GROUPS = [
6883
6962
  { group: "core", rules: coreRules },
@@ -6890,7 +6969,8 @@ var RULE_GROUPS = [
6890
6969
  { group: "fonts", rules: fontRules },
6891
6970
  { group: "slideshow", rules: slideshowRules },
6892
6971
  { group: "structure", rules: structureRules },
6893
- { group: "shaders", rules: shaderRules }
6972
+ { group: "shaders", rules: shaderRules },
6973
+ { group: "rootPaths", rules: rootPathRules }
6894
6974
  ];
6895
6975
  var LINT_RULE_COUNT = RULE_GROUPS.reduce((n, g) => n + g.rules.length, 0);
6896
6976
  var LINT_RULE_GROUP_COUNTS = Object.fromEntries(
@@ -7066,6 +7146,7 @@ import {
7066
7146
  resolveLocalAssetCandidates,
7067
7147
  resolveProjectRelativeSrc
7068
7148
  } from "@hyperframes/parsers/asset-resolution";
7149
+ import { encodeUrlPath } from "@hyperframes/parsers";
7069
7150
 
7070
7151
  // src/hevcPreviewLint.ts
7071
7152
  import { execFile } from "child_process";
@@ -7591,10 +7672,18 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
7591
7672
  }
7592
7673
  return findings;
7593
7674
  }
7675
+ function urlSafeSrcOf(projectDir, rootRelative) {
7676
+ if (!/[?#]/.test(rootRelative)) return null;
7677
+ const literal = resolve(projectDir, rootRelative.replace(/^\//, ""));
7678
+ if (!existsSync2(literal)) return null;
7679
+ return encodeUrlPath(rootRelative);
7680
+ }
7681
+ var urlSafeHint = (safe) => `The file exists, but a "#" or "?" in a src ends the file name there. Write the src as "${safe}".`;
7594
7682
  function lintAudioSrcNotFound(projectDir, htmlSources) {
7595
7683
  const findings = [];
7596
7684
  const audioSrcRe = mediaSrcTagRe("audio");
7597
7685
  const missingByFile = /* @__PURE__ */ new Map();
7686
+ const urlSafe = /* @__PURE__ */ new Map();
7598
7687
  for (const { html, compSrcPath } of htmlSources) {
7599
7688
  const scannable = maskNonScannableRanges2(html);
7600
7689
  let match;
@@ -7604,6 +7693,8 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
7604
7693
  if (isUnresolvedAssetPlaceholder2(src)) continue;
7605
7694
  const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
7606
7695
  if (!existsSync2(resolveProjectRelativeSrc(rootRelative, projectDir))) {
7696
+ const safe = urlSafeSrcOf(projectDir, rootRelative);
7697
+ if (safe) urlSafe.set(src, safe);
7607
7698
  const file = sourceFile(projectDir, compSrcPath);
7608
7699
  missingByFile.set(file, (missingByFile.get(file) ?? /* @__PURE__ */ new Set()).add(src));
7609
7700
  }
@@ -7616,7 +7707,7 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
7616
7707
  severity: "error",
7617
7708
  file,
7618
7709
  message: `<audio> element references file(s) not found in the project: ${unique.join(", ")}. The rendered video will be silent.`,
7619
- 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.`
7710
+ fixHint: unique.length === 1 && urlSafe.has(unique[0]) ? urlSafeHint(urlSafe.get(unique[0])) : 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.`
7620
7711
  });
7621
7712
  }
7622
7713
  return findings;
@@ -7625,6 +7716,7 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
7625
7716
  const findings = [];
7626
7717
  const localAssetSrcRe = mediaSrcTagRe("video|img|source");
7627
7718
  const missingByTag = /* @__PURE__ */ new Map();
7719
+ const urlSafe = /* @__PURE__ */ new Map();
7628
7720
  for (const { html, compSrcPath } of htmlSources) {
7629
7721
  const scannable = maskNonScannableRanges2(html);
7630
7722
  const re = new RegExp(localAssetSrcRe.source, localAssetSrcRe.flags);
@@ -7639,6 +7731,12 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
7639
7731
  const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
7640
7732
  const resolvedAsset = resolveExistingLocalAsset3(projectDir, rootRelative);
7641
7733
  if (resolvedAsset) continue;
7734
+ const rawRoot = rawSrc.trim();
7735
+ const safe = urlSafeSrcOf(
7736
+ projectDir,
7737
+ compSrcPath ? rewriteAssetPath2(compSrcPath, rawRoot, (path) => existsSync2(join3(projectDir, path))) : rawRoot
7738
+ );
7739
+ if (safe) urlSafe.set(src, safe);
7642
7740
  const resolvedKey = resolve(projectDir, rootRelative);
7643
7741
  const file = sourceFile(projectDir, compSrcPath);
7644
7742
  const bucketKey = `${file}\0${tagName}`;
@@ -7657,7 +7755,7 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
7657
7755
  severity: "error",
7658
7756
  file,
7659
7757
  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.`,
7660
- 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. Verify against capture/contact-sheets/ and capture/extracted/asset-descriptions.md."
7758
+ fixHint: unique.length === 1 && urlSafe.has(unique[0]) ? urlSafeHint(urlSafe.get(unique[0])) : 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. Verify against capture/contact-sheets/ and capture/extracted/asset-descriptions.md."
7661
7759
  });
7662
7760
  }
7663
7761
  return findings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.206",
3
+ "version": "0.8.208",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -35,7 +35,7 @@
35
35
  "linkedom": "^0.18.12",
36
36
  "postcss": "^8.5.8",
37
37
  "postcss-selector-parser": "^7.1.4",
38
- "@hyperframes/parsers": "0.8.206"
38
+ "@hyperframes/parsers": "0.8.208"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",