@hyperframes/studio-server 0.8.142 → 0.8.144

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.
@@ -13,7 +13,7 @@ import {
13
13
  resolveProxyVariantRequest,
14
14
  scanProjectMediaCodecMap,
15
15
  shouldPrewarmProxy
16
- } from "../chunk-74JVVCWL.js";
16
+ } from "../chunk-RN33UALN.js";
17
17
  import "../chunk-DTKK7TKL.js";
18
18
  import "../chunk-6XMC64FJ.js";
19
19
  import "../chunk-4ETS2LXI.js";
@@ -1,5 +1,5 @@
1
1
  import '@hyperframes/core';
2
- export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-CJ4lbPcF.js';
2
+ export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-ozy7FkMP.js';
3
3
  import './mediaCodecMap.js';
4
4
  import '@hyperframes/parsers';
5
5
  import '@hyperframes/core/compiler';
@@ -4,9 +4,9 @@ import {
4
4
  isAutoProxyEnabled,
5
5
  proxyEtagSalt,
6
6
  resolvePreviewMediaCodecProbeCache
7
- } from "../chunk-5VCIZYUE.js";
8
- import "../chunk-QP7LKFU2.js";
9
- import "../chunk-74JVVCWL.js";
7
+ } from "../chunk-VE2AOX4K.js";
8
+ import "../chunk-AZBGTB3N.js";
9
+ import "../chunk-RN33UALN.js";
10
10
  import "../chunk-DTKK7TKL.js";
11
11
  import "../chunk-6XMC64FJ.js";
12
12
  import "../chunk-4ETS2LXI.js";
@@ -7,15 +7,9 @@ import { ProxyVariant } from './mediaCodecMap.js';
7
7
  * project routes (U3/U4) to serve a `?hf-proxy=` request; never used on
8
8
  * the render path (render always sees the original file).
9
9
  *
10
- * IMPORTANT — request-lifecycle detachment: nothing here accepts or wires an
11
- * AbortSignal. `resolveProxy` returns a promise shared by every concurrent
12
- * caller for the same cache key (in-flight dedupe below); if a route handler
13
- * killed the ffmpeg child on client abort (page reload, HMR), every other
14
- * caller waiting on that same promise would fail too, and the next request
15
- * would restart a transcode that may have been minutes into a long asset.
16
- * Callers MUST let the child run to completion regardless of request
17
- * cancellation and simply let the held response also abort — the cache
18
- * entry still lands for the next request.
10
+ * A started ffmpeg child is never killed for one caller: every caller of a cache
11
+ * key shares it (in-flight dedupe below). A caller's `signal` only detaches that
12
+ * caller, and drops the copy while it is still queued with no caller left.
19
13
  */
20
14
  declare const PROXY_PARAMS_VERSION = "v5";
21
15
  declare const CACHE_DIR_NAME = ".transcode-cache";
