@hyperframes/lint 0.8.220 → 0.8.222

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.d.ts CHANGED
@@ -51,6 +51,7 @@ type HyperframeLinterOptions = {
51
51
  * render time — the font will silently fall back to whatever the OS provides.
52
52
  */
53
53
  distributed?: boolean;
54
+ projectFontFamilies?: string[];
54
55
  /** Who is running the lint: Studio raises the structure rules to errors, the CLI keeps them warnings. */
55
56
  host?: "studio" | "cli";
56
57
  };
package/dist/browser.js CHANGED
@@ -4309,7 +4309,7 @@ function hiddenSetTargetSelectors(target, aliases) {
4309
4309
  const parts = target.startsWith("[") && target.endsWith("]") ? target.slice(1, -1).split(",") : [target];
4310
4310
  return parts.flatMap((part) => {
4311
4311
  const trimmed = part.trim();
4312
- const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
4312
+ const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? /^[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\1\s*\)$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
4313
4313
  return resolved === void 0 ? [] : resolved.split(",");
4314
4314
  }).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
4315
4315
  }
@@ -4323,7 +4323,12 @@ function extractStandaloneHiddenSelectors(script) {
4323
4323
  )) {
4324
4324
  aliases.set(match2[1] ?? "", match2[3] ?? "");
4325
4325
  }
4326
- const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
4326
+ for (const match2 of source.matchAll(
4327
+ /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\2\s*\)/g
4328
+ )) {
4329
+ aliases.set(match2[1] ?? "", match2[3] ?? "");
4330
+ }
4331
+ const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[\w$.]+\.querySelector(?:All)?\(\s*(?:"[^"]*"|'[^']*'|`[^`]*`)\s*\)|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
4327
4332
  let match;
4328
4333
  while ((match = pattern.exec(source)) !== null) {
4329
4334
  if (indexInsideNonIifeRange(match.index, source, functionRanges)) continue;
@@ -4984,7 +4989,12 @@ ${right.raw}`)
4984
4989
  (win) => win.position <= SCENE_BOUNDARY_EPSILON_SECONDS && (isHiddenGsapState(win.propertyValues) || win.fromPropertyValues !== void 0 && isHiddenGsapState(win.fromPropertyValues))
4985
4990
  );
4986
4991
  if (startsHiddenAtZero) continue;
4987
- if (selectors.some((selector2) => authoredHiddenSelectors.has(selector2))) continue;
4992
+ if (selectors.some(
4993
+ (selector2) => [...authoredHiddenSelectors].some(
4994
+ (hidden) => hidden === selector2 || targetedSelectorTokens(hidden).has(selector2)
4995
+ )
4996
+ ))
4997
+ continue;
4988
4998
  const firstVisible = visibilityWindows.find((win) => makesOverlayVisible(win));
4989
4999
  if (!firstVisible) continue;
4990
5000
  const selector = selectors.find(
@@ -6429,6 +6439,9 @@ function extractFontFaceFamilies(styles) {
6429
6439
  }
6430
6440
  return families;
6431
6441
  }
6442
+ function declaredFontFamilies(styles, options) {
6443
+ return /* @__PURE__ */ new Set([...extractFontFaceFamilies(styles), ...options.projectFontFamilies ?? []]);
6444
+ }
6432
6445
  function normalizeUsedFontName(part) {
6433
6446
  const name = part.trim().replace(/\s*!important\s*$/i, "").replace(/^['"]|['"]$/g, "").trim().toLowerCase();
6434
6447
  if (!name || name.includes("(") || name.includes(")")) return null;
@@ -6538,7 +6551,7 @@ var fontRules = [
6538
6551
  // so there is nothing for the author to act on.
6539
6552
  ({ styles, options }) => {
6540
6553
  if (!options.distributed) return [];
6541
- const declared = extractFontFaceFamilies(styles);
6554
+ const declared = declaredFontFamilies(styles, options);
6542
6555
  const used = extractUsedFontFamilies(styles);
6543
6556
  const aliased = collectAliasedFonts(used, declared);
6544
6557
  if (aliased.length === 0) return [];
@@ -6554,7 +6567,7 @@ var fontRules = [
6554
6567
  ({ styles, source, rawSource, options, locate }) => {
6555
6568
  if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
6556
6569
  const findings = [];
6557
- const declared = extractFontFaceFamilies(styles);
6570
+ const declared = declaredFontFamilies(styles, options);
6558
6571
  const used = extractUsedFontFamilies(styles);
6559
6572
  const googleFonts = collectGoogleFontFamilies(source, styles);
6560
6573
  const undeclared = used.filter(
package/dist/index.d.ts CHANGED
@@ -51,6 +51,7 @@ type HyperframeLinterOptions = {
51
51
  * render time — the font will silently fall back to whatever the OS provides.
52
52
  */
53
53
  distributed?: boolean;
54
+ projectFontFamilies?: string[];
54
55
  /** Who is running the lint: Studio raises the structure rules to errors, the CLI keeps them warnings. */
55
56
  host?: "studio" | "cli";
56
57
  };
package/dist/index.js CHANGED
@@ -4315,7 +4315,7 @@ function hiddenSetTargetSelectors(target, aliases) {
4315
4315
  const parts = target.startsWith("[") && target.endsWith("]") ? target.slice(1, -1).split(",") : [target];
4316
4316
  return parts.flatMap((part) => {
4317
4317
  const trimmed = part.trim();
4318
- const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
4318
+ const resolved = /^(["'`])([^"'`]+)\1$/.exec(trimmed)?.[2] ?? /^[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\1\s*\)$/.exec(trimmed)?.[2] ?? aliases.get(trimmed);
4319
4319
  return resolved === void 0 ? [] : resolved.split(",");
4320
4320
  }).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
