@hyperframes/studio-server 0.8.142 → 0.8.143
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/{chunk-DSVQ2U7J.js → chunk-32IH7WBY.js} +36 -14
- package/dist/chunk-32IH7WBY.js.map +1 -0
- package/dist/{chunk-QP7LKFU2.js → chunk-6EFY2WPN.js} +2 -2
- package/dist/{chunk-5VCIZYUE.js → chunk-AYKLBPP2.js} +3 -3
- package/dist/{chunk-74JVVCWL.js → chunk-RN33UALN.js} +16 -18
- package/dist/chunk-RN33UALN.js.map +1 -0
- package/dist/helpers/loudness.js +4 -1
- package/dist/helpers/mediaCodecMap.js +1 -1
- package/dist/helpers/mediaProxyPreview.js +3 -3
- package/dist/helpers/proxyTranscoder.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +242 -49
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-74JVVCWL.js.map +0 -1
- package/dist/chunk-DSVQ2U7J.js.map +0 -1
- /package/dist/{chunk-QP7LKFU2.js.map → chunk-6EFY2WPN.js.map} +0 -0
- /package/dist/{chunk-5VCIZYUE.js.map → chunk-AYKLBPP2.js.map} +0 -0
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-
|
|
64
|
+
} from "./chunk-32IH7WBY.js";
|
|
62
65
|
import {
|
|
63
66
|
injectMediaCodecMap,
|
|
64
67
|
isAutoProxyEnabled,
|
|
65
68
|
proxyEtagSalt,
|
|
66
69
|
resolvePreviewMediaCodecProbeCache
|
|
67
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-AYKLBPP2.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-
|
|
81
|
+
} from "./chunk-6EFY2WPN.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-
|
|
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(`\${char}`), escapeRegExp(`\${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(
|
|
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 *
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
const normalized = (text2) =>
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
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 ? /^(?:["']|"|'|')/.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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
3471
|
-
return c.json({ ok: true, path:
|
|
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"));
|
|
@@ -4760,12 +4960,7 @@ async function generateThumbnail(path, signal) {
|
|
|
4760
4960
|
async function resolveSource(adapter, id, url) {
|
|
4761
4961
|
const project = await adapter.resolveProject(id);
|
|
4762
4962
|
if (!project) throw new HTTPException(404);
|
|
4763
|
-
|
|
4764
|
-
try {
|
|
4765
|
-
relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
|
|
4766
|
-
} catch {
|
|
4767
|
-
throw new HTTPException(400);
|
|
4768
|
-
}
|
|
4963
|
+
const relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
|
|
4769
4964
|
if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
|
|
4770
4965
|
const path = pinWithinProject(project.dir, relative5);
|
|
4771
4966
|
if (!path) throw new HTTPException(404);
|
|
@@ -5079,7 +5274,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
5079
5274
|
// src/routes/freezeFrame.ts
|
|
5080
5275
|
import { execFile } from "child_process";
|
|
5081
5276
|
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
|
|
5277
|
+
import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep3 } from "path";
|
|
5083
5278
|
import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
|
|
5084
5279
|
import {
|
|
5085
5280
|
atomicTempPath,
|
|
@@ -5343,7 +5538,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
5343
5538
|
imagePath
|
|
5344
5539
|
);
|
|
5345
5540
|
if (failed) return failed;
|
|
5346
|
-
const depth = relative4(projectDir, fileDir).split(
|
|
5541
|
+
const depth = relative4(projectDir, fileDir).split(sep3).filter(Boolean).length;
|
|
5347
5542
|
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
5348
5543
|
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
5349
5544
|
}
|
|
@@ -5920,9 +6115,6 @@ function isVideoPath(path) {
|
|
|
5920
6115
|
function isImagePath(path) {
|
|
5921
6116
|
return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
5922
6117
|
}
|
|
5923
|
-
function normalizeProjectAssetPath(path) {
|
|
5924
|
-
return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
|
|
5925
|
-
}
|
|
5926
6118
|
function containsNullByte(path) {
|
|
5927
6119
|
return path.includes("\0");
|
|
5928
6120
|
}
|
|
@@ -5987,7 +6179,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5987
6179
|
api.get("/projects/:id/media/metadata", async (c) => {
|
|
5988
6180
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
5989
6181
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
5990
|
-
const assetPath =
|
|
6182
|
+
const assetPath = requestedProjectPath(project.dir, c.req.query("path") ?? "");
|
|
5991
6183
|
if (!assetPath) return c.json({ error: "path required" }, 400);
|
|
5992
6184
|
if (containsNullByte(assetPath)) return c.json({ error: "forbidden" }, 403);
|
|
5993
6185
|
if (/^(?:https?:|data:|blob:)/i.test(assetPath)) {
|
|
@@ -6009,7 +6201,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
6009
6201
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
6010
6202
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
6011
6203
|
const body = await c.req.json().catch(() => ({}));
|
|
6012
|
-
const inputAssetPath = body.inputPath ?
|
|
6204
|
+
const inputAssetPath = body.inputPath ? requestedProjectPath(project.dir, body.inputPath) : "";
|
|
6013
6205
|
if (!inputAssetPath) return c.json({ error: "inputPath required" }, 400);
|
|
6014
6206
|
if (containsNullByte(inputAssetPath)) return c.json({ error: "forbidden" }, 403);
|
|
6015
6207
|
if (/^(?:https?:|data:|blob:)/i.test(inputAssetPath)) {
|
|
@@ -6023,7 +6215,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
6023
6215
|
if (!inputIsVideo && !inputIsImage) {
|
|
6024
6216
|
return c.json({ error: "background removal supports video or image assets only" }, 400);
|
|
6025
6217
|
}
|
|
6026
|
-
const requestedOutput = body.outputPath ? posix.normalize(
|
|
6218
|
+
const requestedOutput = body.outputPath ? posix.normalize(requestedProjectPath(project.dir, body.outputPath)) : "";
|
|
6027
6219
|
if (requestedOutput && containsNullByte(requestedOutput)) {
|
|
6028
6220
|
return c.json({ error: "forbidden" }, 403);
|
|
6029
6221
|
}
|
|
@@ -6497,6 +6689,7 @@ export {
|
|
|
6497
6689
|
createStudioManualEditsRenderBodyScript,
|
|
6498
6690
|
createStudioMotionRenderBodyScript,
|
|
6499
6691
|
createStudioPositionSeekReapplyScript,
|
|
6692
|
+
decodeWellFormedEscapes,
|
|
6500
6693
|
dedupeClonedCompositionId,
|
|
6501
6694
|
duplicateElementInHtml,
|
|
6502
6695
|
fileContentVersion,
|