@@ -51,7 +45,7 @@ declare function getProxyCachePath(projectDir: string, absoluteSourcePath: strin
51
45
  /** Test hook: forget remembered transcode failures (module state persists
52
46
  * across tests that don't reload the module). */
53
47
  declare function clearFailedTranscodesForTest(): void;
54
- /** Null while a copy for this project is being made; otherwise a mark that moves when any copy finishes. */
48
+ /** Null while this project has a copy in progress; otherwise a mark that moves when any copy finishes or is refused. */
55
49
  declare function proxyActivityMark(projectDir: string): string | null;
56
50
  /**
57
51
  * Resolves the cached proxy variant for `absoluteSourcePath`, transcoding it at
@@ -62,7 +56,13 @@ declare function proxyActivityMark(projectDir: string): string | null;
62
56
  * nonzero exit) — callers (route handlers) decide how to surface that (502).
63
57
  * A `box` makes a smaller preview-only copy with its own cache entry; without
64
58
  * one the copy keeps the source size (CLI play, static servers, publish).
59
+ * `priority` moves the copy ahead of every normal queued copy; a `signal`
60
+ * lets this caller leave (see the detachment note at the top of this file).
65
61
  */
66
- declare function resolveProxy(projectDir: string, absoluteSourcePath: string, variant?: ProxyVariant, box?: PreviewProxyBox): Promise<string>;
62
+ declare function resolveProxy(projectDir: string, absoluteSourcePath: string, variant?: ProxyVariant, box?: PreviewProxyBox, options?: ResolveProxyOptions): Promise<string>;
63
+ interface ResolveProxyOptions {
64
+ priority?: boolean;
65
+ signal?: AbortSignal;
66
+ }
67
67
 
68
- export { CACHE_DIR_NAME, DEFAULT_PROXY_WAIT_TIMEOUT_MS, FfmpegMissingFilterError, PROXY_PARAMS_VERSION, PROXY_PENDING_RETRY_AFTER_SECONDS, ProxyCapacityError, ProxySourceOutsideProjectError, ProxyTranscodeError, ProxyWaitTimeoutError, TRANSCODE_TIMEOUT_MS, clearFailedTranscodesForTest, getProxyCachePath, proxyActivityMark, resolveProxy, waitForProxy };
68
+ export { CACHE_DIR_NAME, DEFAULT_PROXY_WAIT_TIMEOUT_MS, FfmpegMissingFilterError, PROXY_PARAMS_VERSION, PROXY_PENDING_RETRY_AFTER_SECONDS, ProxyCapacityError, ProxySourceOutsideProjectError, ProxyTranscodeError, ProxyWaitTimeoutError, type ResolveProxyOptions, TRANSCODE_TIMEOUT_MS, clearFailedTranscodesForTest, getProxyCachePath, proxyActivityMark, resolveProxy, waitForProxy };
@@ -14,8 +14,8 @@ import {
14
14
  proxyActivityMark,
15
15
  resolveProxy,
16
16
  waitForProxy
17
- } from "../chunk-QP7LKFU2.js";
18
- import "../chunk-74JVVCWL.js";
17
+ } from "../chunk-AZBGTB3N.js";
18
+ import "../chunk-RN33UALN.js";
19
19
  import "../chunk-DTKK7TKL.js";
20
20
  import "../chunk-6XMC64FJ.js";
21
21
  import "../chunk-4ETS2LXI.js";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-CJ4lbPcF.js';
3
- export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-CJ4lbPcF.js';
2
+ import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-ozy7FkMP.js';
3
+ export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-ozy7FkMP.js';
4
4
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryWho, g as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, h as ProjectHistoryOptions, U as UNDO_MODES, i as UndoMode, o as openProjectHistory } from './projectHistory-Cm8Mdmmv.js';
5
5
  export { HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, historyCache, listProjectHistories, pruneGoneProjectHistories } from './history/index.js';
6
6
  export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
@@ -11,6 +11,7 @@ export { PatchOperation, dedupeClonedCompositionId, findTargetElement, parseSour
11
11
  export { ScreenshotClip, clearElementScreenshotIsolation, getElementScreenshotClip } from './helpers/screenshotClip.js';
12
12
  export { STUDIO_MANUAL_EDITS_PATH, StudioManualEditsRenderScriptOptions, createStudioManualEditsRenderBodyScript, createStudioPositionSeekReapplyScript } from './helpers/manualEditsRenderScript.js';
13
13
  export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotionRenderBodyScript } from './helpers/studioMotionRenderScript.js';
14
+ export { decodeWellFormedEscapes } from '@hyperframes/parsers/asset-paths';
14
15
  export { isSafePath } from '@hyperframes/core/safe-path';
15
16
  import '@hyperframes/core';
16
17
  import '@hyperframes/parsers';
