@hyperframes/parsers 0.8.50 → 0.8.51
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/assetResolution.d.ts +3 -1
- package/dist/assetResolution.js +24 -2
- package/dist/assetResolution.js.map +1 -1
- package/dist/compositionDuration.d.ts +19 -0
- package/dist/compositionDuration.js +22 -0
- package/dist/compositionDuration.js.map +1 -0
- package/dist/gsapParser.d.ts +2 -2
- package/dist/gsapParser.js.map +1 -1
- package/dist/gsapParserAcorn.d.ts +2 -2
- package/dist/gsapParserAcorn.js +71 -21
- package/dist/gsapParserAcorn.js.map +1 -1
- package/dist/gsapParserExports.d.ts +1 -1
- package/dist/gsapParserExports.js +71 -21
- package/dist/gsapParserExports.js.map +1 -1
- package/dist/{gsapSerialize-QZcXUoVf.d.ts → gsapSerialize-b8lNzSbI.d.ts} +2 -0
- package/dist/gsapWriterAcorn.d.ts +1 -1
- package/dist/gsapWriterAcorn.js +30 -8
- package/dist/gsapWriterAcorn.js.map +1 -1
- package/dist/index.d.ts +37 -7
- package/dist/index.js +402 -40
- package/dist/index.js.map +1 -1
- package/dist/mediaDuration.d.ts +48 -0
- package/dist/mediaDuration.js +104 -0
- package/dist/mediaDuration.js.map +1 -0
- package/dist/mediaDurationFixtures.d.ts +14 -0
- package/dist/mediaDurationFixtures.js +84 -0
- package/dist/mediaDurationFixtures.js.map +1 -0
- package/package.json +14 -2
|
@@ -69,8 +69,10 @@ declare function resolveExistingLocalAsset(projectDir: string, url: string): {
|
|
|
69
69
|
resolved: string;
|
|
70
70
|
rootRelativePath: string;
|
|
71
71
|
} | null;
|
|
72
|
+
/** Resolves a media `src` like a browser URL (`..` clamps at the project root); a miss returns the base-dir join. */
|
|
73
|
+
declare function resolveProjectRelativeSrc(src: string, baseDir: string, compiledDir?: string): string;
|
|
72
74
|
/** Blanks out comments, `<style>`, and `<script>` bodies so tag-scanning
|
|
73
75
|
* regexes don't false-positive on commented-out or scripted markup. */
|
|
74
76
|
declare function maskNonScannableRanges(html: string): string;
|
|
75
77
|
|
|
76
|
-
export { cleanAssetUrl, collectSubCompositionSrcs, hasUnresolvedTemplatingToken, isRemoteOrInlineUrl, isUnresolvedAssetPlaceholder, isWithinProjectRoot, maskNonScannableRanges, resolveExistingLocalAsset, resolveLocalAssetCandidates };
|
|
78
|
+
export { cleanAssetUrl, collectSubCompositionSrcs, hasUnresolvedTemplatingToken, isRemoteOrInlineUrl, isUnresolvedAssetPlaceholder, isWithinProjectRoot, maskNonScannableRanges, resolveExistingLocalAsset, resolveLocalAssetCandidates, resolveProjectRelativeSrc };
|
package/dist/assetResolution.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/assetResolution.ts
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
|
-
import { isAbsolute, posix, relative, resolve } from "path";
|
|
3
|
+
import { isAbsolute, join, posix, relative, resolve, sep } from "path";
|
|
4
4
|
|
|
5
5
|
// src/utils/urlPath.ts
|
|
6
6
|
function decodeUrlPathVariants(path) {
|
|
@@ -82,6 +82,27 @@ function resolveExistingLocalAsset(projectDir, url) {
|
|
|
82
82
|
if (!resolved) return null;
|
|
83
83
|
return { resolved, rootRelativePath: relative(projectRoot, resolved) };
|
|
84
84
|
}
|
|
85
|
+
function reanchoredCandidates(variant, baseDir, compiledDir) {
|
|
86
|
+
const baseAbs = resolve(baseDir);
|
|
87
|
+
const joinedAbs = resolve(join(baseDir, variant));
|
|
88
|
+
if (joinedAbs === baseAbs || joinedAbs.startsWith(baseAbs + sep)) return [];
|
|
89
|
+
const stripped = posix.normalize(variant.replace(/\\/g, "/")).replace(/^(\.\.\/)+/, "");
|
|
90
|
+
if (!stripped || stripped === variant || stripped.startsWith("..")) return [];
|
|
91
|
+
return compiledDir ? [join(compiledDir, stripped), join(baseDir, stripped)] : [join(baseDir, stripped)];
|
|
92
|
+
}
|
|
93
|
+
function resolveProjectRelativeSrc(src, baseDir, compiledDir) {
|
|
94
|
+
const cleanSrc = cleanAssetUrl(src);
|
|
95
|
+
if (isAbsolute(cleanSrc) && existsSync(cleanSrc)) return cleanSrc;
|
|
96
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
97
|
+
for (const variant of decodeUrlPathVariants(cleanSrc)) {
|
|
98
|
+
for (const candidate of reanchoredCandidates(variant, baseDir, compiledDir)) {
|
|
99
|
+
candidates.add(candidate);
|
|
100
|
+
}
|
|
101
|
+
if (compiledDir) candidates.add(join(compiledDir, variant));
|
|
102
|
+
candidates.add(join(baseDir, variant));
|
|
103
|
+
}
|
|
104
|
+
return [...candidates].find(existsSync) ?? join(baseDir, cleanSrc);
|
|
105
|
+
}
|
|
85
106
|
function maskRange(src, pattern) {
|
|
86
107
|
return src.replace(pattern, (m) => " ".repeat(m.length));
|
|
87
108
|
}
|
|
@@ -114,6 +135,7 @@ export {
|
|
|
114
135
|
isWithinProjectRoot,
|
|
115
136
|
maskNonScannableRanges,
|
|
116
137
|
resolveExistingLocalAsset,
|
|
117
|
-
resolveLocalAssetCandidates
|
|
138
|
+
resolveLocalAssetCandidates,
|
|
139
|
+
resolveProjectRelativeSrc
|
|
118
140
|
};
|
|
119
141
|
//# sourceMappingURL=assetResolution.js.map
|
|
@@ -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 `__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\n/** `data-composition-src=\"...\"`, matched within a single already-delimited tag. */\nconst COMPOSITION_SRC_ATTR = /\\bdata-composition-src\\s*=\\s*[\"']([^\"']+)[\"']/i;\n\n/**\n * Every `data-composition-src` reference in one composition file's raw text, in\n * document order, deduped. The single owner of \"which sub-compositions does\n * this file mount\", so lint, telemetry, and any future scanner cannot drift\n * apart on the answer.\n *\n * Text-scanning rather than DOM-walking, and that is the load-bearing choice.\n * Every sub-composition except the render entry is authored inside a\n * `<template>` (the root index.html is forbidden from using that wrapper;\n * everything else prefers it). Template content is inert: it lives under\n * `template.content`, not the live document, so `document.querySelectorAll`\n * on a raw sub-composition file finds nothing and every nested reference\n * disappears. The renderer only gets away with a DOM scan because it recurses\n * on COMPILED html, where the wrapper is already gone.\n *\n * Callers must resolve each value against the PROJECT ROOT, never the\n * referencing file's directory: `data-composition-src` is root-relative at\n * every nesting level (see `parseSubCompositions` in htmlCompiler.ts).\n *\n * Comments, `<style>`, and `<script>` bodies are masked first so a\n * commented-out mount is not counted as a real one. Build-time placeholders and\n * remote or inline URLs are dropped: neither names a file on disk, and every\n * caller resolves what comes back against the project root.\n *\n * The scan walks tag by tag with `indexOf` rather than running one regex with\n * two open-ended `[^>]*` spans across the whole file. That shape is quadratic:\n * on input full of `<` with no `>`, every `<` starts a scan to end-of-string\n * that then backtracks, measured at 41ms / 165ms / 660ms / 2640ms for 10k /\n * 20k / 40k / 80k characters. This function runs on every render (via the\n * render plan), so a truncated download or a blob full of stray `<` would hang\n * the plan step before any video is produced. Bounding each regex to one\n * already-delimited tag makes the whole scan linear.\n */\nexport function collectSubCompositionSrcs(html: string): string[] {\n const scannable = maskNonScannableRanges(html);\n const srcs: string[] = [];\n const seen = new Set<string>();\n\n let cursor = 0;\n while (cursor < scannable.length) {\n const open = scannable.indexOf(\"<\", cursor);\n if (open === -1) break;\n const close = scannable.indexOf(\">\", open + 1);\n // An unterminated final tag is not a tag. The previous whole-file regex\n // also required a closing `>`, so this drops nothing it used to find.\n if (close === -1) break;\n cursor = close + 1;\n\n const match = COMPOSITION_SRC_ATTR.exec(scannable.slice(open, cursor));\n if (!match) continue;\n const src = (match[1] ?? \"\").trim();\n if (!src || seen.has(src)) continue;\n // __UPPER__ placeholder or late-bound templating token — not a real reference.\n if (isUnresolvedAssetPlaceholder(src)) continue;\n // A remote or inline mount names no file on disk. Every caller resolves\n // these against the project root, so letting one through produces a\n // nonsense path (`<projectDir>/https:/host/a.html`) that then reads as a\n // missing local file: a false \"does not exist\" for lint, and a wasted\n // visit against the telemetry walk's file budget.\n if (isRemoteOrInlineUrl(src)) continue;\n seen.add(src);\n srcs.push(src);\n }\n return srcs;\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/assetResolution.ts","../src/utils/urlPath.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { isAbsolute, join, posix, relative, resolve, sep } 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\n/** `data-composition-src=\"...\"`, matched within a single already-delimited tag. */\nconst COMPOSITION_SRC_ATTR = /\\bdata-composition-src\\s*=\\s*[\"']([^\"']+)[\"']/i;\n\n/**\n * Every `data-composition-src` reference in one composition file's raw text, in\n * document order, deduped. The single owner of \"which sub-compositions does\n * this file mount\", so lint, telemetry, and any future scanner cannot drift\n * apart on the answer.\n *\n * Text-scanning rather than DOM-walking, and that is the load-bearing choice.\n * Every sub-composition except the render entry is authored inside a\n * `<template>` (the root index.html is forbidden from using that wrapper;\n * everything else prefers it). Template content is inert: it lives under\n * `template.content`, not the live document, so `document.querySelectorAll`\n * on a raw sub-composition file finds nothing and every nested reference\n * disappears. The renderer only gets away with a DOM scan because it recurses\n * on COMPILED html, where the wrapper is already gone.\n *\n * Callers must resolve each value against the PROJECT ROOT, never the\n * referencing file's directory: `data-composition-src` is root-relative at\n * every nesting level (see `parseSubCompositions` in htmlCompiler.ts).\n *\n * Comments, `<style>`, and `<script>` bodies are masked first so a\n * commented-out mount is not counted as a real one. Build-time placeholders and\n * remote or inline URLs are dropped: neither names a file on disk, and every\n * caller resolves what comes back against the project root.\n *\n * The scan walks tag by tag with `indexOf` rather than running one regex with\n * two open-ended `[^>]*` spans across the whole file. That shape is quadratic:\n * on input full of `<` with no `>`, every `<` starts a scan to end-of-string\n * that then backtracks, measured at 41ms / 165ms / 660ms / 2640ms for 10k /\n * 20k / 40k / 80k characters. This function runs on every render (via the\n * render plan), so a truncated download or a blob full of stray `<` would hang\n * the plan step before any video is produced. Bounding each regex to one\n * already-delimited tag makes the whole scan linear.\n */\nexport function collectSubCompositionSrcs(html: string): string[] {\n const scannable = maskNonScannableRanges(html);\n const srcs: string[] = [];\n const seen = new Set<string>();\n\n let cursor = 0;\n while (cursor < scannable.length) {\n const open = scannable.indexOf(\"<\", cursor);\n if (open === -1) break;\n const close = scannable.indexOf(\">\", open + 1);\n // An unterminated final tag is not a tag. The previous whole-file regex\n // also required a closing `>`, so this drops nothing it used to find.\n if (close === -1) break;\n cursor = close + 1;\n\n const match = COMPOSITION_SRC_ATTR.exec(scannable.slice(open, cursor));\n if (!match) continue;\n const src = (match[1] ?? \"\").trim();\n if (!src || seen.has(src)) continue;\n // __UPPER__ placeholder or late-bound templating token — not a real reference.\n if (isUnresolvedAssetPlaceholder(src)) continue;\n // A remote or inline mount names no file on disk. Every caller resolves\n // these against the project root, so letting one through produces a\n // nonsense path (`<projectDir>/https:/host/a.html`) that then reads as a\n // missing local file: a false \"does not exist\" for lint, and a wasted\n // visit against the telemetry walk's file budget.\n if (isRemoteOrInlineUrl(src)) continue;\n seen.add(src);\n srcs.push(src);\n }\n return srcs;\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\n// Candidates for a variant whose join escaped the project root, re-anchored at the root.\nfunction reanchoredCandidates(variant: string, baseDir: string, compiledDir?: string): string[] {\n const baseAbs = resolve(baseDir);\n const joinedAbs = resolve(join(baseDir, variant));\n if (joinedAbs === baseAbs || joinedAbs.startsWith(baseAbs + sep)) return [];\n // Normalize before stripping, or `assets/../../assets/foo` becomes `assets/assets/foo`.\n const stripped = posix.normalize(variant.replace(/\\\\/g, \"/\")).replace(/^(\\.\\.\\/)+/, \"\");\n if (!stripped || stripped === variant || stripped.startsWith(\"..\")) return [];\n return compiledDir\n ? [join(compiledDir, stripped), join(baseDir, stripped)]\n : [join(baseDir, stripped)];\n}\n\n/** Resolves a media `src` like a browser URL (`..` clamps at the project root); a miss returns the base-dir join. */\nexport function resolveProjectRelativeSrc(\n src: string,\n baseDir: string,\n compiledDir?: string,\n): string {\n const cleanSrc = cleanAssetUrl(src);\n\n // A leading slash is an origin-root URL served from the project root, unless the absolute path exists.\n if (isAbsolute(cleanSrc) && existsSync(cleanSrc)) return cleanSrc;\n\n const candidates = new Set<string>();\n for (const variant of decodeUrlPathVariants(cleanSrc)) {\n for (const candidate of reanchoredCandidates(variant, baseDir, compiledDir)) {\n candidates.add(candidate);\n }\n if (compiledDir) candidates.add(join(compiledDir, variant));\n candidates.add(join(baseDir, variant));\n }\n return [...candidates].find(existsSync) ?? join(baseDir, cleanSrc);\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":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** How long a composition is when nothing runs it: its authored `data-duration`, else the latest
|
|
2
|
+
* end among its clips. A clip whose length is not known yet is counted, never guessed. */
|
|
3
|
+
type CompositionDurationSource = "authored" | "derived" | "unresolved";
|
|
4
|
+
interface CompositionDurationResult {
|
|
5
|
+
seconds: number | null;
|
|
6
|
+
source: CompositionDurationSource;
|
|
7
|
+
/** Clips whose end is not known yet; a `derived` length is a lower bound while this is above 0. */
|
|
8
|
+
pendingClips: number;
|
|
9
|
+
/** Set only when source is "unresolved". */
|
|
10
|
+
reason?: string;
|
|
11
|
+
}
|
|
12
|
+
interface ResolveCompositionDurationInput {
|
|
13
|
+
authoredDurationSeconds: number | null;
|
|
14
|
+
/** Each clip's end on the composition timeline, or null while its length is pending. */
|
|
15
|
+
clipEndsSeconds: readonly (number | null)[];
|
|
16
|
+
}
|
|
17
|
+
declare function resolveCompositionDuration(input: ResolveCompositionDurationInput): CompositionDurationResult;
|
|
18
|
+
|
|
19
|
+
export { type CompositionDurationResult, type CompositionDurationSource, type ResolveCompositionDurationInput, resolveCompositionDuration };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/compositionDuration.ts
|
|
2
|
+
var isPositiveFinite = (value) => value !== null && Number.isFinite(value) && value > 0;
|
|
3
|
+
function resolveCompositionDuration(input) {
|
|
4
|
+
const pendingClips = input.clipEndsSeconds.filter((end) => end === null).length;
|
|
5
|
+
if (isPositiveFinite(input.authoredDurationSeconds)) {
|
|
6
|
+
return { seconds: input.authoredDurationSeconds, source: "authored", pendingClips };
|
|
7
|
+
}
|
|
8
|
+
const known = input.clipEndsSeconds.filter(isPositiveFinite);
|
|
9
|
+
if (known.length > 0) {
|
|
10
|
+
return { seconds: Math.max(...known), source: "derived", pendingClips };
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
seconds: null,
|
|
14
|
+
source: "unresolved",
|
|
15
|
+
pendingClips,
|
|
16
|
+
reason: pendingClips > 0 ? "every clip's length is still pending" : "no authored duration and no clip with a length"
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
resolveCompositionDuration
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=compositionDuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compositionDuration.ts"],"sourcesContent":["/** How long a composition is when nothing runs it: its authored `data-duration`, else the latest\n * end among its clips. A clip whose length is not known yet is counted, never guessed. */\n\nexport type CompositionDurationSource = \"authored\" | \"derived\" | \"unresolved\";\n\nexport interface CompositionDurationResult {\n seconds: number | null;\n source: CompositionDurationSource;\n /** Clips whose end is not known yet; a `derived` length is a lower bound while this is above 0. */\n pendingClips: number;\n /** Set only when source is \"unresolved\". */\n reason?: string;\n}\n\nexport interface ResolveCompositionDurationInput {\n authoredDurationSeconds: number | null;\n /** Each clip's end on the composition timeline, or null while its length is pending. */\n clipEndsSeconds: readonly (number | null)[];\n}\n\nconst isPositiveFinite = (value: number | null): value is number =>\n value !== null && Number.isFinite(value) && value > 0;\n\nexport function resolveCompositionDuration(\n input: ResolveCompositionDurationInput,\n): CompositionDurationResult {\n const pendingClips = input.clipEndsSeconds.filter((end) => end === null).length;\n if (isPositiveFinite(input.authoredDurationSeconds)) {\n return { seconds: input.authoredDurationSeconds, source: \"authored\", pendingClips };\n }\n const known = input.clipEndsSeconds.filter(isPositiveFinite);\n if (known.length > 0) {\n return { seconds: Math.max(...known), source: \"derived\", pendingClips };\n }\n return {\n seconds: null,\n source: \"unresolved\",\n pendingClips,\n reason:\n pendingClips > 0\n ? \"every clip's length is still pending\"\n : \"no authored duration and no clip with a length\",\n };\n}\n"],"mappings":";AAoBA,IAAM,mBAAmB,CAAC,UACxB,UAAU,QAAQ,OAAO,SAAS,KAAK,KAAK,QAAQ;AAE/C,SAAS,2BACd,OAC2B;AAC3B,QAAM,eAAe,MAAM,gBAAgB,OAAO,CAAC,QAAQ,QAAQ,IAAI,EAAE;AACzE,MAAI,iBAAiB,MAAM,uBAAuB,GAAG;AACnD,WAAO,EAAE,SAAS,MAAM,yBAAyB,QAAQ,YAAY,aAAa;AAAA,EACpF;AACA,QAAM,QAAQ,MAAM,gBAAgB,OAAO,gBAAgB;AAC3D,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,EAAE,SAAS,KAAK,IAAI,GAAG,KAAK,GAAG,QAAQ,WAAW,aAAa;AAAA,EACxE;AACA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,IACR;AAAA,IACA,QACE,eAAe,IACX,yCACA;AAAA,EACR;AACF;","names":[]}
|
package/dist/gsapParser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GsapAnimation, P as ParsedGsap, A as ArcPathConfig, a as ArcPathSegment } from './gsapSerialize-
|
|
2
|
-
export { n as GsapKeyframeFormat, b as GsapKeyframesData, c as GsapMethod, d as GsapPercentageKeyframe, j as getAnimationsForElementId, k as gsapAnimationsToKeyframes, l as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-
|
|
1
|
+
import { G as GsapAnimation, P as ParsedGsap, A as ArcPathConfig, a as ArcPathSegment } from './gsapSerialize-b8lNzSbI.js';
|
|
2
|
+
export { n as GsapKeyframeFormat, b as GsapKeyframesData, c as GsapMethod, d as GsapPercentageKeyframe, j as getAnimationsForElementId, k as gsapAnimationsToKeyframes, l as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-b8lNzSbI.js';
|
|
3
3
|
export { PROPERTY_GROUPS, PropertyGroupName, SUPPORTED_EASES, SUPPORTED_PROPS, classifyPropertyGroup, classifyTweenPropertyGroup } from './gsapConstants.js';
|
|
4
4
|
export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEase.js';
|
|
5
5
|
import './types-Y0NXTiVo.js';
|