@hyperframes/lint 0.9.16 → 0.9.18

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
@@ -2797,6 +2797,9 @@ function findAudioGroupCarveAttrFindings(ctx) {
2797
2797
  return findings;
2798
2798
  }
2799
2799
 
2800
+ // src/rules/gsap.ts
2801
+ import postcss2 from "postcss";
2802
+
2800
2803
  // src/scriptPositions.ts
2801
2804
  import { parse as parse3 } from "acorn";
2802
2805
  import { simple as simple3 } from "acorn-walk";
@@ -4920,17 +4923,30 @@ function indexInsideNonIifeRange(index, source, ranges) {
4920
4923
  (range) => index > range.start && index < range.end && !isIifeBody(source, range)
4921
4924
  );
4922
4925
  }
4926
+ function cssRuleSubjects(styles) {
4927
+ const rules = [];
4928
+ for (const style of styles) {
4929
+ let root;
4930
+ try {
4931
+ root = postcss2.parse(style.content);
4932
+ } catch {
4933
+ continue;
4934
+ }
4935
+ root.walkRules((rule) => {
4936
+ const subjects = rule.selectors.flatMap(
4937
+ (selector) => /[#.][a-zA-Z0-9_-]+$/.exec(selector.trim())?.[0] ?? []
4938
+ );
4939
+ if (subjects.length) rules.push({ declarations: rule.nodes.map(String), subjects });
4940
+ });
4941
+ }
4942
+ return rules;
4943
+ }
4923
4944
  function collectCssOpacityZeroSelectors(styles, tags) {
4924
4945
  const selectors = /* @__PURE__ */ new Set();
4925
4946
  const opacityExactlyZero = /opacity\s*:\s*0(?:\.0+)?\s*(?:;|$)/;
4926
- for (const style of styles) {
4927
- for (const [, selector, body] of style.content.matchAll(
4928
- /([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
4929
- )) {
4930
- if (body && opacityExactlyZero.test(body)) {
4931
- selectors.add((selector ?? "").trim());
4932
- }
4933
- }
4947
+ for (const { declarations, subjects } of cssRuleSubjects(styles)) {
4948
+ if (!declarations.some((declaration) => opacityExactlyZero.test(declaration))) continue;
4949
+ for (const subject of subjects) selectors.add(subject);
4934
4950
  }
4935
4951
  for (const tag of tags) {
4936
4952
  const inlineStyle = readAttr(tag.raw, "style");
@@ -5138,17 +5154,13 @@ ${right.raw}`)
5138
5154
  const findings = [];
5139
5155
  const cssTranslateSelectors = /* @__PURE__ */ new Map();
5140
5156
  const cssScaleSelectors = /* @__PURE__ */ new Map();
5141
- for (const style of styles) {
5142
- for (const [, selector, body] of style.content.matchAll(
5143
- /([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
5144
- )) {
5145
- const tMatch = body?.match(/transform\s*:\s*([^;]+)/);
5146
- if (!tMatch || !tMatch[1]) continue;
5147
- const transformVal = tMatch[1].trim();
5148
- if (/translate/i.test(transformVal))
5149
- cssTranslateSelectors.set((selector ?? "").trim(), transformVal);
5150
- if (/scale/i.test(transformVal))
5151
- cssScaleSelectors.set((selector ?? "").trim(), transformVal);
5157
+ for (const { declarations, subjects } of cssRuleSubjects(styles)) {
5158
+ const tMatch = declarations.join(";").match(/transform\s*:\s*([^;]+)/);
5159
+ if (!tMatch || !tMatch[1]) continue;
5160
+ const transformVal = tMatch[1].trim();
5161
+ for (const subject of subjects) {
5162
+ if (/translate/i.test(transformVal)) cssTranslateSelectors.set(subject, transformVal);
5163
+ if (/scale/i.test(transformVal)) cssScaleSelectors.set(subject, transformVal);
5152
5164
  }
5153
5165
  }
5154
5166
  for (const tag of tags) {
@@ -6308,7 +6320,7 @@ var adapterRules = [
6308
6320
  ];
6309
6321
 
6310
6322
  // src/rules/textures.ts
6311
- import postcss2 from "postcss";
6323
+ import postcss3 from "postcss";
6312
6324
  var TEXTURE_BASE_CLASS = "hf-texture-text";
6313
6325
  var TEXTURE_CLASS_PREFIX = "hf-texture-";
6314
6326
  function classNames(tag) {
@@ -6355,7 +6367,7 @@ function collectTextureCss(styles) {
6355
6367
  for (const style of styles) {
6356
6368
  let root;
6357
6369
  try {
6358
- root = postcss2.parse(style.content);
6370
+ root = postcss3.parse(style.content);
6359
6371
  } catch {
6360
6372
  continue;
6361
6373
  }
@@ -6485,7 +6497,7 @@ var textureRules = [
6485
6497
  ];
6486
6498
 
6487
6499
  // src/rules/fonts.ts
6488
- import postcss3 from "postcss";
6500
+ import postcss4 from "postcss";
6489
6501
  import {
6490
6502
  FONT_ALIAS_KEYS,
6491
6503
  parseFontShorthand,
@@ -6518,7 +6530,7 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
6518
6530
  ]);
6519
6531
  function parseFontCss(css) {
6520
6532
  try {
6521
- return postcss3.parse(css);
6533
+ return postcss4.parse(css);
6522
6534
  } catch {
6523
6535
  return null;
6524
6536
  }
@@ -6560,7 +6572,7 @@ function collectFontCustomProperties(styles) {
6560
6572
  return properties;
6561
6573
  }
6562
6574
  function resolveUsedFontNames(value, properties, resolving = /* @__PURE__ */ new Set()) {
6563
- return postcss3.list.comma(value).flatMap((part) => {
6575
+ return postcss4.list.comma(value).flatMap((part) => {
6564
6576
  const variable = /^var\(\s*(--[^\s,()]+)\s*(?:,[\s\S]*)?\)$/i.exec(part.trim());
6565
6577
  if (!variable) {
6566
6578
  const name = normalizeUsedFontName(part);
@@ -6722,7 +6734,7 @@ function uniqueFontLocation(styles, families, locate) {
6722
6734
  const properties = collectFontCustomProperties(styles);
6723
6735
  for (const style of styles) {
6724
6736
  try {
6725
- postcss3.parse(style.content).walkDecls(FONT_FAMILY_PROPS, (decl) => {
6737
+ postcss4.parse(style.content).walkDecls(FONT_FAMILY_PROPS, (decl) => {
6726
6738
  const named = resolveUsedFontNames(usedFamilyList(decl), properties);
6727
6739
  if (!named.some((name) => families.includes(name))) return;
6728
6740
  locations.push(locate(style, decl.source?.start?.offset));
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Ignore } from 'ignore';
2
+
1
3
  type HyperframeLintSeverity = "error" | "warning" | "info";
2
4
  type HyperframeLintFinding = {
3
5
  code: string;
@@ -106,9 +108,12 @@ interface ProjectLintResult {
106
108
  totalWarnings: number;
107
109
  totalInfos: number;
108
110
  }
109
- declare function collectExternalStyles(projectDir: string, html: string, compSrcPath?: string): NonNullable<HyperframeLinterOptions["externalStyles"]>;
111
+ declare function linkedSources(projectDir: string, html: string, compSrcPath?: string): Pick<HyperframeLinterOptions, "externalStyles" | "externalScripts">;
110
112
  declare function lintProject(projectDir: string, entryFile?: string, hostOptions?: Pick<HyperframeLinterOptions, "host" | "isSubComposition">): Promise<ProjectLintResult>;
111
113
 
114
+ /** What the project leaves out; ask with a relative `/` path, a folder with a trailing `/`. */
115
+ declare function projectIgnore(projectDir: string): Ignore;
116
+
112
117
  type AudioAttribute = "data-automation" | "data-fx-chain";
113
118
  /**
114
119
  * Why the renderer would refuse an audio attribute's value before reading any lane or effect, or null when the
@@ -116,4 +121,4 @@ type AudioAttribute = "data-automation" | "data-fx-chain";
116
121
  */
117
122
  declare function audioAttributeShapeError(attr: AudioAttribute, json: string): string | null;
118
123
 
119
- export { type AudioAttribute, type HyperframeLintFinding, type HyperframeLintResult, type HyperframeLintSeverity, type HyperframeLinterOptions, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS, type LintTimings, type ProjectLintResult, audioAttributeShapeError, collectExternalStyles, lintHyperframeHtml, lintMediaUrls, lintProject, shouldBlockRender };
124
+ export { type AudioAttribute, type HyperframeLintFinding, type HyperframeLintResult, type HyperframeLintSeverity, type HyperframeLinterOptions, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS, type LintTimings, type ProjectLintResult, audioAttributeShapeError, linkedSources, lintHyperframeHtml, lintMediaUrls, lintProject, projectIgnore, shouldBlockRender };
package/dist/index.js CHANGED
@@ -2803,6 +2803,9 @@ function findAudioGroupCarveAttrFindings(ctx) {
2803
2803
  return findings;
2804
2804
  }
2805
2805
 
2806
+ // src/rules/gsap.ts
2807
+ import postcss2 from "postcss";
2808
+
2806
2809
  // src/scriptPositions.ts
2807
2810
  import { parse as parse3 } from "acorn";
2808
2811
  import { simple as simple3 } from "acorn-walk";
@@ -4926,17 +4929,30 @@ function indexInsideNonIifeRange(index, source, ranges) {
4926
4929
  (range) => index > range.start && index < range.end && !isIifeBody(source, range)
4927
4930
  );
4928
4931
  }
4932
+ function cssRuleSubjects(styles) {
4933
+ const rules = [];
4934
+ for (const style of styles) {
4935
+ let root;
4936
+ try {
4937
+ root = postcss2.parse(style.content);
4938
+ } catch {
4939
+ continue;
4940
+ }
4941
+ root.walkRules((rule) => {
4942
+ const subjects = rule.selectors.flatMap(
4943
+ (selector) => /[#.][a-zA-Z0-9_-]+$/.exec(selector.trim())?.[0] ?? []
4944
+ );
4945
+ if (subjects.length) rules.push({ declarations: rule.nodes.map(String), subjects });
4946
+ });
4947
+ }
4948
+ return rules;
4949
+ }
4929
4950
  function collectCssOpacityZeroSelectors(styles, tags) {
4930
4951
  const selectors = /* @__PURE__ */ new Set();
4931
4952
  const opacityExactlyZero = /opacity\s*:\s*0(?:\.0+)?\s*(?:;|$)/;
4932
- for (const style of styles) {
4933
- for (const [, selector, body] of style.content.matchAll(
4934
- /([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
4935
- )) {
4936
- if (body && opacityExactlyZero.test(body)) {
4937
- selectors.add((selector ?? "").trim());
4938
- }
4939
- }
4953
+ for (const { declarations, subjects } of cssRuleSubjects(styles)) {
4954
+ if (!declarations.some((declaration) => opacityExactlyZero.test(declaration))) continue;
4955
+ for (const subject of subjects) selectors.add(subject);
4940
4956
  }
4941
4957
  for (const tag of tags) {
4942
4958
  const inlineStyle = readAttr(tag.raw, "style");
@@ -5144,17 +5160,13 @@ ${right.raw}`)
5144
5160
  const findings = [];
5145
5161
  const cssTranslateSelectors = /* @__PURE__ */ new Map();
5146
5162
  const cssScaleSelectors = /* @__PURE__ */ new Map();
5147
- for (const style of styles) {
5148
- for (const [, selector, body] of style.content.matchAll(
5149
- /([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
5150
- )) {
5151
- const tMatch = body?.match(/transform\s*:\s*([^;]+)/);
5152
- if (!tMatch || !tMatch[1]) continue;
5153
- const transformVal = tMatch[1].trim();
5154
- if (/translate/i.test(transformVal))
5155
- cssTranslateSelectors.set((selector ?? "").trim(), transformVal);
5156
- if (/scale/i.test(transformVal))
5157
- cssScaleSelectors.set((selector ?? "").trim(), transformVal);
5163
+ for (const { declarations, subjects } of cssRuleSubjects(styles)) {
5164
+ const tMatch = declarations.join(";").match(/transform\s*:\s*([^;]+)/);
5165
+ if (!tMatch || !tMatch[1]) continue;
5166
+ const transformVal = tMatch[1].trim();
5167
+ for (const subject of subjects) {
5168
+ if (/translate/i.test(transformVal)) cssTranslateSelectors.set(subject, transformVal);
5169
+ if (/scale/i.test(transformVal)) cssScaleSelectors.set(subject, transformVal);
5158
5170
  }
5159
5171
  }
5160
5172
  for (const tag of tags) {
@@ -6314,7 +6326,7 @@ var adapterRules = [
6314
6326
  ];
6315
6327
 
6316
6328
  // src/rules/textures.ts
6317
- import postcss2 from "postcss";
6329
+ import postcss3 from "postcss";
6318
6330
  var TEXTURE_BASE_CLASS = "hf-texture-text";
6319
6331
  var TEXTURE_CLASS_PREFIX = "hf-texture-";
6320
6332
  function classNames(tag) {
@@ -6361,7 +6373,7 @@ function collectTextureCss(styles) {
6361
6373
  for (const style of styles) {
6362
6374
  let root;
6363
6375
  try {
6364
- root = postcss2.parse(style.content);
6376
+ root = postcss3.parse(style.content);
6365
6377
  } catch {
6366
6378
  continue;
6367
6379
  }
@@ -6491,7 +6503,7 @@ var textureRules = [
6491
6503
  ];
6492
6504
 
6493
6505
  // src/rules/fonts.ts
6494
- import postcss3 from "postcss";
6506
+ import postcss4 from "postcss";
6495
6507
  import {
6496
6508
  FONT_ALIAS_KEYS,
6497
6509
  parseFontShorthand,
@@ -6524,7 +6536,7 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
6524
6536
  ]);
6525
6537
  function parseFontCss(css) {
6526
6538
  try {
6527
- return postcss3.parse(css);
6539
+ return postcss4.parse(css);
6528
6540
  } catch {
6529
6541
  return null;
6530
6542
  }
@@ -6566,7 +6578,7 @@ function collectFontCustomProperties(styles) {
6566
6578
  return properties;
6567
6579
  }
6568
6580
  function resolveUsedFontNames(value, properties, resolving = /* @__PURE__ */ new Set()) {
6569
- return postcss3.list.comma(value).flatMap((part) => {
6581
+ return postcss4.list.comma(value).flatMap((part) => {
6570
6582
  const variable = /^var\(\s*(--[^\s,()]+)\s*(?:,[\s\S]*)?\)$/i.exec(part.trim());
6571
6583
  if (!variable) {
6572
6584
  const name = normalizeUsedFontName(part);
@@ -6728,7 +6740,7 @@ function uniqueFontLocation(styles, families, locate) {
6728
6740
  const properties = collectFontCustomProperties(styles);
6729
6741
  for (const style of styles) {
6730
6742
  try {
6731
- postcss3.parse(style.content).walkDecls(FONT_FAMILY_PROPS, (decl) => {
6743
+ postcss4.parse(style.content).walkDecls(FONT_FAMILY_PROPS, (decl) => {
6732
6744
  const named = resolveUsedFontNames(usedFamilyList(decl), properties);
6733
6745
  if (!named.some((name) => families.includes(name))) return;
6734
6746
  locations.push(locate(style, decl.source?.start?.offset));
@@ -7249,8 +7261,22 @@ function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings)
7249
7261
 
7250
7262
  // src/project.ts
7251
7263
  import { createHash } from "crypto";
7252
- import { existsSync as existsSync2, readdirSync } from "fs";
7253
- import { dirname as dirname2, extname, join as join3, relative as relative2, resolve } from "path";
7264
+ import { existsSync as existsSync3, readdirSync } from "fs";
7265
+
7266
+ // src/projectIgnore.ts
7267
+ import { existsSync, readFileSync } from "fs";
7268
+ import { join } from "path";
7269
+ import ignore from "ignore";
7270
+ var DEFAULT_PROJECT_IGNORE = ["/renders/", "/snapshots/", ".*"];
7271
+ function projectIgnore(projectDir) {
7272
+ const matcher = ignore().add(DEFAULT_PROJECT_IGNORE);
7273
+ const ignorePath = join(projectDir, ".hyperframesignore");
7274
+ if (existsSync(ignorePath)) matcher.add(readFileSync(ignorePath, "utf-8"));
7275
+ return matcher;
7276
+ }
7277
+
7278
+ // src/project.ts
7279
+ import { dirname as dirname2, extname, join as join4, relative as relative2, resolve } from "path";
7254
7280
  import { rewriteAssetPath as rewriteAssetPath2 } from "@hyperframes/parsers/asset-paths";
7255
7281
  import { spansOverlap } from "@hyperframes/parsers/time-spans";
7256
7282
  import {
@@ -7266,7 +7292,7 @@ import {
7266
7292
  } from "@hyperframes/parsers/reference-resolver";
7267
7293
  import { checkSubCompositionUsability } from "@hyperframes/parsers/sub-composition-validity";
7268
7294
  import { parseHTML as parseHTML3 } from "linkedom";
7269
- import postcss4 from "postcss";
7295
+ import postcss5 from "postcss";
7270
7296
  import {
7271
7297
  cleanAssetUrl as cleanAssetUrl2,
7272
7298
  collectSubCompositionSrcs,
@@ -7284,8 +7310,8 @@ import { encodeUrlPath } from "@hyperframes/parsers";
7284
7310
 
7285
7311
  // src/hevcPreviewLint.ts
7286
7312
  import { execFile } from "child_process";
7287
- import { existsSync } from "fs";
7288
- import { join } from "path";
7313
+ import { existsSync as existsSync2 } from "fs";
7314
+ import { join as join2 } from "path";
7289
7315
  import { rewriteAssetPath } from "@hyperframes/parsers/asset-paths";
7290
7316
  import { parseNumeric } from "@hyperframes/parsers/composition-contract";
7291
7317
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
@@ -7326,7 +7352,7 @@ function resolveLocalVideoReference(projectDir, rawSrc, compSrcPath) {
7326
7352
  if (isUnresolvedAssetPlaceholder(rawSrc)) return null;
7327
7353
  const src = cleanAssetUrl(rawSrc);
7328
7354
  if (!src || isRemoteOrInlineUrl(src)) return null;
7329
- const rootRelative = compSrcPath ? rewriteAssetPath(compSrcPath, src, (path) => existsSync(join(projectDir, path))) : src;
7355
+ const rootRelative = compSrcPath ? rewriteAssetPath(compSrcPath, src, (path) => existsSync2(join2(projectDir, path))) : src;
7330
7356
  const asset = resolveExistingLocalAsset(projectDir, rootRelative);
7331
7357
  return asset ? { resolved: asset.resolved, src } : null;
7332
7358
  }
@@ -7520,7 +7546,7 @@ async function lintHevcPreviewCodec(candidates) {
7520
7546
 
7521
7547
  // src/projectShaders.ts
7522
7548
  import { realpathSync } from "fs";
7523
- import { dirname, join as join2, relative } from "path";
7549
+ import { dirname, join as join3, relative } from "path";
7524
7550
  import {
7525
7551
  isWithinProjectRoot,
7526
7552
  readProjectFile,
@@ -7531,7 +7557,7 @@ function isAuthoredScript(path) {
7531
7557
  }
7532
7558
  function localScript(projectDir, src, compSrcPath) {
7533
7559
  if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(src)) return;
7534
- const besideFile = src.startsWith("/") ? src : join2(dirname(compSrcPath ?? "index.html"), src);
7560
+ const besideFile = src.startsWith("/") ? src : join3(dirname(compSrcPath ?? "index.html"), src);
7535
7561
  const asset = resolveExistingLocalAsset2(projectDir, besideFile);
7536
7562
  if (!asset) return;
7537
7563
  try {
@@ -7604,7 +7630,7 @@ function collectLocalStylesheets(projectDir, document, compSrcPath) {
7604
7630
  if (!rel.split(/\s+/).some((part) => part.toLowerCase() === "stylesheet")) continue;
7605
7631
  const href = link.getAttribute("href") ?? "";
7606
7632
  if (!isLocalStylesheetHref(href)) continue;
7607
- const besideFile = compSrcPath ? join3(dirname2(compSrcPath), href) : href;
7633
+ const besideFile = compSrcPath ? join4(dirname2(compSrcPath), href) : href;
7608
7634
  const stylesheet = resolveExistingLocalAsset3(projectDir, besideFile) ?? (compSrcPath && staysInsideProjectRoot(href) ? resolveExistingLocalAsset3(projectDir, href) : null);
7609
7635
  if (!stylesheet) continue;
7610
7636
  const read = readProjectFile2(stylesheet.resolved);
@@ -7621,11 +7647,17 @@ function collectExternalStyles(projectDir, html, compSrcPath) {
7621
7647
  document,
7622
7648
  compSrcPath
7623
7649
  )) {
7624
- styles.push({ href, content, file: join3(projectDir, rootRelativePath), rootRelativePath });
7650
+ styles.push({ href, content, file: join4(projectDir, rootRelativePath), rootRelativePath });
7625
7651
  }
7626
7652
  return styles;
7627
7653
  }
7628
7654
  var MAX_EXTERNAL_SCRIPT_BYTES = 1e6;
7655
+ function linkedSources(projectDir, html, compSrcPath) {
7656
+ return {
7657
+ externalStyles: collectExternalStyles(projectDir, html, compSrcPath),
7658
+ externalScripts: collectExternalScripts(projectDir, html, compSrcPath)
7659
+ };
7660
+ }
7629
7661
  function collectExternalScripts(projectDir, html, compSrcPath) {
7630
7662
  const scripts = [];
7631
7663
  const { document } = parseHTML3(html);
@@ -7633,7 +7665,7 @@ function collectExternalScripts(projectDir, html, compSrcPath) {
7633
7665
  const src = element.getAttribute("src") ?? "";
7634
7666
  if (!isLocalStylesheetHref(src) || /\.min\.m?js(?:[?#]|$)/i.test(src)) continue;
7635
7667
  const path = src.split(/[?#]/)[0] ?? src;
7636
- const besideFile = compSrcPath ? join3(dirname2(compSrcPath), path) : path;
7668
+ const besideFile = compSrcPath ? join4(dirname2(compSrcPath), path) : path;
7637
7669
  const script = resolveExistingLocalAsset3(projectDir, besideFile) ?? (compSrcPath && staysInsideProjectRoot(path) ? resolveExistingLocalAsset3(projectDir, path) : null);
7638
7670
  if (!script) continue;
7639
7671
  const read = readProjectFile2(script.resolved);
@@ -7643,7 +7675,7 @@ function collectExternalScripts(projectDir, html, compSrcPath) {
7643
7675
  src,
7644
7676
  attrs,
7645
7677
  content: read.text,
7646
- file: join3(projectDir, script.rootRelativePath),
7678
+ file: join4(projectDir, script.rootRelativePath),
7647
7679
  rootRelativePath: script.rootRelativePath
7648
7680
  });
7649
7681
  }
@@ -7672,12 +7704,12 @@ function collectCssSources(projectDir, html, compSrcPath) {
7672
7704
  function resolveCssAssetCandidates(projectDir, url, htmlCompSrcPath, cssRootRelativePath) {
7673
7705
  if (url.startsWith("/")) return resolveLocalAssetCandidates(projectDir, url);
7674
7706
  if (cssRootRelativePath) {
7675
- return resolveLocalAssetCandidates(projectDir, join3(dirname2(cssRootRelativePath), url));
7707
+ return resolveLocalAssetCandidates(projectDir, join4(dirname2(cssRootRelativePath), url));
7676
7708
  }
7677
7709
  if (htmlCompSrcPath) {
7678
7710
  return resolveLocalAssetCandidates(
7679
7711
  projectDir,
7680
- rewriteAssetPath2(htmlCompSrcPath, url, (path) => existsSync2(join3(projectDir, path)))
7712
+ rewriteAssetPath2(htmlCompSrcPath, url, (path) => existsSync3(join4(projectDir, path)))
7681
7713
  );
7682
7714
  }
7683
7715
  return resolveLocalAssetCandidates(projectDir, url);
@@ -7738,9 +7770,8 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7738
7770
  ...hostOptions,
7739
7771
  filePath: indexPath,
7740
7772
  compSrcPath: rootFile,
7741
- externalStyles: collectExternalStyles(projectDir, rootHtml, rootCompSrcPath),
7742
- projectFontFamilies,
7743
- externalScripts: collectExternalScripts(projectDir, rootHtml, rootCompSrcPath)
7773
+ ...linkedSources(projectDir, rootHtml, rootCompSrcPath),
7774
+ projectFontFamilies
7744
7775
  });
7745
7776
  results.push({ file: rootFile, result: rootResult, contentHash: contentDigest(rootHtml) });
7746
7777
  totalErrors += rootResult.errorCount;
@@ -7748,14 +7779,16 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7748
7779
  totalInfos += rootResult.infoCount;
7749
7780
  const allHtmlSources = [{ html: rootHtml, compSrcPath: rootCompSrcPath }];
7750
7781
  const compositionsDir = resolve(projectDir, "compositions");
7751
- if (!entryFile && existsSync2(compositionsDir)) {
7782
+ if (!entryFile && existsSync3(compositionsDir)) {
7783
+ const ignored = projectIgnore(projectDir);
7752
7784
  const collectHtmlFiles = (dir, rel) => {
7753
7785
  const out = [];
7754
7786
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
7755
7787
  const relPath = rel ? `${rel}/${entry.name}` : entry.name;
7788
+ if (ignored.ignores(`compositions/${relPath}${entry.isDirectory() ? "/" : ""}`)) continue;
7756
7789
  if (entry.isDirectory()) {
7757
7790
  if (!rel && entry.name === "components") continue;
7758
- out.push(...collectHtmlFiles(join3(dir, entry.name), relPath));
7791
+ out.push(...collectHtmlFiles(join4(dir, entry.name), relPath));
7759
7792
  } else if (entry.isFile() && entry.name.endsWith(".html") && !entry.name.startsWith("._")) {
7760
7793
  out.push(relPath);
7761
7794
  }
@@ -7764,7 +7797,7 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7764
7797
  };
7765
7798
  const files = collectHtmlFiles(compositionsDir, "").sort();
7766
7799
  for (const file of files) {
7767
- const filePath = join3(compositionsDir, file);
7800
+ const filePath = join4(compositionsDir, file);
7768
7801
  const html = readTextFile(filePath);
7769
7802
  const compSrcPath = `compositions/${file}`;
7770
7803
  allHtmlSources.push({ html, compSrcPath });
@@ -7774,9 +7807,8 @@ async function lintProject(projectDir, entryFile, hostOptions = {}) {
7774
7807
  filePath,
7775
7808
  isSubComposition: true,
7776
7809
  compSrcPath,
7777
- externalStyles: collectExternalStyles(projectDir, html, compSrcPath),
7778
- projectFontFamilies,
7779
- externalScripts: collectExternalScripts(projectDir, html, compSrcPath)
7810
+ ...linkedSources(projectDir, html, compSrcPath),
7811
+ projectFontFamilies
7780
7812
  });
7781
7813
  results.push({
7782
7814
  file: `compositions/${file}`,
@@ -7878,7 +7910,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
7878
7910
  function urlSafeSrcOf(projectDir, rootRelative) {
7879
7911
  if (!/[?#]/.test(rootRelative)) return null;
7880
7912
  const literal = resolve(projectDir, rootRelative.replace(/^\//, ""));
7881
- if (!existsSync2(literal)) return null;
7913
+ if (!existsSync3(literal)) return null;
7882
7914
  return encodeUrlPath(rootRelative);
7883
7915
  }
7884
7916
  var urlSafeHint = (safe) => `The file exists, but a "#" or "?" in a src ends the file name there. Write the src as "${safe}".`;
@@ -7894,8 +7926,8 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
7894
7926
  const src = mediaSrcOf(match);
7895
7927
  if (/^(https?:|data:|blob:)/i.test(src)) continue;
7896
7928
  if (isUnresolvedAssetPlaceholder2(src)) continue;
7897
- const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
7898
- if (!existsSync2(resolveProjectRelativeSrc(rootRelative, projectDir))) {
7929
+ const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync3(join4(projectDir, path))) : src;
7930
+ if (!existsSync3(resolveProjectRelativeSrc(rootRelative, projectDir))) {
7899
7931
  const safe = urlSafeSrcOf(projectDir, rootRelative);
7900
7932
  if (safe) urlSafe.set(src, safe);
7901
7933
  const file = sourceFile(projectDir, compSrcPath);
@@ -7931,13 +7963,13 @@ function lintMissingLocalAsset(projectDir, htmlSources) {
7931
7963
  const src = cleanAssetUrl2(rawSrc);
7932
7964
  if (!src) continue;
7933
7965
  if (isRemoteOrInlineUrl2(src)) continue;
7934
- const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync2(join3(projectDir, path))) : src;
7966
+ const rootRelative = compSrcPath ? rewriteAssetPath2(compSrcPath, src, (path) => existsSync3(join4(projectDir, path))) : src;
7935
7967
  const resolvedAsset = resolveExistingLocalAsset3(projectDir, rootRelative);
7936
7968
  if (resolvedAsset) continue;
7937
7969
  const rawRoot = rawSrc.trim();
7938
7970
  const safe = urlSafeSrcOf(
7939
7971
  projectDir,
7940
- compSrcPath ? rewriteAssetPath2(compSrcPath, rawRoot, (path) => existsSync2(join3(projectDir, path))) : rawRoot
7972
+ compSrcPath ? rewriteAssetPath2(compSrcPath, rawRoot, (path) => existsSync3(join4(projectDir, path))) : rawRoot
7941
7973
  );
7942
7974
  if (safe) urlSafe.set(src, safe);
7943
7975
  const resolvedKey = resolve(projectDir, rootRelative);
@@ -7970,11 +8002,11 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
7970
8002
  for (const cssSource of collectCssSources(projectDir, html, compSrcPath)) {
7971
8003
  let matches = [];
7972
8004
  try {
7973
- postcss4.parse(cssSource.content).walkDecls(/^(?:-webkit-)?mask-image$/i, (declaration) => {
8005
+ postcss5.parse(cssSource.content).walkDecls(/^(?:-webkit-)?mask-image$/i, (declaration) => {
7974
8006
  matches.push(...declaration.value.matchAll(CSS_URL_RE));
7975
8007
  });
7976
8008
  } catch (error) {
7977
- if (!(error instanceof postcss4.CssSyntaxError)) throw error;
8009
+ if (!(error instanceof postcss5.CssSyntaxError)) throw error;
7978
8010
  matches = [...cssSource.content.matchAll(MASK_IMAGE_URL_RE)];
7979
8011
  }
7980
8012
  for (const match of matches) {
@@ -7988,7 +8020,7 @@ function lintTextureMaskAssetNotFound(projectDir, htmlSources) {
7988
8020
  compSrcPath,
7989
8021
  cssSource.rootRelativePath
7990
8022
  );
7991
- if (candidates.some(existsSync2)) continue;
8023
+ if (candidates.some(existsSync3)) continue;
7992
8024
  missingByFile.set(file, (missingByFile.get(file) ?? /* @__PURE__ */ new Set()).add(url));
7993
8025
  }
7994
8026
  }
@@ -8008,8 +8040,8 @@ function nearestAncestorMatch(projectDir, cssRootRelativePath, url) {
8008
8040
  const root = resolve(projectDir);
8009
8041
  for (let dir = resolve(root, dirname2(cssRootRelativePath), ".."); ; dir = dirname2(dir)) {
8010
8042
  if (!isWithinProjectRoot2(projectDir, dir)) return null;
8011
- if (resolveLocalAssetCandidates(projectDir, relative2(root, join3(dir, url))).some(existsSync2)) {
8012
- return relative2(dirname2(resolve(root, cssRootRelativePath)), join3(dir, url)).replace(
8043
+ if (resolveLocalAssetCandidates(projectDir, relative2(root, join4(dir, url))).some(existsSync3)) {
8044
+ return relative2(dirname2(resolve(root, cssRootRelativePath)), join4(dir, url)).replace(
8013
8045
  /\\/g,
8014
8046
  "/"
8015
8047
  );
@@ -8028,9 +8060,9 @@ function lintLinkedStylesheetAssets(projectDir, htmlSources) {
8028
8060
  )) {
8029
8061
  let root;
8030
8062
  try {
8031
- root = postcss4.parse(content);
8063
+ root = postcss5.parse(content);
8032
8064
  } catch (error) {
8033
- if (!(error instanceof postcss4.CssSyntaxError)) throw error;
8065
+ if (!(error instanceof postcss5.CssSyntaxError)) throw error;
8034
8066
  continue;
8035
8067
  }
8036
8068
  root.walkDecls((declaration) => {
@@ -8040,7 +8072,7 @@ function lintLinkedStylesheetAssets(projectDir, htmlSources) {
8040
8072
  if (isUnresolvedAssetPlaceholder2(rawUrl)) continue;
8041
8073
  const url = cleanAssetUrl2(rawUrl);
8042
8074
  if (!url || isRemoteOrInlineUrl2(url) || url.startsWith("/")) continue;
8043
- if (resolveCssAssetCandidates(projectDir, url, void 0, rootRelativePath).some(existsSync2))
8075
+ if (resolveCssAssetCandidates(projectDir, url, void 0, rootRelativePath).some(existsSync3))
8044
8076
  continue;
8045
8077
  const key = `${rootRelativePath}\0${url}`;
8046
8078
  if (seen.has(key)) continue;
@@ -8049,7 +8081,7 @@ function lintLinkedStylesheetAssets(projectDir, htmlSources) {
8049
8081
  findings.push({
8050
8082
  code: "missing_stylesheet_asset",
8051
8083
  severity: "warning",
8052
- file: join3(projectDir, rootRelativePath),
8084
+ file: join4(projectDir, rootRelativePath),
8053
8085
  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.`,
8054
8086
  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.`
8055
8087
  });
@@ -8069,7 +8101,7 @@ function lintMultipleRootCompositions(projectDir) {
8069
8101
  const rootCompositions = [];
8070
8102
  for (const file of rootHtmlFiles) {
8071
8103
  if (PAGES_OPENED_BESIDE_INDEX.has(file)) continue;
8072
- const content = readTextFile(join3(projectDir, file));
8104
+ const content = readTextFile(join4(projectDir, file));
8073
8105
  if (/data-composition-id/i.test(content)) {
8074
8106
  rootCompositions.push(file);
8075
8107
  }
@@ -8234,9 +8266,10 @@ export {
8234
8266
  LINT_RULE_COUNT,
8235
8267
  LINT_RULE_GROUP_COUNTS,
8236
8268
  audioAttributeShapeError,
8237
- collectExternalStyles,
8269
+ linkedSources,
8238
8270
  lintHyperframeHtml,
8239
8271
  lintMediaUrls,
8240
8272
  lintProject,
8273
+ projectIgnore,
8241
8274
  shouldBlockRender
8242
8275
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/lint",
3
- "version": "0.9.16",
3
+ "version": "0.9.18",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -32,10 +32,11 @@
32
32
  "acorn": "^8.17.0",
33
33
  "acorn-walk": "^8.3.5",
34
34
  "htmlparser2": "^10.1.0",
35
+ "ignore": "^5.3.2",
35
36
  "linkedom": "^0.18.12",
36
37
  "postcss": "^8.5.8",
37
38
  "postcss-selector-parser": "^7.1.4",
38
- "@hyperframes/parsers": "0.9.16"
39
+ "@hyperframes/parsers": "0.9.18"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/node": "^25.0.10",