package/dist/index.js CHANGED
@@ -55,16 +55,19 @@ import {
55
55
  import {
56
56
  DEFAULT_TARGET_LUFS,
57
57
  absoluteLoudnessPlan,
58
+ decodeWellFormedEscapes,
58
59
  measureAudio,
60
+ requestSubPath,
61
+ requestedProjectPath,
59
62
  requiredFfmpeg,
60
63
  resolveLocalAudioPath
61
- } from "./chunk-DSVQ2U7J.js";
64
+ } from "./chunk-32IH7WBY.js";
62
65
  import {
63
66
  injectMediaCodecMap,
64
67
  isAutoProxyEnabled,
65
68
  proxyEtagSalt,
66
69
  resolvePreviewMediaCodecProbeCache
67
- } from "./chunk-5VCIZYUE.js";
70
+ } from "./chunk-VE2AOX4K.js";
68
71
  import {
69
72
  CACHE_DIR_NAME,
70
73
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -75,7 +78,7 @@ import {
75
78
  proxyActivityMark,
76
79
  resolveProxy,
77
80
  waitForProxy
78
- } from "./chunk-QP7LKFU2.js";
81
+ } from "./chunk-AZBGTB3N.js";
79
82
  import {
80
83
  PROXY_VARIANT_CONFIG,
81
84
  decideMediaProxyEligibility,
@@ -84,7 +87,7 @@ import {
84
87
  probeMediaMetadata,
85
88
  recordProxyRequest,
86
89
  resolveProxyVariantRequest
87
- } from "./chunk-74JVVCWL.js";
90
+ } from "./chunk-RN33UALN.js";
88
91
  import {
89
92
  folderGone,
90
93
  isInHiddenOrVendorDir,
@@ -158,6 +161,12 @@ function registerProjectRoutes(api, adapter) {
158
161
  }
159
162
 
160
163
  // src/routes/files.ts
164
+ import { encodeUrlPath, decodedUrlPath, decodeCssEscapes } from "@hyperframes/parsers/asset-paths";
165
+ import {
166
+ scanHtmlOpeningTags,
167
+ decodeAuthoredAttribute,
168
+ HTML_ATTRIBUTE_ENTITIES
169
+ } from "@hyperframes/parsers";
161
170
  import { bodyLimit } from "hono/body-limit";
162
171
  import {
163
172
  closeSync,
@@ -174,7 +183,7 @@ import {
174
183
  readdirSync as readdirSync2,
175
184
  realpathSync
176
185
  } from "fs";
177
- import { resolve as resolve2, dirname as dirname3, join as join6 } from "path";
186
+ import { resolve as resolve2, dirname as dirname3, join as join6, sep as sep2 } from "path";
178
187
 
179
188
  // src/helpers/mime.ts
180
189
  var MIME_TYPES = {
@@ -1191,14 +1200,6 @@ function resolveGsapWriter(env) {
1191
1200
  throw new Error(`Invalid ${GSAP_WRITER_MIGRATION.flag}=${configured}; expected recast or acorn`);
1192
1201
  }
1193
1202
 
1194
- // src/helpers/requestSubPath.ts
1195
- function requestSubPath(url, route) {
1196
- const routeSegments = route.split("/");
1197
- const segments = new URL(url).pathname.split("/");
1198
- const start = segments.indexOf(routeSegments[0] ?? "");
1199
- return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
1200
- }
1201
-
1202
1203
  // src/routes/files.ts
1203
1204
  import {
1204
1205
  hasCompositionOutsideTemplates,
@@ -1505,15 +1506,114 @@ var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1505
1506
  var SEPARATOR = String.raw`\\{0,2}[\\/]`;
1506
1507
  var REFERENCE_START = String.raw`(?<![\w./\\+-])`;
1507
1508
  var FILE_END = String.raw`(?![\w-]|\.\w)`;
1509
+ var ENTITY_SPELLINGS = HTML_ATTRIBUTE_ENTITIES;
1510
+ function referenceSpellings(char) {
1511
+ const spellings = [escapeRegExp(char)];
1512
+ const encoded = encodeUrlPath(char);
1513
+ if (encoded !== char)
1514
+ spellings.push(encoded.replace(/[A-F]/g, (hex) => `[${hex}${hex.toLowerCase()}]`));
1515
+ if (ENTITY_SPELLINGS[char]) spellings.push(...ENTITY_SPELLINGS[char]);
1516
+ if (["'", '"', "`", "$", " ", "(", ")"].includes(char)) {
1517
+ spellings.push(escapeRegExp(`&#92;${char}`), escapeRegExp(`&#92;${escapeHtmlReference(char)}`));
1518
+ }
1519
+ const scriptSpelling = escapeQuotedReference(char, '"');
1520
+ if (scriptSpelling !== char) spellings.push(escapeRegExp(scriptSpelling));
1521
+ if (["'", "`", "$", " ", "(", ")"].includes(char)) spellings.push(escapeRegExp(`\\${char}`));
1522
+ if (/[\t\n\f\r =<>`]/.test(char)) spellings.push(`&#${char.codePointAt(0)};`);
1523
+ if (char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) {
1524
+ spellings.push(escapeRegExp(`\\${char.charCodeAt(0).toString(16)} `));
1525
+ }
1526
+ return spellings.length === 1 ? spellings[0] : `(?:${spellings.join("|")})`;
1527
+ }
1528
+ function escapeHtmlReference(path) {
1529
+ return path.replace(/[&"']/g, (char) => ENTITY_SPELLINGS[char][0]);
1530
+ }
1531
+ function escapeQuotedReference(path, quote) {
1532
+ const escaped = JSON.stringify(path).slice(1, -1);
1533
+ return quote === '"' ? escaped : escaped.replace(new RegExp(escapeRegExp(quote), "g"), `\\${quote}`);
1534
+ }
1535
+ function escapeRawUrlPath(path) {
1536
+ const encodeSpaces = path.startsWith(" ") || path.endsWith(" ");
1537
+ return path.replace(/[\s\S]/g, (char) => {
1538
+ if (char.charCodeAt(0) < 32 || ":?%#\\".includes(char) || encodeSpaces && char === " ") {
1539
+ return encodeUrlPath(char);
1540
+ }
1541
+ return char;
1542
+ });
1543
+ }
1544
+ function spellLike(reference, path, oldPath, context) {
1545
+ const { syntax, quote, url, encodeAll } = context;
1546
+ if (url && (encodeAll || reference !== oldPath && /%[0-9A-Fa-f]{2}/.test(reference)))
1547
+ return encodeUrlPath(path);
1548
+ switch (syntax) {
1549
+ case "html": {
1550
+ const escaped = escapeHtmlReference(url ? escapeRawUrlPath(path) : path);
1551
+ return ["'", '"'].includes(quote) ? escaped : escaped.replace(/[\t\n\f\r =<>`]/g, (char) => `&#${char.codePointAt(0)};`);
1552
+ }
1553
+ case "script": {
1554
+ if (!["'", '"', "`"].includes(quote)) return path;
1555
+ const escaped = escapeQuotedReference(path, quote);
1556
+ return quote === "`" ? escaped.replace(/\$\{/g, "\\${") : escaped;
1557
+ }
1558
+ case "css":
1559
+ return url ? encodeUrlPath(path) : path;
1560
+ case "plain":
1561
+ return path;
1562
+ }
1563
+ }
1564
+ function referenceSyntax(file) {
1565
+ if (/\.html$/i.test(file)) return "html";
1566
+ if (/\.css$/i.test(file)) return "css";
1567
+ if (/\.(?:[cm]?js|jsx|tsx?|json)$/i.test(file)) return "script";
1568
+ return "plain";
1569
+ }
1570
+ function decodeQuotedReference(text2, syntax) {
1571
+ if (syntax === "css") return decodeCssEscapes(text2);
1572
+ if (syntax === "script") {
1573
+ const controls = { b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
1574
+ return text2.replace(
1575
+ /\\(?:u([0-9a-fA-F]{4})|([bfnrt"'`$\\/]))/g,
1576
+ (_escaped, hex, char) => {
1577
+ if (hex !== void 0) return String.fromCharCode(Number.parseInt(hex, 16));
1578
+ return controls[char] ?? char;
1579
+ }
1580
+ );
1581
+ }
1582
+ return text2;
1583
+ }
1508
1584
  function referencePattern(oldPath, isDirectory) {
1509
- const name = oldPath.split("/").map(escapeRegExp).join(SEPARATOR);
1585
+ const name = oldPath.split("/").map((segment) => Array.from(segment, referenceSpellings).join("")).join(SEPARATOR);
1510
1586
  const end = isDirectory ? `(?=${SEPARATOR})` : FILE_END;
1511
1587
  return new RegExp(
1512
1588
  String.raw`${REFERENCE_START}(?<lead>(?:\.{1,2}${SEPARATOR}|${SEPARATOR}){0,4})${name}${end}`,
1513
1589
  "g"
1514
1590
  );
1515
1591
  }
1592
+ function rewriteEntityAttributes(text2, rewrite) {
1593
+ const parts = [];
1594
+ let cursor = 0;
1595
+ for (const tag of scanHtmlOpeningTags(text2)) {
1596
+ for (const attr of tag.attributes) {
1597
+ if (attr.kind !== "value") continue;
1598
+ const decoded = decodeAuthoredAttribute(attr.value);
1599
+ if (decoded === attr.value) continue;
1600
+ let value = escapeHtmlReference(decoded);
1601
+ if (!attr.quote)
1602
+ value = value.replace(/[\t\n\f\r =<>`]/g, (char) => `&#${char.codePointAt(0)};`);
1603
+ const source = `<x ${attr.name}=${attr.quote}${value}${attr.quote}>`;
1604
+ const updated = rewrite(source);
1605
+ if (updated === source) continue;
1606
+ const replacement = scanHtmlOpeningTags(updated)[0].attributes[0];
1607
+ if (replacement.kind !== "value") throw new Error("Reference rewrite removed its attribute");
1608
+ parts.push(text2.slice(cursor, attr.valueStart), replacement.value);
1609
+ cursor = attr.valueEnd;
1610
+ }
1611
+ }
1612
+ parts.push(text2.slice(cursor));
1613
+ return parts.join("");
1614
+ }
1516
1615
  function referenceRewriter(oldPath, newPath, isDirectory, existing = []) {
1616
+ const oldIdentity = sep2 === "\\" ? oldPath.replace(/\\/g, "/") : oldPath;
1517
1617
  const pattern = referencePattern(oldPath, isDirectory);
1518
1618
  const around = /* @__PURE__ */ new Map();
1519
1619
  const afterLengths = /* @__PURE__ */ new Set();
@@ -1527,24 +1627,123 @@ function referenceRewriter(oldPath, newPath, isDirectory, existing = []) {
1527
1627
  before
1528
1628
  );
1529
1629
  afterLengths.add(after.length);
1530
- window = Math.max(window, path.length * 3);
1531
- }
1532
- }
1533
- const normalized = (text2) => text2.replace(/\\{0,2}[\\/]/g, "/");
1534
- return (text2) => text2.replace(pattern, (...args) => {
1535
- const [match, offset] = [args[0], args.at(-3)];
1536
- const { lead } = args.at(-1);
1537
- const at = offset + lead.length;
1538
- const head = normalized(text2.slice(Math.max(0, at - window), at));
1539
- const tail = normalized(text2.slice(offset + match.length, offset + match.length + window));
1540
- const inLonger = [...afterLengths].some((after) => {
1541
- const befores = tail.length >= after && around.get(tail.slice(0, after));
1542
- return befores && [...befores].some(
1543
- ([length, set]) => head.length >= length && set.has(head.slice(head.length - length))
1630
+ window = Math.max(window, path.length * 9);
1631
+ }
1632
+ }
1633
+ const normalized = (text2, context, full = false) => {
1634
+ let value = text2;
1635
+ if (context.syntax === "html" || context.htmlAttribute) {
1636
+ value = decodeAuthoredAttribute(value);
1637
+ }
1638
+ value = decodeQuotedReference(value, context.syntax);
1639
+ if (context.url) {
1640
+ value = value.replace(/\\/g, "/");
1641
+ value = full ? decodedUrlPath(value) : decodeWellFormedEscapes(value);
1642
+ }
1643
+ return sep2 === "\\" ? value.replace(/\\/g, "/") : value;
1644
+ };
1645
+ return function rewriteText(text2, syntax = "html", normalizeEntities = true) {
1646
+ if (syntax === "html" && normalizeEntities) {
1647
+ text2 = rewriteEntityAttributes(text2, (source) => rewriteText(source, "html", false));
1648
+ }
1649
+ pattern.lastIndex = 0;
1650
+ if (!pattern.test(text2)) return text2;
1651
+ pattern.lastIndex = 0;
1652
+ const tags = syntax === "html" ? scanHtmlOpeningTags(text2) : [];
1653
+ const inlineRegions = tags.filter(
1654
+ (tag) => tag.closed && ["script", "style"].includes(tag.name) && tag.rawTextEnd !== null
1655
+ ).map((tag) => ({
1656
+ start: tag.end,
1657
+ end: tag.rawTextEnd,
1658
+ syntax: tag.name === "style" ? "css" : "script"
1659
+ }));
1660
+ const attributes = tags.flatMap(
1661
+ (tag) => tag.attributes.flatMap((attr) => attr.kind === "value" ? [attr] : [])
1662
+ );
1663
+ const cssContexts = syntax === "css" ? [{ start: 0, end: text2.length, htmlAttribute: false }] : [
1664
+ ...inlineRegions.filter((region) => region.syntax === "css").map((region) => ({ ...region, htmlAttribute: false })),
1665
+ ...attributes.filter((attr) => attr.name === "style").map((attr) => ({ ...attr, htmlAttribute: true }))
1666
+ ];
1667
+ const cssFields = (pattern2) => cssContexts.flatMap(
1668
+ (context) => [...text2.slice(context.start, context.end).matchAll(pattern2)].map((match) => {
1669
+ const prefix = /^(?:url\(\s*|@import\s+)/i.exec(match[0])[0];
1670
+ const rest = match[0].slice(prefix.length);
1671
+ const delimiter = context.htmlAttribute ? /^(?:["']|&quot;|&#39;|&apos;)/.exec(rest)?.[0] : /^["']/.exec(rest)?.[0];
1672
+ const start = context.start + match.index + prefix.length + (delimiter?.length ?? 0);
1673
+ return {
1674
+ start,
1675
+ end: context.start + match.index + match[0].length,
1676
+ syntax: "css",
1677
+ htmlAttribute: context.htmlAttribute,
1678
+ encodeAll: true
1679
+ };
1680
+ })
1681
+ );
1682
+ const urlRegions = [
1683
+ ...attributes.filter((attr) => ["src", "href", "poster", "xlink:href"].includes(attr.name)).map((attr) => ({
1684
+ start: attr.valueStart + (/^[\t\n\f\r ]*/.exec(attr.value)?.[0].length ?? 0),
1685
+ end: attr.valueEnd,
1686
+ syntax: "html",
1687
+ htmlAttribute: true,
1688
+ encodeAll: false
1689
+ })),
1690
+ ...attributes.filter((attr) => attr.name === "srcset").flatMap(
1691
+ (attr) => [...attr.value.matchAll(/(^|,)[\t\n\f\r ]*([^,\t\n\f\r ]+)/g)].map((match) => ({
1692
+ start: attr.valueStart + match.index + match[0].length - match[2].length,
1693
+ end: attr.valueStart + match.index + match[0].length,
1694
+ syntax: "html",
1695
+ htmlAttribute: true,
1696
+ encodeAll: true
1697
+ }))
1698
+ ),
1699
+ ...cssFields(
1700
+ /\burl\((?:\s*"(?:\\.|[^"\\])*"\s*|\s*'(?:\\.|[^'\\])*'\s*|(?:\\.|[^)\\])*)\)/gi
1701
+ ),
1702
+ ...cssFields(/@import\s+(?:"[^"]*"|'[^']*')/gi)
1703
+ ].sort((a, b) => a.start - b.start);
1704
+ let urlIndex = 0;
1705
+ let regionIndex = 0;
1706
+ return text2.replace(pattern, (...args) => {
1707
+ const [match, offset] = [args[0], args.at(-3)];
1708
+ const { lead } = args.at(-1);
1709
+ while (inlineRegions[regionIndex] && inlineRegions[regionIndex].end <= offset) regionIndex++;
1710
+ const region = inlineRegions[regionIndex];
1711
+ const matchSyntax = region && offset >= region.start ? region.syntax : syntax;
1712
+ while (urlRegions[urlIndex] && urlRegions[urlIndex].end <= offset) urlIndex++;
1713
+ const urlRegion = urlRegions[urlIndex];
1714
+ const url = matchSyntax !== "script" && !!urlRegion && offset >= urlRegion.start;
1715
+ const context = {
1716
+ syntax: url ? urlRegion.syntax : matchSyntax,
1717
+ url,
1718
+ htmlAttribute: url && urlRegion.htmlAttribute,
1719
+ encodeAll: url && urlRegion.encodeAll,
1720
+ quote: text2[offset - 1] ?? ""
1721
+ };
1722
+ if (url && offset !== urlRegion.start) return match;
1723
+ const reference = match.slice(lead.length);
1724
+ if (normalized(reference, context, true) !== oldIdentity) return match;
1725
+ if (isDirectory && !normalized(
1726
+ text2.slice(offset + match.length, offset + match.length + 4),
1727
+ context
1728
+ ).startsWith("/"))
1729
+ return match;
1730
+ const at = offset + lead.length;
1731
+ const head = normalized(text2.slice(Math.max(0, at - window), at), context);
1732
+ const tail = normalized(
1733
+ text2.slice(offset + match.length, offset + match.length + window),
1734
+ context,
1735
+ context.url
1544
1736
  );
1737
+ const inLonger = [...afterLengths].some((after) => {
1738
+ const befores = tail.length >= after && around.get(tail.slice(0, after));
1739
+ return befores && [...befores].some(
1740
+ ([length, set]) => head.length >= length && set.has(head.slice(head.length - length))
1741
+ );
1742
+ });
1743
+ if (inLonger) return match;
1744
+ return `${lead}${spellLike(reference, newPath, oldPath, context)}`;
1545
1745
  });
1546
- return inLonger ? match : `${lead}${newPath}`;
1547
- });
1746
+ };
1548
1747
  }
1549
1748
  function occurrences(text2, part) {
1550
1749
  const at = [];
@@ -1577,7 +1776,7 @@ function updateReferences(projectDir, oldPath, newPath, isDirectory) {
1577
1776
  if (!isSafePath(projectDir, file) || isPrivateProjectFile(projectDir, file)) continue;
1578
1777
  const content = readableText(file);
1579
1778
  if (content === null) continue;
1580
- const updated = rewrite(content);
1779
+ const updated = rewrite(content, referenceSyntax(file));
1581
1780
  if (updated !== content) {
1582
1781
  replaceFileAtomically2(file, updated, statSync3(file).mode);
1583
1782
  updatedCount++;
@@ -3454,10 +3653,11 @@ function registerFileRoutes(api, adapter) {
3454
3653
  const res = await resolveProjectFile(c, adapter, { mustExist: true });
3455
3654
  if ("error" in res) return res.error;
3456
3655
  const body = await c.req.json();
3457
- if (!body.newPath || body.newPath.includes("\0")) {
3656
+ const newPath = body !== null && typeof body === "object" && "newPath" in body ? body.newPath : void 0;
3657
+ if (typeof newPath !== "string" || !newPath || newPath.includes("\0") || /[\uD800-\uDFFF]/u.test(newPath)) {
3458
3658
  return c.json({ error: "newPath required" }, 400);
3459
3659
  }
3460
- const newAbs = resolveWithinProject(res.project.dir, body.newPath);
3660
+ const newAbs = resolveWithinProject(res.project.dir, newPath);
3461
3661
  if (!newAbs) {
3462
3662
  return c.json({ error: "forbidden" }, 403);
3463
3663
  }
@@ -3467,8 +3667,8 @@ function registerFileRoutes(api, adapter) {
3467
3667
  const isDirectory = statSync3(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
3468
3668
  ensureDir(res.project.dir, newAbs);
3469
3669
  renameSync2(res.absPath, newAbs);
3470
- const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath, isDirectory);
3471
- return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
3670
+ const updatedFiles = updateReferences(res.project.dir, res.filePath, newPath, isDirectory);
3671
+ return c.json({ ok: true, path: newPath, updatedReferences: updatedFiles });
3472
3672
  });
3473
3673
  api.post("/projects/:id/duplicate-file", async (c) => {
3474
3674
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -4306,7 +4506,11 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
4306
4506
  for (const file of htmlFiles) {
4307
4507
  if (coveredFiles.has(file)) continue;
4308
4508
  const content = readFileSync7(join9(projectDir, file), "utf-8");
4309
- const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
4509
+ const result = await adapter.lint(content, {
4510
+ filePath: file,
4511
+ isSubComposition: true,
4512
+ compSrcPath: file
4513
+ });
4310
4514
  for (const finding of result?.findings ?? []) {
4311
4515
  findings.push({ ...finding, file });
4312
4516
  }
@@ -4760,12 +4964,7 @@ async function generateThumbnail(path, signal) {
4760
4964
  async function resolveSource(adapter, id, url) {
4761
4965
  const project = await adapter.resolveProject(id);
4762
4966
  if (!project) throw new HTTPException(404);
4763
- let relative5;
4764
- try {
4765
- relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
4766
- } catch {
4767
- throw new HTTPException(400);
4768
- }
4967
+ const relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
4769
4968
  if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
4770
4969
  const path = pinWithinProject(project.dir, relative5);
4771
4970
  if (!path) throw new HTTPException(404);
@@ -5079,7 +5278,7 @@ function registerWaveformRoutes(api, adapter) {
5079
5278
  // src/routes/freezeFrame.ts
5080
5279
  import { execFile } from "child_process";
5081
5280
  import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
5082
- import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep2 } from "path";
5281
+ import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep3 } from "path";
5083
5282
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
5084
5283
  import {
5085
5284
  atomicTempPath,
@@ -5343,7 +5542,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5343
5542
  imagePath
5344
5543
  );
5345
5544
  if (failed) return failed;
5346
- const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5545
+ const depth = relative4(projectDir, fileDir).split(sep3).filter(Boolean).length;
5347
5546
  const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5348
5547
  return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
5349
5548
  }
@@ -5920,9 +6119,6 @@ function isVideoPath(path) {
5920
6119
  function isImagePath(path) {
5921
6120
  return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
5922
6121
  }
5923
- function normalizeProjectAssetPath(path) {
5924
- return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
5925
- }
5926
6122
  function containsNullByte(path) {
5927
6123
  return path.includes("\0");
5928
6124
  }
@@ -5987,7 +6183,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5987
6183
  api.get("/projects/:id/media/metadata", async (c) => {
5988
6184
  const project = await adapter.resolveProject(c.req.param("id"));
5989
6185
  if (!project) return c.json({ error: "not found" }, 404);
5990
- const assetPath = normalizeProjectAssetPath(c.req.query("path") ?? "");
6186
+ const assetPath = requestedProjectPath(project.dir, c.req.query("path") ?? "");
5991
6187
  if (!assetPath) return c.json({ error: "path required" }, 400);
5992
6188
  if (containsNullByte(assetPath)) return c.json({ error: "forbidden" }, 403);
5993
6189
  if (/^(?:https?:|data:|blob:)/i.test(assetPath)) {
@@ -6009,7 +6205,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
6009
6205
  const project = await adapter.resolveProject(c.req.param("id"));
6010
6206
  if (!project) return c.json({ error: "not found" }, 404);
6011
6207
  const body = await c.req.json().catch(() => ({}));
6012
- const inputAssetPath = body.inputPath ? normalizeProjectAssetPath(body.inputPath) : "";
6208
+ const inputAssetPath = body.inputPath ? requestedProjectPath(project.dir, body.inputPath) : "";
6013
6209
  if (!inputAssetPath) return c.json({ error: "inputPath required" }, 400);
6014
6210
  if (containsNullByte(inputAssetPath)) return c.json({ error: "forbidden" }, 403);
6015
6211
  if (/^(?:https?:|data:|blob:)/i.test(inputAssetPath)) {
@@ -6023,7 +6219,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
6023
6219
  if (!inputIsVideo && !inputIsImage) {
6024
6220
  return c.json({ error: "background removal supports video or image assets only" }, 400);
6025
6221
  }
6026
- const requestedOutput = body.outputPath ? posix.normalize(normalizeProjectAssetPath(body.outputPath)) : "";
6222
+ const requestedOutput = body.outputPath ? posix.normalize(requestedProjectPath(project.dir, body.outputPath)) : "";
6027
6223
  if (requestedOutput && containsNullByte(requestedOutput)) {
6028
6224
  return c.json({ error: "forbidden" }, 403);
6029
6225
  }
@@ -6497,6 +6693,7 @@ export {
6497
6693
  createStudioManualEditsRenderBodyScript,
6498
6694
  createStudioMotionRenderBodyScript,
6499
6695
  createStudioPositionSeekReapplyScript,
6696
+ decodeWellFormedEscapes,
6500
6697
  dedupeClonedCompositionId,
6501
6698
  duplicateElementInHtml,
6502
6699
  fileContentVersion,