hyperframes 0.8.138 → 0.8.140
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-IZQPSHT5.js → add-7SPPQB64.js} +20 -22
- package/dist/{animatedGifPrep-MQF3GRFW.js → animatedGifPrep-HN3CJSFX.js} +15 -8
- package/dist/{asset-RNZX4ISG.js → asset-AZMFUTRL.js} +3 -3
- package/dist/{assetCataloger-5YRUY3PJ.js → assetCataloger-TQUWYLHE.js} +6 -3
- package/dist/{auth-LUELYGJ4.js → auth-XGY74R4A.js} +7 -7
- package/dist/{autoUpdate-ECMQWUU5.js → autoUpdate-44NTJBQU.js} +2 -2
- package/dist/{backgroundChecks-PBEJMSA3.js → backgroundChecks-ET4DJEPG.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-2GXKBPFQ.js → batchRender-HTWWNF3P.js} +11 -8
- package/dist/beat-analyzer.global.js +5 -2
- package/dist/{beats-AAAZVDOY.js → beats-4GYTZUZG.js} +24 -17
- package/dist/{benchmark-EI767IB4.js → benchmark-CJQHE3QN.js} +20 -23
- package/dist/{browser-KAA2VXYH.js → browser-NXOVPGDG.js} +14 -28
- package/dist/{bundleWithLocalizedFonts-MWDYAPGB.js → bundleWithLocalizedFonts-6PJP54TC.js} +5 -5
- package/dist/{capture-IRRHK6BT.js → capture-5EHGRJZM.js} +2 -2
- package/dist/{capture-VYT5NQQI.js → capture-EHOMO4MP.js} +22 -15
- package/dist/{catalog-FYTZ2ET6.js → catalog-PTX5Z52R.js} +28 -30
- package/dist/{catch-up-XH3OAKQR.js → catch-up-I45SPSCU.js} +11 -25
- package/dist/{check-MUMIOK5K.js → check-LJ7LG7OI.js} +72 -54
- package/dist/{checkBrowser-LKSZFOU6.js → checkBrowser-6EK5E4PG.js} +81 -36
- package/dist/chunk-24OWOBQH.js +3656 -0
- package/dist/{chunk-3F6YLT72.js → chunk-2GMKWYEH.js} +12 -12
- package/dist/{chunk-KRLYIT22.js → chunk-5IKJONRY.js} +1 -1
- package/dist/chunk-6IHWCZ3W.js +54 -0
- package/dist/{chunk-P5WDMFBB.js → chunk-6U3MN4HW.js} +5 -106
- package/dist/chunk-A4KQZXZI.js +116 -0
- package/dist/{chunk-MKVKVMUH.js → chunk-ABJNXQUA.js} +10 -10
- package/dist/{chunk-YGXLBX3N.js → chunk-ASW6NWON.js} +1 -1
- package/dist/chunk-B3I3XJ47.js +3711 -0
- package/dist/{chunk-FSSMOTMS.js → chunk-BJ5JWOES.js} +1 -1
- package/dist/{chunk-OBC75KXL.js → chunk-BQKEGPPL.js} +1 -1
- package/dist/{chunk-SHB5VDMH.js → chunk-BRITLL56.js} +1 -1
- package/dist/{chunk-VR5UQWGV.js → chunk-C2JUHIG5.js} +6 -6
- package/dist/{chunk-YNBXJWSD.js → chunk-DDQ4LOML.js} +1 -1
- package/dist/{chunk-HUZYBV2Z.js → chunk-DQNOPYAG.js} +1 -1
- package/dist/{chunk-JH556Q42.js → chunk-EDYBFSPE.js} +2 -2
- package/dist/{chunk-SIMYRD5P.js → chunk-F3SPK6SO.js} +78 -20
- package/dist/chunk-FXMKHHPO.js +123 -0
- package/dist/chunk-G75ENCEZ.js +283 -0
- package/dist/{chunk-MIOBYUXT.js → chunk-GQJZORQP.js} +1 -1
- package/dist/{chunk-Y6W4DVPS.js → chunk-HKCNZITN.js} +1 -1
- package/dist/{chunk-APMG37MX.js → chunk-IFY57LUW.js} +1 -1
- package/dist/{chunk-JOUVZO7M.js → chunk-IRBESGJ7.js} +4 -4
- package/dist/chunk-JFKL3WAE.js +161 -0
- package/dist/{chunk-WH6PRFNX.js → chunk-JSGANFFH.js} +12 -3
- package/dist/{chunk-S3S2LSZX.js → chunk-KDPYVTEU.js} +2 -1
- package/dist/{chunk-PHXBLIKK.js → chunk-KECV5G4V.js} +165 -59
- package/dist/{chunk-SDRKEYZV.js → chunk-KFQNMY36.js} +8 -8
- package/dist/{chunk-QPOK6I32.js → chunk-KJLY2SDJ.js} +2 -2
- package/dist/{chunk-SZITAGRK.js → chunk-KMKWRIMC.js} +1 -1
- package/dist/{chunk-UUMK6VME.js → chunk-KOEWEFHK.js} +1 -1
- package/dist/{chunk-HXXX6N2G.js → chunk-L2H2L22P.js} +2 -2
- package/dist/{chunk-DJMXTKKK.js → chunk-LQG6GPVK.js} +1 -1
- package/dist/{chunk-GJGVYREG.js → chunk-MHK5DE2P.js} +268 -1964
- package/dist/{chunk-W3KQCJVP.js → chunk-MTVDXB6S.js} +18 -289
- package/dist/{chunk-IRYC5BRO.js → chunk-MU6C2VP6.js} +7 -7
- package/dist/{chunk-P75OFLZN.js → chunk-MUINFURL.js} +2 -2
- package/dist/{chunk-L5E5ZN6G.js → chunk-PB3P4MKP.js} +1 -1
- package/dist/{chunk-PHRWQQOJ.js → chunk-PE2I3AFA.js} +1 -1
- package/dist/{chunk-DXHYACUC.js → chunk-PFJFHNU3.js} +3 -3
- package/dist/{chunk-KBUUCAIZ.js → chunk-PPKFM5WE.js} +2 -2
- package/dist/{chunk-RH5VML6E.js → chunk-PUBKHEEA.js} +13 -13
- package/dist/{chunk-R6C6URKB.js → chunk-QBK7WKHT.js} +8 -8
- package/dist/{chunk-I3BRUEKD.js → chunk-QG3PDRSO.js} +1 -1
- package/dist/{chunk-RLNB3JCY.js → chunk-QP3BRKGF.js} +1 -163
- package/dist/chunk-QZ3AZTXI.js +1740 -0
- package/dist/{chunk-GNHJ4ONK.js → chunk-SLEPCESN.js} +2 -2
- package/dist/chunk-T76PLPD6.js +57 -0
- package/dist/{chunk-7Y5SMPRV.js → chunk-TPECLKPA.js} +168 -165
- package/dist/{chunk-IC5L6L7K.js → chunk-TYT2TLJR.js} +1 -1
- package/dist/{chunk-ASPTQ6DT.js → chunk-U7E736AJ.js} +1 -1
- package/dist/chunk-UOFAE5RF.js +42 -0
- package/dist/chunk-USDEMXXL.js +110 -0
- package/dist/{chunk-5K33TNUJ.js → chunk-VVJFZU6B.js} +2 -2
- package/dist/{chunk-XQODEVF3.js → chunk-WKTHUC2R.js} +1 -1
- package/dist/{chunk-7OLMGFDN.js → chunk-WQBA3MH4.js} +3 -3
- package/dist/{chunk-STA43FMY.js → chunk-WY5OODN5.js} +71 -3855
- package/dist/{chunk-JCRJBB4E.js → chunk-X3X3VETN.js} +8 -8
- package/dist/{chunk-ZTZ7NCJT.js → chunk-XTFJOR3K.js} +1 -1
- package/dist/chunk-YA62XGXS.js +45 -0
- package/dist/{clean-LZPDOAV4.js → clean-Y3AQITOX.js} +29 -20
- package/dist/cli.js +55 -55
- package/dist/{cloud-RVAH5NNL.js → cloud-TDPOMBZF.js} +7 -7
- package/dist/{cloudrun-NFGM6TRT.js → cloudrun-FPAVAVHP.js} +11 -8
- package/dist/commands/layout-audit.browser.js +37 -8
- package/dist/commands/motion-signature.browser.js +51 -2
- package/dist/{compare-S3M5OAL2.js → compare-LHL225ND.js} +40 -31
- package/dist/{compiler-SZ4GTYFB.js → compiler-RTU3KTLF.js} +7 -3
- package/dist/{component-PR2WZTQ4.js → component-L2YX7J56.js} +4 -4
- package/dist/{compositions-6RYXH2UA.js → compositions-V2UZKG7S.js} +23 -16
- package/dist/{delete-EO7C3LQ6.js → delete-OXZEXUQY.js} +8 -8
- package/dist/{dist-RVC3JTID.js → dist-N33CM63P.js} +21 -15
- package/dist/{dist-OJTEVNJR.js → dist-NZSQCYOF.js} +3 -1
- package/dist/{doctor-UTCCMN32.js → doctor-MWTPSIVD.js} +20 -13
- package/dist/{events-RE3OR6KY.js → events-4TZGQZWF.js} +6 -22
- package/dist/{events-RCGWL4LI.js → events-7VJJGC3D.js} +10 -23
- package/dist/{feedback-V7C64JIR.js → feedback-HSGAPVN7.js} +67 -45
- package/dist/{figma-26LOX5PV.js → figma-AMC6FAK3.js} +6 -6
- package/dist/fontLocalizeCli.js +17 -10
- package/dist/{get-E2K2JRTV.js → get-Y7DFEFYM.js} +8 -8
- package/dist/{gpuPolicy-Z3PQN6ZK.js → gpuPolicy-A45VSUKR.js} +15 -8
- package/dist/{grade-compare-OTZDU463.js → grade-compare-ERSA3OUI.js} +40 -31
- package/dist/{gsapParser-2OL54XZ5.js → gsapParser-UP3PNPC2.js} +3 -2
- package/dist/{help-IRSQLNI5.js → help-TW2AADLZ.js} +4 -4
- package/dist/{history-G44U6762.js → history-DKGDSVWF.js} +12 -30
- package/dist/hyperframe-runtime.js +59 -59
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +59 -59
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/{info-KXQ2TAHX.js → info-I3NRRNX4.js} +17 -20
- package/dist/{init-7SXMLHJE.js → init-5Y3ZWZNF.js} +50 -41
- package/dist/{inspect-7JTINR25.js → inspect-BJQKBB3X.js} +39 -30
- package/dist/{installer-RH4E6WXT.js → installer-27K4JS2I.js} +8 -5
- package/dist/{keyframes-PIV573S6.js → keyframes-6V2NZO22.js} +14 -25
- package/dist/{lambda-GO5HAA4S.js → lambda-AIMF3K4A.js} +16 -13
- package/dist/{layout-N2736OO3.js → layout-YAPEBTU4.js} +39 -30
- package/dist/{lint-WUXZ5WWZ.js → lint-3LWWUIV7.js} +17 -23
- package/dist/{lintProject-ANMPLBIT.js → lintProject-LM3XZRPZ.js} +3 -1
- package/dist/{list-QF7AMX5F.js → list-NTSGEZJ6.js} +8 -8
- package/dist/{login-6RGSXHQJ.js → login-ZTDFGCCW.js} +9 -9
- package/dist/{logout-Q654A3SI.js → logout-GJXZQKOA.js} +5 -5
- package/dist/{media-treatment-T4KDEDYW.js → media-treatment-ARHX446F.js} +21 -20
- package/dist/{models-6JMNK3XI.js → models-VHNQVTHG.js} +20 -13
- package/dist/{motionShot-74WM7KF6.js → motionShot-35HCSNCI.js} +22 -15
- package/dist/{normalize-O76AC4HE.js → normalize-IGLHCWTF.js} +0 -4
- package/dist/{normalize-audio-ZZU36ZMD.js → normalize-audio-AUC73JH3.js} +13 -26
- package/dist/{open-G523K45B.js → open-C5LVNU3U.js} +11 -25
- package/dist/{pipeline-AKDGCWRE.js → pipeline-4GDTK54T.js} +15 -8
- package/dist/{play-SHIGXVON.js → play-U7UANF7Z.js} +38 -32
- package/dist/{preflight-6ERLORWK.js → preflight-Y5DN5D37.js} +3 -18
- package/dist/{present-QOYYNZCE.js → present-X6V6TX3E.js} +32 -26
- package/dist/{preview-KUXTDJL3.js → preview-K43RTRU2.js} +33 -26
- package/dist/{producer-CH33JXHK.js → producer-PVMFMW23.js} +1 -1
- package/dist/{projectConfig-NKIPRWAE.js → projectConfig-PI6MEN2T.js} +10 -7
- package/dist/{publication-4RBVVR5Z.js → publication-67C7KVTL.js} +6 -3
- package/dist/{publish-OLHR4Z4U.js → publish-RBV7EAMH.js} +27 -20
- package/dist/{refresh-K24ETHMA.js → refresh-MFITBIKV.js} +5 -5
- package/dist/{remove-background-NPKZSIPY.js → remove-background-Z2ECZX2Q.js} +22 -15
- package/dist/{render-UOKPAJNH.js → render-2GQMQBU3.js} +10 -7
- package/dist/{render-GYYGWYQC.js → render-EFU4BZRZ.js} +23 -26
- package/dist/{render-P4NWFUS7.js → render-YBS3XGRJ.js} +52 -32
- package/dist/{render-batch-ZDG3EXSD.js → render-batch-QC64OTB5.js} +11 -8
- package/dist/{renderCancellation-QYQLWHZY.js → renderCancellation-NTVU7IMB.js} +15 -8
- package/dist/renderSetupWorker.js +4 -2
- package/dist/{resolver-CWH3VVB7.js → resolver-GBWYG3EI.js} +8 -5
- package/dist/{sherpa-WEQE23S5.js → sherpa-ZW6PWJS4.js} +46 -14
- package/dist/sherpaWorker.js +11 -3
- package/dist/skills/hyperframes/references/capability-menu.md +1 -1
- package/dist/skills/hyperframes-cli/SKILL.md +5 -0
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -1
- package/dist/skills/hyperframes-cli/references/preview-render.md +1 -1
- package/dist/skills/media-use/SKILL.md +6 -4
- package/dist/skills/media-use/audio/scripts/lib/host-audio.mjs +28 -0
- package/dist/skills/media-use/audio/scripts/lib/host-audio.test.mjs +106 -0
- package/dist/skills/media-use/references/setup-providers.md +3 -2
- package/dist/skills/media-use/scripts/lib/heygen-cli.mjs +21 -1
- package/dist/skills/media-use/scripts/lib/heygen-cli.test.mjs +14 -0
- package/dist/skills/media-use/scripts/resolve.mjs +11 -6
- package/dist/{skills-BVONI6TC.js → skills-VKZAX5AS.js} +15 -30
- package/dist/{skillsUpdateCheck-KKDPTFM7.js → skillsUpdateCheck-PAY2OPDE.js} +3 -3
- package/dist/{snapshot-PWZFJEDR.js → snapshot-SD5R3NWX.js} +41 -32
- package/dist/{src-YO7MAWGD.js → src-7SSERHNP.js} +19 -11
- package/dist/{src-HH4C5GZV.js → src-RXTXL3VL.js} +16 -9
- package/dist/{staticProjectServer-PR5INXSA.js → staticProjectServer-XELJXX4J.js} +24 -16
- package/dist/{status-AK67DTED.js → status-JA6SKQVH.js} +7 -24
- package/dist/studio/assets/{hyperframes-player-B30apksz.js → hyperframes-player-BLuyxnK7.js} +1 -1
- package/dist/studio/assets/{index-DXVvWfQG.js → index-CWfuDCoA.js} +1 -1
- package/dist/studio/assets/{index-DIJuIBwI.js → index-Dr3Bo9uH.js} +1 -1
- package/dist/studio/assets/{index-BdjA0CLD.js → index-VAHDM_zL.js} +210 -210
- package/dist/studio/{chunk-4S3AYCOG.js → chunk-H2DUOXTF.js} +3 -2
- package/dist/studio/{chunk-TDWGXBUT.js → chunk-TFLU2MGI.js} +1 -6
- package/dist/studio/chunk-TFLU2MGI.js.map +1 -0
- package/dist/studio/{chunk-7UQWVN52.js → chunk-UYWON5AX.js} +2 -2
- package/dist/studio/{domEditingLayers-IEVLO54Z.js → domEditingLayers-KBTPQKGA.js} +3 -3
- package/dist/studio/index.d.ts +20 -3
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +7327 -6265
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +2 -2
- package/dist/{studio-api-NFJMFAB7.js → studio-api-4FNUZPJ4.js} +40 -28
- package/dist/{studioServer-TQZVCIEL.js → studioServer-GHLL3F2M.js} +99 -68
- package/dist/{system-ZWIBWWE6.js → system-OQLOGGPJ.js} +2 -19
- package/dist/{telemetry-63L2ORO3.js → telemetry-7AO77PMH.js} +8 -23
- package/dist/{timeline-QKZQL5DJ.js → timeline-6LTHSRXM.js} +32 -22
- package/dist/{tokens-ELK62U4A.js → tokens-K6B46KNL.js} +3 -3
- package/dist/{transcribe-OSK7HEOJ.js → transcribe-CLLISRLZ.js} +18 -9
- package/dist/{transcribe-VKSXTM7K.js → transcribe-K2OU6FZF.js} +37 -78
- package/dist/{updateCheck-PM4FEI7P.js → updateCheck-X6UQBS6I.js} +2 -2
- package/dist/{upgrade-G635QCNO.js → upgrade-BZFKEADI.js} +5 -5
- package/dist/{urlDownloader-MVYQTP73.js → urlDownloader-VAVUXYEQ.js} +14 -7
- package/dist/{validate-AZTMI3HR.js → validate-VO5KEXWN.js} +26 -19
- package/dist/{video-H56FK33V.js → video-JV6Y4UJR.js} +18 -11
- package/package.json +1 -1
- package/dist/chunk-UINDCCO7.js +0 -3742
- package/dist/studio/chunk-TDWGXBUT.js.map +0 -1
- package/dist/{chunk-I465DBM6.js → chunk-246MOR5U.js} +3 -3
- package/dist/{deploy-MX76RZ36.js → deploy-5G4Z6LPA.js} +1 -1
- package/dist/{destroy-H2TZBYHH.js → destroy-GFQLTIY3.js} +1 -1
- package/dist/{docs-QAPJEGLS.js → docs-MPOM3PHH.js} +3 -3
- package/dist/{policies-FSDZVS2E.js → policies-4FEXMXPK.js} +3 -3
- package/dist/{progress-FBIEEB7Y.js → progress-5AM2MZ7V.js} +3 -3
- package/dist/{sites-TNVKZS73.js → sites-HZMADOA3.js} +1 -1
- package/dist/studio/{chunk-4S3AYCOG.js.map → chunk-H2DUOXTF.js.map} +0 -0
- package/dist/studio/{chunk-7UQWVN52.js.map → chunk-UYWON5AX.js.map} +0 -0
- package/dist/studio/{domEditingLayers-IEVLO54Z.js.map → domEditingLayers-KBTPQKGA.js.map} +0 -0
- package/dist/{telemetry-2YC4SGQU.js → telemetry-W5AKPI4R.js} +3 -3
- package/dist/{tts-FU35O6YB.js → tts-TOHSPWEG.js} +4 -4
|
@@ -21,14 +21,27 @@ import {
|
|
|
21
21
|
splitElementInHtml,
|
|
22
22
|
unwrapElementsFromHtml,
|
|
23
23
|
wrapElementsInHtml
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-QBK7WKHT.js";
|
|
25
|
+
import {
|
|
26
|
+
MAX_WINDOW_IDLE_MS,
|
|
27
|
+
STUDIO_SIGNATURE_MANIFEST_PATHS,
|
|
28
|
+
UNDO_MODES,
|
|
29
|
+
clearFileWriteReceipt,
|
|
30
|
+
createProjectSignature,
|
|
31
|
+
createWriteToken,
|
|
32
|
+
fileContentVersion,
|
|
33
|
+
recordFileWriteReceipt,
|
|
34
|
+
resolveProjectAndSignature,
|
|
35
|
+
resolveProjectSignature,
|
|
36
|
+
settledFileTag
|
|
37
|
+
} from "./chunk-QZ3AZTXI.js";
|
|
25
38
|
import {
|
|
26
39
|
DEFAULT_TARGET_LUFS,
|
|
27
40
|
absoluteLoudnessPlan,
|
|
28
41
|
measureAudio,
|
|
29
42
|
requiredFfmpeg,
|
|
30
43
|
resolveLocalAudioPath
|
|
31
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-U7E736AJ.js";
|
|
32
45
|
import {
|
|
33
46
|
PROXY_PARAMS_VERSION,
|
|
34
47
|
PROXY_PENDING_RETRY_AFTER_SECONDS,
|
|
@@ -36,34 +49,35 @@ import {
|
|
|
36
49
|
ProxyCapacityError,
|
|
37
50
|
ProxyTranscodeError,
|
|
38
51
|
ProxyWaitTimeoutError,
|
|
39
|
-
STUDIO_MOTION_PATH,
|
|
40
52
|
createMediaCodecProbeCache,
|
|
41
|
-
createStudioMotionRenderBodyScript,
|
|
42
53
|
decideMediaProxyEligibility,
|
|
43
|
-
isInHiddenOrVendorDir,
|
|
44
|
-
isPrivateProjectFile,
|
|
45
|
-
isPrivateProjectPath,
|
|
46
54
|
isProxyVariantRequest,
|
|
47
|
-
pinWithinProject,
|
|
48
55
|
probeAssetCodec,
|
|
49
56
|
probeMediaMetadata,
|
|
50
57
|
proxyActivityMark,
|
|
51
58
|
proxyVariantFor,
|
|
52
|
-
realFilePath,
|
|
53
59
|
recordProxyPrewarm,
|
|
54
60
|
recordProxyRequest,
|
|
55
61
|
resolveProxy,
|
|
56
62
|
resolveProxyVariantRequest,
|
|
57
|
-
resolveWithinProject,
|
|
58
63
|
scanProjectMediaCodecMap,
|
|
59
64
|
shouldPrewarmProxy,
|
|
60
|
-
waitForProxy
|
|
65
|
+
waitForProxy
|
|
66
|
+
} from "./chunk-MTVDXB6S.js";
|
|
67
|
+
import {
|
|
68
|
+
STUDIO_MOTION_PATH,
|
|
69
|
+
createStudioMotionRenderBodyScript,
|
|
70
|
+
isInHiddenOrVendorDir,
|
|
71
|
+
isPrivateProjectFile,
|
|
72
|
+
isPrivateProjectPath,
|
|
73
|
+
pinWithinProject,
|
|
74
|
+
realFilePath,
|
|
75
|
+
resolveWithinProject,
|
|
61
76
|
walkDir
|
|
62
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-G75ENCEZ.js";
|
|
63
78
|
import {
|
|
64
79
|
atomicTempPath,
|
|
65
80
|
createFileAtomically,
|
|
66
|
-
isAtomicTempPath,
|
|
67
81
|
replaceFileAtomically
|
|
68
82
|
} from "./chunk-EOVYQ3IG.js";
|
|
69
83
|
import {
|
|
@@ -80,22 +94,24 @@ import {
|
|
|
80
94
|
getSystemProfilerFamilies,
|
|
81
95
|
locateSystemFont
|
|
82
96
|
} from "./chunk-TJSTGSAW.js";
|
|
97
|
+
import {
|
|
98
|
+
addScenePartsManifest
|
|
99
|
+
} from "./chunk-6U3MN4HW.js";
|
|
100
|
+
import {
|
|
101
|
+
ensureHfIds
|
|
102
|
+
} from "./chunk-A4KQZXZI.js";
|
|
83
103
|
import {
|
|
84
104
|
formatRenderOutputTimestamp,
|
|
85
105
|
gsapCdnDist,
|
|
86
106
|
motionPathPluginUrl,
|
|
87
107
|
parseFps,
|
|
88
108
|
sameInstant
|
|
89
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-QP3BRKGF.js";
|
|
90
110
|
import {
|
|
91
111
|
MagicString,
|
|
92
112
|
VALID_CANVAS_RESOLUTIONS,
|
|
93
113
|
unrollComputedTimeline
|
|
94
114
|
} from "./chunk-OZUQF4NB.js";
|
|
95
|
-
import {
|
|
96
|
-
addScenePartsManifest,
|
|
97
|
-
ensureHfIds
|
|
98
|
-
} from "./chunk-P5WDMFBB.js";
|
|
99
115
|
import {
|
|
100
116
|
AFTER_FONTS_SCRIPT_TYPE,
|
|
101
117
|
RUNTIME_BOOTSTRAP_ATTR,
|
|
@@ -109,7 +125,7 @@ import {
|
|
|
109
125
|
resolveRateSpec,
|
|
110
126
|
sourceTimeAt,
|
|
111
127
|
stripEmbeddedRuntimeScripts
|
|
112
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-B3I3XJ47.js";
|
|
113
129
|
import {
|
|
114
130
|
ProjectRootMissingError,
|
|
115
131
|
folderGone,
|
|
@@ -121,7 +137,7 @@ import {
|
|
|
121
137
|
import {
|
|
122
138
|
isWithinProjectRoot,
|
|
123
139
|
readProjectFile
|
|
124
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-JFKL3WAE.js";
|
|
125
141
|
import {
|
|
126
142
|
FFPROBE_PATH_ENV,
|
|
127
143
|
findFfBinary
|
|
@@ -226,1701 +242,6 @@ function stampFileHfIds(filePath) {
|
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
|
|
229
|
-
// ../studio-server/dist/chunk-K3H6HTVC.js
|
|
230
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
231
|
-
import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
|
|
232
|
-
import {
|
|
233
|
-
existsSync as existsSync2,
|
|
234
|
-
lstatSync as lstatSync2,
|
|
235
|
-
statSync as statSync3,
|
|
236
|
-
mkdirSync as mkdirSync4,
|
|
237
|
-
readdirSync as readdirSync3,
|
|
238
|
-
readFileSync as readFileSync6,
|
|
239
|
-
realpathSync,
|
|
240
|
-
rmSync as rmSync2
|
|
241
|
-
} from "fs";
|
|
242
|
-
import { homedir } from "os";
|
|
243
|
-
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
244
|
-
import { createHash as createHash2, randomUUID } from "crypto";
|
|
245
|
-
import { createHash as createHash22 } from "crypto";
|
|
246
|
-
import { lstatSync, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
247
|
-
import { extname, isAbsolute, relative, resolve, sep } from "path";
|
|
248
|
-
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
249
|
-
import { constants as constants2, createReadStream, renameSync } from "fs";
|
|
250
|
-
import { copyFile, mkdir, readdir, readFile, rename, rm, stat } from "fs/promises";
|
|
251
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
252
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
253
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
254
|
-
import { lstat, readdir as readdir2, rm as rm2 } from "fs/promises";
|
|
255
|
-
import { tmpdir } from "os";
|
|
256
|
-
import { dirname as dirname22, join as join4 } from "path";
|
|
257
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
258
|
-
import { existsSync, mkdirSync, readFileSync as readFileSync22, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
259
|
-
import { basename, join as join22, resolve as resolve2 } from "path";
|
|
260
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
261
|
-
import { linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync22 } from "fs";
|
|
262
|
-
import { join as join3 } from "path";
|
|
263
|
-
import { closeSync as closeSync2, constants as constants22, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync5, writeSync } from "fs";
|
|
264
|
-
import { dirname as dirname3 } from "path";
|
|
265
|
-
var RECEIPT_TTL_MS = 1e4;
|
|
266
|
-
var receipts = /* @__PURE__ */ new Map();
|
|
267
|
-
var overwriteListeners = /* @__PURE__ */ new Set();
|
|
268
|
-
function fileContentVersion(content) {
|
|
269
|
-
return hashVersion(createHash2("sha256").update(content).digest("hex"));
|
|
270
|
-
}
|
|
271
|
-
var DELETED_VERSION = '"deleted"';
|
|
272
|
-
function hashVersion(hex) {
|
|
273
|
-
return `"sha256:${hex}"`;
|
|
274
|
-
}
|
|
275
|
-
function hashOfVersion(version) {
|
|
276
|
-
return /^"sha256:([0-9a-f]{64})"$/.exec(version)?.[1];
|
|
277
|
-
}
|
|
278
|
-
function settledFileTag(stat22, now = Date.now()) {
|
|
279
|
-
if (now - stat22.ctimeMs < 3e3) return null;
|
|
280
|
-
return [stat22.ino, stat22.ctimeMs, stat22.size].map((n) => n.toString(36)).join("-");
|
|
281
|
-
}
|
|
282
|
-
function createWriteToken(requestToken) {
|
|
283
|
-
const token = requestToken?.trim();
|
|
284
|
-
return token && token.length <= 200 ? token : randomUUID();
|
|
285
|
-
}
|
|
286
|
-
function onFileOverwritten(listener) {
|
|
287
|
-
overwriteListeners.add(listener);
|
|
288
|
-
return () => overwriteListeners.delete(listener);
|
|
289
|
-
}
|
|
290
|
-
function recordFileWriteReceipt(filePath, {
|
|
291
|
-
overwrote,
|
|
292
|
-
path,
|
|
293
|
-
version,
|
|
294
|
-
writeToken
|
|
295
|
-
}) {
|
|
296
|
-
const receipt = { path, version, writeToken };
|
|
297
|
-
const absPath = realFilePath(filePath);
|
|
298
|
-
if (overwrote !== void 0)
|
|
299
|
-
for (const listener of overwriteListeners) listener(absPath, receipt.version, overwrote);
|
|
300
|
-
const now = Date.now();
|
|
301
|
-
for (const [path2, list] of receipts) {
|
|
302
|
-
const live = list.filter((entry) => now - entry.recordedAt < RECEIPT_TTL_MS);
|
|
303
|
-
if (live.length > 0) receipts.set(path2, live);
|
|
304
|
-
else receipts.delete(path2);
|
|
305
|
-
}
|
|
306
|
-
const from = overwrote === void 0 ? void 0 : fileContentVersion(overwrote);
|
|
307
|
-
receipts.set(absPath, [
|
|
308
|
-
...receipts.get(absPath) ?? [],
|
|
309
|
-
{ ...receipt, ...from && { from }, recordedAt: now }
|
|
310
|
-
]);
|
|
311
|
-
}
|
|
312
|
-
function clearFileWriteReceipt(filePath, version, writeToken) {
|
|
313
|
-
const absPath = realFilePath(filePath);
|
|
314
|
-
const current = (receipts.get(absPath) ?? []).filter(
|
|
315
|
-
(entry) => entry.version !== version || entry.writeToken !== writeToken
|
|
316
|
-
);
|
|
317
|
-
if (current.length > 0) receipts.set(absPath, current);
|
|
318
|
-
else receipts.delete(absPath);
|
|
319
|
-
}
|
|
320
|
-
function identifyFileWrite(filePath, expectedVersion) {
|
|
321
|
-
const receipt = newestReceipt(realFilePath(filePath), expectedVersion);
|
|
322
|
-
if (!receipt) return null;
|
|
323
|
-
const { path, version, writeToken, from } = receipt;
|
|
324
|
-
return { path, version, writeToken, ...from && { from } };
|
|
325
|
-
}
|
|
326
|
-
function newestReceipt(absPath, expectedVersion) {
|
|
327
|
-
const now = Date.now();
|
|
328
|
-
const current = (receipts.get(absPath) ?? []).filter(
|
|
329
|
-
(entry) => now - entry.recordedAt < RECEIPT_TTL_MS
|
|
330
|
-
);
|
|
331
|
-
if (current.length > 0) receipts.set(absPath, current);
|
|
332
|
-
else receipts.delete(absPath);
|
|
333
|
-
let receipt;
|
|
334
|
-
for (let i = current.length - 1; i >= 0 && !receipt; i -= 1) {
|
|
335
|
-
if (current[i]?.version === expectedVersion) receipt = current[i];
|
|
336
|
-
}
|
|
337
|
-
return receipt;
|
|
338
|
-
}
|
|
339
|
-
var consumeFileWriteReceipt = identifyFileWrite;
|
|
340
|
-
var SIGNATURE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
341
|
-
".cjs",
|
|
342
|
-
".css",
|
|
343
|
-
".html",
|
|
344
|
-
".js",
|
|
345
|
-
".json",
|
|
346
|
-
".jsx",
|
|
347
|
-
".mjs",
|
|
348
|
-
".svg",
|
|
349
|
-
".ts",
|
|
350
|
-
".tsx"
|
|
351
|
-
]);
|
|
352
|
-
var SIGNATURE_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
353
|
-
".cache",
|
|
354
|
-
".git",
|
|
355
|
-
".hyperframes",
|
|
356
|
-
".next",
|
|
357
|
-
".thumbnails",
|
|
358
|
-
".transcode-cache",
|
|
359
|
-
".vite",
|
|
360
|
-
".waveform-cache",
|
|
361
|
-
"build",
|
|
362
|
-
"coverage",
|
|
363
|
-
"dist",
|
|
364
|
-
"node_modules",
|
|
365
|
-
"outputs",
|
|
366
|
-
"renders"
|
|
367
|
-
]);
|
|
368
|
-
var MAX_SIGNATURE_TEXT_BYTES = 2e6;
|
|
369
|
-
var STUDIO_SIGNATURE_MANIFEST_PATHS = [
|
|
370
|
-
".hyperframes/studio-manual-edits.json",
|
|
371
|
-
".hyperframes/studio-motion.json"
|
|
372
|
-
];
|
|
373
|
-
function affectsProjectSignature(projectDir, changedPath) {
|
|
374
|
-
const relativePath = relative(resolve(projectDir), resolve(changedPath));
|
|
375
|
-
if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath) || isAtomicTempPath(relativePath)) {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
const segments = relativePath.split(sep);
|
|
379
|
-
if (STUDIO_SIGNATURE_MANIFEST_PATHS.includes(segments.join("/"))) return true;
|
|
380
|
-
return !segments.some((segment) => SIGNATURE_EXCLUDED_DIRS.has(segment));
|
|
381
|
-
}
|
|
382
|
-
var projectSignatureCache = /* @__PURE__ */ new Map();
|
|
383
|
-
var COARSEST_FILE_TIME_TICK_MS = 2e3;
|
|
384
|
-
function isPathWithin(parentDir, childPath) {
|
|
385
|
-
const childRelativePath = relative(parentDir, childPath);
|
|
386
|
-
return childRelativePath === "" || !childRelativePath.startsWith("..") && !isAbsolute(childRelativePath);
|
|
387
|
-
}
|
|
388
|
-
function isTextContentEligible(file, size) {
|
|
389
|
-
return SIGNATURE_TEXT_EXTENSIONS.has(extname(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
|
|
390
|
-
}
|
|
391
|
-
var isSkippedEntry = (entry) => SIGNATURE_EXCLUDED_DIRS.has(entry) || isAtomicTempPath(entry);
|
|
392
|
-
function collectProjectSignatureFiles(projectDir, dir, files) {
|
|
393
|
-
let entries;
|
|
394
|
-
try {
|
|
395
|
-
entries = readdirSync(dir).sort();
|
|
396
|
-
} catch {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
for (const entry of entries) {
|
|
400
|
-
if (isSkippedEntry(entry)) continue;
|
|
401
|
-
const file = resolve(dir, entry);
|
|
402
|
-
if (!isPathWithin(projectDir, file)) continue;
|
|
403
|
-
let stat22;
|
|
404
|
-
try {
|
|
405
|
-
stat22 = lstatSync(file);
|
|
406
|
-
} catch {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
if (stat22.isSymbolicLink()) continue;
|
|
410
|
-
if (stat22.isDirectory()) {
|
|
411
|
-
collectProjectSignatureFiles(projectDir, file, files);
|
|
412
|
-
} else if (stat22.isFile()) {
|
|
413
|
-
files.push({
|
|
414
|
-
file,
|
|
415
|
-
mtimeMs: stat22.mtimeMs,
|
|
416
|
-
ctimeMs: stat22.ctimeMs,
|
|
417
|
-
size: stat22.size,
|
|
418
|
-
textContentEligible: isTextContentEligible(file, stat22.size)
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
function collectProjectSignatureManifestFiles(projectDir, files) {
|
|
424
|
-
const seen = new Set(files.map((entry) => entry.file));
|
|
425
|
-
for (const manifestPath of STUDIO_SIGNATURE_MANIFEST_PATHS) {
|
|
426
|
-
const file = resolve(projectDir, manifestPath);
|
|
427
|
-
if (seen.has(file) || !isPathWithin(projectDir, file)) continue;
|
|
428
|
-
let stat22;
|
|
429
|
-
try {
|
|
430
|
-
stat22 = lstatSync(file);
|
|
431
|
-
} catch {
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
if (stat22.isSymbolicLink() || !stat22.isFile()) continue;
|
|
435
|
-
files.push({
|
|
436
|
-
file,
|
|
437
|
-
mtimeMs: stat22.mtimeMs,
|
|
438
|
-
ctimeMs: stat22.ctimeMs,
|
|
439
|
-
size: stat22.size,
|
|
440
|
-
textContentEligible: isTextContentEligible(file, stat22.size)
|
|
441
|
-
});
|
|
442
|
-
seen.add(file);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function createProjectFingerprint(projectDir, files) {
|
|
446
|
-
const hash = createHash22("sha256");
|
|
447
|
-
for (const entry of files) {
|
|
448
|
-
hash.update(relative(projectDir, entry.file));
|
|
449
|
-
hash.update("\0");
|
|
450
|
-
hash.update(String(entry.size));
|
|
451
|
-
hash.update("\0");
|
|
452
|
-
hash.update(String(entry.mtimeMs));
|
|
453
|
-
hash.update("\0");
|
|
454
|
-
hash.update(String(entry.ctimeMs));
|
|
455
|
-
hash.update("\0");
|
|
456
|
-
hash.update(entry.textContentEligible ? "text" : "binary");
|
|
457
|
-
hash.update("\0");
|
|
458
|
-
}
|
|
459
|
-
return hash.digest("hex").slice(0, 24);
|
|
460
|
-
}
|
|
461
|
-
function collectProjectFiles(normalizedProjectDir) {
|
|
462
|
-
const collected = [];
|
|
463
|
-
collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir, collected);
|
|
464
|
-
collectProjectSignatureManifestFiles(normalizedProjectDir, collected);
|
|
465
|
-
return collected;
|
|
466
|
-
}
|
|
467
|
-
function listProjectFiles(projectDir) {
|
|
468
|
-
const normalizedProjectDir = resolve(projectDir);
|
|
469
|
-
return collectProjectFiles(normalizedProjectDir).map((entry) => ({
|
|
470
|
-
path: relative(normalizedProjectDir, entry.file).split(sep).join("/"),
|
|
471
|
-
size: entry.size,
|
|
472
|
-
mtimeMs: entry.mtimeMs,
|
|
473
|
-
ctimeMs: entry.ctimeMs
|
|
474
|
-
}));
|
|
475
|
-
}
|
|
476
|
-
function resolveProjectSignature(adapter, projectDir) {
|
|
477
|
-
return adapter.getProjectSignature?.(projectDir) ?? createProjectSignature(projectDir);
|
|
478
|
-
}
|
|
479
|
-
async function resolveProjectAndSignature(adapter, projectId) {
|
|
480
|
-
const project = await adapter.resolveProject(projectId);
|
|
481
|
-
if (!project) return null;
|
|
482
|
-
return { project, signature: resolveProjectSignature(adapter, project.dir) };
|
|
483
|
-
}
|
|
484
|
-
function createProjectSignature(projectDir, excluding = /* @__PURE__ */ new Set()) {
|
|
485
|
-
const normalizedProjectDir = resolve(projectDir);
|
|
486
|
-
const signedAt = Date.now();
|
|
487
|
-
const collected = collectProjectFiles(normalizedProjectDir);
|
|
488
|
-
const files = collected.filter(
|
|
489
|
-
(entry) => !excluding.has(relative(normalizedProjectDir, entry.file).split(sep).join("/"))
|
|
490
|
-
);
|
|
491
|
-
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
492
|
-
const fingerprint = createProjectFingerprint(normalizedProjectDir, files);
|
|
493
|
-
const cacheKey = excluding.size ? `${normalizedProjectDir}\0${createHash22("sha256").update([...excluding].sort().join("\0")).digest("hex")}` : normalizedProjectDir;
|
|
494
|
-
const cached = projectSignatureCache.get(cacheKey);
|
|
495
|
-
if (cached?.fingerprint === fingerprint) return cached.signature;
|
|
496
|
-
const hash = createHash22("sha256");
|
|
497
|
-
for (const entry of files) {
|
|
498
|
-
const relativePath = relative(normalizedProjectDir, entry.file);
|
|
499
|
-
hash.update(relativePath);
|
|
500
|
-
hash.update("\0");
|
|
501
|
-
hash.update(String(entry.size));
|
|
502
|
-
hash.update("\0");
|
|
503
|
-
if (entry.textContentEligible) {
|
|
504
|
-
try {
|
|
505
|
-
hash.update(readFileSync2(entry.file));
|
|
506
|
-
} catch {
|
|
507
|
-
hash.update(String(entry.mtimeMs));
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
hash.update(String(entry.mtimeMs));
|
|
511
|
-
}
|
|
512
|
-
hash.update("\0");
|
|
513
|
-
}
|
|
514
|
-
const signature = hash.digest("hex").slice(0, 24);
|
|
515
|
-
const settledATickBeforeSigning = files.every(
|
|
516
|
-
(entry) => Math.max(entry.mtimeMs, entry.ctimeMs) < signedAt - COARSEST_FILE_TIME_TICK_MS
|
|
517
|
-
);
|
|
518
|
-
if (settledATickBeforeSigning) projectSignatureCache.set(cacheKey, { fingerprint, signature });
|
|
519
|
-
return signature;
|
|
520
|
-
}
|
|
521
|
-
var BLOB_HASH = /^[0-9a-f]{64}$/;
|
|
522
|
-
async function hashFile(path) {
|
|
523
|
-
const hash = createHash3("sha256");
|
|
524
|
-
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
525
|
-
return hash.digest("hex");
|
|
526
|
-
}
|
|
527
|
-
async function cloneOrCopy(from, to, beforeReplace) {
|
|
528
|
-
await mkdir(dirname2(to), { recursive: true });
|
|
529
|
-
const temp = atomicTempPath(to);
|
|
530
|
-
try {
|
|
531
|
-
await copyFile(from, temp, constants2.COPYFILE_FICLONE);
|
|
532
|
-
beforeReplace?.();
|
|
533
|
-
renameSync(temp, to);
|
|
534
|
-
} catch (error) {
|
|
535
|
-
await rm(temp, { force: true });
|
|
536
|
-
throw error;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
async function openBlobStore(dir) {
|
|
540
|
-
const sizes = /* @__PURE__ */ new Map();
|
|
541
|
-
await mkdir(dir, { recursive: true });
|
|
542
|
-
for (const shard of await readdir(dir))
|
|
543
|
-
for (const hash of await readdir(join2(dir, shard)).catch(() => []))
|
|
544
|
-
sizes.set(hash, (await stat(join2(dir, shard, hash))).size);
|
|
545
|
-
const pathOf = (hash) => {
|
|
546
|
-
if (!BLOB_HASH.test(hash)) throw new Error("That is not a history blob.");
|
|
547
|
-
return join2(dir, hash.slice(0, 2), hash);
|
|
548
|
-
};
|
|
549
|
-
let total = [...sizes.values()].reduce((sum, size) => sum + size, 0);
|
|
550
|
-
return {
|
|
551
|
-
async put(absPath) {
|
|
552
|
-
const temp = join2(dir, `incoming-${randomUUID2()}`);
|
|
553
|
-
await mkdir(dir, { recursive: true });
|
|
554
|
-
await copyFile(absPath, temp, constants2.COPYFILE_FICLONE);
|
|
555
|
-
const hash = await hashFile(temp);
|
|
556
|
-
if (sizes.has(hash)) {
|
|
557
|
-
await rm(temp, { force: true });
|
|
558
|
-
return hash;
|
|
559
|
-
}
|
|
560
|
-
await mkdir(dirname2(pathOf(hash)), { recursive: true });
|
|
561
|
-
const size = (await stat(temp)).size;
|
|
562
|
-
await rename(temp, pathOf(hash));
|
|
563
|
-
sizes.set(hash, size);
|
|
564
|
-
total += size;
|
|
565
|
-
return hash;
|
|
566
|
-
},
|
|
567
|
-
has: (hash) => sizes.has(hash),
|
|
568
|
-
read: async (hash) => readFile(pathOf(hash)),
|
|
569
|
-
writeTo: async (hash, absPath, beforeReplace) => cloneOrCopy(pathOf(hash), absPath, beforeReplace),
|
|
570
|
-
bytes: () => total,
|
|
571
|
-
size: (hash) => sizes.get(hash) ?? 0,
|
|
572
|
-
async prune(keep) {
|
|
573
|
-
for (const [hash, size] of sizes) {
|
|
574
|
-
if (keep.has(hash)) continue;
|
|
575
|
-
await rm(pathOf(hash), { force: true });
|
|
576
|
-
sizes.delete(hash);
|
|
577
|
-
total -= size;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
var ID_PATH = join22(".hyperframes", "history-id");
|
|
583
|
-
var ID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
584
|
-
var isHistoryId = (name) => ID_SHAPE.test(name);
|
|
585
|
-
function readId(projectDir) {
|
|
586
|
-
try {
|
|
587
|
-
const id = readFileSync22(join22(projectDir, ID_PATH), "utf-8").trim();
|
|
588
|
-
return ID_SHAPE.test(id) ? id : null;
|
|
589
|
-
} catch {
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
var sameFolder = (a, b) => a.ino === b.ino && a.birthtimeMs === b.birthtimeMs;
|
|
594
|
-
function isRecordedFolder(historyDir, folder) {
|
|
595
|
-
const was = readRecord(historyDir);
|
|
596
|
-
if (typeof was?.ino === "number")
|
|
597
|
-
return sameFolder({ ino: was.ino, birthtimeMs: was.born ?? NaN }, folder);
|
|
598
|
-
const unreadableSo0878KeptTheId = typeof was?.dir !== "string";
|
|
599
|
-
if (unreadableSo0878KeptTheId) return true;
|
|
600
|
-
return !isCopyOf0878Folder(was.dir, basename(historyDir), folder);
|
|
601
|
-
}
|
|
602
|
-
function readRecord(historyDir) {
|
|
603
|
-
try {
|
|
604
|
-
return JSON.parse(readFileSync22(join22(historyDir, "project.json"), "utf-8"));
|
|
605
|
-
} catch {
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
function isCopyOf0878Folder(recordedDir, id, folder) {
|
|
610
|
-
let there;
|
|
611
|
-
try {
|
|
612
|
-
there = statSync(recordedDir, { throwIfNoEntry: false });
|
|
613
|
-
} catch {
|
|
614
|
-
return false;
|
|
615
|
-
}
|
|
616
|
-
return !!there && !sameFolder(there, folder) && readId(recordedDir) === id;
|
|
617
|
-
}
|
|
618
|
-
function projectHistoryId(projectDir, historyRoot) {
|
|
619
|
-
const dir = resolve2(projectDir);
|
|
620
|
-
const folder = statSync(dir);
|
|
621
|
-
let id = readId(dir);
|
|
622
|
-
if (!id && existsSync(join22(dir, ID_PATH)))
|
|
623
|
-
throw new HistoryIdError(
|
|
624
|
-
`${join22(dir, ID_PATH)} holds no history id this version can read; move it aside to start anew.`
|
|
625
|
-
);
|
|
626
|
-
if (!id || existsSync(join22(historyRoot, id)) && !isRecordedFolder(join22(historyRoot, id), folder)) {
|
|
627
|
-
id = randomUUID3();
|
|
628
|
-
mkdirWithinProject(dir, join22(dir, ".hyperframes"));
|
|
629
|
-
writeFileSync2(join22(dir, ID_PATH), `${id}
|
|
630
|
-
`);
|
|
631
|
-
}
|
|
632
|
-
recordProject(join22(historyRoot, id), dir, folder);
|
|
633
|
-
return id;
|
|
634
|
-
}
|
|
635
|
-
var HistoryIdError = class extends Error {
|
|
636
|
-
constructor(message) {
|
|
637
|
-
super(message);
|
|
638
|
-
this.name = "HistoryIdError";
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
function recordProject(historyDir, dir, folder) {
|
|
642
|
-
const record = { dir, ino: folder.ino, born: folder.birthtimeMs, dev: folder.dev };
|
|
643
|
-
mkdirSync(historyDir, { recursive: true });
|
|
644
|
-
replaceFileAtomically(join22(historyDir, "project.json"), JSON.stringify(record), 420);
|
|
645
|
-
}
|
|
646
|
-
var HistoryBusyError = class extends Error {
|
|
647
|
-
constructor(pid) {
|
|
648
|
-
super(`This project's history is open in another process (pid ${pid}).`);
|
|
649
|
-
this.pid = pid;
|
|
650
|
-
this.name = "HistoryBusyError";
|
|
651
|
-
}
|
|
652
|
-
pid;
|
|
653
|
-
};
|
|
654
|
-
function alive(pid) {
|
|
655
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
656
|
-
try {
|
|
657
|
-
process.kill(pid, 0);
|
|
658
|
-
return true;
|
|
659
|
-
} catch (error) {
|
|
660
|
-
return error.code === "EPERM";
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
function ownerOf(file) {
|
|
664
|
-
try {
|
|
665
|
-
const text2 = readFileSync3(file, "utf-8");
|
|
666
|
-
return /^\d+$/.test(text2) ? Number(text2) : Number.NaN;
|
|
667
|
-
} catch (error) {
|
|
668
|
-
if (error.code === "ENOENT") return null;
|
|
669
|
-
throw error;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
function claim(file) {
|
|
673
|
-
const draft = `${file}-${randomUUID4()}.tmp`;
|
|
674
|
-
writeFileSync22(draft, String(process.pid));
|
|
675
|
-
try {
|
|
676
|
-
linkSync(draft, file);
|
|
677
|
-
return true;
|
|
678
|
-
} catch (error) {
|
|
679
|
-
if (error.code !== "EEXIST") throw error;
|
|
680
|
-
return false;
|
|
681
|
-
} finally {
|
|
682
|
-
rmSync(draft, { force: true });
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
function releaseOwn(file) {
|
|
686
|
-
if (ownerOf(file) === process.pid) rmSync(file, { force: true });
|
|
687
|
-
}
|
|
688
|
-
function evictDeadOwner(file) {
|
|
689
|
-
const evictor = `${file}.evict`;
|
|
690
|
-
if (!claim(evictor)) {
|
|
691
|
-
const holder = ownerOf(evictor);
|
|
692
|
-
if (holder !== null && !alive(holder)) rmSync(evictor, { force: true });
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
try {
|
|
696
|
-
const pid = ownerOf(file);
|
|
697
|
-
if (pid !== null && !alive(pid)) rmSync(file, { force: true });
|
|
698
|
-
return true;
|
|
699
|
-
} finally {
|
|
700
|
-
releaseOwn(evictor);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
async function takeHistoryOwnership(home, waitMs) {
|
|
704
|
-
const file = join3(home, "owner.pid");
|
|
705
|
-
const deadline = Date.now() + waitMs;
|
|
706
|
-
mkdirSync2(home, { recursive: true });
|
|
707
|
-
for (; ; ) {
|
|
708
|
-
if (claim(file)) {
|
|
709
|
-
let held = true;
|
|
710
|
-
return () => {
|
|
711
|
-
if (held) releaseOwn(file);
|
|
712
|
-
held = false;
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
const pid = ownerOf(file);
|
|
716
|
-
if (pid === null) continue;
|
|
717
|
-
if (!alive(pid) && evictDeadOwner(file)) continue;
|
|
718
|
-
if (Date.now() >= deadline) throw new HistoryBusyError(pid);
|
|
719
|
-
await new Promise((settle) => setTimeout(settle, 50));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
723
|
-
var KEEP_GONE_PROJECT_HISTORY_MS = 14 * DAY_MS;
|
|
724
|
-
var PRUNED_PREFIX = ".pruned-";
|
|
725
|
-
var LAST_PRUNE_FILE = ".last-prune";
|
|
726
|
-
function listProjectHistories(historyRoot) {
|
|
727
|
-
return namesIn(historyRoot).filter(isHistoryId).flatMap((id) => {
|
|
728
|
-
const home = join4(historyRoot, id);
|
|
729
|
-
const projectDir = readRecord(home)?.dir;
|
|
730
|
-
if (typeof projectDir !== "string" || readId(projectDir) !== id) return [];
|
|
731
|
-
return [{ id, projectDir, lastUsedMs: lastUsed(home) }];
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
function namesIn(historyRoot) {
|
|
735
|
-
try {
|
|
736
|
-
return readdirSync2(historyRoot);
|
|
737
|
-
} catch (error) {
|
|
738
|
-
if (error.code === "ENOENT") return [];
|
|
739
|
-
throw error;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
async function pruneGoneProjectHistories(historyRoot, options = {}) {
|
|
743
|
-
return (await pruneWithin(historyRoot, options)).pruned;
|
|
744
|
-
}
|
|
745
|
-
async function pruneWithin(historyRoot, {
|
|
746
|
-
now = Date.now(),
|
|
747
|
-
dryRun = false,
|
|
748
|
-
tempDir = tmpdir(),
|
|
749
|
-
onError = (error) => console.warn("[history] could not prune a history:", error),
|
|
750
|
-
budgetMs = Infinity,
|
|
751
|
-
measure = true
|
|
752
|
-
}) {
|
|
753
|
-
const started = performance.now();
|
|
754
|
-
const names = namesIn(historyRoot);
|
|
755
|
-
const pruned = [];
|
|
756
|
-
for (const name of names) {
|
|
757
|
-
if (performance.now() - started >= budgetMs) return { pruned, finished: false };
|
|
758
|
-
try {
|
|
759
|
-
const gone = await pruneEntry(historyRoot, name, { now, dryRun, tempDir, measure });
|
|
760
|
-
if (gone) pruned.push(gone);
|
|
761
|
-
} catch (error) {
|
|
762
|
-
onError(error);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
return { pruned, finished: true };
|
|
766
|
-
}
|
|
767
|
-
async function pruneEntry(historyRoot, name, run) {
|
|
768
|
-
if (name.startsWith(PRUNED_PREFIX)) {
|
|
769
|
-
if (!run.dryRun) await rm2(join4(historyRoot, name), { recursive: true, force: true });
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
772
|
-
return isHistoryId(name) ? pruneOne(historyRoot, name, run) : null;
|
|
773
|
-
}
|
|
774
|
-
async function pruneOne(historyRoot, id, run) {
|
|
775
|
-
const home = join4(historyRoot, id);
|
|
776
|
-
if (abandonedProject(home, id, run.now, run.tempDir) === null) return null;
|
|
777
|
-
const release = await takeUnlessBusy(home);
|
|
778
|
-
if (!release) return null;
|
|
779
|
-
let taken;
|
|
780
|
-
try {
|
|
781
|
-
taken = recheckAndMoveAway(historyRoot, id, run);
|
|
782
|
-
} catch (error) {
|
|
783
|
-
release();
|
|
784
|
-
throw error;
|
|
785
|
-
}
|
|
786
|
-
if (!taken?.trash) release();
|
|
787
|
-
if (!taken) return null;
|
|
788
|
-
const bytes = await clearAway(taken.trash, home, run.measure);
|
|
789
|
-
return { id, projectDir: taken.projectDir, bytes };
|
|
790
|
-
}
|
|
791
|
-
async function clearAway(trash, home, measure) {
|
|
792
|
-
const bytes = measure ? await bytesIn(trash ?? home) : 0;
|
|
793
|
-
if (trash) await rm2(trash, { recursive: true, force: true });
|
|
794
|
-
return bytes;
|
|
795
|
-
}
|
|
796
|
-
async function takeUnlessBusy(home) {
|
|
797
|
-
try {
|
|
798
|
-
return await takeHistoryOwnership(home, 0);
|
|
799
|
-
} catch (error) {
|
|
800
|
-
if (error instanceof HistoryBusyError) return null;
|
|
801
|
-
throw error;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
function recheckAndMoveAway(historyRoot, id, { now, dryRun, tempDir }) {
|
|
805
|
-
const home = join4(historyRoot, id);
|
|
806
|
-
const projectDir = abandonedProject(home, id, now, tempDir);
|
|
807
|
-
if (projectDir === null) return null;
|
|
808
|
-
if (dryRun) return { projectDir, trash: null };
|
|
809
|
-
const trash = join4(historyRoot, `${PRUNED_PREFIX}${id}-${randomUUID5()}`);
|
|
810
|
-
renameSync2(home, trash);
|
|
811
|
-
return { projectDir, trash };
|
|
812
|
-
}
|
|
813
|
-
function abandonedProject(home, id, now, tempDir) {
|
|
814
|
-
const record = readRecord(home);
|
|
815
|
-
const dir = record?.dir;
|
|
816
|
-
if (typeof dir !== "string") return null;
|
|
817
|
-
if (now - lastUsed(home) < KEEP_GONE_PROJECT_HISTORY_MS && !isSafePath(tempDir, dir)) return null;
|
|
818
|
-
return projectGone(dir, id, record?.dev, tempDir) ? dir : null;
|
|
819
|
-
}
|
|
820
|
-
function projectGone(dir, id, dev, tempDir) {
|
|
821
|
-
if (!statSync2(join4(dir, ID_PATH), { throwIfNoEntry: false }))
|
|
822
|
-
return diskStillHere(dir, dev, tempDir);
|
|
823
|
-
const there = readId(dir);
|
|
824
|
-
return there !== null && there !== id;
|
|
825
|
-
}
|
|
826
|
-
function diskStillHere(dir, dev, tempDir) {
|
|
827
|
-
let at = dir;
|
|
828
|
-
let stat22 = statSync2(at, { throwIfNoEntry: false });
|
|
829
|
-
while (!stat22) {
|
|
830
|
-
if (dirname22(at) === at) return false;
|
|
831
|
-
at = dirname22(at);
|
|
832
|
-
stat22 = statSync2(at, { throwIfNoEntry: false });
|
|
833
|
-
}
|
|
834
|
-
if (typeof dev === "number") return stat22.dev === dev;
|
|
835
|
-
return at === dir || isSafePath(tempDir, at);
|
|
836
|
-
}
|
|
837
|
-
function lastUsed(home) {
|
|
838
|
-
const mtime = (file) => statSync2(join4(home, file), { throwIfNoEntry: false })?.mtimeMs ?? 0;
|
|
839
|
-
return Math.max(mtime("project.json"), mtime("log.jsonl"));
|
|
840
|
-
}
|
|
841
|
-
async function bytesIn(dir) {
|
|
842
|
-
let total = 0;
|
|
843
|
-
for (const entry of await readdir2(dir, { withFileTypes: true })) {
|
|
844
|
-
const path = join4(dir, entry.name);
|
|
845
|
-
total += entry.isDirectory() ? await bytesIn(path) : (await lstat(path)).size;
|
|
846
|
-
}
|
|
847
|
-
return total;
|
|
848
|
-
}
|
|
849
|
-
function prunedWithinADay(stamp, now) {
|
|
850
|
-
try {
|
|
851
|
-
const last = Number(readFileSync4(stamp, "utf-8"));
|
|
852
|
-
return last <= now && now - last < DAY_MS;
|
|
853
|
-
} catch {
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
var pruning = /* @__PURE__ */ new Set();
|
|
858
|
-
function pruneGoneProjectHistoriesDaily(historyRoot, now, { onError, budgetMs } = {}) {
|
|
859
|
-
const stamp = join4(historyRoot, LAST_PRUNE_FILE);
|
|
860
|
-
if (pruning.has(historyRoot) || prunedWithinADay(stamp, now)) return;
|
|
861
|
-
pruning.add(historyRoot);
|
|
862
|
-
setImmediate(() => {
|
|
863
|
-
pruneWithin(historyRoot, { now, onError, budgetMs, measure: false }).then(({ finished }) => finished && writeFileSync3(stamp, String(now))).catch((error) => onError?.(error)).finally(() => pruning.delete(historyRoot));
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
var START = "start";
|
|
867
|
-
function parseRecord(line) {
|
|
868
|
-
try {
|
|
869
|
-
return JSON.parse(line);
|
|
870
|
-
} catch {
|
|
871
|
-
return null;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
function readLog(file, onUnreadable) {
|
|
875
|
-
let text2;
|
|
876
|
-
try {
|
|
877
|
-
text2 = readFileSync5(file, "utf-8");
|
|
878
|
-
} catch {
|
|
879
|
-
return null;
|
|
880
|
-
}
|
|
881
|
-
const log = { baseline: /* @__PURE__ */ new Map(), entries: [], pins: /* @__PURE__ */ new Set() };
|
|
882
|
-
const lines = text2.split("\n");
|
|
883
|
-
lines.forEach((line, index) => {
|
|
884
|
-
const record = line ? parseRecord(line) : null;
|
|
885
|
-
if (record) applyRecord(log, record);
|
|
886
|
-
else if (line && index < lines.length - 1) onUnreadable(index + 1);
|
|
887
|
-
});
|
|
888
|
-
return log;
|
|
889
|
-
}
|
|
890
|
-
function applyRecord(log, record) {
|
|
891
|
-
if (record.type === "baseline") {
|
|
892
|
-
log.baseline = new Map(Object.entries(record.files));
|
|
893
|
-
log.keepsLedger = record.keepsLedger === true;
|
|
894
|
-
} else if (record.type === "entry") log.entries.push(record.entry);
|
|
895
|
-
else if (record.pinned) log.pins.add(record.id);
|
|
896
|
-
else log.pins.delete(record.id);
|
|
897
|
-
}
|
|
898
|
-
function saveRecord(file, log, record) {
|
|
899
|
-
let fd;
|
|
900
|
-
try {
|
|
901
|
-
fd = openSync2(file, constants22.O_WRONLY | constants22.O_APPEND);
|
|
902
|
-
} catch (error) {
|
|
903
|
-
if (error.code !== "ENOENT") throw error;
|
|
904
|
-
return writeLog(file, log);
|
|
905
|
-
}
|
|
906
|
-
try {
|
|
907
|
-
writeSync(fd, `${JSON.stringify(record)}
|
|
908
|
-
`);
|
|
909
|
-
} finally {
|
|
910
|
-
closeSync2(fd);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
function baselineRecord(log) {
|
|
914
|
-
return {
|
|
915
|
-
type: "baseline",
|
|
916
|
-
files: Object.fromEntries(log.baseline),
|
|
917
|
-
...log.keepsLedger && { keepsLedger: true }
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
function writeLog(file, log) {
|
|
921
|
-
const records = [
|
|
922
|
-
baselineRecord(log),
|
|
923
|
-
...log.entries.map((entry) => ({ type: "entry", entry })),
|
|
924
|
-
...[...log.pins].map((id) => ({ type: "pin", id, pinned: true }))
|
|
925
|
-
];
|
|
926
|
-
mkdirSync3(dirname3(file), { recursive: true });
|
|
927
|
-
replaceFileAtomically(file, records.map((r) => `${JSON.stringify(r)}
|
|
928
|
-
`).join(""), 420);
|
|
929
|
-
}
|
|
930
|
-
function applyEntry(manifest, entry, side) {
|
|
931
|
-
for (const file of entry.files) {
|
|
932
|
-
const hash = file[side];
|
|
933
|
-
if (hash === null) manifest.delete(file.path);
|
|
934
|
-
else manifest.set(file.path, hash);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
function manifestAt(log, point) {
|
|
938
|
-
return point === START ? new Map(log.baseline) : manifestAround(log, point, "after");
|
|
939
|
-
}
|
|
940
|
-
function manifestAround(log, id, side) {
|
|
941
|
-
const manifest = new Map(log.baseline);
|
|
942
|
-
for (const entry of log.entries) {
|
|
943
|
-
applyEntry(manifest, entry, entry.id === id ? side : "after");
|
|
944
|
-
if (entry.id === id) return manifest;
|
|
945
|
-
}
|
|
946
|
-
return null;
|
|
947
|
-
}
|
|
948
|
-
function undoneIds(entries) {
|
|
949
|
-
const undone = /* @__PURE__ */ new Set();
|
|
950
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
951
|
-
const entry = entries[i];
|
|
952
|
-
if (entry.undoes && !undone.has(entry.id)) undone.add(entry.undoes);
|
|
953
|
-
}
|
|
954
|
-
return undone;
|
|
955
|
-
}
|
|
956
|
-
function stepTarget(entries, direction, mine) {
|
|
957
|
-
const undone = undoneIds(entries);
|
|
958
|
-
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
959
|
-
const owns = ownerOf2(entries, byId, mine);
|
|
960
|
-
const newestFirst = [...entries].reverse();
|
|
961
|
-
if (direction === "back")
|
|
962
|
-
return newestFirst.find((entry) => !entry.undoes && !undone.has(entry.id) && owns(entry));
|
|
963
|
-
for (const entry of newestFirst) {
|
|
964
|
-
if (!entry.undoes) {
|
|
965
|
-
if (owns(entry)) return void 0;
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
const target = byId.get(entry.undoes);
|
|
969
|
-
if (mine(entry) && !undone.has(entry.id) && target && !target.undoes) return entry;
|
|
970
|
-
}
|
|
971
|
-
return void 0;
|
|
972
|
-
}
|
|
973
|
-
function ownerOf2(entries, byId, mine) {
|
|
974
|
-
const redoneBy = /* @__PURE__ */ new Map();
|
|
975
|
-
for (const entry of entries) {
|
|
976
|
-
const target = entry.undoes ? byId.get(entry.undoes) : void 0;
|
|
977
|
-
if (target?.undoes) redoneBy.set(target.undoes, entry);
|
|
978
|
-
}
|
|
979
|
-
return (entry) => mine(entry) || mine(redoneBy.get(entry.id) ?? entry);
|
|
980
|
-
}
|
|
981
|
-
function foldOldest(log) {
|
|
982
|
-
const oldest = log.entries[0];
|
|
983
|
-
if (!oldest || log.pins.has(oldest.id)) return false;
|
|
984
|
-
applyEntry(log.baseline, oldest, "after");
|
|
985
|
-
log.entries.shift();
|
|
986
|
-
return true;
|
|
987
|
-
}
|
|
988
|
-
function referencedHashes(log, current) {
|
|
989
|
-
const keep = /* @__PURE__ */ new Set([...log.baseline.values(), ...current.values()]);
|
|
990
|
-
for (const entry of log.entries)
|
|
991
|
-
for (const file of entry.files) {
|
|
992
|
-
if (file.before) keep.add(file.before);
|
|
993
|
-
if (file.after) keep.add(file.after);
|
|
994
|
-
}
|
|
995
|
-
return keep;
|
|
996
|
-
}
|
|
997
|
-
var DEFAULT_HISTORY_ROOT = join5(homedir(), ".cache", "hyperframes", "history");
|
|
998
|
-
var MAX_WINDOW_IDLE_MS = 10 * 6e4;
|
|
999
|
-
var UNDO_MODES = ["just-this", "back-to-before", "keep-later-edits"];
|
|
1000
|
-
var OUTSIDE = { kind: "outside", name: "Outside" };
|
|
1001
|
-
var OUTSIDE_LABEL = "Changed outside the app";
|
|
1002
|
-
var RACY_MS = 2e3;
|
|
1003
|
-
var statKey = (file, sweptAt) => sweptAt - Math.max(file.mtimeMs, file.ctimeMs) < RACY_MS ? "" : `${file.size}:${file.mtimeMs}:${file.ctimeMs}`;
|
|
1004
|
-
var MEDIA_LEDGER = ".media/manifest.jsonl";
|
|
1005
|
-
var KEPT_HIDDEN_PATHS = /* @__PURE__ */ new Set([...STUDIO_SIGNATURE_MANIFEST_PATHS, MEDIA_LEDGER]);
|
|
1006
|
-
function isHistoryPath(path) {
|
|
1007
|
-
if (KEPT_HIDDEN_PATHS.has(path)) return true;
|
|
1008
|
-
return !path.split("/").some((segment) => segment.startsWith("."));
|
|
1009
|
-
}
|
|
1010
|
-
function withoutHiddenPaths(log) {
|
|
1011
|
-
for (const path of log.baseline.keys()) if (!isHistoryPath(path)) log.baseline.delete(path);
|
|
1012
|
-
for (const entry of log.entries)
|
|
1013
|
-
entry.files = entry.files.filter((file) => isHistoryPath(file.path));
|
|
1014
|
-
return log;
|
|
1015
|
-
}
|
|
1016
|
-
var historyFiles = (dir) => listProjectFiles(dir).filter((file) => isHistoryPath(file.path));
|
|
1017
|
-
var COPY_LATER_ABOVE_BYTES = 1024 ** 2;
|
|
1018
|
-
var sameWho = (a, b) => a.kind === b.kind && a.name === b.name;
|
|
1019
|
-
function splitAt(change, at, kept) {
|
|
1020
|
-
if (at === change.after) return [change, null];
|
|
1021
|
-
if (!at || at === change.before || !kept) return [null, change];
|
|
1022
|
-
return [
|
|
1023
|
-
{ ...change, after: at },
|
|
1024
|
-
{ ...change, before: at }
|
|
1025
|
-
];
|
|
1026
|
-
}
|
|
1027
|
-
var isMissingFolder = (error) => ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
|
|
1028
|
-
async function removeEmptyFolders(dir) {
|
|
1029
|
-
let entries;
|
|
1030
|
-
try {
|
|
1031
|
-
entries = await readdir3(dir, { withFileTypes: true });
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
if (isMissingFolder(error)) return;
|
|
1034
|
-
throw error;
|
|
1035
|
-
}
|
|
1036
|
-
for (const entry of entries)
|
|
1037
|
-
if (entry.isDirectory()) await removeEmptyFolders(join5(dir, entry.name));
|
|
1038
|
-
await rmdir(dir);
|
|
1039
|
-
}
|
|
1040
|
-
function fileLeftIn(dir, at, deleted) {
|
|
1041
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1042
|
-
const path = `${at}/${entry.name}`;
|
|
1043
|
-
const left = entry.isDirectory() ? fileLeftIn(join5(dir, entry.name), path, deleted) : deleted.has(path) ? void 0 : path;
|
|
1044
|
-
if (left) return left;
|
|
1045
|
-
}
|
|
1046
|
-
return void 0;
|
|
1047
|
-
}
|
|
1048
|
-
function ignoresCase(dir) {
|
|
1049
|
-
const self = statSync3(join5(dir, ID_PATH), { throwIfNoEntry: false });
|
|
1050
|
-
const other = statSync3(join5(dir, ID_PATH.toUpperCase()), { throwIfNoEntry: false });
|
|
1051
|
-
return self !== void 0 && other?.ino === self.ino && other.dev === self.dev;
|
|
1052
|
-
}
|
|
1053
|
-
var blocks = (removed, added) => added.startsWith(`${removed}/`) || removed.startsWith(`${added}/`);
|
|
1054
|
-
var MAX_TIMER_MS = 2 ** 31 - 1;
|
|
1055
|
-
var takesWrite = (window2, at) => window2.idleMs === void 0 || at - (window2.lastWriteAt ?? at) <= window2.idleMs;
|
|
1056
|
-
function chainBackFrom(byAfter, hash) {
|
|
1057
|
-
const chain = /* @__PURE__ */ new Map();
|
|
1058
|
-
let at = hash;
|
|
1059
|
-
let prior = byAfter?.get(at);
|
|
1060
|
-
while (prior !== void 0 && !chain.has(at)) {
|
|
1061
|
-
chain.set(at, prior);
|
|
1062
|
-
at = prior;
|
|
1063
|
-
prior = byAfter?.get(at);
|
|
1064
|
-
}
|
|
1065
|
-
return chain;
|
|
1066
|
-
}
|
|
1067
|
-
function addChange(group, path, before, after) {
|
|
1068
|
-
const earlier = group.changes.get(path);
|
|
1069
|
-
const from = earlier ? earlier.before : before;
|
|
1070
|
-
if (from === after) group.changes.delete(path);
|
|
1071
|
-
else group.changes.set(path, { path, before: from, after });
|
|
1072
|
-
}
|
|
1073
|
-
var Engine = class {
|
|
1074
|
-
constructor(options, projectId, blobs) {
|
|
1075
|
-
this.options = options;
|
|
1076
|
-
this.projectId = projectId;
|
|
1077
|
-
this.blobs = blobs;
|
|
1078
|
-
this.dir = resolve3(options.projectDir);
|
|
1079
|
-
this.home = join5(options.historyRoot, projectId);
|
|
1080
|
-
this.foldsCase = ignoresCase(this.dir);
|
|
1081
|
-
const folder = statSync3(this.dir, { throwIfNoEntry: false });
|
|
1082
|
-
if (!folder || readId(this.dir) !== projectId || !isRecordedFolder(this.home, folder))
|
|
1083
|
-
throw this.replaced();
|
|
1084
|
-
this.folder = { dev: folder.dev, ino: folder.ino, birthtimeMs: folder.birthtimeMs };
|
|
1085
|
-
}
|
|
1086
|
-
options;
|
|
1087
|
-
projectId;
|
|
1088
|
-
blobs;
|
|
1089
|
-
dir;
|
|
1090
|
-
foldsCase;
|
|
1091
|
-
folder;
|
|
1092
|
-
home;
|
|
1093
|
-
log = { baseline: /* @__PURE__ */ new Map(), entries: [], pins: /* @__PURE__ */ new Set() };
|
|
1094
|
-
tracked = /* @__PURE__ */ new Map();
|
|
1095
|
-
windows = [];
|
|
1096
|
-
outside = null;
|
|
1097
|
-
/** A coalescing claim, open until another key, its idle timer, an operation, a window, or another write. */
|
|
1098
|
-
claimed = null;
|
|
1099
|
-
adopting = /* @__PURE__ */ new Map();
|
|
1100
|
-
/** Per path and hash an API write left, the hash of the bytes it replaced, until walked or written past. */
|
|
1101
|
-
overwritten = /* @__PURE__ */ new Map();
|
|
1102
|
-
stopHearing;
|
|
1103
|
-
writeToken;
|
|
1104
|
-
quietTimer;
|
|
1105
|
-
maxTimer;
|
|
1106
|
-
notedTimer = null;
|
|
1107
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1108
|
-
tail = Promise.resolve();
|
|
1109
|
-
closed = false;
|
|
1110
|
-
closing;
|
|
1111
|
-
/** A path as the project's disk compares names: folded where `A` and `a` are one name. */
|
|
1112
|
-
nameKey(path) {
|
|
1113
|
-
return this.foldsCase ? path.toLowerCase() : path;
|
|
1114
|
-
}
|
|
1115
|
-
get logFile() {
|
|
1116
|
-
return join5(this.home, "log.jsonl");
|
|
1117
|
-
}
|
|
1118
|
-
now() {
|
|
1119
|
-
return (this.options.now ?? Date.now)();
|
|
1120
|
-
}
|
|
1121
|
-
/** One operation at a time, in order: sweeps, windows and undos never interleave. */
|
|
1122
|
-
queue(task) {
|
|
1123
|
-
if (this.closed)
|
|
1124
|
-
return Promise.reject(new HistoryClosedError("This project's history is closed."));
|
|
1125
|
-
const run = this.tail.then(task);
|
|
1126
|
-
this.tail = run.catch(() => void 0);
|
|
1127
|
-
return run;
|
|
1128
|
-
}
|
|
1129
|
-
/** Another project if the folder or its id changed, or the id is gone: a recreated folder may reuse the inode. */
|
|
1130
|
-
whereFolder() {
|
|
1131
|
-
const now = statSync3(this.dir, { throwIfNoEntry: false });
|
|
1132
|
-
if (!now) return "gone";
|
|
1133
|
-
const same = now.dev === this.folder.dev && sameFolder(now, this.folder);
|
|
1134
|
-
return same && readId(this.dir) === this.projectId ? "here" : "replaced";
|
|
1135
|
-
}
|
|
1136
|
-
assertWritable() {
|
|
1137
|
-
const folder = this.whereFolder();
|
|
1138
|
-
if (folder === "gone") throw new Error(`The project folder is gone: ${this.dir}`);
|
|
1139
|
-
if (folder === "replaced") throw this.replaced();
|
|
1140
|
-
}
|
|
1141
|
-
persistLog(record) {
|
|
1142
|
-
if (record) saveRecord(this.logFile, this.log, record);
|
|
1143
|
-
else writeLog(this.logFile, this.log);
|
|
1144
|
-
if (!existsSync2(join5(this.home, "project.json")))
|
|
1145
|
-
recordProject(this.home, this.dir, this.folder);
|
|
1146
|
-
}
|
|
1147
|
-
replaced() {
|
|
1148
|
-
return new HistoryClosedError(`${this.dir} is now another project.`);
|
|
1149
|
-
}
|
|
1150
|
-
assertOpen() {
|
|
1151
|
-
if (this.closed) throw new HistoryClosedError("This project's history is closed.");
|
|
1152
|
-
if (this.whereFolder() === "replaced") throw this.replaced();
|
|
1153
|
-
}
|
|
1154
|
-
async start() {
|
|
1155
|
-
const log = readLog(
|
|
1156
|
-
this.logFile,
|
|
1157
|
-
(line) => this.options.onError?.(
|
|
1158
|
-
new Error(`History log line ${line} could not be read and was skipped.`)
|
|
1159
|
-
)
|
|
1160
|
-
);
|
|
1161
|
-
if (!log) return this.firstOpen();
|
|
1162
|
-
this.log = withoutHiddenPaths(log);
|
|
1163
|
-
if (!this.log.keepsLedger) await this.takeInUnnamedLedger();
|
|
1164
|
-
const cache = this.readStatCache();
|
|
1165
|
-
const last = this.log.entries.at(-1)?.id ?? START;
|
|
1166
|
-
for (const [path, hash] of manifestAt(this.log, last) ?? []) {
|
|
1167
|
-
const cached = cache.get(path);
|
|
1168
|
-
this.tracked.set(path, { hash, stat: cached?.hash === hash ? cached.stat : "" });
|
|
1169
|
-
}
|
|
1170
|
-
const left = this.readAdopting();
|
|
1171
|
-
const unadopted = (file) => left.has(file.path) && !this.tracked.has(file.path);
|
|
1172
|
-
this.adoptMediaInBackground(historyFiles(this.dir).filter(unadopted), this.now());
|
|
1173
|
-
this.reopenClosedWindow();
|
|
1174
|
-
await this.settleAll();
|
|
1175
|
-
}
|
|
1176
|
-
hearWrites() {
|
|
1177
|
-
const realDir = realFilePath(this.dir);
|
|
1178
|
-
this.stopHearing = onFileOverwritten((absPath, version, bytes) => {
|
|
1179
|
-
const after = hashOfVersion(version);
|
|
1180
|
-
if (!after || !affectsProjectSignature(realDir, absPath)) return;
|
|
1181
|
-
const replaced = hashOfVersion(fileContentVersion(bytes));
|
|
1182
|
-
if (!replaced || replaced === after) return;
|
|
1183
|
-
const path = relative2(realDir, absPath).split(sep2).join("/");
|
|
1184
|
-
if (!isHistoryPath(path)) return;
|
|
1185
|
-
this.overwritten.set(
|
|
1186
|
-
path,
|
|
1187
|
-
chainBackFrom(this.overwritten.get(path), replaced).set(after, replaced)
|
|
1188
|
-
);
|
|
1189
|
-
this.queue(async () => {
|
|
1190
|
-
if (!this.blobs.has(replaced)) await this.storeBytes(bytes);
|
|
1191
|
-
}).catch((error) => this.options.onError?.(error));
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
async storeBytes(bytes) {
|
|
1195
|
-
const staged = join5(this.home, `overwrote-${randomUUID6()}`);
|
|
1196
|
-
await mkdir2(this.home, { recursive: true });
|
|
1197
|
-
await writeFile(staged, bytes);
|
|
1198
|
-
try {
|
|
1199
|
-
await this.blobs.put(staged);
|
|
1200
|
-
} finally {
|
|
1201
|
-
await rm3(staged, { force: true });
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
/** Read at a state no API write left, with no write heard since `heard`: no walk passes it, so forget the rest. */
|
|
1205
|
-
forgetWritesBefore(path, hash, heard) {
|
|
1206
|
-
if (this.overwritten.get(path) === heard && !(hash && heard?.has(hash)))
|
|
1207
|
-
this.overwritten.delete(path);
|
|
1208
|
-
}
|
|
1209
|
-
reopenClosedWindow() {
|
|
1210
|
-
const closed = this.options.closedWindow;
|
|
1211
|
-
if (!closed || this.log.entries.some((entry) => entry.id === closed.id)) return;
|
|
1212
|
-
const { id, who, label, startedAt, lastWriteAt, idleMs } = closed;
|
|
1213
|
-
this.windows.push({ id, who, label, startedAt, lastWriteAt, idleMs, changes: /* @__PURE__ */ new Map() });
|
|
1214
|
-
}
|
|
1215
|
-
async takeInUnnamedLedger() {
|
|
1216
|
-
const named = (path) => this.log.entries.some((entry) => entry.files.some((file) => file.path === path));
|
|
1217
|
-
const listed = () => historyFiles(this.dir).some((file) => file.path === MEDIA_LEDGER);
|
|
1218
|
-
const hash = this.log.baseline.has(MEDIA_LEDGER) || named(MEDIA_LEDGER) || !listed() ? null : await this.storeIfPresent(MEDIA_LEDGER);
|
|
1219
|
-
if (hash !== null) this.log.baseline.set(MEDIA_LEDGER, hash);
|
|
1220
|
-
this.log.keepsLedger = true;
|
|
1221
|
-
this.persistLog();
|
|
1222
|
-
}
|
|
1223
|
-
async firstOpen() {
|
|
1224
|
-
const sweptAt = this.now();
|
|
1225
|
-
const files = historyFiles(this.dir);
|
|
1226
|
-
for (const file of files.filter((file2) => file2.size <= COPY_LATER_ABOVE_BYTES)) {
|
|
1227
|
-
const hash = await this.storeIfPresent(file.path);
|
|
1228
|
-
if (this.whereFolder() !== "here") throw this.replaced();
|
|
1229
|
-
if (hash !== null) this.tracked.set(file.path, { hash, stat: statKey(file, sweptAt) });
|
|
1230
|
-
}
|
|
1231
|
-
this.log.baseline = this.manifest();
|
|
1232
|
-
this.log.keepsLedger = true;
|
|
1233
|
-
this.persistLog();
|
|
1234
|
-
this.saveStatCache();
|
|
1235
|
-
this.adoptMediaInBackground(
|
|
1236
|
-
files.filter((file) => file.size > COPY_LATER_ABOVE_BYTES),
|
|
1237
|
-
sweptAt
|
|
1238
|
-
);
|
|
1239
|
-
}
|
|
1240
|
-
adoptMediaInBackground(files, sweptAt) {
|
|
1241
|
-
let copied = Promise.resolve();
|
|
1242
|
-
for (const file of files) {
|
|
1243
|
-
copied = copied.then(() => this.adopt(file, sweptAt));
|
|
1244
|
-
this.adopting.set(file.path, copied);
|
|
1245
|
-
}
|
|
1246
|
-
this.saveAdopting();
|
|
1247
|
-
}
|
|
1248
|
-
saveAdopting() {
|
|
1249
|
-
const file = join5(this.home, "adopting.json");
|
|
1250
|
-
if (!this.adopting.size) return rmSync2(file, { force: true });
|
|
1251
|
-
mkdirSync4(this.home, { recursive: true });
|
|
1252
|
-
replaceFileAtomically(file, JSON.stringify([...this.adopting.keys()]), 420);
|
|
1253
|
-
}
|
|
1254
|
-
async adopt(file, sweptAt) {
|
|
1255
|
-
try {
|
|
1256
|
-
if (this.whereFolder() !== "here") return;
|
|
1257
|
-
const stored = await this.storeIfPresent(file.path);
|
|
1258
|
-
await this.queue(async () => {
|
|
1259
|
-
await this.recordAdopted(file, sweptAt, stored);
|
|
1260
|
-
this.adopting.delete(file.path);
|
|
1261
|
-
this.saveAdopting();
|
|
1262
|
-
});
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
if (!(error instanceof HistoryClosedError)) this.options.onError?.(error);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
async recordAdopted(file, sweptAt, stored) {
|
|
1268
|
-
if (stored === null || this.whereFolder() !== "here") return;
|
|
1269
|
-
const hash = this.blobs.has(stored) ? stored : await this.storeIfPresent(file.path);
|
|
1270
|
-
if (hash === null) return;
|
|
1271
|
-
const change = { path: file.path, before: null, after: hash };
|
|
1272
|
-
const found = this.overwrittenBy(change, void 0, /* @__PURE__ */ new Set());
|
|
1273
|
-
const start = found && this.blobs.has(found) ? found : hash;
|
|
1274
|
-
this.tracked.set(file.path, { hash, stat: statKey(file, sweptAt) });
|
|
1275
|
-
this.log.baseline.set(file.path, start);
|
|
1276
|
-
if (start !== hash) addChange(this.outsideGroup(), file.path, start, hash);
|
|
1277
|
-
this.persistLog(baselineRecord(this.log));
|
|
1278
|
-
this.saveStatCache();
|
|
1279
|
-
}
|
|
1280
|
-
manifest() {
|
|
1281
|
-
return new Map([...this.tracked].map(([path, file]) => [path, file.hash]));
|
|
1282
|
-
}
|
|
1283
|
-
readStatCache() {
|
|
1284
|
-
try {
|
|
1285
|
-
const files = JSON.parse(readFileSync6(join5(this.home, "stat.json"), "utf-8"));
|
|
1286
|
-
return new Map(Object.entries(files));
|
|
1287
|
-
} catch {
|
|
1288
|
-
return /* @__PURE__ */ new Map();
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
readAdopting() {
|
|
1292
|
-
try {
|
|
1293
|
-
return new Set(
|
|
1294
|
-
JSON.parse(readFileSync6(join5(this.home, "adopting.json"), "utf-8"))
|
|
1295
|
-
);
|
|
1296
|
-
} catch {
|
|
1297
|
-
return /* @__PURE__ */ new Set();
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
saveStatCache() {
|
|
1301
|
-
const files = JSON.stringify(Object.fromEntries(this.tracked));
|
|
1302
|
-
mkdirSync4(this.home, { recursive: true });
|
|
1303
|
-
replaceFileAtomically(join5(this.home, "stat.json"), files, 420);
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* Takes in every write since the last sweep and files each change to its writer. ponytail: always the whole
|
|
1307
|
-
* project (a stat per file, a hash only when the stat moved); per-path sweeps if projects reach tens of thousands.
|
|
1308
|
-
*/
|
|
1309
|
-
async sweep() {
|
|
1310
|
-
if (this.whereFolder() !== "here") return;
|
|
1311
|
-
const sweptAt = this.now();
|
|
1312
|
-
const changedAt = (file) => Math.min(sweptAt, Math.max(file.mtimeMs, file.ctimeMs));
|
|
1313
|
-
const seen = historyFiles(this.dir).filter((file) => !this.adopting.has(file.path));
|
|
1314
|
-
const heard = new Map(this.overwritten);
|
|
1315
|
-
const present = new Set(seen.map((file) => file.path));
|
|
1316
|
-
const removed = [...this.tracked.keys()].filter((path) => !present.has(path)).map((path) => {
|
|
1317
|
-
const standing = this.standingAt(path);
|
|
1318
|
-
return { at: standing ? changedAt(standing.stat) : sweptAt, path, file: null };
|
|
1319
|
-
});
|
|
1320
|
-
const appearedAt = (file) => Math.max(
|
|
1321
|
-
changedAt(file),
|
|
1322
|
-
...removed.filter((gone) => blocks(this.nameKey(gone.path), this.nameKey(file.path))).map((gone) => gone.at)
|
|
1323
|
-
);
|
|
1324
|
-
const events = [
|
|
1325
|
-
...removed,
|
|
1326
|
-
...seen.map((file) => ({ at: appearedAt(file), path: file.path, file }))
|
|
1327
|
-
].sort((a, b) => a.at - b.at);
|
|
1328
|
-
let changed = false;
|
|
1329
|
-
for (const { at, path, file } of events) {
|
|
1330
|
-
if (file) {
|
|
1331
|
-
changed = await this.observe(path, statKey(file, sweptAt), at) || changed;
|
|
1332
|
-
continue;
|
|
1333
|
-
}
|
|
1334
|
-
const known = this.tracked.get(path);
|
|
1335
|
-
this.tracked.delete(path);
|
|
1336
|
-
this.forgetWritesBefore(path, null, heard.get(path));
|
|
1337
|
-
await this.record(path, known.hash, null, at);
|
|
1338
|
-
changed = true;
|
|
1339
|
-
}
|
|
1340
|
-
if (changed) this.saveStatCache();
|
|
1341
|
-
}
|
|
1342
|
-
/** The file's hash once copied in; null when it was removed before the copy (the next sweep records that). */
|
|
1343
|
-
async storeIfPresent(path) {
|
|
1344
|
-
try {
|
|
1345
|
-
return await this.blobs.put(join5(this.dir, path));
|
|
1346
|
-
} catch (error) {
|
|
1347
|
-
if (error.code === "ENOENT") return null;
|
|
1348
|
-
throw error;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
async observe(path, stat22, at) {
|
|
1352
|
-
const known = this.tracked.get(path) ?? { hash: null, stat: null };
|
|
1353
|
-
if (stat22 && known.stat === stat22) return false;
|
|
1354
|
-
const heard = this.overwritten.get(path);
|
|
1355
|
-
const hash = await this.storeIfPresent(path);
|
|
1356
|
-
if (hash === null || this.whereFolder() !== "here") return false;
|
|
1357
|
-
this.tracked.set(path, { hash, stat: stat22 });
|
|
1358
|
-
this.forgetWritesBefore(path, hash, heard);
|
|
1359
|
-
if (known.hash !== hash) await this.record(path, known.hash, hash, at);
|
|
1360
|
-
return known.hash !== hash || known.stat !== stat22;
|
|
1361
|
-
}
|
|
1362
|
-
async record(path, before, after, at) {
|
|
1363
|
-
const endedBeforeThisWrite = this.windows.filter((open2) => !takesWrite(open2, at));
|
|
1364
|
-
if (endedBeforeThisWrite.length) await this.commitOutside();
|
|
1365
|
-
for (const window22 of endedBeforeThisWrite) await this.endWindow(window22);
|
|
1366
|
-
const window2 = this.windows.at(-1);
|
|
1367
|
-
if (window2) this.touch(window2, at);
|
|
1368
|
-
addChange(window2 ?? this.outsideGroup(), path, before, after);
|
|
1369
|
-
}
|
|
1370
|
-
/** ponytail: another's write between Studio's write and its sweep folds into Studio's entry (per-write tokens). */
|
|
1371
|
-
async claimNow(who, label, paths, { coalesceKey, idleMs, overwrote = {} }) {
|
|
1372
|
-
await this.sweep();
|
|
1373
|
-
const taken = await this.takeClaimed(who, paths, overwrote);
|
|
1374
|
-
if (!taken.length) return this.claimNothing(coalesceKey);
|
|
1375
|
-
const held = this.heldFor(coalesceKey, taken);
|
|
1376
|
-
if (!held) await this.commitClaim();
|
|
1377
|
-
await this.commitPending();
|
|
1378
|
-
const group = held ?? this.newGroup(who, label);
|
|
1379
|
-
for (const change of taken) addChange(group, change.path, change.before, change.after);
|
|
1380
|
-
if (coalesceKey) return this.holdClaim(group, coalesceKey, idleMs);
|
|
1381
|
-
const entry = await this.commit(group);
|
|
1382
|
-
return entry && { id: entry.id };
|
|
1383
|
-
}
|
|
1384
|
-
/** A claim that took nothing still ends a held claim under another key. */
|
|
1385
|
-
async claimNothing(coalesceKey) {
|
|
1386
|
-
if (coalesceKey !== this.claimed?.key) await this.commitClaim();
|
|
1387
|
-
return null;
|
|
1388
|
-
}
|
|
1389
|
-
/** Commits every other writer's pending changes, oldest first; open windows stay open. */
|
|
1390
|
-
async commitPending() {
|
|
1391
|
-
await this.commitOutside();
|
|
1392
|
-
for (const window2 of [...this.windows])
|
|
1393
|
-
if (window2.changes.size) await this.commitCut(window2, [...window2.changes.keys()]);
|
|
1394
|
-
}
|
|
1395
|
-
pendingElsewhere() {
|
|
1396
|
-
return Boolean(this.outside?.changes.size) || this.windows.some((open2) => open2.changes.size > 0);
|
|
1397
|
-
}
|
|
1398
|
-
/** The held claim `taken` continues: same key, each file from its own last change, no held file changed since. */
|
|
1399
|
-
heldFor(key, taken) {
|
|
1400
|
-
const group = key && this.claimed?.key === key && !this.pendingElsewhere() ? this.claimed.group : null;
|
|
1401
|
-
const after = (path, before) => {
|
|
1402
|
-
const own = group?.changes.get(path);
|
|
1403
|
-
return own ? own.after : before;
|
|
1404
|
-
};
|
|
1405
|
-
const takenPaths = new Set(taken.map((change) => change.path));
|
|
1406
|
-
const untouched = (change) => takenPaths.has(change.path) || (this.tracked.get(change.path)?.hash ?? null) === change.after;
|
|
1407
|
-
return group && taken.every((change) => after(change.path, change.before) === change.before) && [...group.changes.values()].every(untouched) ? group : null;
|
|
1408
|
-
}
|
|
1409
|
-
logPath(path) {
|
|
1410
|
-
return relative2(this.dir, resolve3(this.dir, path)).split(sep2).join("/");
|
|
1411
|
-
}
|
|
1412
|
-
/** Takes `paths`' uncommitted changes from others, cut at what `who` overwrote. */
|
|
1413
|
-
async takeClaimed(who, paths, overwrote) {
|
|
1414
|
-
const wanted = new Set(paths.map((path) => this.logPath(path)));
|
|
1415
|
-
const at = new Map(
|
|
1416
|
-
Object.entries(overwrote).map(([path, version]) => [
|
|
1417
|
-
this.logPath(path),
|
|
1418
|
-
hashOfVersion(version)
|
|
1419
|
-
])
|
|
1420
|
-
);
|
|
1421
|
-
const others = this.windows.filter((open2) => !sameWho(open2.who, who));
|
|
1422
|
-
const groups = this.outside ? [this.outside, ...others] : others;
|
|
1423
|
-
const hits = groups.flatMap(
|
|
1424
|
-
(group) => [...group.changes.values()].filter((change) => wanted.has(change.path)).map((change) => ({ group, change }))
|
|
1425
|
-
);
|
|
1426
|
-
const cuts = [];
|
|
1427
|
-
const walked = hits.map(() => /* @__PURE__ */ new Set());
|
|
1428
|
-
for (const [i, { change }] of hits.entries()) {
|
|
1429
|
-
const told = at.get(change.path);
|
|
1430
|
-
cuts.push(this.overwrittenBy(change, told, walked[i]) ?? told);
|
|
1431
|
-
}
|
|
1432
|
-
hits.forEach(({ change }, i) => this.forgetOverwritten(change.path, walked[i]));
|
|
1433
|
-
return hits.flatMap(({ group, change }, i) => this.cutOut(group, change, cuts[i]) ?? []);
|
|
1434
|
-
}
|
|
1435
|
-
/** Walks the server's writes back from `change.after` to the stored bytes they replaced. */
|
|
1436
|
-
overwrittenBy(change, told, seen) {
|
|
1437
|
-
let found;
|
|
1438
|
-
for (let hash = change.after; hash; ) {
|
|
1439
|
-
if (seen.has(hash)) return void 0;
|
|
1440
|
-
seen.add(hash);
|
|
1441
|
-
const replaced = this.overwritten.get(change.path)?.get(hash);
|
|
1442
|
-
if (replaced === void 0) break;
|
|
1443
|
-
found = hash = replaced;
|
|
1444
|
-
if (hash === change.before || hash === told) return hash;
|
|
1445
|
-
}
|
|
1446
|
-
return found;
|
|
1447
|
-
}
|
|
1448
|
-
forgetOverwritten(path, hashes) {
|
|
1449
|
-
const byAfter = this.overwritten.get(path);
|
|
1450
|
-
for (const hash of hashes) byAfter?.delete(hash);
|
|
1451
|
-
if (byAfter?.size === 0) this.overwritten.delete(path);
|
|
1452
|
-
}
|
|
1453
|
-
cutOut(group, change, cut) {
|
|
1454
|
-
const [kept, claimed] = splitAt(change, cut, cut !== void 0 && this.blobs.has(cut));
|
|
1455
|
-
group.changes.delete(change.path);
|
|
1456
|
-
if (kept) group.changes.set(kept.path, kept);
|
|
1457
|
-
return claimed;
|
|
1458
|
-
}
|
|
1459
|
-
/** Commits the window's cut `paths` as their own entry, other files staying: a window holds one change per file,
|
|
1460
|
-
* so the writer's next write to a cut file would otherwise span the claimer's edit. */
|
|
1461
|
-
async commitCut(window2, paths) {
|
|
1462
|
-
const part = { ...this.newGroup(window2.who, window2.label), startedAt: window2.startedAt };
|
|
1463
|
-
for (const path of paths) {
|
|
1464
|
-
part.changes.set(path, window2.changes.get(path));
|
|
1465
|
-
window2.changes.delete(path);
|
|
1466
|
-
}
|
|
1467
|
-
(window2.parts ??= /* @__PURE__ */ new Set()).add(part.id);
|
|
1468
|
-
window2.entry = await this.commit(part);
|
|
1469
|
-
}
|
|
1470
|
-
holdClaim(group, key, idleMs = this.options.quietMs ?? 2e3) {
|
|
1471
|
-
clearTimeout(this.claimed?.timer);
|
|
1472
|
-
const timer = Number.isFinite(idleMs) ? setTimeout(() => this.background(() => this.commitClaim()), idleMs) : void 0;
|
|
1473
|
-
timer?.unref?.();
|
|
1474
|
-
group.lastWriteAt = this.now();
|
|
1475
|
-
this.claimed = { group, key, timer };
|
|
1476
|
-
return group.changes.size ? { id: group.id } : null;
|
|
1477
|
-
}
|
|
1478
|
-
async commitClaim() {
|
|
1479
|
-
const held = this.claimed;
|
|
1480
|
-
this.claimed = null;
|
|
1481
|
-
if (!held) return;
|
|
1482
|
-
clearTimeout(held.timer);
|
|
1483
|
-
await this.commit(held.group);
|
|
1484
|
-
}
|
|
1485
|
-
outsideGroup() {
|
|
1486
|
-
const { quietMs = 2e3, maxGroupMs = 3e4 } = this.options;
|
|
1487
|
-
const closeIn = (ms) => {
|
|
1488
|
-
const timer = setTimeout(() => this.background(() => this.commitOutside()), ms);
|
|
1489
|
-
timer.unref?.();
|
|
1490
|
-
return timer;
|
|
1491
|
-
};
|
|
1492
|
-
if (!this.outside) {
|
|
1493
|
-
this.outside = this.newGroup(OUTSIDE, OUTSIDE_LABEL);
|
|
1494
|
-
this.maxTimer = closeIn(maxGroupMs);
|
|
1495
|
-
}
|
|
1496
|
-
clearTimeout(this.quietTimer);
|
|
1497
|
-
this.quietTimer = closeIn(quietMs);
|
|
1498
|
-
return this.outside;
|
|
1499
|
-
}
|
|
1500
|
-
newGroup(who, label) {
|
|
1501
|
-
return { id: randomUUID6(), who, label, startedAt: this.now(), changes: /* @__PURE__ */ new Map() };
|
|
1502
|
-
}
|
|
1503
|
-
async commitOutside() {
|
|
1504
|
-
if (this.outside?.changes.size) await this.commitClaim();
|
|
1505
|
-
clearTimeout(this.quietTimer);
|
|
1506
|
-
clearTimeout(this.maxTimer);
|
|
1507
|
-
const group = this.outside;
|
|
1508
|
-
this.outside = null;
|
|
1509
|
-
if (group) await this.commit(group);
|
|
1510
|
-
}
|
|
1511
|
-
async commit(group, extra = {}) {
|
|
1512
|
-
const undoesEmptied = this.log.entries.find((entry2) => entry2.id === extra.undoes)?.files.length === 0;
|
|
1513
|
-
if (!group.changes.size && !undoesEmptied) return null;
|
|
1514
|
-
const pending = this.pendingEntry(group);
|
|
1515
|
-
const endedAt = Math.max(pending.endedAt, this.log.entries.at(-1)?.endedAt ?? 0);
|
|
1516
|
-
const entry = { ...pending, endedAt, ...extra };
|
|
1517
|
-
this.log.entries.push(entry);
|
|
1518
|
-
try {
|
|
1519
|
-
this.persistLog({ type: "entry", entry });
|
|
1520
|
-
} catch (error) {
|
|
1521
|
-
this.log.entries.pop();
|
|
1522
|
-
throw error;
|
|
1523
|
-
}
|
|
1524
|
-
for (const listener of this.listeners) listener(entry);
|
|
1525
|
-
for (const change of group.changes.values()) {
|
|
1526
|
-
const walked = /* @__PURE__ */ new Set();
|
|
1527
|
-
this.overwrittenBy(change, void 0, walked);
|
|
1528
|
-
this.forgetOverwritten(change.path, walked);
|
|
1529
|
-
}
|
|
1530
|
-
await this.keepWithinBudget();
|
|
1531
|
-
return entry;
|
|
1532
|
-
}
|
|
1533
|
-
/** Stored bytes beyond what the current files need: a project larger than the budget still keeps its history. */
|
|
1534
|
-
historyBytes() {
|
|
1535
|
-
let current = 0;
|
|
1536
|
-
for (const hash of new Set([...this.tracked.values()].map((file) => file.hash)))
|
|
1537
|
-
current += this.blobs.size(hash);
|
|
1538
|
-
return this.blobs.bytes() - current;
|
|
1539
|
-
}
|
|
1540
|
-
async keepWithinBudget() {
|
|
1541
|
-
const budget = this.options.budgetBytes ?? 2 * 1024 ** 3;
|
|
1542
|
-
let folded = false;
|
|
1543
|
-
while (this.historyBytes() > budget) {
|
|
1544
|
-
await this.blobs.prune(
|
|
1545
|
-
/* @__PURE__ */ new Set([
|
|
1546
|
-
...referencedHashes(this.log, this.manifest()),
|
|
1547
|
-
...this.pendingHashes(),
|
|
1548
|
-
...[...this.overwritten.values()].flatMap((byAfter) => [...byAfter.values()])
|
|
1549
|
-
])
|
|
1550
|
-
);
|
|
1551
|
-
if (this.historyBytes() <= budget || !foldOldest(this.log)) break;
|
|
1552
|
-
folded = true;
|
|
1553
|
-
}
|
|
1554
|
-
if (folded) this.persistLog();
|
|
1555
|
-
}
|
|
1556
|
-
/** Hashes only pending changes point at yet, such as a claim's stored cut. */
|
|
1557
|
-
pendingHashes() {
|
|
1558
|
-
const changes = [...this.windows, this.outside, this.claimed?.group].flatMap((group) => [
|
|
1559
|
-
...group?.changes.values() ?? []
|
|
1560
|
-
]);
|
|
1561
|
-
return changes.flatMap((change) => [change.before, change.after]).filter((hash) => hash !== null);
|
|
1562
|
-
}
|
|
1563
|
-
/** Before an operation or a window: every write is filed and every pending change committed, in write order. */
|
|
1564
|
-
async settle() {
|
|
1565
|
-
await this.sweep();
|
|
1566
|
-
await this.commitClaim();
|
|
1567
|
-
await this.commitPending();
|
|
1568
|
-
}
|
|
1569
|
-
/** A watcher saw a write: one sweep per burst takes it in (a deleted folder is reported by its name alone). */
|
|
1570
|
-
noteChange(path) {
|
|
1571
|
-
const absolute = resolve3(this.dir, path);
|
|
1572
|
-
if (this.notedTimer || !affectsProjectSignature(this.dir, absolute)) return;
|
|
1573
|
-
if (!isHistoryPath(relative2(this.dir, absolute).split(sep2).join("/"))) return;
|
|
1574
|
-
this.notedTimer = setTimeout(() => {
|
|
1575
|
-
this.notedTimer = null;
|
|
1576
|
-
this.background(() => this.sweep());
|
|
1577
|
-
}, 20);
|
|
1578
|
-
this.notedTimer.unref?.();
|
|
1579
|
-
}
|
|
1580
|
-
background(task) {
|
|
1581
|
-
this.queue(task).catch((error) => {
|
|
1582
|
-
if (!(error instanceof HistoryClosedError)) this.options.onError?.(error);
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1585
|
-
beginWindow(who, label, idleMs) {
|
|
1586
|
-
return this.queue(async () => {
|
|
1587
|
-
await this.settle();
|
|
1588
|
-
const window2 = { ...this.newGroup(who, label), idleMs };
|
|
1589
|
-
this.windows.push(window2);
|
|
1590
|
-
this.touch(window2, this.now());
|
|
1591
|
-
const close = () => this.queue(() => this.sweepAndEnd(window2));
|
|
1592
|
-
return { id: window2.id, startedAt: window2.startedAt, close };
|
|
1593
|
-
});
|
|
1594
|
-
}
|
|
1595
|
-
/** A window with no write for its idleMs ends, so a close that never comes cannot hold every later write. */
|
|
1596
|
-
touch(window2, at) {
|
|
1597
|
-
window2.lastWriteAt = Math.max(window2.lastWriteAt ?? at, at);
|
|
1598
|
-
this.endWhenIdle(window2, window2.idleMs);
|
|
1599
|
-
}
|
|
1600
|
-
endWhenIdle(window2, delay) {
|
|
1601
|
-
clearTimeout(window2.idleTimer);
|
|
1602
|
-
if (window2.idleMs === void 0 || !Number.isFinite(window2.idleMs) || delay === void 0)
|
|
1603
|
-
return;
|
|
1604
|
-
window2.idleTimer = setTimeout(
|
|
1605
|
-
() => {
|
|
1606
|
-
const left = window2.idleMs - (this.now() - (window2.lastWriteAt ?? this.now()));
|
|
1607
|
-
if (left > 0) this.endWhenIdle(window2, left);
|
|
1608
|
-
else this.background(() => this.sweepAndEnd(window2));
|
|
1609
|
-
},
|
|
1610
|
-
Math.min(delay, MAX_TIMER_MS)
|
|
1611
|
-
);
|
|
1612
|
-
window2.idleTimer.unref?.();
|
|
1613
|
-
}
|
|
1614
|
-
async sweepAndEnd(window2) {
|
|
1615
|
-
await this.sweep();
|
|
1616
|
-
return this.endWindow(window2);
|
|
1617
|
-
}
|
|
1618
|
-
/** Commits an open window once; ending it again returns the entry it became. */
|
|
1619
|
-
async endWindow(window2) {
|
|
1620
|
-
if (!this.windows.includes(window2)) return window2.entry ?? null;
|
|
1621
|
-
clearTimeout(window2.idleTimer);
|
|
1622
|
-
await this.commitClaim();
|
|
1623
|
-
this.windows = this.windows.filter((open2) => open2 !== window2);
|
|
1624
|
-
window2.entry = await this.commit(window2) ?? window2.entry ?? null;
|
|
1625
|
-
return window2.entry;
|
|
1626
|
-
}
|
|
1627
|
-
/** Every pending write, open window and outside group, committed: for flush and close. */
|
|
1628
|
-
async settleAll() {
|
|
1629
|
-
await this.sweep();
|
|
1630
|
-
await this.commitClaim();
|
|
1631
|
-
for (const window2 of [...this.windows]) await this.endWindow(window2);
|
|
1632
|
-
await this.commitOutside();
|
|
1633
|
-
}
|
|
1634
|
-
/** What took a removed path's place, dating its removal: an entry at the path, or a file at a folder above it. */
|
|
1635
|
-
standingAt(path) {
|
|
1636
|
-
const parts = path.split("/");
|
|
1637
|
-
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
1638
|
-
const at = parts.slice(0, depth).join("/");
|
|
1639
|
-
const stat22 = lstatSync2(join5(this.dir, at), { throwIfNoEntry: false });
|
|
1640
|
-
if (!stat22) return void 0;
|
|
1641
|
-
if (at === path || !stat22.isDirectory()) return { at, stat: stat22 };
|
|
1642
|
-
}
|
|
1643
|
-
return void 0;
|
|
1644
|
-
}
|
|
1645
|
-
/** What a write of `path` would have to delete: a file or link at or above it, or a file in a folder at it. */
|
|
1646
|
-
inTheWay(path, deleted) {
|
|
1647
|
-
const standing = this.standingAt(path);
|
|
1648
|
-
if (!standing) return void 0;
|
|
1649
|
-
if (standing.stat.isSymbolicLink()) return standing.at;
|
|
1650
|
-
if (standing.at === path && !standing.stat.isDirectory()) return void 0;
|
|
1651
|
-
const real = realpathSync.native(join5(this.dir, standing.at));
|
|
1652
|
-
const at = relative2(realpathSync.native(this.dir), real).split(sep2).join("/");
|
|
1653
|
-
if (standing.at !== path) return deleted.has(at) ? void 0 : at;
|
|
1654
|
-
return fileLeftIn(real, at, deleted);
|
|
1655
|
-
}
|
|
1656
|
-
/** Writes `target` (path to hash, null deletes) as one entry of `who`'s. */
|
|
1657
|
-
async writeAs(who, label, target, extra) {
|
|
1658
|
-
this.assertWritable();
|
|
1659
|
-
const changes = [...target].filter(([path, hash]) => (this.tracked.get(path)?.hash ?? null) !== hash).sort(([, a], [, b]) => Number(a !== null) - Number(b !== null));
|
|
1660
|
-
const deleted = new Set(changes.filter(([, hash]) => hash === null).map(([path]) => path));
|
|
1661
|
-
const blocked = changes.filter(([, hash]) => hash !== null).map(([path]) => this.inTheWay(path, deleted)).find(Boolean);
|
|
1662
|
-
if (blocked) throw new Error(`${blocked} is in the way; move or delete it, then try again.`);
|
|
1663
|
-
const group = this.newGroup(who, label);
|
|
1664
|
-
this.windows.push(group);
|
|
1665
|
-
try {
|
|
1666
|
-
for (const [path, hash] of changes) await this.writeProjectFile(path, hash);
|
|
1667
|
-
await this.sweep();
|
|
1668
|
-
} finally {
|
|
1669
|
-
this.windows = this.windows.filter((open2) => open2 !== group);
|
|
1670
|
-
}
|
|
1671
|
-
return this.commit(group, extra);
|
|
1672
|
-
}
|
|
1673
|
-
/** Writes one project file (null deletes it), first leaving the running operation's receipt for its echo. */
|
|
1674
|
-
async writeProjectFile(path, hash) {
|
|
1675
|
-
this.assertWritable();
|
|
1676
|
-
const absPath = join5(this.dir, path);
|
|
1677
|
-
const { writeToken } = this;
|
|
1678
|
-
if (writeToken) {
|
|
1679
|
-
const version = hash === null ? DELETED_VERSION : hashVersion(hash);
|
|
1680
|
-
recordFileWriteReceipt(absPath, { path, version, writeToken });
|
|
1681
|
-
}
|
|
1682
|
-
if (hash === null) {
|
|
1683
|
-
this.assertWritable();
|
|
1684
|
-
rmSync2(absPath, { force: true });
|
|
1685
|
-
} else {
|
|
1686
|
-
await removeEmptyFolders(absPath);
|
|
1687
|
-
await this.blobs.writeTo(hash, absPath, () => this.assertWritable());
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
entry(id) {
|
|
1691
|
-
const entry = this.log.entries.find((candidate) => candidate.id === id);
|
|
1692
|
-
if (!entry) throw new Error("That change is no longer kept in this project's history.");
|
|
1693
|
-
return entry;
|
|
1694
|
-
}
|
|
1695
|
-
undoLabel(entry) {
|
|
1696
|
-
if (!entry.undoes) return `Undid: ${entry.label}`;
|
|
1697
|
-
const original = this.log.entries.find((candidate) => candidate.id === entry.undoes);
|
|
1698
|
-
return `Redid: ${original?.label ?? entry.label}`;
|
|
1699
|
-
}
|
|
1700
|
-
async undoNow(id, who, mode) {
|
|
1701
|
-
const entry = this.entry(id);
|
|
1702
|
-
const changed = this.movedOn(entry);
|
|
1703
|
-
if (changed.length && !mode) return { ok: false, conflict: this.conflict(entry, changed) };
|
|
1704
|
-
if (mode === "back-to-before") {
|
|
1705
|
-
const index = this.log.entries.indexOf(entry);
|
|
1706
|
-
const point = index > 0 ? this.log.entries[index - 1].id : START;
|
|
1707
|
-
return {
|
|
1708
|
-
ok: true,
|
|
1709
|
-
entry: await this.restoreNow(point, who, `Went back to before: ${entry.label}`)
|
|
1710
|
-
};
|
|
1711
|
-
}
|
|
1712
|
-
const target = new Map(
|
|
1713
|
-
entry.files.filter((file) => mode !== "keep-later-edits" || !changed.includes(file)).map((file) => [file.path, file.before])
|
|
1714
|
-
);
|
|
1715
|
-
return {
|
|
1716
|
-
ok: true,
|
|
1717
|
-
entry: await this.writeAs(who, this.undoLabel(entry), target, { undoes: id })
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
movedOn(entry) {
|
|
1721
|
-
return entry.files.filter((file) => (this.tracked.get(file.path)?.hash ?? null) !== file.after);
|
|
1722
|
-
}
|
|
1723
|
-
next(direction, who) {
|
|
1724
|
-
const mine = (author) => sameWho(author, who) || sameWho(author, OUTSIDE);
|
|
1725
|
-
const pending = [...[...this.windows].reverse(), this.outside, this.claimed?.group].find(
|
|
1726
|
-
(group) => group?.changes.size && mine(group.who)
|
|
1727
|
-
);
|
|
1728
|
-
if (pending) return direction === "back" ? this.pendingEntry(pending) : void 0;
|
|
1729
|
-
const everyone = this.options.undoScope === "everyone";
|
|
1730
|
-
const ofOpenTurn = (entry) => this.windows.some((open2) => open2.parts?.has(entry.id));
|
|
1731
|
-
return stepTarget(
|
|
1732
|
-
this.log.entries,
|
|
1733
|
-
direction,
|
|
1734
|
-
(entry) => entry.files.length > 0 && (mine(entry.who) || everyone && !ofOpenTurn(entry))
|
|
1735
|
-
);
|
|
1736
|
-
}
|
|
1737
|
-
/** A pending group as the entry it becomes once committed (a window's part gets a fresh id). */
|
|
1738
|
-
pendingEntry(group) {
|
|
1739
|
-
const files = [...group.changes.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
1740
|
-
const { id, who, label, startedAt, lastWriteAt } = group;
|
|
1741
|
-
return { id, who, label, startedAt, endedAt: lastWriteAt ?? this.now(), files };
|
|
1742
|
-
}
|
|
1743
|
-
conflict(entry, changed) {
|
|
1744
|
-
const paths = new Set(changed.map((file) => file.path));
|
|
1745
|
-
const after = this.log.entries.slice(this.log.entries.indexOf(entry) + 1);
|
|
1746
|
-
const newer = after.filter((later) => later.files.some((file) => paths.has(file.path)));
|
|
1747
|
-
return { files: [...paths], newer: newer.map((later) => later.id) };
|
|
1748
|
-
}
|
|
1749
|
-
async restoreNow(point, who, label) {
|
|
1750
|
-
const files = manifestAt(this.log, point);
|
|
1751
|
-
if (!files) throw new Error("That point is no longer kept in this project's history.");
|
|
1752
|
-
const target = new Map(
|
|
1753
|
-
[...this.tracked.keys()].map((path) => [path, null])
|
|
1754
|
-
);
|
|
1755
|
-
for (const [path, hash] of files) target.set(path, hash);
|
|
1756
|
-
return this.writeAs(who, label, target, { restoredTo: point });
|
|
1757
|
-
}
|
|
1758
|
-
/** Settles, then runs `task` with its writes labelled `writeToken`. */
|
|
1759
|
-
operation(writeToken, task) {
|
|
1760
|
-
return this.queue(async () => {
|
|
1761
|
-
await this.settle();
|
|
1762
|
-
this.writeToken = writeToken;
|
|
1763
|
-
try {
|
|
1764
|
-
return await task();
|
|
1765
|
-
} finally {
|
|
1766
|
-
this.writeToken = void 0;
|
|
1767
|
-
}
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
pointLabel(point) {
|
|
1771
|
-
return point === START ? "the start" : this.entry(point).label;
|
|
1772
|
-
}
|
|
1773
|
-
api() {
|
|
1774
|
-
return {
|
|
1775
|
-
projectId: this.projectId,
|
|
1776
|
-
beginWindow: (who, label, options = {}) => this.beginWindow(who, label, options.idleMs ?? this.options.maxGroupMs ?? 3e4),
|
|
1777
|
-
claim: async (who, label, paths, options = {}) => {
|
|
1778
|
-
await Promise.all(paths.map((path) => this.adopting.get(this.logPath(path))));
|
|
1779
|
-
return this.queue(() => this.claimNow(who, label, paths, options));
|
|
1780
|
-
},
|
|
1781
|
-
noteChange: (path) => {
|
|
1782
|
-
if (!this.closed) this.noteChange(path);
|
|
1783
|
-
},
|
|
1784
|
-
list: () => {
|
|
1785
|
-
this.assertOpen();
|
|
1786
|
-
const undone = undoneIds(this.log.entries);
|
|
1787
|
-
return this.log.entries.map((entry) => ({
|
|
1788
|
-
...entry,
|
|
1789
|
-
pinned: this.log.pins.has(entry.id),
|
|
1790
|
-
undone: undone.has(entry.id)
|
|
1791
|
-
}));
|
|
1792
|
-
},
|
|
1793
|
-
step: (direction, who, { writeToken } = {}) => this.operation(writeToken, async () => {
|
|
1794
|
-
const target = this.next(direction, who);
|
|
1795
|
-
return target ? this.undoNow(target.id, who) : { ok: true, entry: null };
|
|
1796
|
-
}),
|
|
1797
|
-
undo: (id, { who, mode, writeToken }) => this.operation(writeToken, () => this.undoNow(id, who, mode)),
|
|
1798
|
-
restore: (point, who, { writeToken } = {}) => this.operation(
|
|
1799
|
-
writeToken,
|
|
1800
|
-
() => this.restoreNow(point, who, `Restored: ${this.pointLabel(point)}`)
|
|
1801
|
-
),
|
|
1802
|
-
peek: (point) => {
|
|
1803
|
-
this.assertOpen();
|
|
1804
|
-
const files = manifestAt(this.log, point);
|
|
1805
|
-
return files && Object.fromEntries(files);
|
|
1806
|
-
},
|
|
1807
|
-
checkout: (entryId, side, emptyDir) => this.queue(async () => {
|
|
1808
|
-
this.entry(entryId);
|
|
1809
|
-
if (!existsSync2(this.dir)) throw new Error(`The project folder is gone: ${this.dir}`);
|
|
1810
|
-
const dest = resolve3(emptyDir);
|
|
1811
|
-
if (isWithin(this.dir, dest))
|
|
1812
|
-
throw new Error(`Checkout writes outside the project only: ${dest}`);
|
|
1813
|
-
if ((await readdir3(dest).catch(missingIsEmpty)).length > 0)
|
|
1814
|
-
throw new Error(`Checkout writes into an empty folder only: ${dest}`);
|
|
1815
|
-
const files = manifestAround(this.log, entryId, side);
|
|
1816
|
-
try {
|
|
1817
|
-
for (const [path, hash] of files) await this.blobs.writeTo(hash, join5(dest, path));
|
|
1818
|
-
} catch (error) {
|
|
1819
|
-
for (const name of await readdir3(dest).catch(() => []))
|
|
1820
|
-
await rm3(join5(dest, name), { recursive: true, force: true });
|
|
1821
|
-
throw error;
|
|
1822
|
-
}
|
|
1823
|
-
}),
|
|
1824
|
-
next: (direction, who) => {
|
|
1825
|
-
this.assertOpen();
|
|
1826
|
-
return this.next(direction, who);
|
|
1827
|
-
},
|
|
1828
|
-
readBlob: async (hash) => {
|
|
1829
|
-
this.assertOpen();
|
|
1830
|
-
return this.blobs.read(hash);
|
|
1831
|
-
},
|
|
1832
|
-
pin: (id, pinned) => {
|
|
1833
|
-
this.assertOpen();
|
|
1834
|
-
this.entry(id);
|
|
1835
|
-
if (pinned) this.log.pins.add(id);
|
|
1836
|
-
else this.log.pins.delete(id);
|
|
1837
|
-
this.persistLog({ type: "pin", id, pinned });
|
|
1838
|
-
},
|
|
1839
|
-
onEntry: (listener) => {
|
|
1840
|
-
this.listeners.add(listener);
|
|
1841
|
-
return () => this.listeners.delete(listener);
|
|
1842
|
-
},
|
|
1843
|
-
flush: () => this.queue(() => this.settleAll()),
|
|
1844
|
-
replacedAtPath: () => this.whereFolder() === "replaced",
|
|
1845
|
-
close: () => this.closing ??= (async () => {
|
|
1846
|
-
await Promise.all(this.adopting.values());
|
|
1847
|
-
if (this.notedTimer) clearTimeout(this.notedTimer);
|
|
1848
|
-
this.stopHearing?.();
|
|
1849
|
-
const settled = this.queue(() => this.settleAll());
|
|
1850
|
-
this.closed = true;
|
|
1851
|
-
await settled;
|
|
1852
|
-
})()
|
|
1853
|
-
};
|
|
1854
|
-
}
|
|
1855
|
-
};
|
|
1856
|
-
function isWithin(dir, path) {
|
|
1857
|
-
let probe = resolve3(path);
|
|
1858
|
-
while (!existsSync2(probe) && dirname4(probe) !== probe) probe = dirname4(probe);
|
|
1859
|
-
const rel = relative2(realpathSync.native(dir), realpathSync.native(probe));
|
|
1860
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute2(rel);
|
|
1861
|
-
}
|
|
1862
|
-
function missingIsEmpty(error) {
|
|
1863
|
-
if (error.code === "ENOENT") return [];
|
|
1864
|
-
throw error;
|
|
1865
|
-
}
|
|
1866
|
-
var HistoryClosedError = class extends Error {
|
|
1867
|
-
constructor(message) {
|
|
1868
|
-
super(message);
|
|
1869
|
-
this.name = "HistoryClosedError";
|
|
1870
|
-
}
|
|
1871
|
-
};
|
|
1872
|
-
async function openProjectHistory(options) {
|
|
1873
|
-
if (!existsSync2(options.projectDir))
|
|
1874
|
-
throw new HistoryClosedError(`${options.projectDir} is gone or now another project.`);
|
|
1875
|
-
const projectId = projectHistoryId(options.projectDir, options.historyRoot);
|
|
1876
|
-
const release = await takeHistoryOwnership(
|
|
1877
|
-
join5(options.historyRoot, projectId),
|
|
1878
|
-
options.ownerWaitMs ?? 5e3
|
|
1879
|
-
);
|
|
1880
|
-
try {
|
|
1881
|
-
const blobs = await openBlobStore(join5(options.historyRoot, projectId, "blobs"));
|
|
1882
|
-
const engine = new Engine(options, projectId, blobs);
|
|
1883
|
-
await engine.queue(async () => {
|
|
1884
|
-
await engine.start();
|
|
1885
|
-
engine.hearWrites();
|
|
1886
|
-
});
|
|
1887
|
-
pruneGoneProjectHistoriesDaily(options.historyRoot, engine.now(), {
|
|
1888
|
-
onError: options.onError,
|
|
1889
|
-
budgetMs: options.pruneGoneProjectsBudgetMs
|
|
1890
|
-
});
|
|
1891
|
-
const api = engine.api();
|
|
1892
|
-
return {
|
|
1893
|
-
...api,
|
|
1894
|
-
close: () => api.close().finally(release)
|
|
1895
|
-
};
|
|
1896
|
-
} catch (error) {
|
|
1897
|
-
release();
|
|
1898
|
-
throw error;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
function historyCache(open2) {
|
|
1902
|
-
const histories = /* @__PURE__ */ new Map();
|
|
1903
|
-
const get = async (projectDir) => {
|
|
1904
|
-
let cached = histories.get(projectDir);
|
|
1905
|
-
if (!cached) histories.set(projectDir, cached = open2(projectDir));
|
|
1906
|
-
const opened = await cached;
|
|
1907
|
-
if (!opened?.replacedAtPath()) return opened;
|
|
1908
|
-
const reopen = () => open2(projectDir);
|
|
1909
|
-
if (histories.get(projectDir) === cached)
|
|
1910
|
-
histories.set(projectDir, opened.close().then(reopen, reopen));
|
|
1911
|
-
return get(projectDir);
|
|
1912
|
-
};
|
|
1913
|
-
return {
|
|
1914
|
-
get,
|
|
1915
|
-
/** The history if one was opened, without opening it. */
|
|
1916
|
-
peek: (projectDir) => histories.get(projectDir),
|
|
1917
|
-
forget: (projectDir) => histories.delete(projectDir),
|
|
1918
|
-
closeAll: () => Promise.all(
|
|
1919
|
-
[...histories.values()].map((opened) => opened.then((history) => history?.close()))
|
|
1920
|
-
)
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
245
|
// ../studio-server/dist/chunk-IMG2GSY6.js
|
|
1925
246
|
function getElementScreenshotClip(selector, selectorIndex) {
|
|
1926
247
|
let matches;
|
|
@@ -1968,7 +289,7 @@ function clearElementScreenshotIsolation() {
|
|
|
1968
289
|
}
|
|
1969
290
|
|
|
1970
291
|
// ../studio-server/dist/chunk-G7FEKQ32.js
|
|
1971
|
-
import { resolve
|
|
292
|
+
import { resolve } from "path";
|
|
1972
293
|
function isAutoProxyEnabled(adapter) {
|
|
1973
294
|
return adapter.autoProxy !== false;
|
|
1974
295
|
}
|
|
@@ -1996,7 +317,7 @@ async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeC
|
|
|
1996
317
|
recordProxyPrewarm();
|
|
1997
318
|
resolveProxy(
|
|
1998
319
|
projectDir,
|
|
1999
|
-
|
|
320
|
+
resolve(projectDir, rootRelativePathname.replace(/^\/+/, "")),
|
|
2000
321
|
proxyVariantFor(facts)
|
|
2001
322
|
).catch(() => {
|
|
2002
323
|
});
|
|
@@ -2039,52 +360,52 @@ function findUnsafeDomPatchValues(value) {
|
|
|
2039
360
|
|
|
2040
361
|
// ../studio-server/dist/index.js
|
|
2041
362
|
import { Hono } from "hono";
|
|
2042
|
-
import { readFile
|
|
2043
|
-
import { join as
|
|
363
|
+
import { readFile } from "fs/promises";
|
|
364
|
+
import { join as join2 } from "path";
|
|
2044
365
|
import { bodyLimit } from "hono/body-limit";
|
|
2045
366
|
import {
|
|
2046
|
-
closeSync as
|
|
367
|
+
closeSync as closeSync2,
|
|
2047
368
|
existsSync as existsSync4,
|
|
2048
|
-
lstatSync as
|
|
2049
|
-
openSync as
|
|
2050
|
-
readFileSync as
|
|
369
|
+
lstatSync as lstatSync2,
|
|
370
|
+
openSync as openSync2,
|
|
371
|
+
readFileSync as readFileSync5,
|
|
2051
372
|
readlinkSync,
|
|
2052
373
|
unlinkSync as unlinkSync2,
|
|
2053
374
|
rmSync as rmSync3,
|
|
2054
|
-
statSync as
|
|
375
|
+
statSync as statSync3,
|
|
2055
376
|
fstatSync as fstatSync2,
|
|
2056
|
-
renameSync as
|
|
2057
|
-
readdirSync as
|
|
2058
|
-
realpathSync
|
|
377
|
+
renameSync as renameSync2,
|
|
378
|
+
readdirSync as readdirSync2,
|
|
379
|
+
realpathSync
|
|
2059
380
|
} from "fs";
|
|
2060
|
-
import { resolve as
|
|
2061
|
-
import { readFileSync as
|
|
2062
|
-
import { createHash as
|
|
2063
|
-
import { existsSync
|
|
2064
|
-
import { join as
|
|
2065
|
-
import { existsSync as
|
|
2066
|
-
import { randomUUID
|
|
2067
|
-
import { dirname as
|
|
381
|
+
import { resolve as resolve2, dirname as dirname3, join as join6 } from "path";
|
|
382
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
383
|
+
import { createHash as createHash2 } from "crypto";
|
|
384
|
+
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
385
|
+
import { join as join22 } from "path";
|
|
386
|
+
import { existsSync as existsSync2 } from "fs";
|
|
387
|
+
import { randomUUID } from "crypto";
|
|
388
|
+
import { dirname as dirname2, relative, resolve as resolve3, sep } from "path";
|
|
2068
389
|
import { spawn } from "child_process";
|
|
2069
390
|
import {
|
|
2070
|
-
existsSync as
|
|
2071
|
-
writeFileSync as
|
|
2072
|
-
mkdirSync
|
|
2073
|
-
lstatSync
|
|
391
|
+
existsSync as existsSync3,
|
|
392
|
+
writeFileSync as writeFileSync2,
|
|
393
|
+
mkdirSync,
|
|
394
|
+
lstatSync,
|
|
2074
395
|
mkdtempSync,
|
|
2075
|
-
renameSync
|
|
2076
|
-
rmSync
|
|
2077
|
-
statSync
|
|
396
|
+
renameSync,
|
|
397
|
+
rmSync,
|
|
398
|
+
statSync
|
|
2078
399
|
} from "fs";
|
|
2079
|
-
import { dirname as
|
|
400
|
+
import { dirname as dirname22, join as join3 } from "path";
|
|
2080
401
|
import { spawnSync } from "child_process";
|
|
2081
|
-
import { mkdtempSync as mkdtempSync2, rmSync as
|
|
2082
|
-
import { tmpdir
|
|
2083
|
-
import { basename
|
|
2084
|
-
import { readdirSync
|
|
402
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync22 } from "fs";
|
|
403
|
+
import { tmpdir } from "os";
|
|
404
|
+
import { basename, join as join4 } from "path";
|
|
405
|
+
import { readdirSync, readFileSync as readFileSync32, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2085
406
|
import { Buffer as Buffer2 } from "buffer";
|
|
2086
|
-
import { join as
|
|
2087
|
-
import { readFileSync as
|
|
407
|
+
import { join as join5, relative as relative2 } from "path";
|
|
408
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
2088
409
|
|
|
2089
410
|
// ../parsers/dist/gsapConstants.js
|
|
2090
411
|
var PROPERTY_GROUPS = {
|
|
@@ -2169,7 +490,7 @@ function holdScope(before, previous) {
|
|
|
2169
490
|
held.set(a.targetSelector, props);
|
|
2170
491
|
}
|
|
2171
492
|
return {
|
|
2172
|
-
touched: (a) => previous
|
|
493
|
+
touched: (a) => previous === null || !kept.has(tweenSignature(a)),
|
|
2173
494
|
held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
|
|
2174
495
|
};
|
|
2175
496
|
}
|
|
@@ -2187,7 +508,8 @@ function keyframeHoldForAnimation(animation, animations, scope) {
|
|
|
2187
508
|
const touched = scope.touched(animation);
|
|
2188
509
|
const pinned = scope.held(animation.targetSelector);
|
|
2189
510
|
const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
|
|
2190
|
-
const
|
|
511
|
+
const startsLate = start > 1e-3;
|
|
512
|
+
const pins = (group, property, value) => (group === "position" || group === "size") && ((lone || group === "position" && startsLate) && touched || kept(property, value));
|
|
2191
513
|
const hold = {};
|
|
2192
514
|
for (const [property, value] of Object.entries(atStart.properties)) {
|
|
2193
515
|
if (!pins(classifyPropertyGroup2(property), property, value) || typeof value !== "number")
|
|
@@ -3625,8 +1947,8 @@ function upsertProp(ms, objNode, key, value) {
|
|
|
3625
1947
|
if (existing) {
|
|
3626
1948
|
ms.overwrite(existing.value.start, existing.value.end, valueToCode(value));
|
|
3627
1949
|
} else {
|
|
3628
|
-
const
|
|
3629
|
-
ms.appendLeft(objNode.end - 1, `${
|
|
1950
|
+
const sep3 = objNode.properties.length > 0 ? ", " : "";
|
|
1951
|
+
ms.appendLeft(objNode.end - 1, `${sep3}${safeKey(key)}: ${valueToCode(value)}`);
|
|
3630
1952
|
}
|
|
3631
1953
|
}
|
|
3632
1954
|
function isEditableVarKey(key) {
|
|
@@ -4244,8 +2566,8 @@ function insertNewKeyframe(ms, kfNode, percentage, pctKey, valueCode) {
|
|
|
4244
2566
|
if (insertBeforeProp) {
|
|
4245
2567
|
ms.appendLeft(insertBeforeProp.start, `${JSON.stringify(pctKey)}: ${valueCode}, `);
|
|
4246
2568
|
} else {
|
|
4247
|
-
const
|
|
4248
|
-
ms.appendLeft(kfNode.end - 1, `${
|
|
2569
|
+
const sep3 = allProps.length > 0 ? ", " : "";
|
|
2570
|
+
ms.appendLeft(kfNode.end - 1, `${sep3}${JSON.stringify(pctKey)}: ${valueCode}`);
|
|
4249
2571
|
}
|
|
4250
2572
|
}
|
|
4251
2573
|
function collapseKeyframesToFlat(ms, varsNode, source, remainingRecord) {
|
|
@@ -4723,8 +3045,8 @@ function enableArcPath(ms, call, animation, config) {
|
|
|
4723
3045
|
const k = propKeyName2(p);
|
|
4724
3046
|
return k !== "x" && k !== "y";
|
|
4725
3047
|
});
|
|
4726
|
-
const
|
|
4727
|
-
ms.appendRight(vars.start + 1, ` motionPath: ${motionPathCode}${
|
|
3048
|
+
const sep3 = survivesRemoval ? ", " : "";
|
|
3049
|
+
ms.appendRight(vars.start + 1, ` motionPath: ${motionPathCode}${sep3}`);
|
|
4728
3050
|
stripXYFromKeyframes(ms, findPropertyNode2(call.varsArg, "keyframes"));
|
|
4729
3051
|
removePropsByKey(ms, call.varsArg, /* @__PURE__ */ new Set(["x", "y"]));
|
|
4730
3052
|
return true;
|
|
@@ -5248,10 +3570,10 @@ function unrollDynamicAnimations(script, animationId, elements) {
|
|
|
5248
3570
|
}
|
|
5249
3571
|
|
|
5250
3572
|
// ../studio-server/dist/index.js
|
|
5251
|
-
import { createReadStream
|
|
3573
|
+
import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
5252
3574
|
import { Readable } from "stream";
|
|
5253
|
-
import { join as join8, resolve as
|
|
5254
|
-
import { createHash as
|
|
3575
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
3576
|
+
import { createHash as createHash22 } from "crypto";
|
|
5255
3577
|
|
|
5256
3578
|
// ../core/dist/studioPreviewMark.js
|
|
5257
3579
|
var STUDIO_PREVIEW_MARK_META = "hyperframes-studio-preview";
|
|
@@ -5260,46 +3582,46 @@ var STUDIO_PREVIEW_ERRORS = "__hfPreviewErrors";
|
|
|
5260
3582
|
|
|
5261
3583
|
// ../studio-server/dist/index.js
|
|
5262
3584
|
import { readlinkSync as readlinkSync2 } from "fs";
|
|
5263
|
-
import { dirname as
|
|
5264
|
-
import { readFileSync as
|
|
3585
|
+
import { dirname as dirname4, join as join7, parse as parse2, relative as relative3, resolve as resolve32 } from "path";
|
|
3586
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5265
3587
|
import { join as join9 } from "path";
|
|
5266
3588
|
import { streamSSE } from "hono/streaming";
|
|
5267
|
-
import { existsSync as existsSync6, lstatSync as
|
|
3589
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
5268
3590
|
import { join as join10 } from "path";
|
|
5269
3591
|
import { HTTPException } from "hono/http-exception";
|
|
5270
|
-
import { open, stat
|
|
5271
|
-
import { createHash as
|
|
3592
|
+
import { open, stat } from "fs/promises";
|
|
3593
|
+
import { createHash as createHash3 } from "crypto";
|
|
5272
3594
|
import {
|
|
5273
3595
|
closeSync as closeSync22,
|
|
5274
3596
|
fstatSync as fstatSync22,
|
|
5275
3597
|
openSync as openSync22,
|
|
5276
3598
|
readFileSync as readFileSync9,
|
|
5277
|
-
readdirSync as
|
|
5278
|
-
renameSync as
|
|
5279
|
-
rmSync as
|
|
3599
|
+
readdirSync as readdirSync4,
|
|
3600
|
+
renameSync as renameSync3,
|
|
3601
|
+
rmSync as rmSync4,
|
|
5280
3602
|
statSync as statSync6,
|
|
5281
3603
|
unlinkSync as unlinkSync4,
|
|
5282
|
-
writeFileSync as
|
|
3604
|
+
writeFileSync as writeFileSync4
|
|
5283
3605
|
} from "fs";
|
|
5284
3606
|
import { join as join11 } from "path";
|
|
5285
|
-
import { createHash as
|
|
3607
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
5286
3608
|
import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
5287
|
-
import { join as join12, resolve as
|
|
3609
|
+
import { join as join12, resolve as resolve5 } from "path";
|
|
5288
3610
|
import { execFile } from "child_process";
|
|
5289
3611
|
import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
5290
|
-
import { basename as
|
|
3612
|
+
import { basename as basename2, dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
5291
3613
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
5292
3614
|
import { existsSync as existsSync8 } from "fs";
|
|
5293
3615
|
import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
5294
3616
|
import { join as join14, resolve as resolve6 } from "path";
|
|
5295
3617
|
import { execFile as execFile2, spawn as spawn2 } from "child_process";
|
|
5296
3618
|
import { promisify } from "util";
|
|
5297
|
-
import { closeSync as
|
|
3619
|
+
import { closeSync as closeSync3, constants as constants2, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
|
|
5298
3620
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5299
3621
|
import { existsSync as existsSync10 } from "fs";
|
|
5300
|
-
import { basename as basename3, dirname as dirname6, extname
|
|
3622
|
+
import { basename as basename3, dirname as dirname6, extname, join as join15, posix } from "path";
|
|
5301
3623
|
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
5302
|
-
import { homedir
|
|
3624
|
+
import { homedir } from "os";
|
|
5303
3625
|
import { join as join16 } from "path";
|
|
5304
3626
|
import { createHash as createHash6 } from "crypto";
|
|
5305
3627
|
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
|
|
@@ -5309,7 +3631,7 @@ async function filterCompositionFiles(projectDir, files) {
|
|
|
5309
3631
|
const checks = await Promise.all(
|
|
5310
3632
|
htmlFiles.map(async (f) => {
|
|
5311
3633
|
try {
|
|
5312
|
-
const content = await
|
|
3634
|
+
const content = await readFile(join2(projectDir, f), "utf-8");
|
|
5313
3635
|
return isCompositionSource(content);
|
|
5314
3636
|
} catch {
|
|
5315
3637
|
return false;
|
|
@@ -5390,7 +3712,7 @@ function isAudioFile(name) {
|
|
|
5390
3712
|
return (getMimeType(name) ?? "").startsWith("audio/");
|
|
5391
3713
|
}
|
|
5392
3714
|
function rewriteRelativePaths(root, compPath, projectDir) {
|
|
5393
|
-
const assetExists = (path) =>
|
|
3715
|
+
const assetExists = (path) => existsSync(join22(projectDir, path));
|
|
5394
3716
|
rewriteAssetPaths(
|
|
5395
3717
|
root.querySelectorAll("[src], [href]"),
|
|
5396
3718
|
compPath,
|
|
@@ -5490,9 +3812,9 @@ function rootHeadContent(rootHtml) {
|
|
|
5490
3812
|
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
5491
3813
|
}
|
|
5492
3814
|
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
5493
|
-
const compFile =
|
|
5494
|
-
if (!
|
|
5495
|
-
const rawComp = rawOverride ??
|
|
3815
|
+
const compFile = join22(projectDir, compPath);
|
|
3816
|
+
if (!existsSync(compFile)) return null;
|
|
3817
|
+
const rawComp = rawOverride ?? readFileSync3(compFile, "utf-8");
|
|
5496
3818
|
let compHeadContent = "";
|
|
5497
3819
|
let rewrittenContent;
|
|
5498
3820
|
let htmlAttrs = "";
|
|
@@ -5527,9 +3849,9 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
5527
3849
|
deferScriptsUntilFonts(scriptsDoc);
|
|
5528
3850
|
rewrittenContent = scriptsDoc.body.innerHTML;
|
|
5529
3851
|
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
5530
|
-
const indexPath =
|
|
3852
|
+
const indexPath = join22(projectDir, "index.html");
|
|
5531
3853
|
let headContent = "";
|
|
5532
|
-
if (
|
|
3854
|
+
if (existsSync(indexPath)) headContent = rootHeadContent(readFileSync3(indexPath, "utf-8"));
|
|
5533
3855
|
if (baseHref && !hasBaseElement(headContent)) {
|
|
5534
3856
|
headContent = `<base href="${baseHref}">
|
|
5535
3857
|
${headContent}`;
|
|
@@ -5591,7 +3913,7 @@ function canonicalProjectPath(projectDir, candidate) {
|
|
|
5591
3913
|
if (folderGone(projectDir)) throw new ProjectRootMissingError(projectDir);
|
|
5592
3914
|
throw new CompositionInsertionError("Composition source escapes the project", 400);
|
|
5593
3915
|
}
|
|
5594
|
-
if (!
|
|
3916
|
+
if (!existsSync2(candidate)) {
|
|
5595
3917
|
throw new CompositionInsertionError("Composition source was not found", 404);
|
|
5596
3918
|
}
|
|
5597
3919
|
const canonical = realpath(candidate);
|
|
@@ -5623,7 +3945,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
|
|
|
5623
3945
|
validateSourcePath(sourcePath);
|
|
5624
3946
|
return canonicalProjectPath(
|
|
5625
3947
|
projectDir,
|
|
5626
|
-
resolveWithinProject(projectDir,
|
|
3948
|
+
resolveWithinProject(projectDir, relative(projectDir, resolve3(dirname2(ownerAbs), sourcePath)))
|
|
5627
3949
|
);
|
|
5628
3950
|
}
|
|
5629
3951
|
function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
|
|
@@ -5696,7 +4018,7 @@ function uniqueHostId(root, base) {
|
|
|
5696
4018
|
return `${base}_${suffix}`;
|
|
5697
4019
|
}
|
|
5698
4020
|
function relativeSourcePath(targetAbs, sourceAbs) {
|
|
5699
|
-
return
|
|
4021
|
+
return relative(dirname2(targetAbs), sourceAbs).split(sep).join("/");
|
|
5700
4022
|
}
|
|
5701
4023
|
function insertCompositionIntoSource(input) {
|
|
5702
4024
|
const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
|
|
@@ -5731,7 +4053,7 @@ function insertCompositionIntoSource(input) {
|
|
|
5731
4053
|
const host = document2.createElement("div");
|
|
5732
4054
|
host.id = hostId;
|
|
5733
4055
|
host.className = "clip";
|
|
5734
|
-
host.setAttribute("data-hf-id", `hf-${
|
|
4056
|
+
host.setAttribute("data-hf-id", `hf-${randomUUID()}`);
|
|
5735
4057
|
host.setAttribute("data-composition-id", hostId);
|
|
5736
4058
|
host.setAttribute("data-composition-src", relativeSourcePath(targetAbs, sourceAbs));
|
|
5737
4059
|
host.setAttribute("data-start", String(Math.round(input.start * 100) / 100));
|
|
@@ -5760,7 +4082,7 @@ function projectReader(projectDir) {
|
|
|
5760
4082
|
const file = resolveWithinProject(projectDir, compPath);
|
|
5761
4083
|
let source = null;
|
|
5762
4084
|
try {
|
|
5763
|
-
source = file ?
|
|
4085
|
+
source = file ? readFileSync2(file, "utf-8") : null;
|
|
5764
4086
|
} catch {
|
|
5765
4087
|
}
|
|
5766
4088
|
sources.set(compPath, source);
|
|
@@ -5812,7 +4134,7 @@ function lengthIsTimeline(projectDir, compPath) {
|
|
|
5812
4134
|
}
|
|
5813
4135
|
var inputSignatures = /* @__PURE__ */ new Map();
|
|
5814
4136
|
var rootMemos = /* @__PURE__ */ new Map();
|
|
5815
|
-
var sha1 = (text2) =>
|
|
4137
|
+
var sha1 = (text2) => createHash2("sha1").update(text2).digest("hex");
|
|
5816
4138
|
function rootHashes(read) {
|
|
5817
4139
|
const source = read(ROOT_COMPOSITION) ?? "";
|
|
5818
4140
|
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
@@ -5918,36 +4240,36 @@ function decodeAudioPeaks(audioPath) {
|
|
|
5918
4240
|
});
|
|
5919
4241
|
}
|
|
5920
4242
|
async function generateWaveformCache(projectDir, assetPath) {
|
|
5921
|
-
const audioPath =
|
|
5922
|
-
if (!
|
|
5923
|
-
const stats =
|
|
5924
|
-
const cacheDir =
|
|
5925
|
-
const cachePath =
|
|
5926
|
-
if (isWaveformCacheDirectory(cacheDir) &&
|
|
4243
|
+
const audioPath = join3(projectDir, assetPath);
|
|
4244
|
+
if (!existsSync3(audioPath)) return;
|
|
4245
|
+
const stats = statSync(audioPath);
|
|
4246
|
+
const cacheDir = join3(projectDir, ".waveform-cache");
|
|
4247
|
+
const cachePath = join3(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
4248
|
+
if (isWaveformCacheDirectory(cacheDir) && existsSync3(cachePath)) return;
|
|
5927
4249
|
const peaks = await decodeAudioPeaks(audioPath);
|
|
5928
4250
|
writeWaveformCache(cachePath, peaks);
|
|
5929
4251
|
}
|
|
5930
4252
|
function isWaveformCacheDirectory(cacheDir) {
|
|
5931
|
-
return
|
|
4253
|
+
return lstatSync(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
5932
4254
|
}
|
|
5933
4255
|
function writeWaveformCache(cachePath, peaks) {
|
|
5934
|
-
const cacheDir =
|
|
4256
|
+
const cacheDir = dirname22(cachePath);
|
|
5935
4257
|
try {
|
|
5936
|
-
|
|
4258
|
+
mkdirSync(cacheDir, { mode: 448 });
|
|
5937
4259
|
} catch (error) {
|
|
5938
4260
|
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
5939
4261
|
}
|
|
5940
4262
|
if (!isWaveformCacheDirectory(cacheDir)) {
|
|
5941
4263
|
throw new Error("Waveform cache must be a directory, not a symlink");
|
|
5942
4264
|
}
|
|
5943
|
-
const stagingDir = mkdtempSync(
|
|
4265
|
+
const stagingDir = mkdtempSync(join3(cacheDir, ".waveform-"));
|
|
5944
4266
|
try {
|
|
5945
|
-
const stagingPath =
|
|
5946
|
-
|
|
5947
|
-
|
|
4267
|
+
const stagingPath = join3(stagingDir, "peaks.json");
|
|
4268
|
+
writeFileSync2(stagingPath, JSON.stringify(peaks), { flag: "wx" });
|
|
4269
|
+
renameSync(stagingPath, cachePath);
|
|
5948
4270
|
} finally {
|
|
5949
4271
|
try {
|
|
5950
|
-
|
|
4272
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
5951
4273
|
} catch {
|
|
5952
4274
|
}
|
|
5953
4275
|
}
|
|
@@ -6001,13 +4323,13 @@ function checkStreams(stdout, isVideo, isAudio) {
|
|
|
6001
4323
|
}
|
|
6002
4324
|
}
|
|
6003
4325
|
function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
|
|
6004
|
-
const tempDir = mkdtempSync2(
|
|
6005
|
-
const tempPath =
|
|
4326
|
+
const tempDir = mkdtempSync2(join4(tmpdir(), "hyperframes-upload-"));
|
|
4327
|
+
const tempPath = join4(tempDir, basename(fileName));
|
|
6006
4328
|
try {
|
|
6007
|
-
|
|
4329
|
+
writeFileSync22(tempPath, buffer);
|
|
6008
4330
|
return validateUploadedMedia(tempPath, runner);
|
|
6009
4331
|
} finally {
|
|
6010
|
-
|
|
4332
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
6011
4333
|
}
|
|
6012
4334
|
}
|
|
6013
4335
|
var DEFAULT_KEEP_PER_FILE = 10;
|
|
@@ -6019,7 +4341,7 @@ function timestampPrefix() {
|
|
|
6019
4341
|
}
|
|
6020
4342
|
function backupPathForResponse(projectDir, backupPath) {
|
|
6021
4343
|
if (!backupPath) return null;
|
|
6022
|
-
const rel =
|
|
4344
|
+
const rel = relative2(projectDir, backupPath);
|
|
6023
4345
|
if (!rel || rel.startsWith("..")) return null;
|
|
6024
4346
|
return rel.split("\\").join("/");
|
|
6025
4347
|
}
|
|
@@ -6027,12 +4349,12 @@ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
|
|
|
6027
4349
|
if (!isSafePath(projectDir, absPath)) return { backupPath: null };
|
|
6028
4350
|
try {
|
|
6029
4351
|
const content = readFileSync32(absPath);
|
|
6030
|
-
const relativePath =
|
|
6031
|
-
const backupDir =
|
|
4352
|
+
const relativePath = relative2(projectDir, absPath);
|
|
4353
|
+
const backupDir = join5(projectDir, ".hyperframes", "backup");
|
|
6032
4354
|
mkdirWithinProject(projectDir, backupDir);
|
|
6033
4355
|
const backupKey = backupKeyForPath(relativePath);
|
|
6034
4356
|
const backupPath = nextBackupPath(backupDir, backupKey);
|
|
6035
|
-
|
|
4357
|
+
writeFileSync3(backupPath, content);
|
|
6036
4358
|
pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
|
|
6037
4359
|
return { backupPath };
|
|
6038
4360
|
} catch (error) {
|
|
@@ -6045,7 +4367,7 @@ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
|
|
|
6045
4367
|
}
|
|
6046
4368
|
function nextBackupPath(backupDir, backupKey) {
|
|
6047
4369
|
const base = `${timestampPrefix()}-${backupKey}`;
|
|
6048
|
-
let candidate =
|
|
4370
|
+
let candidate = join5(backupDir, base);
|
|
6049
4371
|
let counter = 2;
|
|
6050
4372
|
while (true) {
|
|
6051
4373
|
try {
|
|
@@ -6056,7 +4378,7 @@ function nextBackupPath(backupDir, backupKey) {
|
|
|
6056
4378
|
}
|
|
6057
4379
|
throw error;
|
|
6058
4380
|
}
|
|
6059
|
-
candidate =
|
|
4381
|
+
candidate = join5(backupDir, `${base}-${counter}`);
|
|
6060
4382
|
counter += 1;
|
|
6061
4383
|
}
|
|
6062
4384
|
}
|
|
@@ -6064,7 +4386,7 @@ function pruneBackups(backupDir, backupKey, keepPerFile) {
|
|
|
6064
4386
|
const keep = Math.max(1, Math.floor(keepPerFile));
|
|
6065
4387
|
const suffix = `-${backupKey}`;
|
|
6066
4388
|
const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
|
|
6067
|
-
const matches =
|
|
4389
|
+
const matches = readdirSync(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join5(backupDir, name)).sort((a, b) => {
|
|
6068
4390
|
return b.localeCompare(a);
|
|
6069
4391
|
});
|
|
6070
4392
|
for (const file of matches.slice(keep)) {
|
|
@@ -6081,26 +4403,26 @@ function replaceWithProjectDirMissing(c, hostHeaders) {
|
|
|
6081
4403
|
c.res = void 0;
|
|
6082
4404
|
c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
|
|
6083
4405
|
}
|
|
6084
|
-
function applyFileMutations(projectDir, mutations, requestToken,
|
|
4406
|
+
function applyFileMutations(projectDir, mutations, requestToken, writeFile = (path, content) => replaceFileAtomically(path, content, statSync2(path).mode)) {
|
|
6085
4407
|
const prepared = mutations.map((mutation) => ({
|
|
6086
4408
|
...mutation,
|
|
6087
|
-
before: mutation.before ??
|
|
4409
|
+
before: mutation.before ?? readFileSync4(mutation.absPath, "utf-8")
|
|
6088
4410
|
}));
|
|
6089
4411
|
const results = [];
|
|
6090
4412
|
const attempted = [];
|
|
6091
4413
|
for (const mutation of prepared)
|
|
6092
|
-
assertExpectedVersion(mutation,
|
|
4414
|
+
assertExpectedVersion(mutation, readFileSync4(mutation.absPath, "utf-8"));
|
|
6093
4415
|
try {
|
|
6094
4416
|
for (const mutation of prepared) {
|
|
6095
|
-
results.push(applyOneMutation(projectDir, mutation, requestToken,
|
|
4417
|
+
results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted));
|
|
6096
4418
|
}
|
|
6097
4419
|
return results;
|
|
6098
4420
|
} catch (error) {
|
|
6099
4421
|
const rollbackErrors = [];
|
|
6100
4422
|
for (const mutation of attempted.reverse()) {
|
|
6101
4423
|
try {
|
|
6102
|
-
if (!mutation.written ||
|
|
6103
|
-
|
|
4424
|
+
if (!mutation.written || readFileSync4(mutation.absPath, "utf-8") === mutation.after)
|
|
4425
|
+
writeFile(mutation.absPath, mutation.before, "utf-8");
|
|
6104
4426
|
clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
|
|
6105
4427
|
} catch (rollbackError) {
|
|
6106
4428
|
rollbackErrors.push(rollbackError);
|
|
@@ -6115,8 +4437,8 @@ function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (p
|
|
|
6115
4437
|
throw error;
|
|
6116
4438
|
}
|
|
6117
4439
|
}
|
|
6118
|
-
function applyOneMutation(projectDir, mutation, requestToken,
|
|
6119
|
-
const current =
|
|
4440
|
+
function applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted) {
|
|
4441
|
+
const current = readFileSync4(mutation.absPath, "utf-8");
|
|
6120
4442
|
assertExpectedVersion(mutation, current);
|
|
6121
4443
|
if (mutation.after === mutation.before) {
|
|
6122
4444
|
return {
|
|
@@ -6130,13 +4452,13 @@ function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attemp
|
|
|
6130
4452
|
}
|
|
6131
4453
|
const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
|
|
6132
4454
|
if (backup.error) throw new Error(`backup failed: ${backup.error}`);
|
|
6133
|
-
assertExpectedVersion(mutation,
|
|
4455
|
+
assertExpectedVersion(mutation, readFileSync4(mutation.absPath, "utf-8"));
|
|
6134
4456
|
const before = current;
|
|
6135
4457
|
const version = fileContentVersion(mutation.after);
|
|
6136
4458
|
const writeToken = createWriteToken(requestToken);
|
|
6137
4459
|
const attempt = { ...mutation, before, version, writeToken, written: false };
|
|
6138
4460
|
attempted.push(attempt);
|
|
6139
|
-
|
|
4461
|
+
writeFile(mutation.absPath, mutation.after, "utf-8");
|
|
6140
4462
|
attempt.written = true;
|
|
6141
4463
|
recordFileWriteReceipt(mutation.absPath, {
|
|
6142
4464
|
path: mutation.sourceFile,
|
|
@@ -6254,21 +4576,21 @@ function requestSubPath(url, route) {
|
|
|
6254
4576
|
return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
|
|
6255
4577
|
}
|
|
6256
4578
|
async function loadGsapParser() {
|
|
6257
|
-
return import("./gsapParser-
|
|
4579
|
+
return import("./gsapParser-UP3PNPC2.js");
|
|
6258
4580
|
}
|
|
6259
4581
|
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
6260
|
-
if (!isSafePath(projectDir,
|
|
4582
|
+
if (!isSafePath(projectDir, dirname3(lexicalPath))) return false;
|
|
6261
4583
|
let stats;
|
|
6262
4584
|
try {
|
|
6263
|
-
stats =
|
|
4585
|
+
stats = lstatSync2(lexicalPath);
|
|
6264
4586
|
} catch {
|
|
6265
4587
|
return false;
|
|
6266
4588
|
}
|
|
6267
4589
|
if (!stats.isSymbolicLink()) return false;
|
|
6268
|
-
const target =
|
|
4590
|
+
const target = resolve2(dirname3(lexicalPath), readlinkSync(lexicalPath));
|
|
6269
4591
|
if (!isSafePath(projectDir, target)) return false;
|
|
6270
4592
|
try {
|
|
6271
|
-
|
|
4593
|
+
statSync3(lexicalPath);
|
|
6272
4594
|
return false;
|
|
6273
4595
|
} catch {
|
|
6274
4596
|
return true;
|
|
@@ -6289,7 +4611,7 @@ async function resolveProjectPath(c, adapter, route, opts) {
|
|
|
6289
4611
|
}
|
|
6290
4612
|
const absPath = (opts?.pin ? pinWithinProject : resolveWithinProject)(project.dir, filePath);
|
|
6291
4613
|
if (!absPath) {
|
|
6292
|
-
if (isDanglingSymlinkInProject(project.dir,
|
|
4614
|
+
if (isDanglingSymlinkInProject(project.dir, resolve2(project.dir, filePath))) {
|
|
6293
4615
|
return { error: c.json({ error: "not found", why: "dangling_symlink" }, 404) };
|
|
6294
4616
|
}
|
|
6295
4617
|
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
@@ -6360,7 +4682,7 @@ function projectHtmlIds(projectDir) {
|
|
|
6360
4682
|
if (!rel.endsWith(".html") || isInHiddenOrVendorDir(rel)) continue;
|
|
6361
4683
|
let html;
|
|
6362
4684
|
try {
|
|
6363
|
-
html =
|
|
4685
|
+
html = readFileSync5(join6(projectDir, rel), "utf-8");
|
|
6364
4686
|
} catch {
|
|
6365
4687
|
continue;
|
|
6366
4688
|
}
|
|
@@ -6368,10 +4690,10 @@ function projectHtmlIds(projectDir) {
|
|
|
6368
4690
|
}
|
|
6369
4691
|
return ids;
|
|
6370
4692
|
}
|
|
6371
|
-
function commitElementPatchBatches(projectDir, batches,
|
|
4693
|
+
function commitElementPatchBatches(projectDir, batches, writeFile = (path, content) => replaceFileAtomically(path, content, statSync3(path).mode), requestToken) {
|
|
6372
4694
|
for (let attempt = 1; ; attempt += 1) {
|
|
6373
4695
|
try {
|
|
6374
|
-
return foldAndCommitElementPatchBatches(projectDir, batches,
|
|
4696
|
+
return foldAndCommitElementPatchBatches(projectDir, batches, writeFile, requestToken);
|
|
6375
4697
|
} catch (error) {
|
|
6376
4698
|
if (!(error instanceof FileChangedError)) throw error;
|
|
6377
4699
|
if (attempt === PATCH_CONFLICT_ATTEMPTS)
|
|
@@ -6379,7 +4701,7 @@ function commitElementPatchBatches(projectDir, batches, writeFile2 = (path, cont
|
|
|
6379
4701
|
}
|
|
6380
4702
|
}
|
|
6381
4703
|
}
|
|
6382
|
-
function foldAndCommitElementPatchBatches(projectDir, batches,
|
|
4704
|
+
function foldAndCommitElementPatchBatches(projectDir, batches, writeFile, requestToken) {
|
|
6383
4705
|
const resolvedPaths = /* @__PURE__ */ new Set();
|
|
6384
4706
|
const prepared = [];
|
|
6385
4707
|
for (const batch of batches) {
|
|
@@ -6389,7 +4711,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
|
|
|
6389
4711
|
resolvedPaths.add(absPath);
|
|
6390
4712
|
let before;
|
|
6391
4713
|
try {
|
|
6392
|
-
before =
|
|
4714
|
+
before = readFileSync5(absPath, "utf-8");
|
|
6393
4715
|
} catch {
|
|
6394
4716
|
return { error: "not-found", sourceFile: batch.sourceFile };
|
|
6395
4717
|
}
|
|
@@ -6424,7 +4746,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
|
|
|
6424
4746
|
after
|
|
6425
4747
|
})),
|
|
6426
4748
|
requestToken,
|
|
6427
|
-
|
|
4749
|
+
writeFile
|
|
6428
4750
|
);
|
|
6429
4751
|
const files = applied.map((file, index) => ({
|
|
6430
4752
|
sourceFile: file.sourceFile,
|
|
@@ -6445,8 +4767,8 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
|
|
|
6445
4767
|
);
|
|
6446
4768
|
}
|
|
6447
4769
|
function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
6448
|
-
const overwrote =
|
|
6449
|
-
replaceFileAtomically(absPath, html,
|
|
4770
|
+
const overwrote = readFileSync5(absPath);
|
|
4771
|
+
replaceFileAtomically(absPath, html, statSync3(absPath).mode);
|
|
6450
4772
|
const version = fileContentVersion(html);
|
|
6451
4773
|
const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
|
|
6452
4774
|
recordFileWriteReceipt(absPath, { path: filePath, version, writeToken, overwrote });
|
|
@@ -6455,7 +4777,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
|
6455
4777
|
function writeMutationResult(c, projectDir, filePath, absPath, html, original) {
|
|
6456
4778
|
const backup = snapshotBeforeWrite(projectDir, absPath);
|
|
6457
4779
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
6458
|
-
if (
|
|
4780
|
+
if (readFileSync5(absPath, "utf-8") !== original) {
|
|
6459
4781
|
return c.json({ error: "file changed", conflict: true, path: filePath }, 409);
|
|
6460
4782
|
}
|
|
6461
4783
|
const { version } = writeFileWithReceipt(c, filePath, absPath, html);
|
|
@@ -6501,7 +4823,7 @@ async function parseMutationBody(c) {
|
|
|
6501
4823
|
return { target: body.target, body };
|
|
6502
4824
|
}
|
|
6503
4825
|
function ensureDir(projectDir, filePath) {
|
|
6504
|
-
mkdirWithinProject(projectDir,
|
|
4826
|
+
mkdirWithinProject(projectDir, dirname3(filePath));
|
|
6505
4827
|
}
|
|
6506
4828
|
function generateCopyPath(projectDir, originalPath) {
|
|
6507
4829
|
const ext = originalPath.includes(".") ? "." + originalPath.split(".").pop() : "";
|
|
@@ -6510,7 +4832,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6510
4832
|
const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
|
|
6511
4833
|
let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
|
|
6512
4834
|
let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
|
|
6513
|
-
while (existsSync4(
|
|
4835
|
+
while (existsSync4(resolve2(projectDir, candidate))) {
|
|
6514
4836
|
num++;
|
|
6515
4837
|
candidate = `${cleanBase} (copy ${num})${ext}`;
|
|
6516
4838
|
}
|
|
@@ -6519,7 +4841,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6519
4841
|
function walkFiles(dir, filter) {
|
|
6520
4842
|
const results = [];
|
|
6521
4843
|
for (const entry of readableEntries(dir)) {
|
|
6522
|
-
const full2 =
|
|
4844
|
+
const full2 = join6(dir, entry.name);
|
|
6523
4845
|
if (entry.isDirectory()) {
|
|
6524
4846
|
if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
|
|
6525
4847
|
continue;
|
|
@@ -6536,7 +4858,7 @@ function skippable(error) {
|
|
|
6536
4858
|
}
|
|
6537
4859
|
function readableEntries(dir) {
|
|
6538
4860
|
try {
|
|
6539
|
-
return
|
|
4861
|
+
return readdirSync2(dir, { withFileTypes: true });
|
|
6540
4862
|
} catch (error) {
|
|
6541
4863
|
if (skippable(error)) return [];
|
|
6542
4864
|
throw error;
|
|
@@ -6544,7 +4866,7 @@ function readableEntries(dir) {
|
|
|
6544
4866
|
}
|
|
6545
4867
|
function readableText(file) {
|
|
6546
4868
|
try {
|
|
6547
|
-
return
|
|
4869
|
+
return readFileSync5(file, "utf-8");
|
|
6548
4870
|
} catch (error) {
|
|
6549
4871
|
if (skippable(error)) return null;
|
|
6550
4872
|
throw error;
|
|
@@ -6602,7 +4924,7 @@ function occurrences(text2, part) {
|
|
|
6602
4924
|
}
|
|
6603
4925
|
function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
|
|
6604
4926
|
return readableEntries(dir).flatMap((entry) => {
|
|
6605
|
-
const [path, full2] = [`${prefix}${entry.name}`,
|
|
4927
|
+
const [path, full2] = [`${prefix}${entry.name}`, join6(dir, entry.name)];
|
|
6606
4928
|
const real = entry.name === "node_modules" ? null : directoryReal(entry, full2);
|
|
6607
4929
|
if (real === null || ancestors.has(real) || !isSafePath(root, full2)) return [path];
|
|
6608
4930
|
return [path, ...projectPaths(root, full2, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
|
|
@@ -6610,7 +4932,7 @@ function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */
|
|
|
6610
4932
|
}
|
|
6611
4933
|
function directoryReal(entry, full2) {
|
|
6612
4934
|
try {
|
|
6613
|
-
return entry.isDirectory() || entry.isSymbolicLink() &&
|
|
4935
|
+
return entry.isDirectory() || entry.isSymbolicLink() && statSync3(full2).isDirectory() ? realpathSync(full2) : null;
|
|
6614
4936
|
} catch {
|
|
6615
4937
|
return null;
|
|
6616
4938
|
}
|
|
@@ -6628,7 +4950,7 @@ function updateReferences(projectDir, oldPath, newPath, isDirectory) {
|
|
|
6628
4950
|
if (content === null) continue;
|
|
6629
4951
|
const updated = rewrite(content);
|
|
6630
4952
|
if (updated !== content) {
|
|
6631
|
-
replaceFileAtomically(file, updated,
|
|
4953
|
+
replaceFileAtomically(file, updated, statSync3(file).mode);
|
|
6632
4954
|
updatedCount++;
|
|
6633
4955
|
}
|
|
6634
4956
|
}
|
|
@@ -6910,12 +5232,12 @@ function containsRawGsapExpression(value) {
|
|
|
6910
5232
|
return Object.values(value).some(containsRawGsapExpression);
|
|
6911
5233
|
}
|
|
6912
5234
|
async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
6913
|
-
const beforeHtml =
|
|
5235
|
+
const beforeHtml = readFileSync5(res.absPath, "utf-8");
|
|
6914
5236
|
let html = beforeHtml;
|
|
6915
5237
|
let block = extractGsapScriptBlock(html);
|
|
6916
5238
|
if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
|
|
6917
5239
|
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
6918
|
-
const { GSAP_CDN } = await import("./dist-
|
|
5240
|
+
const { GSAP_CDN } = await import("./dist-N33CM63P.js");
|
|
6919
5241
|
const bootstrap = [
|
|
6920
5242
|
`<script src="${GSAP_CDN}"></script>`,
|
|
6921
5243
|
"<script>",
|
|
@@ -6973,7 +5295,7 @@ function clipRetimeOf(mutation) {
|
|
|
6973
5295
|
function retimeRun(script, run, root) {
|
|
6974
5296
|
const live = run.filter((slot) => slot !== "no-op");
|
|
6975
5297
|
const retimed = retimeClipTweensInScript(script, live, root);
|
|
6976
|
-
const synced = syncPositionHoldsBeforeKeyframes(retimed.script);
|
|
5298
|
+
const synced = syncPositionHoldsBeforeKeyframes(retimed.script, script);
|
|
6977
5299
|
let next = 0;
|
|
6978
5300
|
const changed = run.map((slot) => slot !== "no-op" && retimed.changed[next++]);
|
|
6979
5301
|
if (synced !== retimed.script) changed[changed.length - 1] = true;
|
|
@@ -7027,7 +5349,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
7027
5349
|
const changed = block.scriptText !== initialScript;
|
|
7028
5350
|
const newHtml = changed ? block.replaceScript(block.scriptText) : html;
|
|
7029
5351
|
let backupPath = null;
|
|
7030
|
-
if (
|
|
5352
|
+
if (readFileSync5(res.absPath, "utf-8") !== beforeHtml) {
|
|
7031
5353
|
return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
|
|
7032
5354
|
}
|
|
7033
5355
|
if (changed) {
|
|
@@ -7748,7 +6070,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7748
6070
|
skipped.push(name);
|
|
7749
6071
|
continue;
|
|
7750
6072
|
}
|
|
7751
|
-
const destPath =
|
|
6073
|
+
const destPath = resolve2(targetDir, name);
|
|
7752
6074
|
if (!isSafePath(projectDir, destPath)) continue;
|
|
7753
6075
|
let finalPath = destPath;
|
|
7754
6076
|
let finalName = name;
|
|
@@ -7759,13 +6081,13 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7759
6081
|
let n = 1;
|
|
7760
6082
|
if (existsSync4(finalPath)) {
|
|
7761
6083
|
n = 2;
|
|
7762
|
-
while (n < MAX_COPY_INDEX && existsSync4(
|
|
6084
|
+
while (n < MAX_COPY_INDEX && existsSync4(resolve2(targetDir, `${base} (${n})${ext}`))) n++;
|
|
7763
6085
|
if (n >= MAX_COPY_INDEX) {
|
|
7764
6086
|
skipped.push(name);
|
|
7765
6087
|
continue;
|
|
7766
6088
|
}
|
|
7767
6089
|
finalName = `${base} (${n})${ext}`;
|
|
7768
|
-
finalPath =
|
|
6090
|
+
finalPath = resolve2(targetDir, finalName);
|
|
7769
6091
|
}
|
|
7770
6092
|
if (!isSafePath(projectDir, finalPath) || isPrivateProjectFile(projectDir, finalPath)) continue;
|
|
7771
6093
|
const buffer = Buffer.from(await value.arrayBuffer());
|
|
@@ -7786,14 +6108,14 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7786
6108
|
}
|
|
7787
6109
|
n++;
|
|
7788
6110
|
finalName = `${base} (${n})${ext}`;
|
|
7789
|
-
finalPath =
|
|
6111
|
+
finalPath = resolve2(targetDir, finalName);
|
|
7790
6112
|
}
|
|
7791
6113
|
}
|
|
7792
6114
|
if (!written) {
|
|
7793
6115
|
if (n >= MAX_COPY_INDEX) skipped.push(name);
|
|
7794
6116
|
continue;
|
|
7795
6117
|
}
|
|
7796
|
-
const relativePath = subDir ?
|
|
6118
|
+
const relativePath = subDir ? join6(subDir, finalName) : finalName;
|
|
7797
6119
|
uploaded.push(relativePath);
|
|
7798
6120
|
if (validation.unchecked) unchecked.push({ name: finalName, reason: validation.unchecked });
|
|
7799
6121
|
if (isAudioFile(finalName)) {
|
|
@@ -7811,7 +6133,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7811
6133
|
if ("error" in res) return res.error;
|
|
7812
6134
|
let fd;
|
|
7813
6135
|
try {
|
|
7814
|
-
fd =
|
|
6136
|
+
fd = openSync2(res.absPath, "r");
|
|
7815
6137
|
} catch {
|
|
7816
6138
|
if (c.req.query("optional") === "1") {
|
|
7817
6139
|
return c.json({ filename: res.filePath, content: "", missing: true });
|
|
@@ -7819,14 +6141,14 @@ function registerFileRoutes(api, adapter) {
|
|
|
7819
6141
|
return c.json({ error: "not found" }, 404);
|
|
7820
6142
|
}
|
|
7821
6143
|
try {
|
|
7822
|
-
const
|
|
7823
|
-
if (!
|
|
6144
|
+
const stat2 = fstatSync2(fd);
|
|
6145
|
+
if (!stat2.isFile()) {
|
|
7824
6146
|
return c.json({ error: "not found", why: "not_a_file" }, 404);
|
|
7825
6147
|
}
|
|
7826
|
-
if (
|
|
6148
|
+
if (stat2.size > MAX_TEXT_READ_BYTES) {
|
|
7827
6149
|
return c.json({ error: "too large to read as text", why: "too_large" }, 413);
|
|
7828
6150
|
}
|
|
7829
|
-
const content =
|
|
6151
|
+
const content = readFileSync5(fd);
|
|
7830
6152
|
const version = fileContentVersion(content);
|
|
7831
6153
|
c.header("ETag", version);
|
|
7832
6154
|
if (content.subarray(0, GIT_BINARY_SNIFF_BYTES).includes(0)) {
|
|
@@ -7839,7 +6161,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7839
6161
|
missing: false
|
|
7840
6162
|
});
|
|
7841
6163
|
} finally {
|
|
7842
|
-
|
|
6164
|
+
closeSync2(fd);
|
|
7843
6165
|
}
|
|
7844
6166
|
});
|
|
7845
6167
|
api.put("/projects/:id/files/*", async (c) => {
|
|
@@ -7851,7 +6173,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7851
6173
|
if (expectedVersion === null && !createOnly) {
|
|
7852
6174
|
let currentContent = null;
|
|
7853
6175
|
try {
|
|
7854
|
-
currentContent =
|
|
6176
|
+
currentContent = readFileSync5(res.absPath);
|
|
7855
6177
|
} catch (error) {
|
|
7856
6178
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7857
6179
|
throw error;
|
|
@@ -7877,7 +6199,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7877
6199
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
7878
6200
|
throw error;
|
|
7879
6201
|
}
|
|
7880
|
-
const currentContent =
|
|
6202
|
+
const currentContent = readFileSync5(res.absPath);
|
|
7881
6203
|
return c.json(
|
|
7882
6204
|
{
|
|
7883
6205
|
error: "file conflict",
|
|
@@ -7891,7 +6213,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7891
6213
|
} else {
|
|
7892
6214
|
let fd;
|
|
7893
6215
|
try {
|
|
7894
|
-
fd =
|
|
6216
|
+
fd = openSync2(res.absPath, "r+");
|
|
7895
6217
|
} catch (error) {
|
|
7896
6218
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7897
6219
|
throw error;
|
|
@@ -7907,7 +6229,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7907
6229
|
);
|
|
7908
6230
|
}
|
|
7909
6231
|
try {
|
|
7910
|
-
const currentContent =
|
|
6232
|
+
const currentContent = readFileSync5(fd);
|
|
7911
6233
|
overwrote = currentContent;
|
|
7912
6234
|
const currentVersion = fileContentVersion(currentContent);
|
|
7913
6235
|
if (expectedVersion !== currentVersion) {
|
|
@@ -7924,11 +6246,11 @@ function registerFileRoutes(api, adapter) {
|
|
|
7924
6246
|
backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
7925
6247
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
7926
6248
|
const mode = fstatSync2(fd).mode;
|
|
7927
|
-
|
|
6249
|
+
closeSync2(fd);
|
|
7928
6250
|
fd = null;
|
|
7929
6251
|
replaceFileAtomically(res.absPath, body, mode);
|
|
7930
6252
|
} finally {
|
|
7931
|
-
if (fd !== null)
|
|
6253
|
+
if (fd !== null) closeSync2(fd);
|
|
7932
6254
|
}
|
|
7933
6255
|
}
|
|
7934
6256
|
const version = fileContentVersion(body);
|
|
@@ -7961,10 +6283,10 @@ function registerFileRoutes(api, adapter) {
|
|
|
7961
6283
|
api.delete("/projects/:id/files/*", async (c) => {
|
|
7962
6284
|
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
|
7963
6285
|
if ("error" in res) return res.error;
|
|
7964
|
-
const
|
|
6286
|
+
const stat2 = statSync3(res.absPath);
|
|
7965
6287
|
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
7966
6288
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
7967
|
-
if (
|
|
6289
|
+
if (stat2.isDirectory()) {
|
|
7968
6290
|
rmSync3(res.absPath, { recursive: true });
|
|
7969
6291
|
} else {
|
|
7970
6292
|
unlinkSync2(res.absPath);
|
|
@@ -7983,7 +6305,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7983
6305
|
}
|
|
7984
6306
|
let before;
|
|
7985
6307
|
try {
|
|
7986
|
-
before =
|
|
6308
|
+
before = readFileSync5(ctx.absPath, "utf-8");
|
|
7987
6309
|
} catch (error) {
|
|
7988
6310
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7989
6311
|
throw error;
|
|
@@ -8012,7 +6334,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8012
6334
|
}
|
|
8013
6335
|
const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
|
|
8014
6336
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
8015
|
-
const current =
|
|
6337
|
+
const current = readFileSync5(ctx.absPath, "utf-8");
|
|
8016
6338
|
if (current !== before) {
|
|
8017
6339
|
const currentVersion2 = fileContentVersion(current);
|
|
8018
6340
|
return c.json({ error: "file conflict", currentVersion: currentVersion2, currentContent: current }, 409);
|
|
@@ -8045,7 +6367,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8045
6367
|
}
|
|
8046
6368
|
const parsed = await parseMutationBody(c);
|
|
8047
6369
|
if ("error" in parsed) return parsed.error;
|
|
8048
|
-
const originalContent =
|
|
6370
|
+
const originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8049
6371
|
return writeIfChanged(
|
|
8050
6372
|
c,
|
|
8051
6373
|
ctx.project.dir,
|
|
@@ -8066,7 +6388,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8066
6388
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
8067
6389
|
return c.json({ error: "targets required" }, 400);
|
|
8068
6390
|
}
|
|
8069
|
-
const originalContent =
|
|
6391
|
+
const originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8070
6392
|
return writeIfChanged(
|
|
8071
6393
|
c,
|
|
8072
6394
|
ctx.project.dir,
|
|
@@ -8102,7 +6424,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8102
6424
|
seen.add(absPath);
|
|
8103
6425
|
let before;
|
|
8104
6426
|
try {
|
|
8105
|
-
before =
|
|
6427
|
+
before = readFileSync5(absPath, "utf-8");
|
|
8106
6428
|
} catch {
|
|
8107
6429
|
return c.json({ error: `not found: ${file.path}` }, 404);
|
|
8108
6430
|
}
|
|
@@ -8129,7 +6451,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8129
6451
|
prepared.push(folded);
|
|
8130
6452
|
}
|
|
8131
6453
|
for (const file of prepared) {
|
|
8132
|
-
const current =
|
|
6454
|
+
const current = readFileSync5(file.absPath, "utf-8");
|
|
8133
6455
|
if (current !== file.before) {
|
|
8134
6456
|
return c.json(
|
|
8135
6457
|
{
|
|
@@ -8159,7 +6481,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8159
6481
|
const written = [];
|
|
8160
6482
|
try {
|
|
8161
6483
|
for (const file of prepared) {
|
|
8162
|
-
replaceFileAtomically(file.absPath, file.after,
|
|
6484
|
+
replaceFileAtomically(file.absPath, file.after, statSync3(file.absPath).mode);
|
|
8163
6485
|
written.push(file);
|
|
8164
6486
|
recordFileWriteReceipt(file.absPath, {
|
|
8165
6487
|
path: file.path,
|
|
@@ -8172,12 +6494,12 @@ function registerFileRoutes(api, adapter) {
|
|
|
8172
6494
|
const conflicts = [];
|
|
8173
6495
|
for (const file of written.reverse()) {
|
|
8174
6496
|
try {
|
|
8175
|
-
const current =
|
|
6497
|
+
const current = readFileSync5(file.absPath, "utf-8");
|
|
8176
6498
|
if (current !== file.after) {
|
|
8177
6499
|
conflicts.push(file.path);
|
|
8178
6500
|
continue;
|
|
8179
6501
|
}
|
|
8180
|
-
replaceFileAtomically(file.absPath, file.before,
|
|
6502
|
+
replaceFileAtomically(file.absPath, file.before, statSync3(file.absPath).mode);
|
|
8181
6503
|
recordFileWriteReceipt(file.absPath, {
|
|
8182
6504
|
path: file.path,
|
|
8183
6505
|
version: fileContentVersion(file.before),
|
|
@@ -8220,7 +6542,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8220
6542
|
const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
|
|
8221
6543
|
let originalContent;
|
|
8222
6544
|
try {
|
|
8223
|
-
originalContent =
|
|
6545
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8224
6546
|
} catch {
|
|
8225
6547
|
return c.json({ error: "not found" }, 404);
|
|
8226
6548
|
}
|
|
@@ -8282,7 +6604,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8282
6604
|
for (let attempt = 1; ; attempt += 1) {
|
|
8283
6605
|
let originalContent;
|
|
8284
6606
|
try {
|
|
8285
|
-
originalContent =
|
|
6607
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8286
6608
|
} catch {
|
|
8287
6609
|
return c.json({ error: "not found" }, 404);
|
|
8288
6610
|
}
|
|
@@ -8394,7 +6716,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8394
6716
|
}
|
|
8395
6717
|
let originalContent;
|
|
8396
6718
|
try {
|
|
8397
|
-
originalContent =
|
|
6719
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8398
6720
|
} catch {
|
|
8399
6721
|
return c.json({ error: "not found" }, 404);
|
|
8400
6722
|
}
|
|
@@ -8450,7 +6772,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8450
6772
|
).map((entry) => ({ target: entry.target, track: entry.track }));
|
|
8451
6773
|
let originalContent;
|
|
8452
6774
|
try {
|
|
8453
|
-
originalContent =
|
|
6775
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8454
6776
|
} catch {
|
|
8455
6777
|
return c.json({ error: "not found" }, 404);
|
|
8456
6778
|
}
|
|
@@ -8479,7 +6801,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8479
6801
|
if ("error" in parsed) return parsed.error;
|
|
8480
6802
|
let content;
|
|
8481
6803
|
try {
|
|
8482
|
-
content =
|
|
6804
|
+
content = readFileSync5(ctx.absPath, "utf-8");
|
|
8483
6805
|
} catch {
|
|
8484
6806
|
return c.json({ exists: false });
|
|
8485
6807
|
}
|
|
@@ -8493,7 +6815,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8493
6815
|
if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
|
|
8494
6816
|
let content;
|
|
8495
6817
|
try {
|
|
8496
|
-
content =
|
|
6818
|
+
content = readFileSync5(ctx.absPath, "utf-8");
|
|
8497
6819
|
} catch {
|
|
8498
6820
|
return c.json({ exists: body.targets.map(() => false) });
|
|
8499
6821
|
}
|
|
@@ -8513,9 +6835,9 @@ function registerFileRoutes(api, adapter) {
|
|
|
8513
6835
|
if (existsSync4(newAbs)) {
|
|
8514
6836
|
return c.json({ error: "already exists" }, 409);
|
|
8515
6837
|
}
|
|
8516
|
-
const isDirectory =
|
|
6838
|
+
const isDirectory = statSync3(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
8517
6839
|
ensureDir(res.project.dir, newAbs);
|
|
8518
|
-
|
|
6840
|
+
renameSync2(res.absPath, newAbs);
|
|
8519
6841
|
const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath, isDirectory);
|
|
8520
6842
|
return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
|
|
8521
6843
|
});
|
|
@@ -8537,7 +6859,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8537
6859
|
}
|
|
8538
6860
|
ensureDir(project.dir, destAbs);
|
|
8539
6861
|
try {
|
|
8540
|
-
createFileAtomically(destAbs,
|
|
6862
|
+
createFileAtomically(destAbs, readFileSync5(srcAbs));
|
|
8541
6863
|
} catch (error) {
|
|
8542
6864
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
8543
6865
|
throw error;
|
|
@@ -8580,7 +6902,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8580
6902
|
mustExist: true
|
|
8581
6903
|
});
|
|
8582
6904
|
if ("error" in res) return res.error;
|
|
8583
|
-
const html =
|
|
6905
|
+
const html = readFileSync5(res.absPath, "utf-8");
|
|
8584
6906
|
const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
|
|
8585
6907
|
const headers = { ETag: etag, "Cache-Control": "no-cache" };
|
|
8586
6908
|
if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
|
|
@@ -8637,11 +6959,11 @@ function registerFileRoutes(api, adapter) {
|
|
|
8637
6959
|
if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
|
|
8638
6960
|
return c.json({ error: "expected and restore contents required" }, 400);
|
|
8639
6961
|
}
|
|
8640
|
-
const current =
|
|
6962
|
+
const current = readFileSync5(res.absPath, "utf-8");
|
|
8641
6963
|
if (current !== body.expected) {
|
|
8642
6964
|
return c.json({ ok: true, restored: false, conflict: true });
|
|
8643
6965
|
}
|
|
8644
|
-
replaceFileAtomically(res.absPath, body.restore,
|
|
6966
|
+
replaceFileAtomically(res.absPath, body.restore, statSync3(res.absPath).mode);
|
|
8645
6967
|
return c.json({ ok: true, restored: true, conflict: false });
|
|
8646
6968
|
});
|
|
8647
6969
|
}
|
|
@@ -8655,7 +6977,7 @@ var pathKey = process.platform === "darwin" || process.platform === "win32" ? (p
|
|
|
8655
6977
|
var readsByProject = /* @__PURE__ */ new Map();
|
|
8656
6978
|
var builtProjects = /* @__PURE__ */ new Set();
|
|
8657
6979
|
var projectKey = (projectDir) => pathKey(realFilePath(resolve32(projectDir)));
|
|
8658
|
-
var inRealProject = (projectDir, path) => join7(realFilePath(resolve32(projectDir)),
|
|
6980
|
+
var inRealProject = (projectDir, path) => join7(realFilePath(resolve32(projectDir)), relative3(resolve32(projectDir), resolve32(projectDir, path)));
|
|
8659
6981
|
var readLink = (path) => {
|
|
8660
6982
|
try {
|
|
8661
6983
|
return readlinkSync2(path);
|
|
@@ -8671,7 +6993,7 @@ function linksOnTheWay(path) {
|
|
|
8671
6993
|
const part = rest.shift();
|
|
8672
6994
|
if (!part || part === ".") continue;
|
|
8673
6995
|
if (part === "..") {
|
|
8674
|
-
at =
|
|
6996
|
+
at = dirname4(at);
|
|
8675
6997
|
continue;
|
|
8676
6998
|
}
|
|
8677
6999
|
const next = join7(at, part);
|
|
@@ -8698,7 +7020,7 @@ function recordPreviewRead(projectDir, filePath) {
|
|
|
8698
7020
|
const read = inRealProject(projectDir, filePath);
|
|
8699
7021
|
if (read.length > MAX_PREVIEW_PATH_LENGTH) return;
|
|
8700
7022
|
for (const found of [read, ...linksOnTheWay(read)]) {
|
|
8701
|
-
for (let path = pathKey(found); !reads.has(path); path =
|
|
7023
|
+
for (let path = pathKey(found); !reads.has(path); path = dirname4(path)) reads.add(path);
|
|
8702
7024
|
}
|
|
8703
7025
|
}
|
|
8704
7026
|
function recordPreviewReferences(projectDir, html) {
|
|
@@ -8809,7 +7131,7 @@ function readStudioMotionManifestContent(projectDir) {
|
|
|
8809
7131
|
const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
|
|
8810
7132
|
if (!existsSync5(manifestPath)) return "";
|
|
8811
7133
|
try {
|
|
8812
|
-
return
|
|
7134
|
+
return readFileSync6(manifestPath, "utf-8");
|
|
8813
7135
|
} catch {
|
|
8814
7136
|
return "";
|
|
8815
7137
|
}
|
|
@@ -8953,7 +7275,7 @@ function parsePreviewVariablesParam(raw) {
|
|
|
8953
7275
|
}
|
|
8954
7276
|
function variablesEtagSalt(raw) {
|
|
8955
7277
|
if (!raw) return "";
|
|
8956
|
-
return `:vars:${
|
|
7278
|
+
return `:vars:${createHash22("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
8957
7279
|
}
|
|
8958
7280
|
function previewVariablesFromRequest(rawVariables) {
|
|
8959
7281
|
const parse22 = parsePreviewVariablesParam(rawVariables);
|
|
@@ -8998,13 +7320,13 @@ function resolveProjectMainHtml(projectDir, projectId) {
|
|
|
8998
7320
|
recordPreviewRead(projectDir, blockHtmlPath);
|
|
8999
7321
|
if (existsSync5(indexPath)) {
|
|
9000
7322
|
return {
|
|
9001
|
-
html:
|
|
7323
|
+
html: readFileSync6(indexPath, "utf-8"),
|
|
9002
7324
|
compositionPath: "index.html"
|
|
9003
7325
|
};
|
|
9004
7326
|
}
|
|
9005
7327
|
if (existsSync5(blockHtmlPath)) {
|
|
9006
7328
|
return {
|
|
9007
|
-
html:
|
|
7329
|
+
html: readFileSync6(blockHtmlPath, "utf-8"),
|
|
9008
7330
|
compositionPath: `${projectId}.html`
|
|
9009
7331
|
};
|
|
9010
7332
|
}
|
|
@@ -9135,7 +7457,7 @@ ${etag}`;
|
|
|
9135
7457
|
function pinSubCompHfIds(compFile, compPath) {
|
|
9136
7458
|
if (!/\.html?$/i.test(compPath)) return void 0;
|
|
9137
7459
|
try {
|
|
9138
|
-
return ensureHfIds(
|
|
7460
|
+
return ensureHfIds(readFileSync6(compFile, "utf-8"));
|
|
9139
7461
|
} catch {
|
|
9140
7462
|
return null;
|
|
9141
7463
|
}
|
|
@@ -9151,10 +7473,10 @@ ${etag}`;
|
|
|
9151
7473
|
const compFile = resolveWithinProject(project.dir, compPath);
|
|
9152
7474
|
if (compFile) recordPreviewRead(project.dir, compFile);
|
|
9153
7475
|
recordPreviewRead(project.dir, "index.html");
|
|
9154
|
-
if (!compFile || !existsSync5(compFile) || !
|
|
7476
|
+
if (!compFile || !existsSync5(compFile) || !statSync4(compFile).isFile()) {
|
|
9155
7477
|
return c.text("not found", 404);
|
|
9156
7478
|
}
|
|
9157
|
-
const compPathHash =
|
|
7479
|
+
const compPathHash = createHash22("sha1").update(compPath).digest("hex");
|
|
9158
7480
|
const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
|
|
9159
7481
|
const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
|
|
9160
7482
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
@@ -9186,14 +7508,14 @@ ${etag}`;
|
|
|
9186
7508
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
9187
7509
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9188
7510
|
const subPath = requestSubPath(c.req.url, "projects/:id/preview");
|
|
9189
|
-
const candidate =
|
|
7511
|
+
const candidate = resolve4(project.dir, subPath);
|
|
9190
7512
|
const file = isWithinProjectRoot(project.dir, candidate) ? candidate : null;
|
|
9191
7513
|
if (!file || isPrivateProjectFile(project.dir, file)) {
|
|
9192
7514
|
return c.text("not found", 404);
|
|
9193
7515
|
}
|
|
9194
7516
|
recordPreviewRead(project.dir, file);
|
|
9195
|
-
const
|
|
9196
|
-
if (!
|
|
7517
|
+
const stat2 = existsSync5(file) ? statSync4(file) : null;
|
|
7518
|
+
if (!stat2?.isFile()) {
|
|
9197
7519
|
return c.text("not found", 404);
|
|
9198
7520
|
}
|
|
9199
7521
|
const contentType = getMimeType(subPath);
|
|
@@ -9215,7 +7537,7 @@ ${etag}`;
|
|
|
9215
7537
|
return c.text("media proxy variant does not match asset", 422);
|
|
9216
7538
|
}
|
|
9217
7539
|
}
|
|
9218
|
-
const tag = settledFileTag(
|
|
7540
|
+
const tag = settledFileTag(stat2);
|
|
9219
7541
|
const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant)}"`;
|
|
9220
7542
|
const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, no-cache", ...etag && { ETag: etag } };
|
|
9221
7543
|
if (!isText && etag) {
|
|
@@ -9246,13 +7568,13 @@ ${etag}`;
|
|
|
9246
7568
|
recordProxyRequest();
|
|
9247
7569
|
servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
|
|
9248
7570
|
}
|
|
9249
|
-
const textBuffer = isText ? Buffer.from(
|
|
9250
|
-
const totalSize = textBuffer ? textBuffer.length :
|
|
7571
|
+
const textBuffer = isText ? Buffer.from(readFileSync6(file, "utf-8"), "utf-8") : null;
|
|
7572
|
+
const totalSize = textBuffer ? textBuffer.length : statSync4(servedPath).size;
|
|
9251
7573
|
const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
|
|
9252
7574
|
// Node's web stream type and the DOM one do not overlap for tsc on
|
|
9253
7575
|
// every platform's lib set; the double cast is the documented bridge.
|
|
9254
7576
|
Readable.toWeb(
|
|
9255
|
-
|
|
7577
|
+
createReadStream(servedPath, { start, end })
|
|
9256
7578
|
)
|
|
9257
7579
|
);
|
|
9258
7580
|
const rangeHeader = c.req.header("Range");
|
|
@@ -9305,7 +7627,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
|
|
|
9305
7627
|
const findings = [];
|
|
9306
7628
|
for (const file of htmlFiles) {
|
|
9307
7629
|
if (coveredFiles.has(file)) continue;
|
|
9308
|
-
const content =
|
|
7630
|
+
const content = readFileSync7(join9(projectDir, file), "utf-8");
|
|
9309
7631
|
const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
|
|
9310
7632
|
for (const finding of result?.findings ?? []) {
|
|
9311
7633
|
findings.push({ ...finding, file });
|
|
@@ -9461,7 +7783,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9461
7783
|
const RENDER_EXTENSIONS = Object.keys(RENDER_MIME);
|
|
9462
7784
|
const isPlainFile = (path) => {
|
|
9463
7785
|
try {
|
|
9464
|
-
return
|
|
7786
|
+
return lstatSync3(path).isFile();
|
|
9465
7787
|
} catch {
|
|
9466
7788
|
return false;
|
|
9467
7789
|
}
|
|
@@ -9544,11 +7866,11 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9544
7866
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9545
7867
|
const rendersDir = adapter.rendersDir(project);
|
|
9546
7868
|
if (!existsSync6(rendersDir)) return c.json({ renders: [] });
|
|
9547
|
-
const files =
|
|
7869
|
+
const files = readdirSync3(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov")).flatMap((f) => {
|
|
9548
7870
|
const fp = resolveWithinProject(rendersDir, f);
|
|
9549
7871
|
return fp && !isPrivateProjectFile(project.dir, fp) ? [{ f, fp }] : [];
|
|
9550
7872
|
}).map(({ f, fp }) => {
|
|
9551
|
-
const
|
|
7873
|
+
const stat2 = statSync5(fp);
|
|
9552
7874
|
const rid = f.replace(/\.(mp4|webm|mov)$/, "");
|
|
9553
7875
|
const metaPath = join10(rendersDir, `${rid}.meta.json`);
|
|
9554
7876
|
let status = "complete";
|
|
@@ -9567,8 +7889,8 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9567
7889
|
id: rid,
|
|
9568
7890
|
filename: f,
|
|
9569
7891
|
path: fp,
|
|
9570
|
-
size:
|
|
9571
|
-
createdAt:
|
|
7892
|
+
size: stat2.size,
|
|
7893
|
+
createdAt: stat2.mtimeMs,
|
|
9572
7894
|
status,
|
|
9573
7895
|
durationMs,
|
|
9574
7896
|
...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
|
|
@@ -9739,7 +8061,7 @@ async function resolveSource(adapter, id, url) {
|
|
|
9739
8061
|
if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
|
|
9740
8062
|
const path = pinWithinProject(project.dir, relative5);
|
|
9741
8063
|
if (!path) throw new HTTPException(404);
|
|
9742
|
-
const info = await
|
|
8064
|
+
const info = await stat(path).catch(() => {
|
|
9743
8065
|
throw new HTTPException(404);
|
|
9744
8066
|
});
|
|
9745
8067
|
if (!info.isFile()) throw new HTTPException(404);
|
|
@@ -9765,7 +8087,7 @@ function registerImageThumbnailRoutes(api, adapter) {
|
|
|
9765
8087
|
api.get("/projects/:id/image-thumbnail/*", async (c) => {
|
|
9766
8088
|
try {
|
|
9767
8089
|
const { path, info } = await resolveSource(adapter, c.req.param("id"), c.req.url);
|
|
9768
|
-
const key =
|
|
8090
|
+
const key = createHash3("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
|
|
9769
8091
|
const etag = `"${key}"`;
|
|
9770
8092
|
c.header("ETag", etag);
|
|
9771
8093
|
c.header("Cache-Control", "private, no-cache");
|
|
@@ -9799,7 +8121,7 @@ var prunedCacheDirs = /* @__PURE__ */ new Set();
|
|
|
9799
8121
|
function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
9800
8122
|
let entries;
|
|
9801
8123
|
try {
|
|
9802
|
-
entries =
|
|
8124
|
+
entries = readdirSync4(cacheDir, { withFileTypes: true });
|
|
9803
8125
|
} catch (err) {
|
|
9804
8126
|
if (err.code === "ENOENT") return;
|
|
9805
8127
|
throw err;
|
|
@@ -9817,7 +8139,7 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
9817
8139
|
const retained = [];
|
|
9818
8140
|
for (const file of files) {
|
|
9819
8141
|
if (!protectedPaths.has(file.path) && now - file.mtimeMs > THUMBNAIL_CACHE_MAX_AGE_MS) {
|
|
9820
|
-
|
|
8142
|
+
rmSync4(file.path, { force: true });
|
|
9821
8143
|
} else {
|
|
9822
8144
|
retained.push(file);
|
|
9823
8145
|
}
|
|
@@ -9834,12 +8156,12 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
9834
8156
|
}
|
|
9835
8157
|
}
|
|
9836
8158
|
function writeThumbnailAtomically(path, buffer) {
|
|
9837
|
-
const temporaryPath = `${path}.${process.pid}.${
|
|
8159
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
9838
8160
|
try {
|
|
9839
|
-
|
|
9840
|
-
|
|
8161
|
+
writeFileSync4(temporaryPath, buffer, { flag: "wx" });
|
|
8162
|
+
renameSync3(temporaryPath, path);
|
|
9841
8163
|
} finally {
|
|
9842
|
-
|
|
8164
|
+
rmSync4(temporaryPath, { force: true });
|
|
9843
8165
|
}
|
|
9844
8166
|
}
|
|
9845
8167
|
function readFileOnce(file) {
|
|
@@ -9853,9 +8175,9 @@ function readFileOnce(file) {
|
|
|
9853
8175
|
throw err;
|
|
9854
8176
|
}
|
|
9855
8177
|
try {
|
|
9856
|
-
const
|
|
9857
|
-
if (!
|
|
9858
|
-
return { data: readFileSync9(fd), mtimeMs:
|
|
8178
|
+
const stat2 = fstatSync22(fd);
|
|
8179
|
+
if (!stat2.isFile()) return "not-a-file";
|
|
8180
|
+
return { data: readFileSync9(fd), mtimeMs: stat2.mtimeMs };
|
|
9859
8181
|
} finally {
|
|
9860
8182
|
closeSync22(fd);
|
|
9861
8183
|
}
|
|
@@ -9863,7 +8185,7 @@ function readFileOnce(file) {
|
|
|
9863
8185
|
function manifestKey(file) {
|
|
9864
8186
|
const manifest = readFileOnce(file);
|
|
9865
8187
|
if (typeof manifest === "string") return { key: "", mtimeMs: -Infinity };
|
|
9866
|
-
const hash =
|
|
8188
|
+
const hash = createHash4("sha1").update(manifest.data).digest("hex").slice(0, 16);
|
|
9867
8189
|
return { key: `_${hash}`, mtimeMs: Math.round(manifest.mtimeMs) };
|
|
9868
8190
|
}
|
|
9869
8191
|
function registerThumbnailRoutes(api, adapter) {
|
|
@@ -9900,7 +8222,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
9900
8222
|
let sourceKey = "";
|
|
9901
8223
|
if (source !== "missing") {
|
|
9902
8224
|
const html = source.data.toString("utf-8");
|
|
9903
|
-
sourceKey = `_${
|
|
8225
|
+
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
9904
8226
|
sourceMtime = Math.round(source.mtimeMs);
|
|
9905
8227
|
if (!vpWidth) {
|
|
9906
8228
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
@@ -9916,9 +8238,9 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
9916
8238
|
const previewPath = compPath === "index.html" ? `${projectUrl}/preview` : `${projectUrl}/preview/comp/${compPath.split("/").map(encodeURIComponent).join("/")}`;
|
|
9917
8239
|
const previewUrl = `${previewPath}?${PREVIEW_CAPTURE_PARAM}=1`;
|
|
9918
8240
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
9919
|
-
const selectorKey = selector ? `_${
|
|
8241
|
+
const selectorKey = selector ? `_${createHash4("sha1").update(selector).digest("hex").slice(0, 16)}_${selectorIndex ?? 0}` : "";
|
|
9920
8242
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
9921
|
-
const inputSignatureKey = `_${
|
|
8243
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
9922
8244
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
9923
8245
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
9924
8246
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
@@ -9996,7 +8318,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
9996
8318
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
9997
8319
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9998
8320
|
const assetPath = requestSubPath(c.req.url, "projects/:id/waveform");
|
|
9999
|
-
const audioPath =
|
|
8321
|
+
const audioPath = resolve5(project.dir, assetPath);
|
|
10000
8322
|
if (!isWithinProjectRoot(project.dir, audioPath)) {
|
|
10001
8323
|
return c.json({ error: "file not found" }, 404);
|
|
10002
8324
|
}
|
|
@@ -10249,20 +8571,20 @@ async function extractThenPublishWhole(extract, mediaPath, mediaTime, imagePath)
|
|
|
10249
8571
|
return { error: `Could not extract the frame: ${reason}`, status: 500 };
|
|
10250
8572
|
}
|
|
10251
8573
|
if (publishStill(imagePath, frame)) return null;
|
|
10252
|
-
return { error: `freeze still already exists: ${
|
|
8574
|
+
return { error: `freeze still already exists: ${basename2(imagePath)}`, status: 409 };
|
|
10253
8575
|
} finally {
|
|
10254
8576
|
rmSync5(unseenByHistory, { force: true });
|
|
10255
8577
|
}
|
|
10256
8578
|
}
|
|
10257
8579
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
10258
|
-
const fileDir =
|
|
8580
|
+
const fileDir = dirname5(absPath);
|
|
10259
8581
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
10260
8582
|
if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
|
|
10261
8583
|
const freezeDir = join13(projectDir, ...FREEZE_DIR);
|
|
10262
8584
|
const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
|
|
10263
8585
|
mkdirWithinProject(projectDir, freezeDir);
|
|
10264
8586
|
const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
|
|
10265
|
-
if (!imagePath ||
|
|
8587
|
+
if (!imagePath || dirname5(imagePath) !== freezeDir) {
|
|
10266
8588
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
10267
8589
|
}
|
|
10268
8590
|
const failed = await extractThenPublishWhole(
|
|
@@ -10272,7 +8594,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
10272
8594
|
imagePath
|
|
10273
8595
|
);
|
|
10274
8596
|
if (failed) return failed;
|
|
10275
|
-
const depth = relative4(projectDir, fileDir).split(
|
|
8597
|
+
const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
|
|
10276
8598
|
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
10277
8599
|
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
10278
8600
|
}
|
|
@@ -10642,17 +8964,17 @@ function registerFontRoutes(api) {
|
|
|
10642
8964
|
if (!located) return c.json({ error: "font not found" }, 404);
|
|
10643
8965
|
let fd;
|
|
10644
8966
|
try {
|
|
10645
|
-
fd =
|
|
8967
|
+
fd = openSync3(located.path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
10646
8968
|
} catch {
|
|
10647
8969
|
return c.json({ error: "font file not accessible" }, 404);
|
|
10648
8970
|
}
|
|
10649
8971
|
try {
|
|
10650
|
-
const
|
|
10651
|
-
if (
|
|
8972
|
+
const stat2 = fstatSync3(fd);
|
|
8973
|
+
if (stat2.size > SYSTEM_FONT_SIZE_LIMIT) {
|
|
10652
8974
|
return c.json({ error: "font file too large" }, 413);
|
|
10653
8975
|
}
|
|
10654
|
-
const buffer = Buffer.alloc(
|
|
10655
|
-
readSync(fd, buffer, 0,
|
|
8976
|
+
const buffer = Buffer.alloc(stat2.size);
|
|
8977
|
+
readSync(fd, buffer, 0, stat2.size, 0);
|
|
10656
8978
|
const mimeType = located.format === "otf" ? "font/otf" : located.format === "woff2" ? "font/woff2" : located.format === "woff" ? "font/woff" : located.format === "ttc" ? "font/collection" : "font/ttf";
|
|
10657
8979
|
const fileName = `${family.replace(/[^a-zA-Z0-9 -]/g, "")}.${located.format}`;
|
|
10658
8980
|
return new Response(buffer, {
|
|
@@ -10664,7 +8986,7 @@ function registerFontRoutes(api) {
|
|
|
10664
8986
|
} catch {
|
|
10665
8987
|
return c.json({ error: "failed to read font file" }, 500);
|
|
10666
8988
|
} finally {
|
|
10667
|
-
|
|
8989
|
+
closeSync3(fd);
|
|
10668
8990
|
}
|
|
10669
8991
|
});
|
|
10670
8992
|
}
|
|
@@ -10810,10 +9132,10 @@ var VIDEO_OUTPUT_EXTENSIONS = /* @__PURE__ */ new Set([".webm", ".mov"]);
|
|
|
10810
9132
|
var QUALITIES = /* @__PURE__ */ new Set(["fast", "balanced", "best"]);
|
|
10811
9133
|
var DEVICES = /* @__PURE__ */ new Set(["auto", "cpu", "coreml", "cuda"]);
|
|
10812
9134
|
function isVideoPath(path) {
|
|
10813
|
-
return VIDEO_EXTENSIONS.has(
|
|
9135
|
+
return VIDEO_EXTENSIONS.has(extname(path).toLowerCase());
|
|
10814
9136
|
}
|
|
10815
9137
|
function isImagePath(path) {
|
|
10816
|
-
return IMAGE_EXTENSIONS.has(
|
|
9138
|
+
return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
10817
9139
|
}
|
|
10818
9140
|
function normalizeProjectAssetPath(path) {
|
|
10819
9141
|
return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
|
|
@@ -10822,11 +9144,11 @@ function containsNullByte(path) {
|
|
|
10822
9144
|
return path.includes("\0");
|
|
10823
9145
|
}
|
|
10824
9146
|
function slugFileBase(path) {
|
|
10825
|
-
const name = basename3(path,
|
|
9147
|
+
const name = basename3(path, extname(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10826
9148
|
return name || "media";
|
|
10827
9149
|
}
|
|
10828
9150
|
function uniqueAssetPath(projectDir, assetPath, taken) {
|
|
10829
|
-
const ext =
|
|
9151
|
+
const ext = extname(assetPath);
|
|
10830
9152
|
const withoutExt = assetPath.slice(0, -ext.length);
|
|
10831
9153
|
let candidate = assetPath;
|
|
10832
9154
|
for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
|
|
@@ -10929,10 +9251,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
10929
9251
|
const outputAssetPath = requestedOutput ? uniqueAssetPath(project.dir, requestedOutput, taken) : defaultOutputPath(project.dir, inputAssetPath, taken);
|
|
10930
9252
|
const outputPath = pinWithinProject(project.dir, outputAssetPath);
|
|
10931
9253
|
if (!outputPath) return c.json({ error: "forbidden" }, 403);
|
|
10932
|
-
if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(
|
|
9254
|
+
if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(extname(outputAssetPath).toLowerCase())) {
|
|
10933
9255
|
return c.json({ error: "video background removal output must be .webm or .mov" }, 400);
|
|
10934
9256
|
}
|
|
10935
|
-
if (inputIsImage &&
|
|
9257
|
+
if (inputIsImage && extname(outputAssetPath).toLowerCase() !== ".png") {
|
|
10936
9258
|
return c.json({ error: "image background removal output must be .png" }, 400);
|
|
10937
9259
|
}
|
|
10938
9260
|
let backgroundOutputAssetPath;
|
|
@@ -11004,7 +9326,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
11004
9326
|
});
|
|
11005
9327
|
});
|
|
11006
9328
|
}
|
|
11007
|
-
function readGlobalAssets(home =
|
|
9329
|
+
function readGlobalAssets(home = homedir()) {
|
|
11008
9330
|
const manifestPath = join16(home, ".media", "manifest.jsonl");
|
|
11009
9331
|
if (!existsSync11(manifestPath)) return [];
|
|
11010
9332
|
const out = [];
|
|
@@ -11341,24 +9663,6 @@ function updateBackgroundRemovalProgress(state, event) {
|
|
|
11341
9663
|
|
|
11342
9664
|
export {
|
|
11343
9665
|
stampProjectHfIds,
|
|
11344
|
-
fileContentVersion,
|
|
11345
|
-
DELETED_VERSION,
|
|
11346
|
-
settledFileTag,
|
|
11347
|
-
identifyFileWrite,
|
|
11348
|
-
consumeFileWriteReceipt,
|
|
11349
|
-
affectsProjectSignature,
|
|
11350
|
-
createProjectSignature,
|
|
11351
|
-
HistoryIdError,
|
|
11352
|
-
HistoryBusyError,
|
|
11353
|
-
listProjectHistories,
|
|
11354
|
-
pruneGoneProjectHistories,
|
|
11355
|
-
START,
|
|
11356
|
-
DEFAULT_HISTORY_ROOT,
|
|
11357
|
-
MAX_WINDOW_IDLE_MS,
|
|
11358
|
-
UNDO_MODES,
|
|
11359
|
-
HistoryClosedError,
|
|
11360
|
-
openProjectHistory,
|
|
11361
|
-
historyCache,
|
|
11362
9666
|
getElementScreenshotClip,
|
|
11363
9667
|
clearElementScreenshotIsolation,
|
|
11364
9668
|
injectMediaCodecMapIntoHtml,
|