@hyperframes/parsers 0.7.86 → 0.7.88

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.
@@ -11,11 +11,23 @@ declare function isRemoteOrInlineUrl(url: string): boolean;
11
11
  * substitutes before render. The static linter runs before that substitution,
12
12
  * so it cannot resolve such a value to a file on disk and must not report it as
13
13
  * a missing asset. Check the RAW url, before any `cleanAssetUrl()` step: that
14
- * splits on `?`/`#`, which also chops inside a `${...}` expression. (The older
15
- * `__UPPER__` placeholder shape predates this and keeps its own inline check at
16
- * each call site.)
14
+ * splits on `?`/`#`, which also chops inside a `${...}` expression. (The `__UPPER__`
15
+ * placeholder shape is combined with this in `isUnresolvedAssetPlaceholder` below, the
16
+ * shared predicate asset-src sites skip on.)
17
17
  */
18
18
  declare function hasUnresolvedTemplatingToken(url: string): boolean;
19
+ /**
20
+ * True when an asset src is a build-time placeholder rather than a resolvable path:
21
+ * the `__UPPER__` shape (e.g. `__DURATION__`) or an unresolved templating token
22
+ * (`<<...>>`, `{{...}}`, `${...}`). Pass the RAW src, before any `cleanAssetUrl()` step —
23
+ * cleanAssetUrl splits on `?`/`#`, which would chop inside a `${...}` expression and defeat
24
+ * the token match. Remote/inline URL handling is deliberately NOT folded in: call sites
25
+ * differ (e.g. audio uses a narrower http/data/blob check), so each keeps its own.
26
+ *
27
+ * This is the single skip predicate every asset-src lint / codec / compile site should
28
+ * route through, so the placeholder rules can't drift apart across call sites.
29
+ */
30
+ declare function isUnresolvedAssetPlaceholder(rawSrc: string): boolean;
19
31
  declare function cleanAssetUrl(url: string): string;
20
32
  declare function isWithinProjectRoot(projectDir: string, candidate: string): boolean;
21
33
  declare function resolveLocalAssetCandidates(projectDir: string, url: string): string[];