4321
4321
  }
@@ -4329,7 +4329,12 @@ function extractStandaloneHiddenSelectors(script) {
4329
4329
  )) {
4330
4330
  aliases.set(match2[1] ?? "", match2[3] ?? "");
4331
4331
  }
4332
- const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
4332
+ for (const match2 of source.matchAll(
4333
+ /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*[\w$.]+\.querySelector(?:All)?\(\s*(["'`])([^"'`]+)\2\s*\)/g
4334
+ )) {
4335
+ aliases.set(match2[1] ?? "", match2[3] ?? "");
4336
+ }
4337
+ const pattern = /gsap\.set\s*\(\s*(\[[^[\]]*\]|"[^"]*"|'[^']*'|`[^`]*`|[\w$.]+\.querySelector(?:All)?\(\s*(?:"[^"]*"|'[^']*'|`[^`]*`)\s*\)|[^,()[\]]+?)\s*,\s*\{([\s\S]*?)\}\s*\)/g;
4333
4338
  let match;
4334
4339
  while ((match = pattern.exec(source)) !== null) {
4335
4340
  if (indexInsideNonIifeRange(match.index, source, functionRanges)) continue;
@@ -4990,7 +4995,12 @@ ${right.raw}`)
4990
4995
  (win) => win.position <= SCENE_BOUNDARY_EPSILON_SECONDS && (isHiddenGsapState(win.propertyValues) || win.fromPropertyValues !== void 0 && isHiddenGsapState(win.fromPropertyValues))
4991
4996
  );
4992
4997
  if (startsHiddenAtZero) continue;
4993
- if (selectors.some((selector2) => authoredHiddenSelectors.has(selector2))) continue;
4998
+ if (selectors.some(
4999
+ (selector2) => [...authoredHiddenSelectors].some(
5000
+ (hidden) => hidden === selector2 || targetedSelectorTokens(hidden).has(selector2)
5001
+ )
5002
+ ))
5003
+ continue;
4994
5004
  const firstVisible = visibilityWindows.find((win) => makesOverlayVisible(win));
4995
5005
  if (!firstVisible) continue;
4996
5006
  const selector = selectors.find(
@@ -6435,6 +6445,9 @@ function extractFontFaceFamilies(styles) {
6435
6445
  }
6436
6446
  return families;
6437
6447
  }
6448
+ function declaredFontFamilies(styles, options) {
6449
+ return /* @__PURE__ */ new Set([...extractFontFaceFamilies(styles), ...options.projectFontFamilies ?? []]);
6450
+ }
6438
6451
  function normalizeUsedFontName(part) {
6439
6452
  const name = part.trim().replace(/\s*!important\s*$/i, "").replace(/^['"]|['"]$/g, "").trim().toLowerCase();
6440
6453
  if (!name || name.includes("(") || name.includes(")")) return null;
@@ -6544,7 +6557,7 @@ var fontRules = [
6544
6557
  // so there is nothing for the author to act on.
6545
6558
  ({ styles, options }) => {
6546
6559
  if (!options.distributed) return [];
6547
- const declared = extractFontFaceFamilies(styles);
6560
+ const declared = declaredFontFamilies(styles, options);
6548
6561
  const used = extractUsedFontFamilies(styles);
6549
6562
  const aliased = collectAliasedFonts(used, declared);
6550
6563
  if (aliased.length === 0) return [];
@@ -6560,7 +6573,7 @@ var fontRules = [
6560
6573
  ({ styles, source, rawSource, options, locate }) => {
6561
6574
  if (isRegistrySourceFile(options.filePath) || isRegistryInstalledFile(rawSource)) return [];
6562
6575
  const findings = [];
6563
- const declared = extractFontFaceFamilies(styles);
6576
+ const declared = declaredFontFamilies(styles, options);
6564
6577
  const used = extractUsedFontFamilies(styles);
6565
6578
  const googleFonts = collectGoogleFontFamilies(source, styles);
6566
6579
  const undeclared = used.filter(
@@ -7512,6 +7525,42 @@ function resolveCssAssetCandidates(projectDir, url, htmlCompSrcPath, cssRootRela
7512
7525
  }
7513
7526
  return resolveLocalAssetCandidates(projectDir, url);
7514
7527
  }
7528
+ function mountedSources(projectDir, rootHtml, rootCompSrcPath) {
7529
+ const sources = [{ html: rootHtml, compSrcPath: rootCompSrcPath }];
7530
+ const seen = /* @__PURE__ */ new Set();
7531
+ for (const source of sources) {
7532
+ const { document } = parseHTML3(source.html);
7533
+ for (const host of querySelectorAllIncludingTemplates(document, "[data-composition-src]")) {
7534
+ const src = host.getAttribute("data-composition-src")?.trim();
7535
+ if (!src || isUnresolvedAssetPlaceholder2(src) || isRemoteOrInlineUrl2(src)) continue;
7536
+ const path = resolve(projectDir, src);
7537
+ if (seen.has(path) || !isWithinProjectRoot2(projectDir, path)) continue;
7538
+ seen.add(path);
7539
+ const read = readProjectFile2(path);
7540
+ if (read.kind !== "file") continue;
7541
+ sources.push({
7542
+ html: read.text,
7543
+ compSrcPath: relative2(resolve(projectDir), path).replace(/\\/g, "/")
7544
+ });
7545
+ }
7546
+ }
7547
+ return sources;
7548
+ }
7549
+ function declaredProjectFontFamilies(projectDir, sources) {
7550
+ const families = /* @__PURE__ */ new Set();
7551
+ for (const { html, compSrcPath } of sources) {
7552
+ const { document } = parseHTML3(html);
7553
+ const styles = [
7554
+ ...querySelectorAllIncludingTemplates(document, "style").map((style) => ({
7555
+ content: style.textContent ?? ""
7556
+ })),
7557
+ ...collectLocalStylesheets(projectDir, document, compSrcPath)
7558
+ ];
7559
+ for (const family of extractFontFaceFamilies(styles)) families.add(family);
7560
+ for (const family of collectGoogleFontFamilies(html, styles)) families.add(family);
7561
+ }
7562
+ return [...families];
7563
+ }
7515
7564
  async function lintProject(projectDir, entryFile, hostOptions = {}) {
7516
7565
  const indexPath = entryFile ? resolve(entryFile) : resolve(projectDir, "index.html");
7517
7566
  if (entryFile && !isWithinProjectRoot2(projectDir, indexPath)) {
@@ -7524,11 +7573,16 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7524
7573
  let totalWarnings = 0;
7525
7574
  let totalInfos = 0;
7526
7575
  const rootHtml = readFileSync(indexPath, "utf-8");
7576
+ const projectFontFamilies = declaredProjectFontFamilies(
7577
+ projectDir,
7578
+ mountedSources(projectDir, rootHtml, rootCompSrcPath)
7579
+ );
7527
7580
  const rootResult = await lintHyperframeHtml(rootHtml, {
7528
7581
  ...hostOptions,
7529
7582
  filePath: indexPath,
7530
7583
  compSrcPath: rootFile,
7531
- externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath)
7584
+ externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath),
7585
+ projectFontFamilies
7532
7586
  });
7533
7587
  results.push({ file: rootFile, result: rootResult, contentHash: contentDigest(rootHtml) });
7534
7588
  totalErrors += rootResult.errorCount;
@@ -7562,7 +7616,8 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7562
7616
  filePath,
7563
7617
  isSubComposition: true,
7564
7618
  compSrcPath,
7565
- externalStyles: collectExternalStyles(projectDir, html, compSrcPath)
7619
+ externalStyles: collectExternalStyles(projectDir, html, compSrcPath),
7620
+ projectFontFamilies
7566
7621
  });
7567
7622
  results.push({
7568
7623
  file: `compositions/${file}`,
@@ -7580,6 +7635,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7580
7635
  ...lintAudioSrcNotFound(projectDir, allHtmlSources),
7581
7636
  ...lintMissingLocalAsset(projectDir, allHtmlSources),
7582
7637
  ...lintTextureMaskAssetNotFound(projectDir, allHtmlSources),
7638
+ ...lintLinkedStylesheetAssets(projectDir, allHtmlSources),
7583
7639
  ...!entryFile ? lintMultipleRootCompositions(projectDir) : [],
7584
7640
  ...!entryFile ? lintBlankRootWithStandaloneComposition(rootHtml, allHtmlSources) : [],
7585
7641
  ...lintDuplicateAudioTracks(projectDir, rootHtml),
@@ -7789,6 +7845,61 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
7789
7845
  };
7790
7846
  });
7791
7847
  }
7848
+ function nearestAncestorMatch(projectDir, cssRootRelativePath, url) {
7849
+ const root = resolve(projectDir);
7850
+ for (let dir = resolve(root, dirname2(cssRootRelativePath), ".."); ; dir = dirname2(dir)) {
7851
+ if (!isWithinProjectRoot2(projectDir, dir)) return null;
7852
+ if (resolveLocalAssetCandidates(projectDir, relative2(root, join3(dir, url))).some(existsSync2)) {
7853
+ return relative2(dirname2(resolve(root, cssRootRelativePath)), join3(dir, url)).replace(
7854
+ /\\/g,
7855
+ "/"
7856
+ );
7857
+ }
7858
+ if (dir === root) return null;
7859
+ }
7860
+ }
7861
+ function lintLinkedStylesheetAssets(projectDir, htmlSources) {
7862
+ const seen = /* @__PURE__ */ new Set();
7863
+ const findings = [];
7864
+ for (const { html, compSrcPath } of htmlSources) {
7865
+ for (const { content, rootRelativePath } of collectLocalStylesheets(
7866
+ projectDir,
7867
+ parseHTML3(html).document,
7868
+ compSrcPath
7869
+ )) {
7870
+ let root;
7871
+ try {
7872
+ root = postcss4.parse(content);
7873
+ } catch (error) {
7874
+ if (!(error instanceof postcss4.CssSyntaxError)) throw error;
7875
+ continue;
7876
+ }
7877
+ root.walkDecls((declaration) => {
7878
+ if (/^(?:-webkit-)?mask-image$/i.test(declaration.prop)) return;
7879
+ for (const match of declaration.value.matchAll(CSS_URL_RE)) {
7880
+ const rawUrl = match[1] ?? match[2] ?? match[3] ?? "";
7881
+ if (isUnresolvedAssetPlaceholder2(rawUrl)) continue;
7882
+ const url = cleanAssetUrl2(rawUrl);
7883
+ if (!url || isRemoteOrInlineUrl2(url) || url.startsWith("/")) continue;
7884
+ if (resolveCssAssetCandidates(projectDir, url, void 0, rootRelativePath).some(existsSync2))
7885
+ continue;
7886
+ const key = `${rootRelativePath}\0${url}`;
7887
+ if (seen.has(key)) continue;
7888
+ seen.add(key);
7889
+ const found = nearestAncestorMatch(projectDir, rootRelativePath, url);
7890
+ findings.push({
7891
+ code: "missing_stylesheet_asset",
7892
+ severity: "warning",
7893
+ file: join3(projectDir, rootRelativePath),
7894
+ message: `${rootRelativePath} references url(${url}), which is not found beside the stylesheet (looked in ${dirname2(rootRelativePath)}/). The browser resolves url() from the stylesheet, so the file will 404.`,
7895
+ fixHint: found ? `A file exists at "${found}" relative to the stylesheet. Change the url() to that path.` : `Add "${url}" beside the stylesheet, or point the url() at an existing file.`
7896
+ });
7897
+ }
7898
+ });
7899
+ }
7900
+ }
7901
+ return findings;
7902
+ }
7792
7903
  function lintMultipleRootCompositions(projectDir) {
7793
7904
  const findings = [];
7794
7905
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.8.220",
3
+ "version": "0.8.222",
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.220"
38
+ "@hyperframes/parsers": "0.8.222"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^25.0.10",