hyperframes 0.8.132 → 0.8.134
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/{add-LR3ZR2A5.js → add-RHGOHK4Z.js} +5 -5
- package/dist/{asset-BE2DOV72.js → asset-OTWT2XTV.js} +2 -2
- package/dist/{auth-RCQB2ZT7.js → auth-VER56QYQ.js} +1 -1
- package/dist/{autoUpdate-RJAX3DAZ.js → autoUpdate-XHFKOSVF.js} +2 -2
- package/dist/{backgroundChecks-H2LCUIMJ.js → backgroundChecks-GAAKX5BU.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{beats-Y2EOP72P.js → beats-QM23K5LR.js} +4 -4
- package/dist/{benchmark-MZ7GY3JW.js → benchmark-GPU6HIVH.js} +5 -5
- package/dist/{browser-HUTAMRQE.js → browser-RUFRY3QG.js} +3 -3
- package/dist/{catalog-KYU55EWR.js → catalog-L6E2TFFY.js} +5 -5
- package/dist/{check-TCG4PXLR.js → check-TTV2DXDB.js} +12 -12
- package/dist/{checkBrowser-RPMO4OW5.js → checkBrowser-WWSIKC72.js} +10 -10
- package/dist/{chunk-UEZXMWG3.js → chunk-4XSWF3CD.js} +1 -1
- package/dist/{chunk-RDNNAKEP.js → chunk-6IXB7QBG.js} +2 -2
- package/dist/{chunk-4THIAPBL.js → chunk-6SRQ4ZQ5.js} +2 -2
- package/dist/{chunk-5IAKAQA7.js → chunk-AJU25DZ2.js} +2 -2
- package/dist/{chunk-2EGFTPWD.js → chunk-ELEWD7K4.js} +3 -3
- package/dist/{chunk-JTHMTOMQ.js → chunk-GVRLL3SB.js} +1 -1
- package/dist/{chunk-IGNAAHS3.js → chunk-ICDMEFU2.js} +1 -1
- package/dist/{chunk-KBJUXVOJ.js → chunk-KBUGELQE.js} +1 -1
- package/dist/{chunk-S3D6GE5W.js → chunk-L2PGUI5F.js} +1 -1
- package/dist/{chunk-H6QMKUYA.js → chunk-MUORWS65.js} +3 -3
- package/dist/{chunk-QNYYOUU3.js → chunk-NIDIM7IR.js} +1 -1
- package/dist/{chunk-FLWXXDNS.js → chunk-NWMOKW7Q.js} +2 -2
- package/dist/{chunk-HW6OYASV.js → chunk-PUYDBQ3R.js} +1 -1
- package/dist/{chunk-S7NPLFWV.js → chunk-SIU7QCPK.js} +758 -650
- package/dist/{chunk-EISRN4E6.js → chunk-SUY33ISN.js} +1 -1
- package/dist/{chunk-F3WZGU7C.js → chunk-TPBT32X6.js} +2 -2
- package/dist/{chunk-XH4DSOS5.js → chunk-WAGW42UH.js} +1 -1
- package/dist/{chunk-PPQUYWTW.js → chunk-Z46SC5ZL.js} +2 -2
- package/dist/{clean-W57E5YQ3.js → clean-YNXRTMJU.js} +4 -4
- package/dist/cli.js +45 -45
- package/dist/{cloud-FFTJGZ24.js → cloud-6ZOWBODT.js} +4 -4
- package/dist/{compare-AYCTT55F.js → compare-I2W3LIPI.js} +7 -7
- package/dist/{component-VFFR2O7T.js → component-F5V44RLH.js} +3 -3
- package/dist/{compositions-QTVN3U2A.js → compositions-KECVMHW5.js} +5 -5
- package/dist/{delete-ZAKYCB7I.js → delete-JGF4FREG.js} +2 -2
- package/dist/{doctor-AI7C3FON.js → doctor-HDIEQSHY.js} +3 -3
- package/dist/{events-7LALYNKZ.js → events-T5YLKXZ4.js} +3 -3
- package/dist/{events-FQY3HGKZ.js → events-Z5QEJRZT.js} +2 -2
- package/dist/{feedback-WCMDMSTK.js → feedback-OCX7QBHA.js} +4 -4
- package/dist/{figma-64WY462P.js → figma-O54GV6BZ.js} +3 -3
- package/dist/fontLocalizeCli.js +1 -1
- package/dist/{get-FPOMJMZB.js → get-PZRBJXZT.js} +2 -2
- package/dist/{grade-compare-E2CKPST4.js → grade-compare-CTWZSR4P.js} +7 -7
- package/dist/{gsapParser-DIYYX6JT.js → gsapParser-GHMVJQLH.js} +40 -9
- package/dist/{help-YRQIDDML.js → help-ZPLDYQJT.js} +1 -1
- package/dist/{history-PP5NDRV3.js → history-2UWBYPTV.js} +6 -6
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/{info-SEX3B7JL.js → info-WEMJI32J.js} +5 -5
- package/dist/{init-U3AKHO5B.js → init-SCEXX6T2.js} +6 -6
- package/dist/{inspect-Q3K6EDEK.js → inspect-ZBYJUBMD.js} +9 -9
- package/dist/{keyframes-DC64JCI3.js → keyframes-FRL5BJBG.js} +6 -6
- package/dist/{layout-XSPPDHOO.js → layout-3W2JK43G.js} +9 -9
- package/dist/{lint-7DQZE7L3.js → lint-HIUPGNCY.js} +6 -6
- package/dist/{list-4K5MVDQX.js → list-SMD55UPW.js} +2 -2
- package/dist/{login-NTX4OWXL.js → login-S3ONWYCS.js} +4 -4
- package/dist/{media-treatment-5FYAOPNL.js → media-treatment-SFW5MCC6.js} +5 -5
- package/dist/{motionShot-S4MIGMT5.js → motionShot-AKIMKJYD.js} +1 -1
- package/dist/{normalize-audio-CSFTXLXD.js → normalize-audio-U5UXJWRR.js} +4 -4
- package/dist/{open-2ZD6BHZG.js → open-3E4ZL3XD.js} +4 -4
- package/dist/{play-CTMUXX6E.js → play-33PZ4CD6.js} +7 -7
- package/dist/{present-WQVHV5CR.js → present-ODOKWMTB.js} +7 -7
- package/dist/{preview-JOJXTH75.js → preview-V3CJ75JK.js} +5 -5
- package/dist/{render-LNO62LEL.js → render-B7L7XGI7.js} +5 -5
- package/dist/{render-TTQI7TGN.js → render-ZUFWRU2R.js} +5 -5
- package/dist/{skills-MJ2J4GEM.js → skills-XZSQQ6O3.js} +5 -5
- package/dist/{skillsUpdateCheck-TKTYQN5R.js → skillsUpdateCheck-I4OHSWVJ.js} +3 -3
- package/dist/{snapshot-KIVDUUMN.js → snapshot-RCEGREHZ.js} +7 -7
- package/dist/{staticProjectServer-7EU666GS.js → staticProjectServer-RIFIORHW.js} +3 -3
- package/dist/studio/assets/{hyperframes-player-Cp8e_Zst.js → hyperframes-player-Bro1gj0Z.js} +1 -1
- package/dist/studio/assets/{index-CASEBNy2.js → index-BwNDTmAD.js} +1 -1
- package/dist/studio/assets/{index-DMTt9nTf.js → index-DnVGIbEC.js} +1 -1
- package/dist/studio/assets/{index-oE1pOdAy.js → index-wnXyQjW-.js} +143 -143
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +717 -702
- package/dist/studio/index.js.map +1 -1
- package/dist/{studio-api-PNQZRPX5.js → studio-api-D2KGZOYL.js} +1 -1
- package/dist/{studioServer-UWRBPIOJ.js → studioServer-PXNJXHMV.js} +5 -5
- package/dist/{telemetry-QMYM4WME.js → telemetry-SGIIMXZ3.js} +3 -3
- package/dist/{timeline-77Z3BBQM.js → timeline-QCU4QE6S.js} +6 -6
- package/dist/{tokens-OBGTYE4Z.js → tokens-LXW56KNH.js} +2 -2
- package/dist/{transcribe-7NVFHCGA.js → transcribe-BTZH3ZVO.js} +3 -3
- package/dist/{updateCheck-UDWIJ4ZQ.js → updateCheck-7XAIYPMV.js} +2 -2
- package/dist/{upgrade-CT3CPO3E.js → upgrade-BMJ32AYR.js} +2 -2
- package/dist/{validate-65QDZJWZ.js → validate-DKXIOSJC.js} +6 -6
- package/package.json +1 -1
|
@@ -2227,10 +2227,10 @@ import { join as join6 } from "path";
|
|
|
2227
2227
|
import { bodyLimit } from "hono/body-limit";
|
|
2228
2228
|
import {
|
|
2229
2229
|
closeSync as closeSync3,
|
|
2230
|
-
existsSync as
|
|
2230
|
+
existsSync as existsSync4,
|
|
2231
2231
|
lstatSync as lstatSync22,
|
|
2232
2232
|
openSync as openSync3,
|
|
2233
|
-
readFileSync as
|
|
2233
|
+
readFileSync as readFileSync52,
|
|
2234
2234
|
readlinkSync,
|
|
2235
2235
|
unlinkSync as unlinkSync2,
|
|
2236
2236
|
rmSync as rmSync3,
|
|
@@ -2240,10 +2240,17 @@ import {
|
|
|
2240
2240
|
readdirSync as readdirSync22,
|
|
2241
2241
|
realpathSync as realpathSync2
|
|
2242
2242
|
} from "fs";
|
|
2243
|
-
import { resolve as resolve22, dirname as dirname32, join as
|
|
2243
|
+
import { resolve as resolve22, dirname as dirname32, join as join62 } from "path";
|
|
2244
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
2245
|
+
import { createHash as createHash4 } from "crypto";
|
|
2246
|
+
import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
|
|
2247
|
+
import { join as join23 } from "path";
|
|
2248
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2249
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
2250
|
+
import { dirname as dirname5, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
|
|
2244
2251
|
import { spawn } from "child_process";
|
|
2245
2252
|
import {
|
|
2246
|
-
existsSync as
|
|
2253
|
+
existsSync as existsSync32,
|
|
2247
2254
|
writeFileSync as writeFileSync4,
|
|
2248
2255
|
mkdirSync as mkdirSync5,
|
|
2249
2256
|
lstatSync as lstatSync3,
|
|
@@ -2252,15 +2259,15 @@ import {
|
|
|
2252
2259
|
rmSync as rmSync4,
|
|
2253
2260
|
statSync as statSync4
|
|
2254
2261
|
} from "fs";
|
|
2255
|
-
import { dirname as
|
|
2262
|
+
import { dirname as dirname23, join as join32 } from "path";
|
|
2256
2263
|
import { spawnSync } from "child_process";
|
|
2257
2264
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync22, writeFileSync as writeFileSync23 } from "fs";
|
|
2258
2265
|
import { tmpdir as tmpdir2 } from "os";
|
|
2259
|
-
import { basename as basename2, join as
|
|
2260
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
2266
|
+
import { basename as basename2, join as join42 } from "path";
|
|
2267
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync32, unlinkSync, writeFileSync as writeFileSync32 } from "fs";
|
|
2261
2268
|
import { Buffer as Buffer2 } from "buffer";
|
|
2262
|
-
import { join as
|
|
2263
|
-
import { readFileSync as
|
|
2269
|
+
import { join as join52, relative as relative22 } from "path";
|
|
2270
|
+
import { readFileSync as readFileSync42, statSync as statSync22 } from "fs";
|
|
2264
2271
|
|
|
2265
2272
|
// ../parsers/dist/gsapConstants.js
|
|
2266
2273
|
var PROPERTY_GROUPS = {
|
|
@@ -2335,23 +2342,43 @@ function knownStart(animation) {
|
|
|
2335
2342
|
if (animation.resolvedStart !== void 0) return animation.resolvedStart;
|
|
2336
2343
|
return typeof animation.position === "number" ? animation.position : void 0;
|
|
2337
2344
|
}
|
|
2338
|
-
|
|
2345
|
+
var tweenSignature = (a) => JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);
|
|
2346
|
+
function holdScope(before, previous) {
|
|
2347
|
+
const kept = new Set(previous?.map(tweenSignature));
|
|
2348
|
+
const held = /* @__PURE__ */ new Map();
|
|
2349
|
+
for (const a of before.filter((b) => b.method === "set" && b.properties?.data === "hf-hold")) {
|
|
2350
|
+
const props = held.get(a.targetSelector) ?? /* @__PURE__ */ new Map();
|
|
2351
|
+
for (const [property, value] of Object.entries(a.properties)) props.set(property, value);
|
|
2352
|
+
held.set(a.targetSelector, props);
|
|
2353
|
+
}
|
|
2354
|
+
return {
|
|
2355
|
+
touched: (a) => previous !== null && !kept.has(tweenSignature(a)),
|
|
2356
|
+
held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
function keyframeHoldForAnimation(animation, animations, scope) {
|
|
2339
2360
|
if (!animation.keyframes) return null;
|
|
2340
|
-
const start = knownStart(animation)
|
|
2341
|
-
|
|
2361
|
+
const start = knownStart(animation);
|
|
2362
|
+
const lone = animation.keyframes.keyframes.length === 1;
|
|
2363
|
+
if (start === void 0 || !(start > 1e-3) && !lone) return null;
|
|
2342
2364
|
const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
|
|
2343
2365
|
if (!atStart) return null;
|
|
2344
2366
|
const earlier = animations.filter((other) => {
|
|
2345
2367
|
const otherStart = knownStart(other);
|
|
2346
2368
|
return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
|
|
2347
2369
|
});
|
|
2348
|
-
const
|
|
2370
|
+
const touched = scope.touched(animation);
|
|
2371
|
+
const pinned = scope.held(animation.targetSelector);
|
|
2372
|
+
const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
|
|
2373
|
+
const pins = (group, property, value) => group === "position" && start > 1e-3 || (group === "position" || group === "size") && (lone && touched || kept(property, value));
|
|
2374
|
+
const hold = {};
|
|
2349
2375
|
for (const [property, value] of Object.entries(atStart.properties)) {
|
|
2350
|
-
if (classifyPropertyGroup2(property)
|
|
2376
|
+
if (!pins(classifyPropertyGroup2(property), property, value) || typeof value !== "number")
|
|
2377
|
+
continue;
|
|
2351
2378
|
if (earlier.some((other) => writesProperty(other, property))) continue;
|
|
2352
|
-
|
|
2379
|
+
hold[property] = value;
|
|
2353
2380
|
}
|
|
2354
|
-
return Object.keys(
|
|
2381
|
+
return Object.keys(hold).length > 0 ? hold : null;
|
|
2355
2382
|
}
|
|
2356
2383
|
function authorsKeyframes(anim) {
|
|
2357
2384
|
return anim.keyframes !== void 0 || anim.hasUnresolvedKeyframes === true;
|
|
@@ -5021,15 +5048,20 @@ function removeStudioHoldSets(script, parsed) {
|
|
|
5021
5048
|
for (const hold of staleHolds) removeCallFromMagicString(ms, hold.call, script);
|
|
5022
5049
|
return ms.toString();
|
|
5023
5050
|
}
|
|
5024
|
-
function syncPositionHoldsBeforeKeyframes(script) {
|
|
5051
|
+
function syncPositionHoldsBeforeKeyframes(script, previous) {
|
|
5025
5052
|
const parsed = parseGsapScriptAcornForWrite(script);
|
|
5026
5053
|
if (!parsed) return script;
|
|
5054
|
+
const tweensOf = (text2) => parseGsapScriptAcornForWrite(text2)?.located.map((entry) => entry.animation) ?? null;
|
|
5055
|
+
const scope = holdScope(
|
|
5056
|
+
parsed.located.map((entry) => entry.animation),
|
|
5057
|
+
previous === void 0 ? null : tweensOf(previous)
|
|
5058
|
+
);
|
|
5027
5059
|
let result = removeStudioHoldSets(script, parsed);
|
|
5028
5060
|
const current = parseGsapScriptAcornForWrite(result);
|
|
5029
5061
|
if (!current) return result;
|
|
5030
5062
|
const animations = current.located.map((entry) => entry.animation);
|
|
5031
5063
|
for (const animation of animations) {
|
|
5032
|
-
const position =
|
|
5064
|
+
const position = keyframeHoldForAnimation(animation, animations, scope);
|
|
5033
5065
|
if (!position) continue;
|
|
5034
5066
|
result = insertInheritedStateSetInScript(result, animation.targetSelector, 0, {
|
|
5035
5067
|
...position,
|
|
@@ -5038,6 +5070,62 @@ function syncPositionHoldsBeforeKeyframes(script) {
|
|
|
5038
5070
|
}
|
|
5039
5071
|
return result;
|
|
5040
5072
|
}
|
|
5073
|
+
var roundPct = (pct) => Math.round(pct * 1e3) / 1e3;
|
|
5074
|
+
var LINEAR_RUN = /* @__PURE__ */ new Set(["none", "linear"]);
|
|
5075
|
+
var PLACED_BY_TIMELINE = /\.(?:add|addLabel|addPause|call|repeat|yoyo|duration|timeScale)\s*\(|\b(?:repeat|yoyo)\s*:/;
|
|
5076
|
+
function linearPercentageKeys(animation) {
|
|
5077
|
+
const data = animation.keyframes;
|
|
5078
|
+
if (data?.format !== "percentage" || data.fromMotionPath || animation.arcPath) return null;
|
|
5079
|
+
if (animation.durationUnresolved || !LINEAR_RUN.has(animation.ease || "none")) return null;
|
|
5080
|
+
return data.keyframes;
|
|
5081
|
+
}
|
|
5082
|
+
var loops = (animation) => Boolean(animation.extras?.repeat || animation.extras?.yoyo);
|
|
5083
|
+
function trimmableKeyframes(animation) {
|
|
5084
|
+
const keyframes = loops(animation) ? null : linearPercentageKeys(animation);
|
|
5085
|
+
const { duration, position } = animation;
|
|
5086
|
+
if (!keyframes || typeof position !== "number" || typeof duration !== "number") return null;
|
|
5087
|
+
return duration > 0 ? { keyframes, duration, position } : null;
|
|
5088
|
+
}
|
|
5089
|
+
function trailingSpanTrim(animation) {
|
|
5090
|
+
const tween = trimmableKeyframes(animation);
|
|
5091
|
+
if (!tween) return null;
|
|
5092
|
+
const last = Math.max(...tween.keyframes.map((keyframe) => keyframe.percentage));
|
|
5093
|
+
if (tween.keyframes.length < 2 || !(last > 0) || last >= 99.999) return null;
|
|
5094
|
+
return {
|
|
5095
|
+
position: tween.position,
|
|
5096
|
+
duration: Math.round(tween.duration * last * 10) / 1e3,
|
|
5097
|
+
pctRemap: tween.keyframes.map(({ percentage: from }) => ({
|
|
5098
|
+
from,
|
|
5099
|
+
to: roundPct(from / last * 100)
|
|
5100
|
+
}))
|
|
5101
|
+
};
|
|
5102
|
+
}
|
|
5103
|
+
function trimTrailingKeyframeSpans(previous, script) {
|
|
5104
|
+
if (script === previous) return script;
|
|
5105
|
+
const located = parseGsapScriptAcornForWrite(script)?.located ?? [];
|
|
5106
|
+
const placedAfter = ({ animation: a }) => !a.global && (a.implicitPosition || typeof a.position !== "number");
|
|
5107
|
+
if (PLACED_BY_TIMELINE.test(script) || located.some(placedAfter)) return script;
|
|
5108
|
+
const trims = located.flatMap((entry) => {
|
|
5109
|
+
const trim = trailingSpanTrim(entry.animation);
|
|
5110
|
+
return trim ? [{ entry, trim }] : [];
|
|
5111
|
+
});
|
|
5112
|
+
if (!trims.length) return script;
|
|
5113
|
+
const source = (text2, { call }) => `${call.method}${text2.slice(call.node.arguments[0]?.start, call.node.end)}`;
|
|
5114
|
+
const previousTweens = parseGsapScriptAcornForWrite(previous)?.located ?? [];
|
|
5115
|
+
const untouched = new Set(previousTweens.map((entry) => source(previous, entry)));
|
|
5116
|
+
let result = script;
|
|
5117
|
+
for (const { entry, trim } of trims) {
|
|
5118
|
+
if (untouched.has(source(script, entry))) continue;
|
|
5119
|
+
result = resizeKeyframedTweenInScript(
|
|
5120
|
+
result,
|
|
5121
|
+
entry.id,
|
|
5122
|
+
trim.position,
|
|
5123
|
+
trim.duration,
|
|
5124
|
+
trim.pctRemap
|
|
5125
|
+
);
|
|
5126
|
+
}
|
|
5127
|
+
return result;
|
|
5128
|
+
}
|
|
5041
5129
|
function computeForwardBaselines(matching, splitTime) {
|
|
5042
5130
|
const before = [];
|
|
5043
5131
|
const acc = {};
|
|
@@ -5330,31 +5418,26 @@ function unrollDynamicAnimations(script, animationId, elements) {
|
|
|
5330
5418
|
}
|
|
5331
5419
|
|
|
5332
5420
|
// ../studio-server/dist/index.js
|
|
5333
|
-
import { existsSync as
|
|
5334
|
-
import { randomUUID as randomUUID7 } from "crypto";
|
|
5335
|
-
import { dirname as dirname23, relative as relative22, resolve as resolve5, sep as sep3 } from "path";
|
|
5336
|
-
import { createReadStream as createReadStream2, existsSync as existsSync5, readFileSync as readFileSync52, statSync as statSync42 } from "fs";
|
|
5421
|
+
import { createReadStream as createReadStream2, existsSync as existsSync5, readFileSync as readFileSync62, statSync as statSync42 } from "fs";
|
|
5337
5422
|
import { Readable } from "stream";
|
|
5338
5423
|
import { join as join8, resolve as resolve42 } from "path";
|
|
5339
|
-
import { createHash as
|
|
5424
|
+
import { createHash as createHash23 } from "crypto";
|
|
5340
5425
|
|
|
5341
5426
|
// ../core/dist/studioPreviewMark.js
|
|
5342
5427
|
var STUDIO_PREVIEW_MARK_META = "hyperframes-studio-preview";
|
|
5343
5428
|
var STUDIO_PREVIEW_LAZY_ATTR = "data-hf-preview-lazy";
|
|
5344
5429
|
|
|
5345
5430
|
// ../studio-server/dist/index.js
|
|
5346
|
-
import { existsSync as existsSync42, readFileSync as readFileSync42 } from "fs";
|
|
5347
|
-
import { join as join62 } from "path";
|
|
5348
5431
|
import { readlinkSync as readlinkSync2 } from "fs";
|
|
5349
5432
|
import { dirname as dirname42, join as join7, parse as parse2, relative as relative32, resolve as resolve32 } from "path";
|
|
5350
|
-
import { readFileSync as
|
|
5433
|
+
import { readFileSync as readFileSync72 } from "fs";
|
|
5351
5434
|
import { join as join9 } from "path";
|
|
5352
5435
|
import { streamSSE } from "hono/streaming";
|
|
5353
|
-
import { existsSync as existsSync6, readFileSync as
|
|
5436
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync5 } from "fs";
|
|
5354
5437
|
import { join as join10 } from "path";
|
|
5355
5438
|
import { HTTPException } from "hono/http-exception";
|
|
5356
5439
|
import { open, stat as stat2 } from "fs/promises";
|
|
5357
|
-
import { createHash as
|
|
5440
|
+
import { createHash as createHash32 } from "crypto";
|
|
5358
5441
|
import {
|
|
5359
5442
|
closeSync as closeSync22,
|
|
5360
5443
|
fstatSync as fstatSync22,
|
|
@@ -5369,8 +5452,6 @@ import {
|
|
|
5369
5452
|
} from "fs";
|
|
5370
5453
|
import { join as join11 } from "path";
|
|
5371
5454
|
import { createHash as createHash42, randomUUID as randomUUID22 } from "crypto";
|
|
5372
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
5373
|
-
import { createHash as createHash32 } from "crypto";
|
|
5374
5455
|
import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
5375
5456
|
import { join as join12, resolve as resolve52 } from "path";
|
|
5376
5457
|
import { execFile } from "child_process";
|
|
@@ -5477,339 +5558,175 @@ function getMimeType(path) {
|
|
|
5477
5558
|
function isAudioFile(name) {
|
|
5478
5559
|
return (getMimeType(name) ?? "").startsWith("audio/");
|
|
5479
5560
|
}
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
if (abs > max) max = abs;
|
|
5499
|
-
}
|
|
5500
|
-
peaks.push(max);
|
|
5561
|
+
function rewriteRelativePaths(root, compPath, projectDir) {
|
|
5562
|
+
const assetExists = (path) => existsSync3(join23(projectDir, path));
|
|
5563
|
+
rewriteAssetPaths(
|
|
5564
|
+
root.querySelectorAll("[src], [href]"),
|
|
5565
|
+
compPath,
|
|
5566
|
+
(el, attr) => el.getAttribute(attr),
|
|
5567
|
+
(el, attr, value) => el.setAttribute(attr, value),
|
|
5568
|
+
assetExists
|
|
5569
|
+
);
|
|
5570
|
+
rewriteInlineStyleAssetUrls(
|
|
5571
|
+
root.querySelectorAll("[style]"),
|
|
5572
|
+
compPath,
|
|
5573
|
+
(el) => el.getAttribute("style"),
|
|
5574
|
+
(el, value) => el.setAttribute("style", value),
|
|
5575
|
+
assetExists
|
|
5576
|
+
);
|
|
5577
|
+
for (const styleEl of root.querySelectorAll("style")) {
|
|
5578
|
+
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
|
|
5501
5579
|
}
|
|
5502
|
-
const maxPeak = Math.max(...peaks, 1e-3);
|
|
5503
|
-
return peaks.map((p) => p / maxPeak);
|
|
5504
|
-
}
|
|
5505
|
-
function decodeAudioPeaks(audioPath) {
|
|
5506
|
-
return new Promise((resolvePromise, reject) => {
|
|
5507
|
-
const proc = spawn(
|
|
5508
|
-
findFfBinary("ffmpeg") ?? "ffmpeg",
|
|
5509
|
-
[
|
|
5510
|
-
"-i",
|
|
5511
|
-
audioPath,
|
|
5512
|
-
"-af",
|
|
5513
|
-
"atrim=start_sample=1152",
|
|
5514
|
-
"-f",
|
|
5515
|
-
"f32le",
|
|
5516
|
-
"-ac",
|
|
5517
|
-
"1",
|
|
5518
|
-
"-ar",
|
|
5519
|
-
String(SAMPLE_RATE),
|
|
5520
|
-
"-vn",
|
|
5521
|
-
"pipe:1"
|
|
5522
|
-
],
|
|
5523
|
-
{ stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
|
|
5524
|
-
);
|
|
5525
|
-
const chunks = [];
|
|
5526
|
-
proc.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
5527
|
-
proc.on("close", (code) => {
|
|
5528
|
-
if (code !== 0 && chunks.length === 0) {
|
|
5529
|
-
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
5530
|
-
return;
|
|
5531
|
-
}
|
|
5532
|
-
const buf = Buffer.concat(chunks);
|
|
5533
|
-
const numSamples = Math.floor(buf.length / 4);
|
|
5534
|
-
if (numSamples === 0) {
|
|
5535
|
-
reject(new Error("ffmpeg produced no audio samples"));
|
|
5536
|
-
return;
|
|
5537
|
-
}
|
|
5538
|
-
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4);
|
|
5539
|
-
resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT));
|
|
5540
|
-
});
|
|
5541
|
-
proc.on("error", reject);
|
|
5542
|
-
});
|
|
5543
|
-
}
|
|
5544
|
-
async function generateWaveformCache(projectDir, assetPath) {
|
|
5545
|
-
const audioPath = join23(projectDir, assetPath);
|
|
5546
|
-
if (!existsSync4(audioPath)) return;
|
|
5547
|
-
const stats = statSync4(audioPath);
|
|
5548
|
-
const cacheDir = join23(projectDir, ".waveform-cache");
|
|
5549
|
-
const cachePath = join23(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
5550
|
-
if (isWaveformCacheDirectory(cacheDir) && existsSync4(cachePath)) return;
|
|
5551
|
-
const peaks = await decodeAudioPeaks(audioPath);
|
|
5552
|
-
writeWaveformCache(cachePath, peaks);
|
|
5553
5580
|
}
|
|
5554
|
-
function
|
|
5555
|
-
return
|
|
5581
|
+
function escapeLeadingDigitIdent(id) {
|
|
5582
|
+
return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`;
|
|
5556
5583
|
}
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
if (
|
|
5563
|
-
}
|
|
5564
|
-
if (!isWaveformCacheDirectory(cacheDir)) {
|
|
5565
|
-
throw new Error("Waveform cache must be a directory, not a symlink");
|
|
5584
|
+
var REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g;
|
|
5585
|
+
function fixDigitLeadingIdSelectors(root) {
|
|
5586
|
+
const digitIds = /* @__PURE__ */ new Set();
|
|
5587
|
+
for (const el of root.querySelectorAll("[id]")) {
|
|
5588
|
+
const id = el.getAttribute("id");
|
|
5589
|
+
if (id && /^\d/.test(id)) digitIds.add(id);
|
|
5566
5590
|
}
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
try {
|
|
5574
|
-
rmSync4(stagingDir, { recursive: true, force: true });
|
|
5575
|
-
} catch {
|
|
5591
|
+
if (digitIds.size === 0) return;
|
|
5592
|
+
for (const styleEl of root.querySelectorAll("style")) {
|
|
5593
|
+
let css = styleEl.textContent || "";
|
|
5594
|
+
for (const id of digitIds) {
|
|
5595
|
+
const pattern = new RegExp(`#${id.replace(REGEXP_SPECIALS, "\\$&")}(?![\\w-])`, "g");
|
|
5596
|
+
css = css.replace(pattern, `#${escapeLeadingDigitIdent(id)}`);
|
|
5576
5597
|
}
|
|
5598
|
+
styleEl.textContent = css;
|
|
5577
5599
|
}
|
|
5578
5600
|
}
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
const isVideo = VIDEO_EXT.test(filePath);
|
|
5585
|
-
const isAudio = AUDIO_EXT.test(filePath);
|
|
5586
|
-
if (!isVideo && !isAudio) {
|
|
5587
|
-
return { ok: true };
|
|
5588
|
-
}
|
|
5589
|
-
const ffprobe = findFfBinary("ffprobe") ?? (runner === defaultRunner ? void 0 : "ffprobe");
|
|
5590
|
-
if (!ffprobe) return { ok: true, unchecked: FFPROBE_MISSING };
|
|
5591
|
-
const result = runner(
|
|
5592
|
-
ffprobe,
|
|
5593
|
-
["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
|
|
5594
|
-
{ windowsHide: true }
|
|
5595
|
-
);
|
|
5596
|
-
if (result.error && process.env[FFPROBE_PATH_ENV]?.trim()) {
|
|
5597
|
-
return {
|
|
5598
|
-
ok: false,
|
|
5599
|
-
reason: `${FFPROBE_PATH_ENV} names "${ffprobe}", which cannot run: fix it or unset it`
|
|
5600
|
-
};
|
|
5601
|
-
}
|
|
5602
|
-
if (result.error?.code === "ENOENT") {
|
|
5603
|
-
return { ok: true, unchecked: FFPROBE_MISSING };
|
|
5604
|
-
}
|
|
5605
|
-
if (result.status !== 0) {
|
|
5606
|
-
return { ok: false, reason: "ffprobe failed to read the media file" };
|
|
5601
|
+
function extractFullDocumentParts(rawHtml, compPath, projectDir) {
|
|
5602
|
+
const { document: doc } = parseHTML(rawHtml);
|
|
5603
|
+
const rewriteTargets = [doc.head, doc.body].filter(Boolean);
|
|
5604
|
+
for (const target of rewriteTargets) {
|
|
5605
|
+
rewriteRelativePaths(target, compPath, projectDir);
|
|
5607
5606
|
}
|
|
5608
|
-
|
|
5607
|
+
fixDigitLeadingIdSelectors(doc);
|
|
5608
|
+
const headContent = doc.head?.innerHTML ?? "";
|
|
5609
|
+
const bodyContent = doc.body?.innerHTML ?? "";
|
|
5610
|
+
const htmlEl = doc.documentElement;
|
|
5611
|
+
const htmlAttrs = extractElementAttrs(htmlEl);
|
|
5612
|
+
const bodyAttrs = doc.body ? extractElementAttrs(doc.body) : "";
|
|
5613
|
+
return { headContent, bodyContent, htmlAttrs, bodyAttrs };
|
|
5609
5614
|
}
|
|
5610
|
-
function
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5615
|
+
function extractTemplateInnerHtml(rawComp) {
|
|
5616
|
+
const { document: doc } = parseHTML(rawComp);
|
|
5617
|
+
const template = doc.querySelector("template");
|
|
5618
|
+
return template ? template.innerHTML : null;
|
|
5619
|
+
}
|
|
5620
|
+
function escapeAttrValue(value) {
|
|
5621
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
5622
|
+
}
|
|
5623
|
+
function extractElementAttrs(el) {
|
|
5624
|
+
const parts = [];
|
|
5625
|
+
for (let i = 0; i < el.attributes.length; i++) {
|
|
5626
|
+
const attr = el.attributes[i];
|
|
5627
|
+
if (attr.value === "") {
|
|
5628
|
+
parts.push(attr.name);
|
|
5629
|
+
} else {
|
|
5630
|
+
parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
|
|
5621
5631
|
}
|
|
5622
|
-
return { ok: true };
|
|
5623
|
-
} catch {
|
|
5624
|
-
return { ok: false, reason: "ffprobe returned unreadable media metadata" };
|
|
5625
5632
|
}
|
|
5633
|
+
return parts.join(" ");
|
|
5626
5634
|
}
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
+
var NON_RENDERED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"]);
|
|
5636
|
+
function promoteTemplateCompositionId(rawComp, body) {
|
|
5637
|
+
let templateCompositionId;
|
|
5638
|
+
for (const tag of rawComp.matchAll(/<template\b[^>]*/gi)) {
|
|
5639
|
+
const id = /\bdata-composition-id\s*=\s*["']([^"']+)["']/i.exec(tag[0] ?? "")?.[1];
|
|
5640
|
+
if (id) {
|
|
5641
|
+
templateCompositionId = id;
|
|
5642
|
+
break;
|
|
5643
|
+
}
|
|
5635
5644
|
}
|
|
5645
|
+
if (!templateCompositionId) return;
|
|
5646
|
+
if (body.querySelector("[data-composition-id]")) return;
|
|
5647
|
+
const root = Array.from(body.children).find((el) => !NON_RENDERED_TAGS.has(el.tagName));
|
|
5648
|
+
root?.setAttribute("data-composition-id", templateCompositionId);
|
|
5636
5649
|
}
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5650
|
+
function tagRootCompositionFile(bodyHtml, compPath) {
|
|
5651
|
+
const match = bodyHtml.match(/<[a-zA-Z][^>]*\bdata-composition-id=/);
|
|
5652
|
+
if (match?.index == null) return bodyHtml;
|
|
5653
|
+
const tagEnd = bodyHtml.indexOf(">", match.index);
|
|
5654
|
+
if (tagEnd === -1) return bodyHtml;
|
|
5655
|
+
if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
|
|
5656
|
+
return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
|
|
5643
5657
|
}
|
|
5644
|
-
function
|
|
5645
|
-
|
|
5646
|
-
const rel = relative3(projectDir, backupPath);
|
|
5647
|
-
if (!rel || rel.startsWith("..")) return null;
|
|
5648
|
-
return rel.split("\\").join("/");
|
|
5658
|
+
function rootHeadContent(rootHtml) {
|
|
5659
|
+
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
5649
5660
|
}
|
|
5650
|
-
function
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
}
|
|
5681
|
-
throw error;
|
|
5682
|
-
}
|
|
5683
|
-
candidate = join42(backupDir, `${base}-${counter}`);
|
|
5684
|
-
counter += 1;
|
|
5685
|
-
}
|
|
5686
|
-
}
|
|
5687
|
-
function pruneBackups(backupDir, backupKey, keepPerFile) {
|
|
5688
|
-
const keep = Math.max(1, Math.floor(keepPerFile));
|
|
5689
|
-
const suffix = `-${backupKey}`;
|
|
5690
|
-
const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
|
|
5691
|
-
const matches = readdirSync4(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join42(backupDir, name)).sort((a, b) => {
|
|
5692
|
-
return b.localeCompare(a);
|
|
5693
|
-
});
|
|
5694
|
-
for (const file of matches.slice(keep)) {
|
|
5695
|
-
try {
|
|
5696
|
-
unlinkSync(file);
|
|
5697
|
-
} catch {
|
|
5698
|
-
}
|
|
5699
|
-
}
|
|
5700
|
-
}
|
|
5701
|
-
var PROJECT_DIR_MISSING = { error: "not found", why: "project_dir_missing" };
|
|
5702
|
-
var projectDirMissing = (c) => c.json(PROJECT_DIR_MISSING, 404);
|
|
5703
|
-
function replaceWithProjectDirMissing(c, hostHeaders) {
|
|
5704
|
-
hostHeaders.delete("content-type");
|
|
5705
|
-
c.res = void 0;
|
|
5706
|
-
c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
|
|
5707
|
-
}
|
|
5708
|
-
function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (path, content) => replaceFileAtomically(path, content, statSync22(path).mode)) {
|
|
5709
|
-
const prepared = mutations.map((mutation) => ({
|
|
5710
|
-
...mutation,
|
|
5711
|
-
before: mutation.before ?? readFileSync23(mutation.absPath, "utf-8")
|
|
5712
|
-
}));
|
|
5713
|
-
const results = [];
|
|
5714
|
-
const attempted = [];
|
|
5715
|
-
for (const mutation of prepared)
|
|
5716
|
-
assertExpectedVersion(mutation, readFileSync23(mutation.absPath, "utf-8"));
|
|
5717
|
-
try {
|
|
5718
|
-
for (const mutation of prepared) {
|
|
5719
|
-
results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted));
|
|
5720
|
-
}
|
|
5721
|
-
return results;
|
|
5722
|
-
} catch (error) {
|
|
5723
|
-
const rollbackErrors = [];
|
|
5724
|
-
for (const mutation of attempted.reverse()) {
|
|
5725
|
-
try {
|
|
5726
|
-
if (!mutation.written || readFileSync23(mutation.absPath, "utf-8") === mutation.after)
|
|
5727
|
-
writeFile2(mutation.absPath, mutation.before, "utf-8");
|
|
5728
|
-
clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
|
|
5729
|
-
} catch (rollbackError) {
|
|
5730
|
-
rollbackErrors.push(rollbackError);
|
|
5731
|
-
}
|
|
5732
|
-
}
|
|
5733
|
-
if (rollbackErrors.length > 0) {
|
|
5734
|
-
throw new AggregateError(
|
|
5735
|
-
[error, ...rollbackErrors],
|
|
5736
|
-
"File mutation failed and rollback did not complete"
|
|
5737
|
-
);
|
|
5738
|
-
}
|
|
5739
|
-
throw error;
|
|
5661
|
+
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
5662
|
+
const compFile = join23(projectDir, compPath);
|
|
5663
|
+
if (!existsSync3(compFile)) return null;
|
|
5664
|
+
const rawComp = rawOverride ?? readFileSync7(compFile, "utf-8");
|
|
5665
|
+
let compHeadContent = "";
|
|
5666
|
+
let rewrittenContent;
|
|
5667
|
+
let htmlAttrs = "";
|
|
5668
|
+
let bodyAttrs = "";
|
|
5669
|
+
const templateInner = extractTemplateInnerHtml(rawComp);
|
|
5670
|
+
if (templateInner != null) {
|
|
5671
|
+
const { document: contentDoc } = parseHTML(
|
|
5672
|
+
`<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
|
|
5673
|
+
);
|
|
5674
|
+
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
5675
|
+
fixDigitLeadingIdSelectors(contentDoc);
|
|
5676
|
+
promoteTemplateCompositionId(rawComp, contentDoc.body);
|
|
5677
|
+
rewrittenContent = contentDoc.body.innerHTML || templateInner;
|
|
5678
|
+
} else if (isFullHtmlDocument(rawComp)) {
|
|
5679
|
+
const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
|
|
5680
|
+
compHeadContent = parts.headContent;
|
|
5681
|
+
rewrittenContent = parts.bodyContent;
|
|
5682
|
+
htmlAttrs = parts.htmlAttrs;
|
|
5683
|
+
bodyAttrs = parts.bodyAttrs;
|
|
5684
|
+
} else {
|
|
5685
|
+
const { document: contentDoc } = parseHTML(
|
|
5686
|
+
`<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
|
|
5687
|
+
);
|
|
5688
|
+
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
5689
|
+
fixDigitLeadingIdSelectors(contentDoc);
|
|
5690
|
+
rewrittenContent = contentDoc.body.innerHTML || rawComp;
|
|
5740
5691
|
}
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5692
|
+
rewrittenContent = stripEmbeddedRuntimeScripts(rewrittenContent);
|
|
5693
|
+
const { document: scriptsDoc } = parseHTML(
|
|
5694
|
+
`<!DOCTYPE html><html><head></head><body>${rewrittenContent}</body></html>`
|
|
5695
|
+
);
|
|
5696
|
+
deferScriptsUntilFonts(scriptsDoc);
|
|
5697
|
+
rewrittenContent = scriptsDoc.body.innerHTML;
|
|
5698
|
+
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
5699
|
+
const indexPath = join23(projectDir, "index.html");
|
|
5700
|
+
let headContent = "";
|
|
5701
|
+
if (existsSync3(indexPath)) headContent = rootHeadContent(readFileSync7(indexPath, "utf-8"));
|
|
5702
|
+
if (baseHref && !hasBaseElement(headContent)) {
|
|
5703
|
+
headContent = `<base href="${baseHref}">
|
|
5704
|
+
${headContent}`;
|
|
5754
5705
|
}
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
writeFile2(mutation.absPath, mutation.after, "utf-8");
|
|
5764
|
-
attempt.written = true;
|
|
5765
|
-
recordFileWriteReceipt(mutation.absPath, {
|
|
5766
|
-
path: mutation.sourceFile,
|
|
5767
|
-
version,
|
|
5768
|
-
writeToken,
|
|
5769
|
-
overwrote: before
|
|
5770
|
-
});
|
|
5771
|
-
return {
|
|
5772
|
-
...mutation,
|
|
5773
|
-
before,
|
|
5774
|
-
changed: true,
|
|
5775
|
-
backupPath: backupPathForResponse(projectDir, backup.backupPath),
|
|
5776
|
-
version,
|
|
5777
|
-
writeToken
|
|
5778
|
-
};
|
|
5779
|
-
}
|
|
5780
|
-
var FileChangedError = class extends Error {
|
|
5781
|
-
constructor(sourceFile) {
|
|
5782
|
-
super("file changed since the timeline was read");
|
|
5783
|
-
this.sourceFile = sourceFile;
|
|
5706
|
+
if (compHeadContent) headContent += `
|
|
5707
|
+
${compHeadContent}`;
|
|
5708
|
+
headContent = stripEmbeddedRuntimeScripts(headContent);
|
|
5709
|
+
headContent += `
|
|
5710
|
+
<script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
|
|
5711
|
+
if (!headContent.includes("gsap")) {
|
|
5712
|
+
headContent += `
|
|
5713
|
+
<script src="${gsapCdnDist()}gsap.min.js"></script>`;
|
|
5784
5714
|
}
|
|
5785
|
-
|
|
5786
|
-
};
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
}
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5715
|
+
const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
|
|
5716
|
+
const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
|
|
5717
|
+
return `<!DOCTYPE html>
|
|
5718
|
+
${htmlOpen}
|
|
5719
|
+
<head>
|
|
5720
|
+
${headContent}
|
|
5721
|
+
</head>
|
|
5722
|
+
${bodyOpen}
|
|
5723
|
+
<script>window.__timelines=window.__timelines||{};</script>
|
|
5724
|
+
${rewrittenContent}
|
|
5725
|
+
</body>
|
|
5726
|
+
</html>`;
|
|
5795
5727
|
}
|
|
5796
|
-
function
|
|
5797
|
-
|
|
5798
|
-
const block = FONT_STYLE_RE.exec(html);
|
|
5799
|
-
if (block) {
|
|
5800
|
-
const next = `${(block[2] ?? "").trim()}
|
|
5801
|
-
${css}`.trim();
|
|
5802
|
-
return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
|
|
5803
|
-
${next}
|
|
5804
|
-
</style>`);
|
|
5805
|
-
}
|
|
5806
|
-
const styleTag = `<style data-hf-studio-fonts="true">
|
|
5807
|
-
${css}
|
|
5808
|
-
</style>`;
|
|
5809
|
-
if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
|
|
5810
|
-
</head>`);
|
|
5811
|
-
return `${styleTag}
|
|
5812
|
-
${html}`;
|
|
5728
|
+
function hasBaseElement(html) {
|
|
5729
|
+
return parseHTML(html).document.querySelector("base") !== null;
|
|
5813
5730
|
}
|
|
5814
5731
|
var CompositionInsertionError = class extends Error {
|
|
5815
5732
|
constructor(message, status) {
|
|
@@ -5875,7 +5792,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
|
|
|
5875
5792
|
validateSourcePath(sourcePath);
|
|
5876
5793
|
return canonicalProjectPath(
|
|
5877
5794
|
projectDir,
|
|
5878
|
-
resolveWithinProject(projectDir,
|
|
5795
|
+
resolveWithinProject(projectDir, relative3(projectDir, resolve5(dirname5(ownerAbs), sourcePath)))
|
|
5879
5796
|
);
|
|
5880
5797
|
}
|
|
5881
5798
|
function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
|
|
@@ -5948,7 +5865,7 @@ function uniqueHostId(root, base) {
|
|
|
5948
5865
|
return `${base}_${suffix}`;
|
|
5949
5866
|
}
|
|
5950
5867
|
function relativeSourcePath(targetAbs, sourceAbs) {
|
|
5951
|
-
return
|
|
5868
|
+
return relative3(dirname5(targetAbs), sourceAbs).split(sep3).join("/");
|
|
5952
5869
|
}
|
|
5953
5870
|
function insertCompositionIntoSource(input) {
|
|
5954
5871
|
const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
|
|
@@ -6001,7 +5918,443 @@ function insertCompositionIntoSource(input) {
|
|
|
6001
5918
|
const name = root.hasAttribute("data-duration") ? "data-duration" : "data-composition-duration";
|
|
6002
5919
|
root.setAttribute(name, String(Math.round((input.start + duration) * 100) / 100));
|
|
6003
5920
|
}
|
|
6004
|
-
return { html: document2.toString(), hostId, track, duration };
|
|
5921
|
+
return { html: document2.toString(), hostId, track, duration };
|
|
5922
|
+
}
|
|
5923
|
+
var ROOT_COMPOSITION = "index.html";
|
|
5924
|
+
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
5925
|
+
function projectReader(projectDir) {
|
|
5926
|
+
const sources = /* @__PURE__ */ new Map();
|
|
5927
|
+
return (compPath) => {
|
|
5928
|
+
if (!sources.has(compPath)) {
|
|
5929
|
+
const file = resolveWithinProject(projectDir, compPath);
|
|
5930
|
+
let source = null;
|
|
5931
|
+
try {
|
|
5932
|
+
source = file ? readFileSync23(file, "utf-8") : null;
|
|
5933
|
+
} catch {
|
|
5934
|
+
}
|
|
5935
|
+
sources.set(compPath, source);
|
|
5936
|
+
}
|
|
5937
|
+
return sources.get(compPath) ?? null;
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5940
|
+
function normalizeSource(src) {
|
|
5941
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
|
|
5942
|
+
const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
|
|
5943
|
+
return path || null;
|
|
5944
|
+
}
|
|
5945
|
+
function closureOf(read, compPath) {
|
|
5946
|
+
const closure = /* @__PURE__ */ new Set();
|
|
5947
|
+
const visit = (path) => {
|
|
5948
|
+
if (closure.has(path)) return;
|
|
5949
|
+
closure.add(path);
|
|
5950
|
+
for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
|
|
5951
|
+
const mounted = normalizeSource(match[2] ?? "");
|
|
5952
|
+
if (mounted) visit(mounted);
|
|
5953
|
+
}
|
|
5954
|
+
};
|
|
5955
|
+
visit(compPath);
|
|
5956
|
+
return closure;
|
|
5957
|
+
}
|
|
5958
|
+
function lengthIsTimeline(projectDir, compPath) {
|
|
5959
|
+
const read = projectReader(projectDir);
|
|
5960
|
+
const documents = /* @__PURE__ */ new Map();
|
|
5961
|
+
const elements = (path, selector) => {
|
|
5962
|
+
let document2 = documents.get(path);
|
|
5963
|
+
if (!document2) documents.set(path, document2 = parseHTML(read(path) ?? "").document);
|
|
5964
|
+
return descendants(document2, selector);
|
|
5965
|
+
};
|
|
5966
|
+
const comp = normalizeSource(compPath) ?? compPath;
|
|
5967
|
+
const unsized = (path) => {
|
|
5968
|
+
const [root] = elements(path, "[data-composition-id]");
|
|
5969
|
+
return Boolean(root && !root.hasAttribute("data-duration"));
|
|
5970
|
+
};
|
|
5971
|
+
if (unsized(ROOT_COMPOSITION)) return true;
|
|
5972
|
+
const hosts = [...closureOf(read, ROOT_COMPOSITION)].flatMap(
|
|
5973
|
+
(path) => elements(path, "[data-composition-src]").filter(
|
|
5974
|
+
(host) => normalizeSource(host.getAttribute("data-composition-src") ?? "") === comp
|
|
5975
|
+
)
|
|
5976
|
+
);
|
|
5977
|
+
if (comp === ROOT_COMPOSITION || hosts.length === 0) return unsized(comp);
|
|
5978
|
+
return hosts.some(
|
|
5979
|
+
(host) => !host.hasAttribute("data-duration") && !host.hasAttribute("data-end")
|
|
5980
|
+
);
|
|
5981
|
+
}
|
|
5982
|
+
var inputSignatures = /* @__PURE__ */ new Map();
|
|
5983
|
+
var rootMemos = /* @__PURE__ */ new Map();
|
|
5984
|
+
var sha1 = (text2) => createHash4("sha1").update(text2).digest("hex");
|
|
5985
|
+
function rootHashes(read) {
|
|
5986
|
+
const source = read(ROOT_COMPOSITION) ?? "";
|
|
5987
|
+
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
5988
|
+
}
|
|
5989
|
+
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
5990
|
+
const key = `${projectDir}\0${compPath}`;
|
|
5991
|
+
const known = inputSignatures.get(key);
|
|
5992
|
+
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
5993
|
+
const read = projectReader(projectDir);
|
|
5994
|
+
const comp = normalizeSource(compPath) ?? compPath;
|
|
5995
|
+
const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
|
|
5996
|
+
const excluded = new Set(
|
|
5997
|
+
[...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
|
|
5998
|
+
);
|
|
5999
|
+
let inputSignature;
|
|
6000
|
+
if (comp === ROOT_COMPOSITION) {
|
|
6001
|
+
inputSignature = createProjectSignature(projectDir, excluded);
|
|
6002
|
+
} else {
|
|
6003
|
+
const hashes = rootHashes(read);
|
|
6004
|
+
if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
|
|
6005
|
+
inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
|
|
6006
|
+
}
|
|
6007
|
+
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
6008
|
+
return inputSignature;
|
|
6009
|
+
}
|
|
6010
|
+
function compositionsAffectedBy(projectDir, changedPath) {
|
|
6011
|
+
const changed = changedPath.replace(/\\/g, "/");
|
|
6012
|
+
const read = projectReader(projectDir);
|
|
6013
|
+
if (changed === ROOT_COMPOSITION) {
|
|
6014
|
+
const now = rootHashes(read);
|
|
6015
|
+
const memo = rootMemos.get(projectDir);
|
|
6016
|
+
if (memo?.root === now.root) return memo.affected;
|
|
6017
|
+
const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
|
|
6018
|
+
rootMemos.set(projectDir, { ...now, affected });
|
|
6019
|
+
return affected;
|
|
6020
|
+
}
|
|
6021
|
+
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
6022
|
+
if (!mounted.has(changed)) return null;
|
|
6023
|
+
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
6024
|
+
}
|
|
6025
|
+
var SAMPLE_RATE = 4e3;
|
|
6026
|
+
var PEAK_COUNT = 4e3;
|
|
6027
|
+
var WAVEFORM_CACHE_VERSION = "v2";
|
|
6028
|
+
function buildWaveformCacheKey(assetPath, fingerprint) {
|
|
6029
|
+
const name = assetPath.replace(/[/\\]/g, "_");
|
|
6030
|
+
if (!fingerprint) return `${WAVEFORM_CACHE_VERSION}_${name}.json`;
|
|
6031
|
+
const stamp = `${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}`;
|
|
6032
|
+
return `${WAVEFORM_CACHE_VERSION}_${name}_${stamp}.json`;
|
|
6033
|
+
}
|
|
6034
|
+
function computePeaks(floats, count) {
|
|
6035
|
+
const step = floats.length / count;
|
|
6036
|
+
const peaks = [];
|
|
6037
|
+
for (let i = 0; i < count; i++) {
|
|
6038
|
+
const start = Math.floor(i * step);
|
|
6039
|
+
const end = Math.min(Math.floor((i + 1) * step), floats.length);
|
|
6040
|
+
let max = 0;
|
|
6041
|
+
for (let j = start; j < end; j++) {
|
|
6042
|
+
const abs = Math.abs(floats[j] ?? 0);
|
|
6043
|
+
if (abs > max) max = abs;
|
|
6044
|
+
}
|
|
6045
|
+
peaks.push(max);
|
|
6046
|
+
}
|
|
6047
|
+
const maxPeak = Math.max(...peaks, 1e-3);
|
|
6048
|
+
return peaks.map((p) => p / maxPeak);
|
|
6049
|
+
}
|
|
6050
|
+
function decodeAudioPeaks(audioPath) {
|
|
6051
|
+
return new Promise((resolvePromise, reject) => {
|
|
6052
|
+
const proc = spawn(
|
|
6053
|
+
findFfBinary("ffmpeg") ?? "ffmpeg",
|
|
6054
|
+
[
|
|
6055
|
+
"-i",
|
|
6056
|
+
audioPath,
|
|
6057
|
+
"-af",
|
|
6058
|
+
"atrim=start_sample=1152",
|
|
6059
|
+
"-f",
|
|
6060
|
+
"f32le",
|
|
6061
|
+
"-ac",
|
|
6062
|
+
"1",
|
|
6063
|
+
"-ar",
|
|
6064
|
+
String(SAMPLE_RATE),
|
|
6065
|
+
"-vn",
|
|
6066
|
+
"pipe:1"
|
|
6067
|
+
],
|
|
6068
|
+
{ stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
|
|
6069
|
+
);
|
|
6070
|
+
const chunks = [];
|
|
6071
|
+
proc.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
6072
|
+
proc.on("close", (code) => {
|
|
6073
|
+
if (code !== 0 && chunks.length === 0) {
|
|
6074
|
+
reject(new Error(`ffmpeg exited with code ${code}`));
|
|
6075
|
+
return;
|
|
6076
|
+
}
|
|
6077
|
+
const buf = Buffer.concat(chunks);
|
|
6078
|
+
const numSamples = Math.floor(buf.length / 4);
|
|
6079
|
+
if (numSamples === 0) {
|
|
6080
|
+
reject(new Error("ffmpeg produced no audio samples"));
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4);
|
|
6084
|
+
resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT));
|
|
6085
|
+
});
|
|
6086
|
+
proc.on("error", reject);
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
async function generateWaveformCache(projectDir, assetPath) {
|
|
6090
|
+
const audioPath = join32(projectDir, assetPath);
|
|
6091
|
+
if (!existsSync32(audioPath)) return;
|
|
6092
|
+
const stats = statSync4(audioPath);
|
|
6093
|
+
const cacheDir = join32(projectDir, ".waveform-cache");
|
|
6094
|
+
const cachePath = join32(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
6095
|
+
if (isWaveformCacheDirectory(cacheDir) && existsSync32(cachePath)) return;
|
|
6096
|
+
const peaks = await decodeAudioPeaks(audioPath);
|
|
6097
|
+
writeWaveformCache(cachePath, peaks);
|
|
6098
|
+
}
|
|
6099
|
+
function isWaveformCacheDirectory(cacheDir) {
|
|
6100
|
+
return lstatSync3(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
6101
|
+
}
|
|
6102
|
+
function writeWaveformCache(cachePath, peaks) {
|
|
6103
|
+
const cacheDir = dirname23(cachePath);
|
|
6104
|
+
try {
|
|
6105
|
+
mkdirSync5(cacheDir, { mode: 448 });
|
|
6106
|
+
} catch (error) {
|
|
6107
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
6108
|
+
}
|
|
6109
|
+
if (!isWaveformCacheDirectory(cacheDir)) {
|
|
6110
|
+
throw new Error("Waveform cache must be a directory, not a symlink");
|
|
6111
|
+
}
|
|
6112
|
+
const stagingDir = mkdtempSync(join32(cacheDir, ".waveform-"));
|
|
6113
|
+
try {
|
|
6114
|
+
const stagingPath = join32(stagingDir, "peaks.json");
|
|
6115
|
+
writeFileSync4(stagingPath, JSON.stringify(peaks), { flag: "wx" });
|
|
6116
|
+
renameSync3(stagingPath, cachePath);
|
|
6117
|
+
} finally {
|
|
6118
|
+
try {
|
|
6119
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
6120
|
+
} catch {
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
|
|
6125
|
+
var AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac|flac)$/i;
|
|
6126
|
+
var defaultRunner = spawnSync;
|
|
6127
|
+
var FFPROBE_MISSING = "not checked: ffprobe was not found. Install FFmpeg or set HYPERFRAMES_FFPROBE_PATH.";
|
|
6128
|
+
function validateUploadedMedia(filePath, runner = defaultRunner) {
|
|
6129
|
+
const isVideo = VIDEO_EXT.test(filePath);
|
|
6130
|
+
const isAudio = AUDIO_EXT.test(filePath);
|
|
6131
|
+
if (!isVideo && !isAudio) {
|
|
6132
|
+
return { ok: true };
|
|
6133
|
+
}
|
|
6134
|
+
const ffprobe = findFfBinary("ffprobe") ?? (runner === defaultRunner ? void 0 : "ffprobe");
|
|
6135
|
+
if (!ffprobe) return { ok: true, unchecked: FFPROBE_MISSING };
|
|
6136
|
+
const result = runner(
|
|
6137
|
+
ffprobe,
|
|
6138
|
+
["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
|
|
6139
|
+
{ windowsHide: true }
|
|
6140
|
+
);
|
|
6141
|
+
if (result.error && process.env[FFPROBE_PATH_ENV]?.trim()) {
|
|
6142
|
+
return {
|
|
6143
|
+
ok: false,
|
|
6144
|
+
reason: `${FFPROBE_PATH_ENV} names "${ffprobe}", which cannot run: fix it or unset it`
|
|
6145
|
+
};
|
|
6146
|
+
}
|
|
6147
|
+
if (result.error?.code === "ENOENT") {
|
|
6148
|
+
return { ok: true, unchecked: FFPROBE_MISSING };
|
|
6149
|
+
}
|
|
6150
|
+
if (result.status !== 0) {
|
|
6151
|
+
return { ok: false, reason: "ffprobe failed to read the media file" };
|
|
6152
|
+
}
|
|
6153
|
+
return checkStreams(String(result.stdout || "{}"), isVideo, isAudio);
|
|
6154
|
+
}
|
|
6155
|
+
function checkStreams(stdout, isVideo, isAudio) {
|
|
6156
|
+
try {
|
|
6157
|
+
const parsed = JSON.parse(stdout);
|
|
6158
|
+
const streams = parsed.streams ?? [];
|
|
6159
|
+
const hasVideo = streams.some((stream) => stream.codec_type === "video");
|
|
6160
|
+
const hasAudio = streams.some((stream) => stream.codec_type === "audio");
|
|
6161
|
+
if (isVideo && !hasVideo) {
|
|
6162
|
+
return { ok: false, reason: "no supported video stream found" };
|
|
6163
|
+
}
|
|
6164
|
+
if (isAudio && !hasAudio) {
|
|
6165
|
+
return { ok: false, reason: "no supported audio stream found" };
|
|
6166
|
+
}
|
|
6167
|
+
return { ok: true };
|
|
6168
|
+
} catch {
|
|
6169
|
+
return { ok: false, reason: "ffprobe returned unreadable media metadata" };
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
|
|
6173
|
+
const tempDir = mkdtempSync2(join42(tmpdir2(), "hyperframes-upload-"));
|
|
6174
|
+
const tempPath = join42(tempDir, basename2(fileName));
|
|
6175
|
+
try {
|
|
6176
|
+
writeFileSync23(tempPath, buffer);
|
|
6177
|
+
return validateUploadedMedia(tempPath, runner);
|
|
6178
|
+
} finally {
|
|
6179
|
+
rmSync22(tempDir, { recursive: true, force: true });
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
var DEFAULT_KEEP_PER_FILE = 10;
|
|
6183
|
+
function backupKeyForPath(path) {
|
|
6184
|
+
return Buffer2.from(path, "utf-8").toString("base64url");
|
|
6185
|
+
}
|
|
6186
|
+
function timestampPrefix() {
|
|
6187
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6188
|
+
}
|
|
6189
|
+
function backupPathForResponse(projectDir, backupPath) {
|
|
6190
|
+
if (!backupPath) return null;
|
|
6191
|
+
const rel = relative22(projectDir, backupPath);
|
|
6192
|
+
if (!rel || rel.startsWith("..")) return null;
|
|
6193
|
+
return rel.split("\\").join("/");
|
|
6194
|
+
}
|
|
6195
|
+
function snapshotBeforeWrite(projectDir, absPath, options = {}) {
|
|
6196
|
+
if (!isSafePath(projectDir, absPath)) return { backupPath: null };
|
|
6197
|
+
try {
|
|
6198
|
+
const content = readFileSync32(absPath);
|
|
6199
|
+
const relativePath = relative22(projectDir, absPath);
|
|
6200
|
+
const backupDir = join52(projectDir, ".hyperframes", "backup");
|
|
6201
|
+
mkdirWithinProject(projectDir, backupDir);
|
|
6202
|
+
const backupKey = backupKeyForPath(relativePath);
|
|
6203
|
+
const backupPath = nextBackupPath(backupDir, backupKey);
|
|
6204
|
+
writeFileSync32(backupPath, content);
|
|
6205
|
+
pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
|
|
6206
|
+
return { backupPath };
|
|
6207
|
+
} catch (error) {
|
|
6208
|
+
if (isProjectRootMissing(error)) throw error;
|
|
6209
|
+
if (error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) {
|
|
6210
|
+
return { backupPath: null };
|
|
6211
|
+
}
|
|
6212
|
+
return { backupPath: null, error: error instanceof Error ? error.message : String(error) };
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
function nextBackupPath(backupDir, backupKey) {
|
|
6216
|
+
const base = `${timestampPrefix()}-${backupKey}`;
|
|
6217
|
+
let candidate = join52(backupDir, base);
|
|
6218
|
+
let counter = 2;
|
|
6219
|
+
while (true) {
|
|
6220
|
+
try {
|
|
6221
|
+
readFileSync32(candidate);
|
|
6222
|
+
} catch (error) {
|
|
6223
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
6224
|
+
return candidate;
|
|
6225
|
+
}
|
|
6226
|
+
throw error;
|
|
6227
|
+
}
|
|
6228
|
+
candidate = join52(backupDir, `${base}-${counter}`);
|
|
6229
|
+
counter += 1;
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
function pruneBackups(backupDir, backupKey, keepPerFile) {
|
|
6233
|
+
const keep = Math.max(1, Math.floor(keepPerFile));
|
|
6234
|
+
const suffix = `-${backupKey}`;
|
|
6235
|
+
const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
|
|
6236
|
+
const matches = readdirSync4(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join52(backupDir, name)).sort((a, b) => {
|
|
6237
|
+
return b.localeCompare(a);
|
|
6238
|
+
});
|
|
6239
|
+
for (const file of matches.slice(keep)) {
|
|
6240
|
+
try {
|
|
6241
|
+
unlinkSync(file);
|
|
6242
|
+
} catch {
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
var PROJECT_DIR_MISSING = { error: "not found", why: "project_dir_missing" };
|
|
6247
|
+
var projectDirMissing = (c) => c.json(PROJECT_DIR_MISSING, 404);
|
|
6248
|
+
function replaceWithProjectDirMissing(c, hostHeaders) {
|
|
6249
|
+
hostHeaders.delete("content-type");
|
|
6250
|
+
c.res = void 0;
|
|
6251
|
+
c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
|
|
6252
|
+
}
|
|
6253
|
+
function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (path, content) => replaceFileAtomically(path, content, statSync22(path).mode)) {
|
|
6254
|
+
const prepared = mutations.map((mutation) => ({
|
|
6255
|
+
...mutation,
|
|
6256
|
+
before: mutation.before ?? readFileSync42(mutation.absPath, "utf-8")
|
|
6257
|
+
}));
|
|
6258
|
+
const results = [];
|
|
6259
|
+
const attempted = [];
|
|
6260
|
+
for (const mutation of prepared)
|
|
6261
|
+
assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
|
|
6262
|
+
try {
|
|
6263
|
+
for (const mutation of prepared) {
|
|
6264
|
+
results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted));
|
|
6265
|
+
}
|
|
6266
|
+
return results;
|
|
6267
|
+
} catch (error) {
|
|
6268
|
+
const rollbackErrors = [];
|
|
6269
|
+
for (const mutation of attempted.reverse()) {
|
|
6270
|
+
try {
|
|
6271
|
+
if (!mutation.written || readFileSync42(mutation.absPath, "utf-8") === mutation.after)
|
|
6272
|
+
writeFile2(mutation.absPath, mutation.before, "utf-8");
|
|
6273
|
+
clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
|
|
6274
|
+
} catch (rollbackError) {
|
|
6275
|
+
rollbackErrors.push(rollbackError);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
if (rollbackErrors.length > 0) {
|
|
6279
|
+
throw new AggregateError(
|
|
6280
|
+
[error, ...rollbackErrors],
|
|
6281
|
+
"File mutation failed and rollback did not complete"
|
|
6282
|
+
);
|
|
6283
|
+
}
|
|
6284
|
+
throw error;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted) {
|
|
6288
|
+
const current = readFileSync42(mutation.absPath, "utf-8");
|
|
6289
|
+
assertExpectedVersion(mutation, current);
|
|
6290
|
+
if (mutation.after === mutation.before) {
|
|
6291
|
+
return {
|
|
6292
|
+
...mutation,
|
|
6293
|
+
before: current,
|
|
6294
|
+
changed: false,
|
|
6295
|
+
backupPath: null,
|
|
6296
|
+
version: fileContentVersion(mutation.before),
|
|
6297
|
+
writeToken: null
|
|
6298
|
+
};
|
|
6299
|
+
}
|
|
6300
|
+
const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
|
|
6301
|
+
if (backup.error) throw new Error(`backup failed: ${backup.error}`);
|
|
6302
|
+
assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
|
|
6303
|
+
const before = current;
|
|
6304
|
+
const version = fileContentVersion(mutation.after);
|
|
6305
|
+
const writeToken = createWriteToken(requestToken);
|
|
6306
|
+
const attempt = { ...mutation, before, version, writeToken, written: false };
|
|
6307
|
+
attempted.push(attempt);
|
|
6308
|
+
writeFile2(mutation.absPath, mutation.after, "utf-8");
|
|
6309
|
+
attempt.written = true;
|
|
6310
|
+
recordFileWriteReceipt(mutation.absPath, {
|
|
6311
|
+
path: mutation.sourceFile,
|
|
6312
|
+
version,
|
|
6313
|
+
writeToken,
|
|
6314
|
+
overwrote: before
|
|
6315
|
+
});
|
|
6316
|
+
return {
|
|
6317
|
+
...mutation,
|
|
6318
|
+
before,
|
|
6319
|
+
changed: true,
|
|
6320
|
+
backupPath: backupPathForResponse(projectDir, backup.backupPath),
|
|
6321
|
+
version,
|
|
6322
|
+
writeToken
|
|
6323
|
+
};
|
|
6324
|
+
}
|
|
6325
|
+
var FileChangedError = class extends Error {
|
|
6326
|
+
constructor(sourceFile) {
|
|
6327
|
+
super("file changed since the timeline was read");
|
|
6328
|
+
this.sourceFile = sourceFile;
|
|
6329
|
+
}
|
|
6330
|
+
sourceFile;
|
|
6331
|
+
};
|
|
6332
|
+
function assertExpectedVersion(mutation, current) {
|
|
6333
|
+
const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
|
|
6334
|
+
if (stale) throw new FileChangedError(mutation.sourceFile);
|
|
6335
|
+
}
|
|
6336
|
+
var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
|
|
6337
|
+
var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
|
|
6338
|
+
function isStudioFontFaceCss(css) {
|
|
6339
|
+
return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
|
|
6340
|
+
}
|
|
6341
|
+
function ensureStudioFontFaceCss(html, css) {
|
|
6342
|
+
if (html.includes(css)) return html;
|
|
6343
|
+
const block = FONT_STYLE_RE.exec(html);
|
|
6344
|
+
if (block) {
|
|
6345
|
+
const next = `${(block[2] ?? "").trim()}
|
|
6346
|
+
${css}`.trim();
|
|
6347
|
+
return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
|
|
6348
|
+
${next}
|
|
6349
|
+
</style>`);
|
|
6350
|
+
}
|
|
6351
|
+
const styleTag = `<style data-hf-studio-fonts="true">
|
|
6352
|
+
${css}
|
|
6353
|
+
</style>`;
|
|
6354
|
+
if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
|
|
6355
|
+
</head>`);
|
|
6356
|
+
return `${styleTag}
|
|
6357
|
+
${html}`;
|
|
6005
6358
|
}
|
|
6006
6359
|
var differential = (family) => ({
|
|
6007
6360
|
family,
|
|
@@ -6070,7 +6423,7 @@ function requestSubPath(url, route) {
|
|
|
6070
6423
|
return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
|
|
6071
6424
|
}
|
|
6072
6425
|
async function loadGsapParser() {
|
|
6073
|
-
return import("./gsapParser-
|
|
6426
|
+
return import("./gsapParser-GHMVJQLH.js");
|
|
6074
6427
|
}
|
|
6075
6428
|
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
6076
6429
|
if (!isSafePath(projectDir, dirname32(lexicalPath))) return false;
|
|
@@ -6110,7 +6463,7 @@ async function resolveProjectPath(c, adapter, route, opts) {
|
|
|
6110
6463
|
}
|
|
6111
6464
|
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
6112
6465
|
}
|
|
6113
|
-
if (opts?.mustExist && !
|
|
6466
|
+
if (opts?.mustExist && !existsSync4(absPath)) {
|
|
6114
6467
|
return { error: c.json({ error: "not found" }, 404) };
|
|
6115
6468
|
}
|
|
6116
6469
|
return { project, filePath, absPath };
|
|
@@ -6176,7 +6529,7 @@ function projectHtmlIds(projectDir) {
|
|
|
6176
6529
|
if (!rel.endsWith(".html") || isInHiddenOrVendorDir(rel)) continue;
|
|
6177
6530
|
let html;
|
|
6178
6531
|
try {
|
|
6179
|
-
html =
|
|
6532
|
+
html = readFileSync52(join62(projectDir, rel), "utf-8");
|
|
6180
6533
|
} catch {
|
|
6181
6534
|
continue;
|
|
6182
6535
|
}
|
|
@@ -6205,7 +6558,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
|
|
|
6205
6558
|
resolvedPaths.add(absPath);
|
|
6206
6559
|
let before;
|
|
6207
6560
|
try {
|
|
6208
|
-
before =
|
|
6561
|
+
before = readFileSync52(absPath, "utf-8");
|
|
6209
6562
|
} catch {
|
|
6210
6563
|
return { error: "not-found", sourceFile: batch.sourceFile };
|
|
6211
6564
|
}
|
|
@@ -6261,7 +6614,7 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
|
|
|
6261
6614
|
);
|
|
6262
6615
|
}
|
|
6263
6616
|
function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
6264
|
-
const overwrote =
|
|
6617
|
+
const overwrote = readFileSync52(absPath);
|
|
6265
6618
|
replaceFileAtomically(absPath, html, statSync32(absPath).mode);
|
|
6266
6619
|
const version = fileContentVersion(html);
|
|
6267
6620
|
const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
|
|
@@ -6271,7 +6624,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
|
6271
6624
|
function writeMutationResult(c, projectDir, filePath, absPath, html, original) {
|
|
6272
6625
|
const backup = snapshotBeforeWrite(projectDir, absPath);
|
|
6273
6626
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
6274
|
-
if (
|
|
6627
|
+
if (readFileSync52(absPath, "utf-8") !== original) {
|
|
6275
6628
|
return c.json({ error: "file changed", conflict: true, path: filePath }, 409);
|
|
6276
6629
|
}
|
|
6277
6630
|
const { version } = writeFileWithReceipt(c, filePath, absPath, html);
|
|
@@ -6326,7 +6679,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6326
6679
|
const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
|
|
6327
6680
|
let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
|
|
6328
6681
|
let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
|
|
6329
|
-
while (
|
|
6682
|
+
while (existsSync4(resolve22(projectDir, candidate))) {
|
|
6330
6683
|
num++;
|
|
6331
6684
|
candidate = `${cleanBase} (copy ${num})${ext}`;
|
|
6332
6685
|
}
|
|
@@ -6335,7 +6688,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6335
6688
|
function walkFiles(dir, filter) {
|
|
6336
6689
|
const results = [];
|
|
6337
6690
|
for (const entry of readableEntries(dir)) {
|
|
6338
|
-
const full2 =
|
|
6691
|
+
const full2 = join62(dir, entry.name);
|
|
6339
6692
|
if (entry.isDirectory()) {
|
|
6340
6693
|
if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
|
|
6341
6694
|
continue;
|
|
@@ -6360,7 +6713,7 @@ function readableEntries(dir) {
|
|
|
6360
6713
|
}
|
|
6361
6714
|
function readableText(file) {
|
|
6362
6715
|
try {
|
|
6363
|
-
return
|
|
6716
|
+
return readFileSync52(file, "utf-8");
|
|
6364
6717
|
} catch (error) {
|
|
6365
6718
|
if (skippable(error)) return null;
|
|
6366
6719
|
throw error;
|
|
@@ -6418,7 +6771,7 @@ function occurrences(text2, part) {
|
|
|
6418
6771
|
}
|
|
6419
6772
|
function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
|
|
6420
6773
|
return readableEntries(dir).flatMap((entry) => {
|
|
6421
|
-
const [path, full2] = [`${prefix}${entry.name}`,
|
|
6774
|
+
const [path, full2] = [`${prefix}${entry.name}`, join62(dir, entry.name)];
|
|
6422
6775
|
const real = entry.name === "node_modules" ? null : directoryReal(entry, full2);
|
|
6423
6776
|
if (real === null || ancestors.has(real) || !isSafePath(root, full2)) return [path];
|
|
6424
6777
|
return [path, ...projectPaths(root, full2, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
|
|
@@ -6726,7 +7079,7 @@ function containsRawGsapExpression(value) {
|
|
|
6726
7079
|
return Object.values(value).some(containsRawGsapExpression);
|
|
6727
7080
|
}
|
|
6728
7081
|
async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
6729
|
-
const beforeHtml =
|
|
7082
|
+
const beforeHtml = readFileSync52(res.absPath, "utf-8");
|
|
6730
7083
|
let html = beforeHtml;
|
|
6731
7084
|
let block = extractGsapScriptBlock(html);
|
|
6732
7085
|
if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
|
|
@@ -6801,6 +7154,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
6801
7154
|
const prepared = await prepareGsapMutationScript(c, res, firstMutation);
|
|
6802
7155
|
if (prepared instanceof Response) return prepared;
|
|
6803
7156
|
const { html, beforeHtml, block } = prepared;
|
|
7157
|
+
let lengthIsAuthored;
|
|
6804
7158
|
const initialScript = block.scriptText;
|
|
6805
7159
|
const skippedSelectors = /* @__PURE__ */ new Set();
|
|
6806
7160
|
const respond = (data, status) => status ? c.json(data, status) : c.json(data);
|
|
@@ -6832,7 +7186,9 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
6832
7186
|
for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
|
|
6833
7187
|
}
|
|
6834
7188
|
if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
|
|
6835
|
-
|
|
7189
|
+
lengthIsAuthored ??= !lengthIsTimeline(res.project.dir, res.filePath);
|
|
7190
|
+
if (lengthIsAuthored) newScript = trimTrailingKeyframeSpans(previousScript, newScript);
|
|
7191
|
+
newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript, previousScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript, previousScript);
|
|
6836
7192
|
}
|
|
6837
7193
|
mutationChanges.push(newScript !== previousScript);
|
|
6838
7194
|
block.scriptText = newScript;
|
|
@@ -6840,7 +7196,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
6840
7196
|
const changed = block.scriptText !== initialScript;
|
|
6841
7197
|
const newHtml = changed ? block.replaceScript(block.scriptText) : html;
|
|
6842
7198
|
let backupPath = null;
|
|
6843
|
-
if (
|
|
7199
|
+
if (readFileSync52(res.absPath, "utf-8") !== beforeHtml) {
|
|
6844
7200
|
return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
|
|
6845
7201
|
}
|
|
6846
7202
|
if (changed) {
|
|
@@ -7531,7 +7887,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
|
|
|
7531
7887
|
for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
|
|
7532
7888
|
}
|
|
7533
7889
|
if (script !== block.scriptText) {
|
|
7534
|
-
script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script);
|
|
7890
|
+
script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script, block.scriptText) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script, block.scriptText);
|
|
7535
7891
|
after = block.replaceScript(script);
|
|
7536
7892
|
}
|
|
7537
7893
|
}
|
|
@@ -7570,9 +7926,9 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7570
7926
|
const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
|
|
7571
7927
|
const MAX_COPY_INDEX = 1e4;
|
|
7572
7928
|
let n = 1;
|
|
7573
|
-
if (
|
|
7929
|
+
if (existsSync4(finalPath)) {
|
|
7574
7930
|
n = 2;
|
|
7575
|
-
while (n < MAX_COPY_INDEX &&
|
|
7931
|
+
while (n < MAX_COPY_INDEX && existsSync4(resolve22(targetDir, `${base} (${n})${ext}`))) n++;
|
|
7576
7932
|
if (n >= MAX_COPY_INDEX) {
|
|
7577
7933
|
skipped.push(name);
|
|
7578
7934
|
continue;
|
|
@@ -7606,7 +7962,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7606
7962
|
if (n >= MAX_COPY_INDEX) skipped.push(name);
|
|
7607
7963
|
continue;
|
|
7608
7964
|
}
|
|
7609
|
-
const relativePath = subDir ?
|
|
7965
|
+
const relativePath = subDir ? join62(subDir, finalName) : finalName;
|
|
7610
7966
|
uploaded.push(relativePath);
|
|
7611
7967
|
if (validation.unchecked) unchecked.push({ name: finalName, reason: validation.unchecked });
|
|
7612
7968
|
if (isAudioFile(finalName)) {
|
|
@@ -7639,7 +7995,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7639
7995
|
if (stat22.size > MAX_TEXT_READ_BYTES) {
|
|
7640
7996
|
return c.json({ error: "too large to read as text", why: "too_large" }, 413);
|
|
7641
7997
|
}
|
|
7642
|
-
const content =
|
|
7998
|
+
const content = readFileSync52(fd);
|
|
7643
7999
|
const version = fileContentVersion(content);
|
|
7644
8000
|
c.header("ETag", version);
|
|
7645
8001
|
if (content.subarray(0, GIT_BINARY_SNIFF_BYTES).includes(0)) {
|
|
@@ -7664,7 +8020,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7664
8020
|
if (expectedVersion === null && !createOnly) {
|
|
7665
8021
|
let currentContent = null;
|
|
7666
8022
|
try {
|
|
7667
|
-
currentContent =
|
|
8023
|
+
currentContent = readFileSync52(res.absPath);
|
|
7668
8024
|
} catch (error) {
|
|
7669
8025
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7670
8026
|
throw error;
|
|
@@ -7690,7 +8046,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7690
8046
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
7691
8047
|
throw error;
|
|
7692
8048
|
}
|
|
7693
|
-
const currentContent =
|
|
8049
|
+
const currentContent = readFileSync52(res.absPath);
|
|
7694
8050
|
return c.json(
|
|
7695
8051
|
{
|
|
7696
8052
|
error: "file conflict",
|
|
@@ -7720,7 +8076,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7720
8076
|
);
|
|
7721
8077
|
}
|
|
7722
8078
|
try {
|
|
7723
|
-
const currentContent =
|
|
8079
|
+
const currentContent = readFileSync52(fd);
|
|
7724
8080
|
overwrote = currentContent;
|
|
7725
8081
|
const currentVersion = fileContentVersion(currentContent);
|
|
7726
8082
|
if (expectedVersion !== currentVersion) {
|
|
@@ -7796,7 +8152,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7796
8152
|
}
|
|
7797
8153
|
let before;
|
|
7798
8154
|
try {
|
|
7799
|
-
before =
|
|
8155
|
+
before = readFileSync52(ctx.absPath, "utf-8");
|
|
7800
8156
|
} catch (error) {
|
|
7801
8157
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7802
8158
|
throw error;
|
|
@@ -7825,7 +8181,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7825
8181
|
}
|
|
7826
8182
|
const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
|
|
7827
8183
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
7828
|
-
const current =
|
|
8184
|
+
const current = readFileSync52(ctx.absPath, "utf-8");
|
|
7829
8185
|
if (current !== before) {
|
|
7830
8186
|
const currentVersion2 = fileContentVersion(current);
|
|
7831
8187
|
return c.json({ error: "file conflict", currentVersion: currentVersion2, currentContent: current }, 409);
|
|
@@ -7853,12 +8209,12 @@ function registerFileRoutes(api, adapter) {
|
|
|
7853
8209
|
api.post("/projects/:id/file-mutations/remove-element/*", async (c) => {
|
|
7854
8210
|
const ctx = await resolveFileMutationContext(c, adapter, "remove-element");
|
|
7855
8211
|
if ("error" in ctx) return ctx.error;
|
|
7856
|
-
if (!
|
|
8212
|
+
if (!existsSync4(ctx.absPath)) {
|
|
7857
8213
|
return c.json({ error: "not found" }, 404);
|
|
7858
8214
|
}
|
|
7859
8215
|
const parsed = await parseMutationBody(c);
|
|
7860
8216
|
if ("error" in parsed) return parsed.error;
|
|
7861
|
-
const originalContent =
|
|
8217
|
+
const originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
7862
8218
|
return writeIfChanged(
|
|
7863
8219
|
c,
|
|
7864
8220
|
ctx.project.dir,
|
|
@@ -7871,7 +8227,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7871
8227
|
api.post("/projects/:id/file-mutations/remove-elements/*", async (c) => {
|
|
7872
8228
|
const ctx = await resolveFileMutationContext(c, adapter, "remove-elements");
|
|
7873
8229
|
if ("error" in ctx) return ctx.error;
|
|
7874
|
-
if (!
|
|
8230
|
+
if (!existsSync4(ctx.absPath)) {
|
|
7875
8231
|
return c.json({ error: "not found" }, 404);
|
|
7876
8232
|
}
|
|
7877
8233
|
const body = await c.req.json().catch(() => null);
|
|
@@ -7879,7 +8235,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7879
8235
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
7880
8236
|
return c.json({ error: "targets required" }, 400);
|
|
7881
8237
|
}
|
|
7882
|
-
const originalContent =
|
|
8238
|
+
const originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
7883
8239
|
return writeIfChanged(
|
|
7884
8240
|
c,
|
|
7885
8241
|
ctx.project.dir,
|
|
@@ -7915,7 +8271,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7915
8271
|
seen.add(absPath);
|
|
7916
8272
|
let before;
|
|
7917
8273
|
try {
|
|
7918
|
-
before =
|
|
8274
|
+
before = readFileSync52(absPath, "utf-8");
|
|
7919
8275
|
} catch {
|
|
7920
8276
|
return c.json({ error: `not found: ${file.path}` }, 404);
|
|
7921
8277
|
}
|
|
@@ -7942,7 +8298,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7942
8298
|
prepared.push(folded);
|
|
7943
8299
|
}
|
|
7944
8300
|
for (const file of prepared) {
|
|
7945
|
-
const current =
|
|
8301
|
+
const current = readFileSync52(file.absPath, "utf-8");
|
|
7946
8302
|
if (current !== file.before) {
|
|
7947
8303
|
return c.json(
|
|
7948
8304
|
{
|
|
@@ -7985,7 +8341,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7985
8341
|
const conflicts = [];
|
|
7986
8342
|
for (const file of written.reverse()) {
|
|
7987
8343
|
try {
|
|
7988
|
-
const current =
|
|
8344
|
+
const current = readFileSync52(file.absPath, "utf-8");
|
|
7989
8345
|
if (current !== file.after) {
|
|
7990
8346
|
conflicts.push(file.path);
|
|
7991
8347
|
continue;
|
|
@@ -8033,7 +8389,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8033
8389
|
const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
|
|
8034
8390
|
let originalContent;
|
|
8035
8391
|
try {
|
|
8036
|
-
originalContent =
|
|
8392
|
+
originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
8037
8393
|
} catch {
|
|
8038
8394
|
return c.json({ error: "not found" }, 404);
|
|
8039
8395
|
}
|
|
@@ -8095,7 +8451,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8095
8451
|
for (let attempt = 1; ; attempt += 1) {
|
|
8096
8452
|
let originalContent;
|
|
8097
8453
|
try {
|
|
8098
|
-
originalContent =
|
|
8454
|
+
originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
8099
8455
|
} catch {
|
|
8100
8456
|
return c.json({ error: "not found" }, 404);
|
|
8101
8457
|
}
|
|
@@ -8207,7 +8563,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8207
8563
|
}
|
|
8208
8564
|
let originalContent;
|
|
8209
8565
|
try {
|
|
8210
|
-
originalContent =
|
|
8566
|
+
originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
8211
8567
|
} catch {
|
|
8212
8568
|
return c.json({ error: "not found" }, 404);
|
|
8213
8569
|
}
|
|
@@ -8263,7 +8619,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8263
8619
|
).map((entry) => ({ target: entry.target, track: entry.track }));
|
|
8264
8620
|
let originalContent;
|
|
8265
8621
|
try {
|
|
8266
|
-
originalContent =
|
|
8622
|
+
originalContent = readFileSync52(ctx.absPath, "utf-8");
|
|
8267
8623
|
} catch {
|
|
8268
8624
|
return c.json({ error: "not found" }, 404);
|
|
8269
8625
|
}
|
|
@@ -8292,7 +8648,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8292
8648
|
if ("error" in parsed) return parsed.error;
|
|
8293
8649
|
let content;
|
|
8294
8650
|
try {
|
|
8295
|
-
content =
|
|
8651
|
+
content = readFileSync52(ctx.absPath, "utf-8");
|
|
8296
8652
|
} catch {
|
|
8297
8653
|
return c.json({ exists: false });
|
|
8298
8654
|
}
|
|
@@ -8306,7 +8662,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8306
8662
|
if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
|
|
8307
8663
|
let content;
|
|
8308
8664
|
try {
|
|
8309
|
-
content =
|
|
8665
|
+
content = readFileSync52(ctx.absPath, "utf-8");
|
|
8310
8666
|
} catch {
|
|
8311
8667
|
return c.json({ exists: body.targets.map(() => false) });
|
|
8312
8668
|
}
|
|
@@ -8323,7 +8679,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8323
8679
|
if (!newAbs) {
|
|
8324
8680
|
return c.json({ error: "forbidden" }, 403);
|
|
8325
8681
|
}
|
|
8326
|
-
if (
|
|
8682
|
+
if (existsSync4(newAbs)) {
|
|
8327
8683
|
return c.json({ error: "already exists" }, 409);
|
|
8328
8684
|
}
|
|
8329
8685
|
const isDirectory = statSync32(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
@@ -8340,7 +8696,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8340
8696
|
return c.json({ error: "path required" }, 400);
|
|
8341
8697
|
}
|
|
8342
8698
|
const srcAbs = resolveWithinProject(project.dir, body.path);
|
|
8343
|
-
if (!srcAbs || !
|
|
8699
|
+
if (!srcAbs || !existsSync4(srcAbs)) {
|
|
8344
8700
|
return c.json({ error: "not found" }, 404);
|
|
8345
8701
|
}
|
|
8346
8702
|
const copyPath = generateCopyPath(project.dir, body.path);
|
|
@@ -8350,7 +8706,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8350
8706
|
}
|
|
8351
8707
|
ensureDir(project.dir, destAbs);
|
|
8352
8708
|
try {
|
|
8353
|
-
createFileAtomically(destAbs,
|
|
8709
|
+
createFileAtomically(destAbs, readFileSync52(srcAbs));
|
|
8354
8710
|
} catch (error) {
|
|
8355
8711
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
8356
8712
|
throw error;
|
|
@@ -8394,7 +8750,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8394
8750
|
mustExist: true
|
|
8395
8751
|
});
|
|
8396
8752
|
if ("error" in res) return res.error;
|
|
8397
|
-
const html =
|
|
8753
|
+
const html = readFileSync52(res.absPath, "utf-8");
|
|
8398
8754
|
const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
|
|
8399
8755
|
const headers = { ETag: etag, "Cache-Control": "no-cache" };
|
|
8400
8756
|
if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
|
|
@@ -8451,7 +8807,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8451
8807
|
if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
|
|
8452
8808
|
return c.json({ error: "expected and restore contents required" }, 400);
|
|
8453
8809
|
}
|
|
8454
|
-
const current =
|
|
8810
|
+
const current = readFileSync52(res.absPath, "utf-8");
|
|
8455
8811
|
if (current !== body.expected) {
|
|
8456
8812
|
return c.json({ ok: true, restored: false, conflict: true });
|
|
8457
8813
|
}
|
|
@@ -8459,176 +8815,6 @@ function registerFileRoutes(api, adapter) {
|
|
|
8459
8815
|
return c.json({ ok: true, restored: true, conflict: false });
|
|
8460
8816
|
});
|
|
8461
8817
|
}
|
|
8462
|
-
function rewriteRelativePaths(root, compPath, projectDir) {
|
|
8463
|
-
const assetExists = (path) => existsSync42(join62(projectDir, path));
|
|
8464
|
-
rewriteAssetPaths(
|
|
8465
|
-
root.querySelectorAll("[src], [href]"),
|
|
8466
|
-
compPath,
|
|
8467
|
-
(el, attr) => el.getAttribute(attr),
|
|
8468
|
-
(el, attr, value) => el.setAttribute(attr, value),
|
|
8469
|
-
assetExists
|
|
8470
|
-
);
|
|
8471
|
-
rewriteInlineStyleAssetUrls(
|
|
8472
|
-
root.querySelectorAll("[style]"),
|
|
8473
|
-
compPath,
|
|
8474
|
-
(el) => el.getAttribute("style"),
|
|
8475
|
-
(el, value) => el.setAttribute("style", value),
|
|
8476
|
-
assetExists
|
|
8477
|
-
);
|
|
8478
|
-
for (const styleEl of root.querySelectorAll("style")) {
|
|
8479
|
-
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
|
|
8480
|
-
}
|
|
8481
|
-
}
|
|
8482
|
-
function escapeLeadingDigitIdent(id) {
|
|
8483
|
-
return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`;
|
|
8484
|
-
}
|
|
8485
|
-
var REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g;
|
|
8486
|
-
function fixDigitLeadingIdSelectors(root) {
|
|
8487
|
-
const digitIds = /* @__PURE__ */ new Set();
|
|
8488
|
-
for (const el of root.querySelectorAll("[id]")) {
|
|
8489
|
-
const id = el.getAttribute("id");
|
|
8490
|
-
if (id && /^\d/.test(id)) digitIds.add(id);
|
|
8491
|
-
}
|
|
8492
|
-
if (digitIds.size === 0) return;
|
|
8493
|
-
for (const styleEl of root.querySelectorAll("style")) {
|
|
8494
|
-
let css = styleEl.textContent || "";
|
|
8495
|
-
for (const id of digitIds) {
|
|
8496
|
-
const pattern = new RegExp(`#${id.replace(REGEXP_SPECIALS, "\\$&")}(?![\\w-])`, "g");
|
|
8497
|
-
css = css.replace(pattern, `#${escapeLeadingDigitIdent(id)}`);
|
|
8498
|
-
}
|
|
8499
|
-
styleEl.textContent = css;
|
|
8500
|
-
}
|
|
8501
|
-
}
|
|
8502
|
-
function extractFullDocumentParts(rawHtml, compPath, projectDir) {
|
|
8503
|
-
const { document: doc } = parseHTML(rawHtml);
|
|
8504
|
-
const rewriteTargets = [doc.head, doc.body].filter(Boolean);
|
|
8505
|
-
for (const target of rewriteTargets) {
|
|
8506
|
-
rewriteRelativePaths(target, compPath, projectDir);
|
|
8507
|
-
}
|
|
8508
|
-
fixDigitLeadingIdSelectors(doc);
|
|
8509
|
-
const headContent = doc.head?.innerHTML ?? "";
|
|
8510
|
-
const bodyContent = doc.body?.innerHTML ?? "";
|
|
8511
|
-
const htmlEl = doc.documentElement;
|
|
8512
|
-
const htmlAttrs = extractElementAttrs(htmlEl);
|
|
8513
|
-
const bodyAttrs = doc.body ? extractElementAttrs(doc.body) : "";
|
|
8514
|
-
return { headContent, bodyContent, htmlAttrs, bodyAttrs };
|
|
8515
|
-
}
|
|
8516
|
-
function extractTemplateInnerHtml(rawComp) {
|
|
8517
|
-
const { document: doc } = parseHTML(rawComp);
|
|
8518
|
-
const template = doc.querySelector("template");
|
|
8519
|
-
return template ? template.innerHTML : null;
|
|
8520
|
-
}
|
|
8521
|
-
function escapeAttrValue(value) {
|
|
8522
|
-
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
8523
|
-
}
|
|
8524
|
-
function extractElementAttrs(el) {
|
|
8525
|
-
const parts = [];
|
|
8526
|
-
for (let i = 0; i < el.attributes.length; i++) {
|
|
8527
|
-
const attr = el.attributes[i];
|
|
8528
|
-
if (attr.value === "") {
|
|
8529
|
-
parts.push(attr.name);
|
|
8530
|
-
} else {
|
|
8531
|
-
parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
|
|
8532
|
-
}
|
|
8533
|
-
}
|
|
8534
|
-
return parts.join(" ");
|
|
8535
|
-
}
|
|
8536
|
-
var NON_RENDERED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"]);
|
|
8537
|
-
function promoteTemplateCompositionId(rawComp, body) {
|
|
8538
|
-
let templateCompositionId;
|
|
8539
|
-
for (const tag of rawComp.matchAll(/<template\b[^>]*/gi)) {
|
|
8540
|
-
const id = /\bdata-composition-id\s*=\s*["']([^"']+)["']/i.exec(tag[0] ?? "")?.[1];
|
|
8541
|
-
if (id) {
|
|
8542
|
-
templateCompositionId = id;
|
|
8543
|
-
break;
|
|
8544
|
-
}
|
|
8545
|
-
}
|
|
8546
|
-
if (!templateCompositionId) return;
|
|
8547
|
-
if (body.querySelector("[data-composition-id]")) return;
|
|
8548
|
-
const root = Array.from(body.children).find((el) => !NON_RENDERED_TAGS.has(el.tagName));
|
|
8549
|
-
root?.setAttribute("data-composition-id", templateCompositionId);
|
|
8550
|
-
}
|
|
8551
|
-
function tagRootCompositionFile(bodyHtml, compPath) {
|
|
8552
|
-
const match = bodyHtml.match(/<[a-zA-Z][^>]*\bdata-composition-id=/);
|
|
8553
|
-
if (match?.index == null) return bodyHtml;
|
|
8554
|
-
const tagEnd = bodyHtml.indexOf(">", match.index);
|
|
8555
|
-
if (tagEnd === -1) return bodyHtml;
|
|
8556
|
-
if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
|
|
8557
|
-
return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
|
|
8558
|
-
}
|
|
8559
|
-
function rootHeadContent(rootHtml) {
|
|
8560
|
-
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
8561
|
-
}
|
|
8562
|
-
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
8563
|
-
const compFile = join62(projectDir, compPath);
|
|
8564
|
-
if (!existsSync42(compFile)) return null;
|
|
8565
|
-
const rawComp = rawOverride ?? readFileSync42(compFile, "utf-8");
|
|
8566
|
-
let compHeadContent = "";
|
|
8567
|
-
let rewrittenContent;
|
|
8568
|
-
let htmlAttrs = "";
|
|
8569
|
-
let bodyAttrs = "";
|
|
8570
|
-
const templateInner = extractTemplateInnerHtml(rawComp);
|
|
8571
|
-
if (templateInner != null) {
|
|
8572
|
-
const { document: contentDoc } = parseHTML(
|
|
8573
|
-
`<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
|
|
8574
|
-
);
|
|
8575
|
-
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
8576
|
-
fixDigitLeadingIdSelectors(contentDoc);
|
|
8577
|
-
promoteTemplateCompositionId(rawComp, contentDoc.body);
|
|
8578
|
-
rewrittenContent = contentDoc.body.innerHTML || templateInner;
|
|
8579
|
-
} else if (isFullHtmlDocument(rawComp)) {
|
|
8580
|
-
const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
|
|
8581
|
-
compHeadContent = parts.headContent;
|
|
8582
|
-
rewrittenContent = parts.bodyContent;
|
|
8583
|
-
htmlAttrs = parts.htmlAttrs;
|
|
8584
|
-
bodyAttrs = parts.bodyAttrs;
|
|
8585
|
-
} else {
|
|
8586
|
-
const { document: contentDoc } = parseHTML(
|
|
8587
|
-
`<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
|
|
8588
|
-
);
|
|
8589
|
-
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
8590
|
-
fixDigitLeadingIdSelectors(contentDoc);
|
|
8591
|
-
rewrittenContent = contentDoc.body.innerHTML || rawComp;
|
|
8592
|
-
}
|
|
8593
|
-
rewrittenContent = stripEmbeddedRuntimeScripts(rewrittenContent);
|
|
8594
|
-
const { document: scriptsDoc } = parseHTML(
|
|
8595
|
-
`<!DOCTYPE html><html><head></head><body>${rewrittenContent}</body></html>`
|
|
8596
|
-
);
|
|
8597
|
-
deferScriptsUntilFonts(scriptsDoc);
|
|
8598
|
-
rewrittenContent = scriptsDoc.body.innerHTML;
|
|
8599
|
-
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
8600
|
-
const indexPath = join62(projectDir, "index.html");
|
|
8601
|
-
let headContent = "";
|
|
8602
|
-
if (existsSync42(indexPath)) headContent = rootHeadContent(readFileSync42(indexPath, "utf-8"));
|
|
8603
|
-
if (baseHref && !hasBaseElement(headContent)) {
|
|
8604
|
-
headContent = `<base href="${baseHref}">
|
|
8605
|
-
${headContent}`;
|
|
8606
|
-
}
|
|
8607
|
-
if (compHeadContent) headContent += `
|
|
8608
|
-
${compHeadContent}`;
|
|
8609
|
-
headContent = stripEmbeddedRuntimeScripts(headContent);
|
|
8610
|
-
headContent += `
|
|
8611
|
-
<script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
|
|
8612
|
-
if (!headContent.includes("gsap")) {
|
|
8613
|
-
headContent += `
|
|
8614
|
-
<script src="${gsapCdnDist()}gsap.min.js"></script>`;
|
|
8615
|
-
}
|
|
8616
|
-
const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
|
|
8617
|
-
const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
|
|
8618
|
-
return `<!DOCTYPE html>
|
|
8619
|
-
${htmlOpen}
|
|
8620
|
-
<head>
|
|
8621
|
-
${headContent}
|
|
8622
|
-
</head>
|
|
8623
|
-
${bodyOpen}
|
|
8624
|
-
<script>window.__timelines=window.__timelines||{};</script>
|
|
8625
|
-
${rewrittenContent}
|
|
8626
|
-
</body>
|
|
8627
|
-
</html>`;
|
|
8628
|
-
}
|
|
8629
|
-
function hasBaseElement(html) {
|
|
8630
|
-
return parseHTML(html).document.querySelector("base") !== null;
|
|
8631
|
-
}
|
|
8632
8818
|
var ALWAYS_AFFECTS = ["hyperframes.json", ...STUDIO_SIGNATURE_MANIFEST_PATHS];
|
|
8633
8819
|
var REFERENCE = /\b(?:src|href|poster|data-composition-src)\s*=\s*(?:"([^"\n]*)"|'([^'\n]*)')|url\(\s*(?:"([^"\n]*)"|'([^'\n]*)'|([^"'\s)]+))/gi;
|
|
8634
8820
|
var pathKey = process.platform === "darwin" || process.platform === "win32" ? (path) => resolve32(path).toLowerCase() : (path) => resolve32(path);
|
|
@@ -8763,7 +8949,7 @@ function readStudioMotionManifestContent(projectDir) {
|
|
|
8763
8949
|
const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
|
|
8764
8950
|
if (!existsSync5(manifestPath)) return "";
|
|
8765
8951
|
try {
|
|
8766
|
-
return
|
|
8952
|
+
return readFileSync62(manifestPath, "utf-8");
|
|
8767
8953
|
} catch {
|
|
8768
8954
|
return "";
|
|
8769
8955
|
}
|
|
@@ -8905,7 +9091,7 @@ function parsePreviewVariablesParam(raw) {
|
|
|
8905
9091
|
}
|
|
8906
9092
|
function variablesEtagSalt(raw) {
|
|
8907
9093
|
if (!raw) return "";
|
|
8908
|
-
return `:vars:${
|
|
9094
|
+
return `:vars:${createHash23("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
8909
9095
|
}
|
|
8910
9096
|
function previewVariablesFromRequest(rawVariables) {
|
|
8911
9097
|
const parse22 = parsePreviewVariablesParam(rawVariables);
|
|
@@ -8945,13 +9131,13 @@ function resolveProjectMainHtml(projectDir, projectId) {
|
|
|
8945
9131
|
recordPreviewRead(projectDir, blockHtmlPath);
|
|
8946
9132
|
if (existsSync5(indexPath)) {
|
|
8947
9133
|
return {
|
|
8948
|
-
html:
|
|
9134
|
+
html: readFileSync62(indexPath, "utf-8"),
|
|
8949
9135
|
compositionPath: "index.html"
|
|
8950
9136
|
};
|
|
8951
9137
|
}
|
|
8952
9138
|
if (existsSync5(blockHtmlPath)) {
|
|
8953
9139
|
return {
|
|
8954
|
-
html:
|
|
9140
|
+
html: readFileSync62(blockHtmlPath, "utf-8"),
|
|
8955
9141
|
compositionPath: `${projectId}.html`
|
|
8956
9142
|
};
|
|
8957
9143
|
}
|
|
@@ -9082,7 +9268,7 @@ ${etag}`;
|
|
|
9082
9268
|
function pinSubCompHfIds(compFile, compPath) {
|
|
9083
9269
|
if (!/\.html?$/i.test(compPath)) return void 0;
|
|
9084
9270
|
try {
|
|
9085
|
-
return ensureHfIds(
|
|
9271
|
+
return ensureHfIds(readFileSync62(compFile, "utf-8"));
|
|
9086
9272
|
} catch {
|
|
9087
9273
|
return null;
|
|
9088
9274
|
}
|
|
@@ -9101,7 +9287,7 @@ ${etag}`;
|
|
|
9101
9287
|
if (!compFile || !existsSync5(compFile) || !statSync42(compFile).isFile()) {
|
|
9102
9288
|
return c.text("not found", 404);
|
|
9103
9289
|
}
|
|
9104
|
-
const compPathHash =
|
|
9290
|
+
const compPathHash = createHash23("sha1").update(compPath).digest("hex");
|
|
9105
9291
|
const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
|
|
9106
9292
|
const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
|
|
9107
9293
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
@@ -9193,7 +9379,7 @@ ${etag}`;
|
|
|
9193
9379
|
recordProxyRequest();
|
|
9194
9380
|
servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
|
|
9195
9381
|
}
|
|
9196
|
-
const textBuffer = isText ? Buffer.from(
|
|
9382
|
+
const textBuffer = isText ? Buffer.from(readFileSync62(file, "utf-8"), "utf-8") : null;
|
|
9197
9383
|
const totalSize = textBuffer ? textBuffer.length : statSync42(servedPath).size;
|
|
9198
9384
|
const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
|
|
9199
9385
|
// Node's web stream type and the DOM one do not overlap for tsc on
|
|
@@ -9252,7 +9438,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
|
|
|
9252
9438
|
const findings = [];
|
|
9253
9439
|
for (const file of htmlFiles) {
|
|
9254
9440
|
if (coveredFiles.has(file)) continue;
|
|
9255
|
-
const content =
|
|
9441
|
+
const content = readFileSync72(join9(projectDir, file), "utf-8");
|
|
9256
9442
|
const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
|
|
9257
9443
|
for (const finding of result?.findings ?? []) {
|
|
9258
9444
|
findings.push({ ...finding, file });
|
|
@@ -9418,7 +9604,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9418
9604
|
}
|
|
9419
9605
|
const contentType = renderContentType(job.outputPath);
|
|
9420
9606
|
const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
|
|
9421
|
-
const content =
|
|
9607
|
+
const content = readFileSync8(job.outputPath);
|
|
9422
9608
|
return new Response(content, {
|
|
9423
9609
|
headers: {
|
|
9424
9610
|
"Content-Type": contentType,
|
|
@@ -9436,7 +9622,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9436
9622
|
}
|
|
9437
9623
|
const contentType = renderContentType(job.outputPath);
|
|
9438
9624
|
const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
|
|
9439
|
-
const content =
|
|
9625
|
+
const content = readFileSync8(job.outputPath);
|
|
9440
9626
|
return new Response(content, {
|
|
9441
9627
|
headers: {
|
|
9442
9628
|
"Content-Type": contentType,
|
|
@@ -9469,7 +9655,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9469
9655
|
if (!fp) return c.json({ error: "forbidden" }, 403);
|
|
9470
9656
|
if (!existsSync6(fp)) return c.json({ error: "not found" }, 404);
|
|
9471
9657
|
const contentType = renderContentType(fp);
|
|
9472
|
-
const content =
|
|
9658
|
+
const content = readFileSync8(fp);
|
|
9473
9659
|
return new Response(content, {
|
|
9474
9660
|
headers: {
|
|
9475
9661
|
"Content-Type": contentType,
|
|
@@ -9494,7 +9680,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9494
9680
|
let audioLoweredDb;
|
|
9495
9681
|
if (existsSync6(metaPath)) {
|
|
9496
9682
|
try {
|
|
9497
|
-
const meta = JSON.parse(
|
|
9683
|
+
const meta = JSON.parse(readFileSync8(metaPath, "utf-8"));
|
|
9498
9684
|
if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
|
|
9499
9685
|
if (meta.durationMs) durationMs = meta.durationMs;
|
|
9500
9686
|
if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
|
|
@@ -9702,7 +9888,7 @@ function registerImageThumbnailRoutes(api, adapter) {
|
|
|
9702
9888
|
api.get("/projects/:id/image-thumbnail/*", async (c) => {
|
|
9703
9889
|
try {
|
|
9704
9890
|
const { path, info } = await resolveSource(adapter, c.req.param("id"), c.req.url);
|
|
9705
|
-
const key =
|
|
9891
|
+
const key = createHash32("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
|
|
9706
9892
|
const etag = `"${key}"`;
|
|
9707
9893
|
c.header("ETag", etag);
|
|
9708
9894
|
c.header("Cache-Control", "private, no-cache");
|
|
@@ -9727,84 +9913,6 @@ function registerImageThumbnailRoutes(api, adapter) {
|
|
|
9727
9913
|
}
|
|
9728
9914
|
});
|
|
9729
9915
|
}
|
|
9730
|
-
var ROOT_COMPOSITION = "index.html";
|
|
9731
|
-
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
9732
|
-
function projectReader(projectDir) {
|
|
9733
|
-
const sources = /* @__PURE__ */ new Map();
|
|
9734
|
-
return (compPath) => {
|
|
9735
|
-
if (!sources.has(compPath)) {
|
|
9736
|
-
const file = resolveWithinProject(projectDir, compPath);
|
|
9737
|
-
let source = null;
|
|
9738
|
-
try {
|
|
9739
|
-
source = file ? readFileSync8(file, "utf-8") : null;
|
|
9740
|
-
} catch {
|
|
9741
|
-
}
|
|
9742
|
-
sources.set(compPath, source);
|
|
9743
|
-
}
|
|
9744
|
-
return sources.get(compPath) ?? null;
|
|
9745
|
-
};
|
|
9746
|
-
}
|
|
9747
|
-
function normalizeSource(src) {
|
|
9748
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
|
|
9749
|
-
const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
|
|
9750
|
-
return path || null;
|
|
9751
|
-
}
|
|
9752
|
-
function closureOf(read, compPath) {
|
|
9753
|
-
const closure = /* @__PURE__ */ new Set();
|
|
9754
|
-
const visit = (path) => {
|
|
9755
|
-
if (closure.has(path)) return;
|
|
9756
|
-
closure.add(path);
|
|
9757
|
-
for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
|
|
9758
|
-
const mounted = normalizeSource(match[2] ?? "");
|
|
9759
|
-
if (mounted) visit(mounted);
|
|
9760
|
-
}
|
|
9761
|
-
};
|
|
9762
|
-
visit(compPath);
|
|
9763
|
-
return closure;
|
|
9764
|
-
}
|
|
9765
|
-
var inputSignatures = /* @__PURE__ */ new Map();
|
|
9766
|
-
var rootMemos = /* @__PURE__ */ new Map();
|
|
9767
|
-
var sha1 = (text2) => createHash32("sha1").update(text2).digest("hex");
|
|
9768
|
-
function rootHashes(read) {
|
|
9769
|
-
const source = read(ROOT_COMPOSITION) ?? "";
|
|
9770
|
-
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
9771
|
-
}
|
|
9772
|
-
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
9773
|
-
const key = `${projectDir}\0${compPath}`;
|
|
9774
|
-
const known = inputSignatures.get(key);
|
|
9775
|
-
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
9776
|
-
const read = projectReader(projectDir);
|
|
9777
|
-
const comp = normalizeSource(compPath) ?? compPath;
|
|
9778
|
-
const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
|
|
9779
|
-
const excluded = new Set(
|
|
9780
|
-
[...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
|
|
9781
|
-
);
|
|
9782
|
-
let inputSignature;
|
|
9783
|
-
if (comp === ROOT_COMPOSITION) {
|
|
9784
|
-
inputSignature = createProjectSignature(projectDir, excluded);
|
|
9785
|
-
} else {
|
|
9786
|
-
const hashes = rootHashes(read);
|
|
9787
|
-
if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
|
|
9788
|
-
inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
|
|
9789
|
-
}
|
|
9790
|
-
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
9791
|
-
return inputSignature;
|
|
9792
|
-
}
|
|
9793
|
-
function compositionsAffectedBy(projectDir, changedPath) {
|
|
9794
|
-
const changed = changedPath.replace(/\\/g, "/");
|
|
9795
|
-
const read = projectReader(projectDir);
|
|
9796
|
-
if (changed === ROOT_COMPOSITION) {
|
|
9797
|
-
const now = rootHashes(read);
|
|
9798
|
-
const memo = rootMemos.get(projectDir);
|
|
9799
|
-
if (memo?.root === now.root) return memo.affected;
|
|
9800
|
-
const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
|
|
9801
|
-
rootMemos.set(projectDir, { ...now, affected });
|
|
9802
|
-
return affected;
|
|
9803
|
-
}
|
|
9804
|
-
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
9805
|
-
if (!mounted.has(changed)) return null;
|
|
9806
|
-
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
9807
|
-
}
|
|
9808
9916
|
var THUMBNAIL_CACHE_VERSION = "v5";
|
|
9809
9917
|
var THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
|
|
9810
9918
|
var THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
|
|
@@ -11381,12 +11489,12 @@ export {
|
|
|
11381
11489
|
createStudioMotionRenderBodyScript,
|
|
11382
11490
|
MIME_TYPES,
|
|
11383
11491
|
getMimeType,
|
|
11492
|
+
buildSubCompositionHtml,
|
|
11493
|
+
compositionsAffectedBy,
|
|
11384
11494
|
applyFileMutations,
|
|
11385
11495
|
requestSubPath,
|
|
11386
|
-
buildSubCompositionHtml,
|
|
11387
11496
|
affectsPreview,
|
|
11388
11497
|
PREVIEW_BUNDLE_OPTIONS,
|
|
11389
|
-
compositionsAffectedBy,
|
|
11390
11498
|
createStudioApi,
|
|
11391
11499
|
duplicateElementInHtml,
|
|
11392
11500
|
createPreviewDocumentStore,
|