@@ -27,4 +39,4 @@ declare function resolveExistingLocalAsset(projectDir: string, url: string): {
27
39
  * regexes don't false-positive on commented-out or scripted markup. */
28
40
  declare function maskNonScannableRanges(html: string): string;
29
41
 
30
- export { cleanAssetUrl, hasUnresolvedTemplatingToken, isRemoteOrInlineUrl, isWithinProjectRoot, maskNonScannableRanges, resolveExistingLocalAsset, resolveLocalAssetCandidates };
42
+ export { cleanAssetUrl, hasUnresolvedTemplatingToken, isRemoteOrInlineUrl, isUnresolvedAssetPlaceholder, isWithinProjectRoot, maskNonScannableRanges, resolveExistingLocalAsset, resolveLocalAssetCandidates };
@@ -20,6 +20,9 @@ function isRemoteOrInlineUrl(url) {
20
20
  function hasUnresolvedTemplatingToken(url) {
21
21
  return /<<[^<>]+>>|\{\{[^{}]+\}\}|\$\{[^{}]+\}/.test(url);
22
22
  }
23
+ function isUnresolvedAssetPlaceholder(rawSrc) {
24
+ return /^__[A-Z_]+__$/.test(rawSrc.trim()) || hasUnresolvedTemplatingToken(rawSrc);
25
+ }
23
26
  function cleanAssetUrl(url) {
24
27
  return url.trim().split(/[?#]/, 1)[0] ?? "";
25
28
  }
@@ -83,6 +86,7 @@ export {
83
86
  cleanAssetUrl,
84
87
  hasUnresolvedTemplatingToken,
85
88
  isRemoteOrInlineUrl,
89
+ isUnresolvedAssetPlaceholder,
86
90
  isWithinProjectRoot,
87
91
  maskNonScannableRanges,
88
92
  resolveExistingLocalAsset,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assetResolution.ts","../src/utils/urlPath.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { isAbsolute, posix, relative, resolve } from \"node:path\";\nimport { decodeUrlPathVariants } from \"./composition.js\";\n\n/**\n * Shared local-asset resolution helpers for every package that maps\n * composition asset URLs to files on disk (lint project rules, the HEVC\n * preview check, studio-server's media codec scan). Import via the\n * `@hyperframes/parsers/asset-resolution` subpath.\n */\n\nexport function isRemoteOrInlineUrl(url: string): boolean {\n return /^(https?:|data:|blob:|\\/\\/|#)/i.test(url);\n}\n\n/**\n * True when a URL still contains an unresolved templating placeholder —\n * `<<token>>`, `{{ token }}`, or `${token}` — that a build or templating step\n * substitutes before render. The static linter runs before that substitution,\n * so it cannot resolve such a value to a file on disk and must not report it as\n * a missing asset. Check the RAW url, before any `cleanAssetUrl()` step: that\n * splits on `?`/`#`, which also chops inside a `${...}` expression. (The older\n * `__UPPER__` placeholder shape predates this and keeps its own inline check at\n * each call site.)\n */\nexport function hasUnresolvedTemplatingToken(url: string): boolean {\n return /<<[^<>]+>>|\\{\\{[^{}]+\\}\\}|\\$\\{[^{}]+\\}/.test(url);\n}\n\nexport function cleanAssetUrl(url: string): string {\n return url.trim().split(/[?#]/, 1)[0] ?? \"\";\n}\n\nexport function isWithinProjectRoot(projectDir: string, candidate: string): boolean {\n const projectRoot = resolve(projectDir);\n const relativePath = relative(projectRoot, candidate);\n return relativePath === \"\" || (!relativePath.startsWith(\"..\") && !isAbsolute(relativePath));\n}\n\nfunction addCandidate(candidates: string[], candidate: string): void {\n if (!candidates.includes(candidate)) candidates.push(candidate);\n}\n\nexport function resolveLocalAssetCandidates(projectDir: string, url: string): string[] {\n const cleanUrl = cleanAssetUrl(url);\n const projectRoot = resolve(projectDir);\n const candidates: string[] = [];\n\n for (const variant of decodeUrlPathVariants(cleanUrl)) {\n const projectRelative = variant.startsWith(\"/\") ? variant.slice(1) : variant;\n const resolved = resolve(projectRoot, projectRelative);\n if (isWithinProjectRoot(projectRoot, resolved)) {\n addCandidate(candidates, resolved);\n continue;\n }\n\n const normalized = posix.normalize(projectRelative.replace(/\\\\/g, \"/\"));\n const clamped = normalized.replace(/^(\\.\\.\\/)+/, \"\");\n if (clamped && !clamped.startsWith(\"..\")) {\n addCandidate(candidates, resolve(projectRoot, clamped));\n }\n }\n\n return candidates;\n}\n\nexport function resolveExistingLocalAsset(\n projectDir: string,\n url: string,\n): { resolved: string; rootRelativePath: string } | null {\n const projectRoot = resolve(projectDir);\n const resolved = resolveLocalAssetCandidates(projectRoot, url).find(existsSync);\n if (!resolved) return null;\n return { resolved, rootRelativePath: relative(projectRoot, resolved) };\n}\n\nfunction maskRange(src: string, pattern: RegExp): string {\n return src.replace(pattern, (m) => \" \".repeat(m.length));\n}\n\nfunction maskHtmlComments(src: string): string {\n const chunks: string[] = [];\n let cursor = 0;\n\n while (true) {\n const start = src.indexOf(\"<!--\", cursor);\n if (start === -1) break;\n const end = src.indexOf(\"-->\", start + 4);\n if (end === -1) break;\n const afterComment = end + 3;\n chunks.push(src.slice(cursor, start), \" \".repeat(afterComment - start));\n cursor = afterComment;\n }\n\n return chunks.length === 0 ? src : chunks.join(\"\") + src.slice(cursor);\n}\n\n/** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning\n * regexes don't false-positive on commented-out or scripted markup. */\nexport function maskNonScannableRanges(html: string): string {\n let out = maskHtmlComments(html);\n out = maskRange(out, /<style\\b[^>]*>[\\s\\S]*?<\\/style\\b[^>]*>/gi);\n out = maskRange(out, /<script\\b[^>]*>[\\s\\S]*?<\\/script\\b[^>]*>/gi);\n return out;\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,OAAO,UAAU,eAAe;;;ACD9C,SAAS,sBAAsB,MAAwB;AAC5D,QAAM,WAAW,CAAC,IAAI;AACtB,MAAI;AACF,UAAM,UAAU,mBAAmB,IAAI;AACvC,QAAI,YAAY,KAAM,UAAS,QAAQ,OAAO;AAAA,EAChD,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;ADCO,SAAS,oBAAoB,KAAsB;AACxD,SAAO,iCAAiC,KAAK,GAAG;AAClD;AAYO,SAAS,6BAA6B,KAAsB;AACjE,SAAO,yCAAyC,KAAK,GAAG;AAC1D;AAEO,SAAS,cAAc,KAAqB;AACjD,SAAO,IAAI,KAAK,EAAE,MAAM,QAAQ,CAAC,EAAE,CAAC,KAAK;AAC3C;AAEO,SAAS,oBAAoB,YAAoB,WAA4B;AAClF,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,eAAe,SAAS,aAAa,SAAS;AACpD,SAAO,iBAAiB,MAAO,CAAC,aAAa,WAAW,IAAI,KAAK,CAAC,WAAW,YAAY;AAC3F;AAEA,SAAS,aAAa,YAAsB,WAAyB;AACnE,MAAI,CAAC,WAAW,SAAS,SAAS,EAAG,YAAW,KAAK,SAAS;AAChE;AAEO,SAAS,4BAA4B,YAAoB,KAAuB;AACrF,QAAM,WAAW,cAAc,GAAG;AAClC,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,aAAuB,CAAC;AAE9B,aAAW,WAAW,sBAAsB,QAAQ,GAAG;AACrD,UAAM,kBAAkB,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AACrE,UAAM,WAAW,QAAQ,aAAa,eAAe;AACrD,QAAI,oBAAoB,aAAa,QAAQ,GAAG;AAC9C,mBAAa,YAAY,QAAQ;AACjC;AAAA,IACF;AAEA,UAAM,aAAa,MAAM,UAAU,gBAAgB,QAAQ,OAAO,GAAG,CAAC;AACtE,UAAM,UAAU,WAAW,QAAQ,cAAc,EAAE;AACnD,QAAI,WAAW,CAAC,QAAQ,WAAW,IAAI,GAAG;AACxC,mBAAa,YAAY,QAAQ,aAAa,OAAO,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,0BACd,YACA,KACuD;AACvD,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,WAAW,4BAA4B,aAAa,GAAG,EAAE,KAAK,UAAU;AAC9E,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,EAAE,UAAU,kBAAkB,SAAS,aAAa,QAAQ,EAAE;AACvE;AAEA,SAAS,UAAU,KAAa,SAAyB;AACvD,SAAO,IAAI,QAAQ,SAAS,CAAC,MAAM,IAAI,OAAO,EAAE,MAAM,CAAC;AACzD;AAEA,SAAS,iBAAiB,KAAqB;AAC7C,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AAEb,SAAO,MAAM;AACX,UAAM,QAAQ,IAAI,QAAQ,QAAQ,MAAM;AACxC,QAAI,UAAU,GAAI;AAClB,UAAM,MAAM,IAAI,QAAQ,OAAO,QAAQ,CAAC;AACxC,QAAI,QAAQ,GAAI;AAChB,UAAM,eAAe,MAAM;AAC3B,WAAO,KAAK,IAAI,MAAM,QAAQ,KAAK,GAAG,IAAI,OAAO,eAAe,KAAK,CAAC;AACtE,aAAS;AAAA,EACX;AAEA,SAAO,OAAO,WAAW,IAAI,MAAM,OAAO,KAAK,EAAE,IAAI,IAAI,MAAM,MAAM;AACvE;AAIO,SAAS,uBAAuB,MAAsB;AAC3D,MAAI,MAAM,iBAAiB,IAAI;AAC/B,QAAM,UAAU,KAAK,0CAA0C;AAC/D,QAAM,UAAU,KAAK,4CAA4C;AACjE,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/assetResolution.ts","../src/utils/urlPath.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { isAbsolute, posix, relative, resolve } from \"node:path\";\nimport { decodeUrlPathVariants } from \"./composition.js\";\n\n/**\n * Shared local-asset resolution helpers for every package that maps\n * composition asset URLs to files on disk (lint project rules, the HEVC\n * preview check, studio-server's media codec scan). Import via the\n * `@hyperframes/parsers/asset-resolution` subpath.\n */\n\nexport function isRemoteOrInlineUrl(url: string): boolean {\n return /^(https?:|data:|blob:|\\/\\/|#)/i.test(url);\n}\n\n/**\n * True when a URL still contains an unresolved templating placeholder —\n * `<<token>>`, `{{ token }}`, or `${token}` — that a build or templating step\n * substitutes before render. The static linter runs before that substitution,\n * so it cannot resolve such a value to a file on disk and must not report it as\n * a missing asset. Check the RAW url, before any `cleanAssetUrl()` step: that\n * splits on `?`/`#`, which also chops inside a `${...}` expression. (The `__UPPER__`\n * placeholder shape is combined with this in `isUnresolvedAssetPlaceholder` below, the\n * shared predicate asset-src sites skip on.)\n */\nexport function hasUnresolvedTemplatingToken(url: string): boolean {\n return /<<[^<>]+>>|\\{\\{[^{}]+\\}\\}|\\$\\{[^{}]+\\}/.test(url);\n}\n\n/**\n * True when an asset src is a build-time placeholder rather than a resolvable path:\n * the `__UPPER__` shape (e.g. `__DURATION__`) or an unresolved templating token\n * (`<<...>>`, `{{...}}`, `${...}`). Pass the RAW src, before any `cleanAssetUrl()` step —\n * cleanAssetUrl splits on `?`/`#`, which would chop inside a `${...}` expression and defeat\n * the token match. Remote/inline URL handling is deliberately NOT folded in: call sites\n * differ (e.g. audio uses a narrower http/data/blob check), so each keeps its own.\n *\n * This is the single skip predicate every asset-src lint / codec / compile site should\n * route through, so the placeholder rules can't drift apart across call sites.\n */\nexport function isUnresolvedAssetPlaceholder(rawSrc: string): boolean {\n return /^__[A-Z_]+__$/.test(rawSrc.trim()) || hasUnresolvedTemplatingToken(rawSrc);\n}\n\nexport function cleanAssetUrl(url: string): string {\n return url.trim().split(/[?#]/, 1)[0] ?? \"\";\n}\n\nexport function isWithinProjectRoot(projectDir: string, candidate: string): boolean {\n const projectRoot = resolve(projectDir);\n const relativePath = relative(projectRoot, candidate);\n return relativePath === \"\" || (!relativePath.startsWith(\"..\") && !isAbsolute(relativePath));\n}\n\nfunction addCandidate(candidates: string[], candidate: string): void {\n if (!candidates.includes(candidate)) candidates.push(candidate);\n}\n\nexport function resolveLocalAssetCandidates(projectDir: string, url: string): string[] {\n const cleanUrl = cleanAssetUrl(url);\n const projectRoot = resolve(projectDir);\n const candidates: string[] = [];\n\n for (const variant of decodeUrlPathVariants(cleanUrl)) {\n const projectRelative = variant.startsWith(\"/\") ? variant.slice(1) : variant;\n const resolved = resolve(projectRoot, projectRelative);\n if (isWithinProjectRoot(projectRoot, resolved)) {\n addCandidate(candidates, resolved);\n continue;\n }\n\n const normalized = posix.normalize(projectRelative.replace(/\\\\/g, \"/\"));\n const clamped = normalized.replace(/^(\\.\\.\\/)+/, \"\");\n if (clamped && !clamped.startsWith(\"..\")) {\n addCandidate(candidates, resolve(projectRoot, clamped));\n }\n }\n\n return candidates;\n}\n\nexport function resolveExistingLocalAsset(\n projectDir: string,\n url: string,\n): { resolved: string; rootRelativePath: string } | null {\n const projectRoot = resolve(projectDir);\n const resolved = resolveLocalAssetCandidates(projectRoot, url).find(existsSync);\n if (!resolved) return null;\n return { resolved, rootRelativePath: relative(projectRoot, resolved) };\n}\n\nfunction maskRange(src: string, pattern: RegExp): string {\n return src.replace(pattern, (m) => \" \".repeat(m.length));\n}\n\nfunction maskHtmlComments(src: string): string {\n const chunks: string[] = [];\n let cursor = 0;\n\n while (true) {\n const start = src.indexOf(\"<!--\", cursor);\n if (start === -1) break;\n const end = src.indexOf(\"-->\", start + 4);\n if (end === -1) break;\n const afterComment = end + 3;\n chunks.push(src.slice(cursor, start), \" \".repeat(afterComment - start));\n cursor = afterComment;\n }\n\n return chunks.length === 0 ? src : chunks.join(\"\") + src.slice(cursor);\n}\n\n/** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning\n * regexes don't false-positive on commented-out or scripted markup. */\nexport function maskNonScannableRanges(html: string): string {\n let out = maskHtmlComments(html);\n out = maskRange(out, /<style\\b[^>]*>[\\s\\S]*?<\\/style\\b[^>]*>/gi);\n out = maskRange(out, /<script\\b[^>]*>[\\s\\S]*?<\\/script\\b[^>]*>/gi);\n return out;\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/parsers",
3
- "version": "0.7.86",
3
+ "version": "0.7.88",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -98,7 +98,7 @@
98
98
  "tsx": "^4.21.0",
99
99
  "typescript": "^5.0.0",
100
100
  "vitest": "^3.2.4",
101
- "@hyperframes/core": "0.7.86"
101
+ "@hyperframes/core": "0.7.88"
102
102
  },
103
103
  "scripts": {
104
104
  "build": "tsup",