@hyperframes/studio-server 0.8.141 → 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-CLWFZQUU.js → chunk-JJ4N3PXT.js} +4 -1
- package/dist/chunk-JJ4N3PXT.js.map +1 -0
- 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/helpers/sourceMutation.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +279 -72
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-74JVVCWL.js.map +0 -1
- package/dist/chunk-CLWFZQUU.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
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
splitElementInHtml,
|
|
18
18
|
unwrapElementsFromHtml,
|
|
19
19
|
wrapElementsInHtml
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-JJ4N3PXT.js";
|
|
21
21
|
import {
|
|
22
22
|
isCompositionSource,
|
|
23
23
|
stampProjectHfIds
|
|
@@ -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"));
|
|
@@ -4339,10 +4539,25 @@ function registerLintRoutes(api, adapter) {
|
|
|
4339
4539
|
// src/routes/render.ts
|
|
4340
4540
|
import { streamSSE } from "hono/streaming";
|
|
4341
4541
|
import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
4342
|
-
import { join as join10 } from "path";
|
|
4542
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
4343
4543
|
import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
|
|
4344
4544
|
import { formatRenderOutputTimestamp, parseFps } from "@hyperframes/core";
|
|
4345
4545
|
var VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
|
|
4546
|
+
var RENDER_MIME = {
|
|
4547
|
+
".mp4": "video/mp4",
|
|
4548
|
+
".webm": "video/webm",
|
|
4549
|
+
".mov": "video/quicktime"
|
|
4550
|
+
};
|
|
4551
|
+
var RENDER_EXTENSIONS = Object.keys(RENDER_MIME);
|
|
4552
|
+
var RENDER_ARTIFACT_EXTENSIONS = [...RENDER_EXTENSIONS, ".meta.json"];
|
|
4553
|
+
function resolveRenderArtifacts(directories, jobId) {
|
|
4554
|
+
const artifacts = [...new Set(directories)].filter(existsSync6).flatMap(
|
|
4555
|
+
(directory) => RENDER_ARTIFACT_EXTENSIONS.map(
|
|
4556
|
+
(extension) => resolveWithinProject(directory, `${jobId}${extension}`)
|
|
4557
|
+
)
|
|
4558
|
+
);
|
|
4559
|
+
return artifacts.every((path) => path !== null) ? artifacts : null;
|
|
4560
|
+
}
|
|
4346
4561
|
function contentDispositionHeader(disposition, filename) {
|
|
4347
4562
|
const fallback = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
|
|
4348
4563
|
const encoded = encodeURIComponent(filename).replace(
|
|
@@ -4460,12 +4675,6 @@ function registerRenderRoutes(api, adapter) {
|
|
|
4460
4675
|
}
|
|
4461
4676
|
return c.json({ status: job.status });
|
|
4462
4677
|
});
|
|
4463
|
-
const RENDER_MIME = {
|
|
4464
|
-
".mp4": "video/mp4",
|
|
4465
|
-
".webm": "video/webm",
|
|
4466
|
-
".mov": "video/quicktime"
|
|
4467
|
-
};
|
|
4468
|
-
const RENDER_EXTENSIONS = Object.keys(RENDER_MIME);
|
|
4469
4678
|
const isPlainFile = (path) => {
|
|
4470
4679
|
try {
|
|
4471
4680
|
return lstatSync3(path).isFile();
|
|
@@ -4511,17 +4720,22 @@ function registerRenderRoutes(api, adapter) {
|
|
|
4511
4720
|
}
|
|
4512
4721
|
});
|
|
4513
4722
|
});
|
|
4514
|
-
|
|
4723
|
+
async function renderDirectories(jobId) {
|
|
4724
|
+
const outputPath = renderJobs.get(jobId)?.outputPath;
|
|
4725
|
+
if (outputPath) return [dirname5(outputPath)];
|
|
4726
|
+
const projects = await adapter.listProjects();
|
|
4727
|
+
return projects.map((project) => adapter.rendersDir(project));
|
|
4728
|
+
}
|
|
4729
|
+
api.delete("/render/:jobId", async (c) => {
|
|
4515
4730
|
const { jobId } = c.req.param();
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
}
|
|
4731
|
+
if (jobId.includes("/") || jobId.includes("\\") || jobId.includes("\0")) {
|
|
4732
|
+
return c.json({ error: "forbidden" }, 403);
|
|
4733
|
+
}
|
|
4734
|
+
const directories = await renderDirectories(jobId);
|
|
4735
|
+
const artifacts = resolveRenderArtifacts(directories, jobId);
|
|
4736
|
+
if (!artifacts) return c.json({ error: "forbidden" }, 403);
|
|
4737
|
+
for (const path of artifacts) {
|
|
4738
|
+
if (existsSync6(path)) unlinkSync3(path);
|
|
4525
4739
|
}
|
|
4526
4740
|
renderJobs.delete(jobId);
|
|
4527
4741
|
return c.json({ deleted: true });
|
|
@@ -4746,12 +4960,7 @@ async function generateThumbnail(path, signal) {
|
|
|
4746
4960
|
async function resolveSource(adapter, id, url) {
|
|
4747
4961
|
const project = await adapter.resolveProject(id);
|
|
4748
4962
|
if (!project) throw new HTTPException(404);
|
|
4749
|
-
|
|
4750
|
-
try {
|
|
4751
|
-
relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
|
|
4752
|
-
} catch {
|
|
4753
|
-
throw new HTTPException(400);
|
|
4754
|
-
}
|
|
4963
|
+
const relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
|
|
4755
4964
|
if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
|
|
4756
4965
|
const path = pinWithinProject(project.dir, relative5);
|
|
4757
4966
|
if (!path) throw new HTTPException(404);
|
|
@@ -5065,7 +5274,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
5065
5274
|
// src/routes/freezeFrame.ts
|
|
5066
5275
|
import { execFile } from "child_process";
|
|
5067
5276
|
import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
5068
|
-
import { basename as basename2, dirname as
|
|
5277
|
+
import { basename as basename2, dirname as dirname6, join as join13, relative as relative4, sep as sep3 } from "path";
|
|
5069
5278
|
import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
|
|
5070
5279
|
import {
|
|
5071
5280
|
atomicTempPath,
|
|
@@ -5312,14 +5521,14 @@ async function extractThenPublishWhole(extract, mediaPath, mediaTime, imagePath)
|
|
|
5312
5521
|
}
|
|
5313
5522
|
}
|
|
5314
5523
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
5315
|
-
const fileDir =
|
|
5524
|
+
const fileDir = dirname6(absPath);
|
|
5316
5525
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
5317
5526
|
if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
|
|
5318
5527
|
const freezeDir = join13(projectDir, ...FREEZE_DIR);
|
|
5319
5528
|
const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
|
|
5320
5529
|
mkdirWithinProject(projectDir, freezeDir);
|
|
5321
5530
|
const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
|
|
5322
|
-
if (!imagePath ||
|
|
5531
|
+
if (!imagePath || dirname6(imagePath) !== freezeDir) {
|
|
5323
5532
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
5324
5533
|
}
|
|
5325
5534
|
const failed = await extractThenPublishWhole(
|
|
@@ -5329,7 +5538,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
5329
5538
|
imagePath
|
|
5330
5539
|
);
|
|
5331
5540
|
if (failed) return failed;
|
|
5332
|
-
const depth = relative4(projectDir, fileDir).split(
|
|
5541
|
+
const depth = relative4(projectDir, fileDir).split(sep3).filter(Boolean).length;
|
|
5333
5542
|
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
5334
5543
|
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
5335
5544
|
}
|
|
@@ -5883,7 +6092,7 @@ function registerSelectionRoutes(api, adapter) {
|
|
|
5883
6092
|
// src/routes/media.ts
|
|
5884
6093
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5885
6094
|
import { existsSync as existsSync10 } from "fs";
|
|
5886
|
-
import { basename as basename3, dirname as
|
|
6095
|
+
import { basename as basename3, dirname as dirname7, extname, join as join15, posix } from "path";
|
|
5887
6096
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5888
6097
|
".mp4",
|
|
5889
6098
|
".mov",
|
|
@@ -5906,9 +6115,6 @@ function isVideoPath(path) {
|
|
|
5906
6115
|
function isImagePath(path) {
|
|
5907
6116
|
return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
5908
6117
|
}
|
|
5909
|
-
function normalizeProjectAssetPath(path) {
|
|
5910
|
-
return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
|
|
5911
|
-
}
|
|
5912
6118
|
function containsNullByte(path) {
|
|
5913
6119
|
return path.includes("\0");
|
|
5914
6120
|
}
|
|
@@ -5973,7 +6179,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5973
6179
|
api.get("/projects/:id/media/metadata", async (c) => {
|
|
5974
6180
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
5975
6181
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
5976
|
-
const assetPath =
|
|
6182
|
+
const assetPath = requestedProjectPath(project.dir, c.req.query("path") ?? "");
|
|
5977
6183
|
if (!assetPath) return c.json({ error: "path required" }, 400);
|
|
5978
6184
|
if (containsNullByte(assetPath)) return c.json({ error: "forbidden" }, 403);
|
|
5979
6185
|
if (/^(?:https?:|data:|blob:)/i.test(assetPath)) {
|
|
@@ -5995,7 +6201,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
5995
6201
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
5996
6202
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
5997
6203
|
const body = await c.req.json().catch(() => ({}));
|
|
5998
|
-
const inputAssetPath = body.inputPath ?
|
|
6204
|
+
const inputAssetPath = body.inputPath ? requestedProjectPath(project.dir, body.inputPath) : "";
|
|
5999
6205
|
if (!inputAssetPath) return c.json({ error: "inputPath required" }, 400);
|
|
6000
6206
|
if (containsNullByte(inputAssetPath)) return c.json({ error: "forbidden" }, 403);
|
|
6001
6207
|
if (/^(?:https?:|data:|blob:)/i.test(inputAssetPath)) {
|
|
@@ -6009,7 +6215,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
6009
6215
|
if (!inputIsVideo && !inputIsImage) {
|
|
6010
6216
|
return c.json({ error: "background removal supports video or image assets only" }, 400);
|
|
6011
6217
|
}
|
|
6012
|
-
const requestedOutput = body.outputPath ? posix.normalize(
|
|
6218
|
+
const requestedOutput = body.outputPath ? posix.normalize(requestedProjectPath(project.dir, body.outputPath)) : "";
|
|
6013
6219
|
if (requestedOutput && containsNullByte(requestedOutput)) {
|
|
6014
6220
|
return c.json({ error: "forbidden" }, 403);
|
|
6015
6221
|
}
|
|
@@ -6039,8 +6245,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
6039
6245
|
return c.json({ error: "forbidden" }, 403);
|
|
6040
6246
|
}
|
|
6041
6247
|
}
|
|
6042
|
-
mkdirWithinProject(project.dir,
|
|
6043
|
-
if (backgroundOutputPath) mkdirWithinProject(project.dir,
|
|
6248
|
+
mkdirWithinProject(project.dir, dirname7(outputPath));
|
|
6249
|
+
if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname7(backgroundOutputPath));
|
|
6044
6250
|
const jobId = makeJobId(project.id, mediaJobs);
|
|
6045
6251
|
const state = adapter.startBackgroundRemoval({
|
|
6046
6252
|
project,
|
|
@@ -6483,6 +6689,7 @@ export {
|
|
|
6483
6689
|
createStudioManualEditsRenderBodyScript,
|
|
6484
6690
|
createStudioMotionRenderBodyScript,
|
|
6485
6691
|
createStudioPositionSeekReapplyScript,
|
|
6692
|
+
decodeWellFormedEscapes,
|
|
6486
6693
|
dedupeClonedCompositionId,
|
|
6487
6694
|
duplicateElementInHtml,
|
|
6488
6695
|
fileContentVersion,
|