hyperframes 0.8.139 → 0.8.141
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-VYJQ4S3P.js → add-KH2CF4FD.js} +20 -22
- package/dist/{animatedGifPrep-IBMW6FZB.js → animatedGifPrep-QO4BSKR7.js} +15 -8
- package/dist/{asset-FLSUS73X.js → asset-4RDZILVJ.js} +3 -3
- package/dist/{assetCataloger-TGD7TRCR.js → assetCataloger-WGR4LYYN.js} +6 -3
- package/dist/{auth-EZXIN4M4.js → auth-WYQCOYE5.js} +7 -7
- package/dist/{autoUpdate-ZPMY5T3X.js → autoUpdate-PC5TN3NL.js} +2 -2
- package/dist/{backgroundChecks-JIDTAUFE.js → backgroundChecks-7M3T44EP.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-FNZ6TJYY.js → batchRender-GOLLOE4V.js} +11 -8
- package/dist/beat-analyzer.global.js +5 -2
- package/dist/{beats-EAA4U3YN.js → beats-XJ7WP6QS.js} +24 -17
- package/dist/{benchmark-KKZQVK2Y.js → benchmark-HCGFBEZJ.js} +20 -23
- package/dist/{browser-ZIVVJLJT.js → browser-VQKO2FKG.js} +14 -28
- package/dist/{bundleWithLocalizedFonts-2XWC3PEM.js → bundleWithLocalizedFonts-FH7PHTTX.js} +5 -5
- package/dist/{capture-EFYPXI42.js → capture-TFZZSF6F.js} +22 -15
- package/dist/{capture-MKJHABUL.js → capture-XD3P3YQR.js} +2 -2
- package/dist/{catalog-EWX4BBI2.js → catalog-4S33QWJJ.js} +28 -30
- package/dist/{catch-up-FJFH7T4W.js → catch-up-RW7YXXNT.js} +11 -25
- package/dist/{check-YJNVDCBP.js → check-XVTRRN4Y.js} +72 -54
- package/dist/{checkBrowser-I6SPPEGD.js → checkBrowser-HZXJ2EDU.js} +81 -36
- package/dist/chunk-24OWOBQH.js +3656 -0
- package/dist/{chunk-OENM2JVZ.js → chunk-34TXESM7.js} +3 -3
- package/dist/{chunk-GWV2Y36Q.js → chunk-35RSNI77.js} +5 -106
- package/dist/{chunk-BC3OY2ED.js → chunk-3A5AAPMP.js} +1 -1
- package/dist/{chunk-U3HX7WE6.js → chunk-3NBKW2QJ.js} +1 -1
- package/dist/{chunk-A4X2YWNQ.js → chunk-4BB7V6AI.js} +38 -301
- package/dist/{chunk-EJ7OWYF2.js → chunk-6E6CM7WS.js} +19 -163
- package/dist/{chunk-CDMB3PRC.js → chunk-6IFXWJDX.js} +7 -7
- package/dist/{chunk-Z67RZ4XO.js → chunk-6Q6FR4KH.js} +279 -1968
- package/dist/{chunk-R4QIE6UY.js → chunk-7M4KAPAA.js} +92 -31
- package/dist/{chunk-25DHUDOK.js → chunk-7XTZVLE6.js} +12 -3
- package/dist/chunk-A4KQZXZI.js +116 -0
- package/dist/{chunk-DUQJF7FK.js → chunk-A7YQNIQT.js} +8 -8
- package/dist/{chunk-XOZ3J3LB.js → chunk-AVWEIOEN.js} +1 -1
- package/dist/{chunk-UQXOHEQ7.js → chunk-BM5PX3DJ.js} +4 -4
- package/dist/{chunk-SHB5VDMH.js → chunk-BRITLL56.js} +1 -1
- package/dist/{chunk-JX7Y57YY.js → chunk-E4BSCXMU.js} +1 -1
- package/dist/{chunk-QXOFUKBF.js → chunk-E6SDW5FV.js} +168 -165
- package/dist/{chunk-JH556Q42.js → chunk-EDYBFSPE.js} +2 -2
- package/dist/{chunk-R7LWDIKB.js → chunk-EKA4ZZBI.js} +1 -1
- package/dist/chunk-FXMKHHPO.js +123 -0
- package/dist/chunk-G75ENCEZ.js +283 -0
- package/dist/{chunk-PLNWWGE7.js → chunk-GQJZORQP.js} +1 -1
- package/dist/{chunk-U3GEXWI5.js → chunk-GS6FE3IW.js} +1 -1
- package/dist/{chunk-Y6W4DVPS.js → chunk-HKCNZITN.js} +1 -1
- package/dist/{chunk-HOIHGPWO.js → chunk-IKVBTDLO.js} +1 -1
- package/dist/{chunk-YOL4QRSL.js → chunk-INI3TM6I.js} +2 -2
- package/dist/{chunk-SIKMUO45.js → chunk-IO7ZIDRP.js} +12 -12
- package/dist/{chunk-W7SAWBQC.js → chunk-IV7EI5E2.js} +1 -1
- package/dist/{chunk-TCWL4BKK.js → chunk-IYFKBFTW.js} +1 -1
- package/dist/{chunk-JIIIBTN2.js → chunk-J5IPIQU2.js} +1 -1
- package/dist/chunk-JFKL3WAE.js +161 -0
- package/dist/{chunk-IBRZUVTH.js → chunk-K2GOFLTX.js} +2 -2
- package/dist/{chunk-X3TMMIDP.js → chunk-KFH6UD26.js} +8 -8
- package/dist/{chunk-O6K5DWXM.js → chunk-KOEWEFHK.js} +1 -1
- package/dist/{chunk-VTVS4IIP.js → chunk-L3JSAB4L.js} +2 -2
- package/dist/{chunk-3RT6D4P6.js → chunk-N77KD7ZZ.js} +1 -1
- package/dist/{chunk-FLRCVYNE.js → chunk-NORZWWLK.js} +2 -2
- package/dist/{chunk-4VZBKOQJ.js → chunk-O3SKIXQK.js} +1 -1
- package/dist/{chunk-PFI3VD77.js → chunk-OLDDJS3S.js} +10 -10
- package/dist/{chunk-UGI77E35.js → chunk-P6E4HFFU.js} +167 -61
- package/dist/{chunk-GIDHR5OL.js → chunk-Q65ZQFF2.js} +1 -1
- package/dist/chunk-QZ3AZTXI.js +1740 -0
- package/dist/{chunk-NTIOLSFE.js → chunk-R7UDXKI3.js} +3 -3
- package/dist/{chunk-7SI7GCX6.js → chunk-RCFCZT5O.js} +6 -6
- package/dist/{chunk-DW32KBHN.js → chunk-RPYPW5NN.js} +13 -13
- package/dist/chunk-SYKKFAUW.js +3711 -0
- package/dist/chunk-T76PLPD6.js +57 -0
- package/dist/{chunk-JTAYFSLO.js → chunk-TC2FBZJZ.js} +1 -1
- package/dist/{chunk-Q73ISOVQ.js → chunk-U7E736AJ.js} +1 -1
- package/dist/chunk-USDEMXXL.js +110 -0
- package/dist/{chunk-UMWROSIP.js → chunk-W6HTOVHU.js} +2 -2
- package/dist/{chunk-STA43FMY.js → chunk-WY5OODN5.js} +71 -3855
- package/dist/{chunk-EZ2P7LAP.js → chunk-XDSOIJC3.js} +8 -8
- package/dist/{chunk-ZTZ7NCJT.js → chunk-XTFJOR3K.js} +1 -1
- package/dist/{chunk-CDJ7FPPJ.js → chunk-XWVGSEUT.js} +1 -1
- package/dist/chunk-YA62XGXS.js +45 -0
- package/dist/{chunk-EH4VUY27.js → chunk-YURXPGAM.js} +2 -2
- package/dist/{clean-P4KTCOUA.js → clean-L2E4VD57.js} +29 -20
- package/dist/cli.js +55 -55
- package/dist/{cloud-PDY7S4HU.js → cloud-IDUQRYLO.js} +7 -7
- package/dist/{cloudrun-54MA2W5H.js → cloudrun-R3JXWXGG.js} +11 -8
- package/dist/commands/layout-audit.browser.js +37 -8
- package/dist/commands/motion-signature.browser.js +51 -2
- package/dist/{compare-LDWVSPC5.js → compare-VYBWSP3Y.js} +40 -31
- package/dist/{compiler-CWSZ4W7J.js → compiler-ACHWOU5N.js} +7 -3
- package/dist/{component-FIEGC22U.js → component-PYYV2V3F.js} +4 -4
- package/dist/{compositions-JR7PX356.js → compositions-B5GQSXH7.js} +23 -16
- package/dist/{delete-FXA2IHOS.js → delete-EAX2U3WI.js} +8 -8
- package/dist/{deterministicFonts-GTITFRSJ.js → deterministicFonts-F6WIVL2W.js} +1 -1
- package/dist/{dist-OJTEVNJR.js → dist-NZSQCYOF.js} +3 -1
- package/dist/{dist-4JZWRWS6.js → dist-ROUDWGLO.js} +27 -15
- package/dist/{doctor-OTDHPM6K.js → doctor-PJX7MLFG.js} +20 -13
- package/dist/{events-UAFTI24U.js → events-SBC54MHO.js} +6 -22
- package/dist/{events-6T7M2VCO.js → events-YFFXKAQQ.js} +10 -23
- package/dist/{feedback-ZGZODHB6.js → feedback-UETLWREV.js} +67 -45
- package/dist/{figma-D26EJ7ZB.js → figma-QDSJPIRW.js} +6 -6
- package/dist/fontLocalizeCli.js +18 -11
- package/dist/{get-IAREU7IK.js → get-B5TS6C7H.js} +8 -8
- package/dist/{gpuPolicy-NS7WZRDJ.js → gpuPolicy-LYOVBAJP.js} +15 -8
- package/dist/{grade-compare-ZXHFHCD2.js → grade-compare-LBIHMAUN.js} +40 -31
- package/dist/{help-RVNYHW3N.js → help-XBP6A6LT.js} +4 -4
- package/dist/{history-THP4VNMF.js → history-2S42ZXKL.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-EOFRHN5A.js → info-I2VDAWT7.js} +17 -20
- package/dist/{init-YLIJE4CG.js → init-BGHSDODI.js} +48 -41
- package/dist/{inspect-3D56ONJD.js → inspect-MZK5B5RP.js} +39 -30
- package/dist/{installer-GRR2GDZK.js → installer-LL4QAKFW.js} +8 -5
- package/dist/{keyframes-NXNCTQPK.js → keyframes-2EBF7OSS.js} +14 -25
- package/dist/{lambda-VGRZD7TR.js → lambda-XKFPAOFE.js} +16 -13
- package/dist/{layout-Z5DOS6DU.js → layout-TP5ERKEF.js} +39 -30
- package/dist/{lint-YD6NJGR2.js → lint-FQ6CEI7E.js} +17 -23
- package/dist/{lintProject-ANMPLBIT.js → lintProject-LM3XZRPZ.js} +3 -1
- package/dist/{list-XGDKILYN.js → list-FRNUJXZ3.js} +8 -8
- package/dist/{login-GD5SA4RD.js → login-C25QU3BQ.js} +9 -9
- package/dist/{logout-Q654A3SI.js → logout-GJXZQKOA.js} +5 -5
- package/dist/{media-treatment-TY4X4GQQ.js → media-treatment-DCANGWSV.js} +21 -20
- package/dist/{models-MXRDH4WJ.js → models-GOD6ARB3.js} +19 -12
- package/dist/{motionShot-6LKR3C2E.js → motionShot-FSKETANX.js} +22 -15
- package/dist/{normalize-O76AC4HE.js → normalize-IGLHCWTF.js} +0 -4
- package/dist/{normalize-audio-5ADDZKPJ.js → normalize-audio-C2IM7OID.js} +13 -26
- package/dist/{open-JXAMO325.js → open-VVISXIII.js} +11 -25
- package/dist/{pipeline-Q5MOPIIX.js → pipeline-7YHZPYIS.js} +15 -8
- package/dist/{play-3SPH5GWZ.js → play-DSELK7G3.js} +38 -32
- package/dist/{preflight-2PWAPKWC.js → preflight-Y5DN5D37.js} +3 -18
- package/dist/{present-ZTJQDKXZ.js → present-N72YFDKP.js} +32 -26
- package/dist/{preview-4A3XZR6J.js → preview-XTYN4NJ2.js} +33 -26
- package/dist/{producer-5ZKKKQPB.js → producer-NDCU3MHK.js} +1 -1
- package/dist/{projectConfig-WA4CS4SN.js → projectConfig-MH4QU4AC.js} +10 -7
- package/dist/{publication-CLG2FRRI.js → publication-KUSHI5MG.js} +6 -3
- package/dist/{publish-YTT2WMXO.js → publish-3XEEFWMF.js} +27 -20
- package/dist/{refresh-K24ETHMA.js → refresh-MFITBIKV.js} +5 -5
- package/dist/{remove-background-W35INR3B.js → remove-background-2QKJ7XOD.js} +22 -15
- package/dist/{render-WUBFQMAH.js → render-2DVYTVLQ.js} +52 -32
- package/dist/{render-7A4P5WMU.js → render-75E342ZU.js} +10 -7
- package/dist/{render-KICXUPNR.js → render-S73DW4KK.js} +23 -26
- package/dist/{render-batch-N7EFGE6S.js → render-batch-YURBS54Q.js} +11 -8
- package/dist/{renderCancellation-LYQPPFAO.js → renderCancellation-373ASJTO.js} +15 -8
- package/dist/renderSetupWorker.js +4 -2
- package/dist/{resolver-YTT2NENF.js → resolver-FE3GBZ4T.js} +8 -5
- package/dist/{sherpa-353MW4UV.js → sherpa-B2D3AGP6.js} +16 -9
- 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 +9 -5
- package/dist/skills/media-use/audio/scripts/lib/heygen.mjs +47 -4
- package/dist/skills/media-use/audio/scripts/lib/heygen.test.mjs +125 -18
- 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 +16 -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-2XJIOEBT.js → skills-ICJ22CZZ.js} +15 -30
- package/dist/{skillsUpdateCheck-LMCCWGNO.js → skillsUpdateCheck-GXZN56I5.js} +3 -3
- package/dist/{snapshot-WBRSTLJ2.js → snapshot-XHKOAWF4.js} +41 -32
- package/dist/{src-ZS3BLTZ5.js → src-74PFDTDX.js} +17 -10
- package/dist/{src-YWFJILQF.js → src-I7UC3JTL.js} +19 -11
- package/dist/{staticProjectServer-4VKVHPNG.js → staticProjectServer-2DGKD6TF.js} +24 -16
- package/dist/{status-73MBN37B.js → status-JA6SKQVH.js} +7 -24
- package/dist/studio/assets/hyperframes-player-5Vx2AiUz.js +473 -0
- package/dist/studio/assets/{index-DsEkE5_G.js → index-BAnh7cSQ.js} +1 -1
- package/dist/studio/assets/{index-Br1Whc0f.js → index-C0thd4eM.js} +204 -204
- package/dist/studio/assets/{index-BmIPrb8I.js → index-P8fgj_Xa.js} +1 -1
- package/dist/studio/{chunk-E422JDF5.js → chunk-H2DUOXTF.js} +2 -1
- package/dist/studio/index.d.ts +19 -3
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +6518 -5813
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +1 -1
- package/dist/{studio-api-5V4TVSQV.js → studio-api-PROBO7JG.js} +40 -28
- package/dist/{studioServer-M5XY3HYC.js → studioServer-ID6UV2NE.js} +100 -69
- package/dist/{system-RCRNX5VF.js → system-OQLOGGPJ.js} +2 -19
- package/dist/{telemetry-LOBRQGUD.js → telemetry-65LFHIWH.js} +8 -23
- package/dist/{timeline-3JCP6PXU.js → timeline-6UV3JJXD.js} +32 -22
- package/dist/{tokens-MLMIJBVF.js → tokens-HIL4TZXI.js} +3 -3
- package/dist/{transcribe-KNFXDEAX.js → transcribe-TIO6W3EQ.js} +16 -9
- package/dist/{transcribe-POH25ZPX.js → transcribe-V7F6QDSN.js} +20 -44
- package/dist/{updateCheck-5ITBSBST.js → updateCheck-GZELANMA.js} +2 -2
- package/dist/{upgrade-K6OK4FKT.js → upgrade-BX3KDDZJ.js} +5 -5
- package/dist/{urlDownloader-TJJQKADT.js → urlDownloader-R76OHFIE.js} +14 -7
- package/dist/{validate-PPPOBJHB.js → validate-7UX4I443.js} +26 -19
- package/dist/{video-OB22HWXZ.js → video-RVGYEFQU.js} +18 -11
- package/package.json +1 -1
- package/dist/chunk-HYYSJI5D.js +0 -3742
- package/dist/studio/assets/hyperframes-player-CckOoCJ8.js +0 -473
- 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-E422JDF5.js.map → chunk-H2DUOXTF.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-A7YQNIQT.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-4BB7V6AI.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 {
|
|
@@ -81,21 +95,26 @@ import {
|
|
|
81
95
|
locateSystemFont
|
|
82
96
|
} from "./chunk-TJSTGSAW.js";
|
|
83
97
|
import {
|
|
98
|
+
addScenePartsManifest
|
|
99
|
+
} from "./chunk-35RSNI77.js";
|
|
100
|
+
import {
|
|
101
|
+
ensureHfIds
|
|
102
|
+
} from "./chunk-A4KQZXZI.js";
|
|
103
|
+
import {
|
|
104
|
+
PREVIEW_PROXY_BOX_PARAM,
|
|
105
|
+
formatPreviewProxyBox,
|
|
84
106
|
formatRenderOutputTimestamp,
|
|
85
107
|
gsapCdnDist,
|
|
86
108
|
motionPathPluginUrl,
|
|
87
109
|
parseFps,
|
|
110
|
+
parsePreviewProxyBox,
|
|
88
111
|
sameInstant
|
|
89
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-6E6CM7WS.js";
|
|
90
113
|
import {
|
|
91
114
|
MagicString,
|
|
92
115
|
VALID_CANVAS_RESOLUTIONS,
|
|
93
116
|
unrollComputedTimeline
|
|
94
117
|
} from "./chunk-OZUQF4NB.js";
|
|
95
|
-
import {
|
|
96
|
-
addScenePartsManifest,
|
|
97
|
-
ensureHfIds
|
|
98
|
-
} from "./chunk-GWV2Y36Q.js";
|
|
99
118
|
import {
|
|
100
119
|
AFTER_FONTS_SCRIPT_TYPE,
|
|
101
120
|
RUNTIME_BOOTSTRAP_ATTR,
|
|
@@ -109,7 +128,7 @@ import {
|
|
|
109
128
|
resolveRateSpec,
|
|
110
129
|
sourceTimeAt,
|
|
111
130
|
stripEmbeddedRuntimeScripts
|
|
112
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-SYKKFAUW.js";
|
|
113
132
|
import {
|
|
114
133
|
ProjectRootMissingError,
|
|
115
134
|
folderGone,
|
|
@@ -121,7 +140,7 @@ import {
|
|
|
121
140
|
import {
|
|
122
141
|
isWithinProjectRoot,
|
|
123
142
|
readProjectFile
|
|
124
|
-
} from "./chunk-
|
|
143
|
+
} from "./chunk-JFKL3WAE.js";
|
|
125
144
|
import {
|
|
126
145
|
FFPROBE_PATH_ENV,
|
|
127
146
|
findFfBinary
|
|
@@ -226,1701 +245,6 @@ function stampFileHfIds(filePath) {
|
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
|
|
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
248
|
// ../studio-server/dist/chunk-IMG2GSY6.js
|
|
1925
249
|
function getElementScreenshotClip(selector, selectorIndex) {
|
|
1926
250
|
let matches;
|
|
@@ -1967,19 +291,19 @@ function clearElementScreenshotIsolation() {
|
|
|
1967
291
|
page.__hfThumbnailFaded = [];
|
|
1968
292
|
}
|
|
1969
293
|
|
|
1970
|
-
// ../studio-server/dist/chunk-
|
|
1971
|
-
import { resolve
|
|
294
|
+
// ../studio-server/dist/chunk-5VCIZYUE.js
|
|
295
|
+
import { resolve } from "path";
|
|
1972
296
|
function isAutoProxyEnabled(adapter) {
|
|
1973
297
|
return adapter.autoProxy !== false;
|
|
1974
298
|
}
|
|
1975
299
|
function resolvePreviewMediaCodecProbeCache(adapter) {
|
|
1976
300
|
return adapter.mediaCodecProbeCache ?? createMediaCodecProbeCache();
|
|
1977
301
|
}
|
|
1978
|
-
function proxyEtagSalt(raw) {
|
|
302
|
+
function proxyEtagSalt(raw, box) {
|
|
1979
303
|
if (raw === void 0) return "";
|
|
1980
|
-
return `:proxy:${raw}:${PROXY_PARAMS_VERSION}`;
|
|
304
|
+
return `:proxy:${raw}:${PROXY_PARAMS_VERSION}${box ? `:${formatPreviewProxyBox(box)}` : ""}`;
|
|
1981
305
|
}
|
|
1982
|
-
async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeCache) {
|
|
306
|
+
async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeCache, prewarm = true) {
|
|
1983
307
|
let map;
|
|
1984
308
|
try {
|
|
1985
309
|
map = await scanProjectMediaCodecMap(
|
|
@@ -1992,11 +316,11 @@ async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeC
|
|
|
1992
316
|
}
|
|
1993
317
|
if (Object.keys(map).length === 0) return html;
|
|
1994
318
|
for (const [rootRelativePathname, facts] of Object.entries(map)) {
|
|
1995
|
-
if (!shouldPrewarmProxy(facts)) continue;
|
|
319
|
+
if (!prewarm || !shouldPrewarmProxy(facts)) continue;
|
|
1996
320
|
recordProxyPrewarm();
|
|
1997
321
|
resolveProxy(
|
|
1998
322
|
projectDir,
|
|
1999
|
-
|
|
323
|
+
resolve(projectDir, rootRelativePathname.replace(/^\/+/, "")),
|
|
2000
324
|
proxyVariantFor(facts)
|
|
2001
325
|
).catch(() => {
|
|
2002
326
|
});
|
|
@@ -2009,7 +333,7 @@ ${html}`;
|
|
|
2009
333
|
}
|
|
2010
334
|
async function injectMediaCodecMap(html, adapter, projectDir, compSrcPath, probeCache) {
|
|
2011
335
|
if (!isAutoProxyEnabled(adapter)) return html;
|
|
2012
|
-
return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);
|
|
336
|
+
return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache, false);
|
|
2013
337
|
}
|
|
2014
338
|
|
|
2015
339
|
// ../studio-server/dist/chunk-X62ASOGO.js
|
|
@@ -2039,52 +363,52 @@ function findUnsafeDomPatchValues(value) {
|
|
|
2039
363
|
|
|
2040
364
|
// ../studio-server/dist/index.js
|
|
2041
365
|
import { Hono } from "hono";
|
|
2042
|
-
import { readFile
|
|
2043
|
-
import { join as
|
|
366
|
+
import { readFile } from "fs/promises";
|
|
367
|
+
import { join as join2 } from "path";
|
|
2044
368
|
import { bodyLimit } from "hono/body-limit";
|
|
2045
369
|
import {
|
|
2046
|
-
closeSync as
|
|
370
|
+
closeSync as closeSync2,
|
|
2047
371
|
existsSync as existsSync4,
|
|
2048
|
-
lstatSync as
|
|
2049
|
-
openSync as
|
|
2050
|
-
readFileSync as
|
|
372
|
+
lstatSync as lstatSync2,
|
|
373
|
+
openSync as openSync2,
|
|
374
|
+
readFileSync as readFileSync5,
|
|
2051
375
|
readlinkSync,
|
|
2052
376
|
unlinkSync as unlinkSync2,
|
|
2053
377
|
rmSync as rmSync3,
|
|
2054
|
-
statSync as
|
|
378
|
+
statSync as statSync3,
|
|
2055
379
|
fstatSync as fstatSync2,
|
|
2056
|
-
renameSync as
|
|
2057
|
-
readdirSync as
|
|
2058
|
-
realpathSync
|
|
380
|
+
renameSync as renameSync2,
|
|
381
|
+
readdirSync as readdirSync2,
|
|
382
|
+
realpathSync
|
|
2059
383
|
} 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
|
|
384
|
+
import { resolve as resolve2, dirname as dirname3, join as join6 } from "path";
|
|
385
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
386
|
+
import { createHash as createHash2 } from "crypto";
|
|
387
|
+
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
388
|
+
import { join as join22 } from "path";
|
|
389
|
+
import { existsSync as existsSync2 } from "fs";
|
|
390
|
+
import { randomUUID } from "crypto";
|
|
391
|
+
import { dirname as dirname2, relative, resolve as resolve3, sep } from "path";
|
|
2068
392
|
import { spawn } from "child_process";
|
|
2069
393
|
import {
|
|
2070
|
-
existsSync as
|
|
2071
|
-
writeFileSync as
|
|
2072
|
-
mkdirSync
|
|
2073
|
-
lstatSync
|
|
394
|
+
existsSync as existsSync3,
|
|
395
|
+
writeFileSync as writeFileSync2,
|
|
396
|
+
mkdirSync,
|
|
397
|
+
lstatSync,
|
|
2074
398
|
mkdtempSync,
|
|
2075
|
-
renameSync
|
|
2076
|
-
rmSync
|
|
2077
|
-
statSync
|
|
399
|
+
renameSync,
|
|
400
|
+
rmSync,
|
|
401
|
+
statSync
|
|
2078
402
|
} from "fs";
|
|
2079
|
-
import { dirname as
|
|
403
|
+
import { dirname as dirname22, join as join3 } from "path";
|
|
2080
404
|
import { spawnSync } from "child_process";
|
|
2081
|
-
import { mkdtempSync as mkdtempSync2, rmSync as
|
|
2082
|
-
import { tmpdir
|
|
2083
|
-
import { basename
|
|
2084
|
-
import { readdirSync
|
|
405
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync22 } from "fs";
|
|
406
|
+
import { tmpdir } from "os";
|
|
407
|
+
import { basename, join as join4 } from "path";
|
|
408
|
+
import { readdirSync, readFileSync as readFileSync32, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2085
409
|
import { Buffer as Buffer2 } from "buffer";
|
|
2086
|
-
import { join as
|
|
2087
|
-
import { readFileSync as
|
|
410
|
+
import { join as join5, relative as relative2 } from "path";
|
|
411
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
2088
412
|
|
|
2089
413
|
// ../parsers/dist/gsapConstants.js
|
|
2090
414
|
var PROPERTY_GROUPS = {
|
|
@@ -3626,8 +1950,8 @@ function upsertProp(ms, objNode, key, value) {
|
|
|
3626
1950
|
if (existing) {
|
|
3627
1951
|
ms.overwrite(existing.value.start, existing.value.end, valueToCode(value));
|
|
3628
1952
|
} else {
|
|
3629
|
-
const
|
|
3630
|
-
ms.appendLeft(objNode.end - 1, `${
|
|
1953
|
+
const sep3 = objNode.properties.length > 0 ? ", " : "";
|
|
1954
|
+
ms.appendLeft(objNode.end - 1, `${sep3}${safeKey(key)}: ${valueToCode(value)}`);
|
|
3631
1955
|
}
|
|
3632
1956
|
}
|
|
3633
1957
|
function isEditableVarKey(key) {
|
|
@@ -4245,8 +2569,8 @@ function insertNewKeyframe(ms, kfNode, percentage, pctKey, valueCode) {
|
|
|
4245
2569
|
if (insertBeforeProp) {
|
|
4246
2570
|
ms.appendLeft(insertBeforeProp.start, `${JSON.stringify(pctKey)}: ${valueCode}, `);
|
|
4247
2571
|
} else {
|
|
4248
|
-
const
|
|
4249
|
-
ms.appendLeft(kfNode.end - 1, `${
|
|
2572
|
+
const sep3 = allProps.length > 0 ? ", " : "";
|
|
2573
|
+
ms.appendLeft(kfNode.end - 1, `${sep3}${JSON.stringify(pctKey)}: ${valueCode}`);
|
|
4250
2574
|
}
|
|
4251
2575
|
}
|
|
4252
2576
|
function collapseKeyframesToFlat(ms, varsNode, source, remainingRecord) {
|
|
@@ -4724,8 +3048,8 @@ function enableArcPath(ms, call, animation, config) {
|
|
|
4724
3048
|
const k = propKeyName2(p);
|
|
4725
3049
|
return k !== "x" && k !== "y";
|
|
4726
3050
|
});
|
|
4727
|
-
const
|
|
4728
|
-
ms.appendRight(vars.start + 1, ` motionPath: ${motionPathCode}${
|
|
3051
|
+
const sep3 = survivesRemoval ? ", " : "";
|
|
3052
|
+
ms.appendRight(vars.start + 1, ` motionPath: ${motionPathCode}${sep3}`);
|
|
4729
3053
|
stripXYFromKeyframes(ms, findPropertyNode2(call.varsArg, "keyframes"));
|
|
4730
3054
|
removePropsByKey(ms, call.varsArg, /* @__PURE__ */ new Set(["x", "y"]));
|
|
4731
3055
|
return true;
|
|
@@ -5249,10 +3573,10 @@ function unrollDynamicAnimations(script, animationId, elements) {
|
|
|
5249
3573
|
}
|
|
5250
3574
|
|
|
5251
3575
|
// ../studio-server/dist/index.js
|
|
5252
|
-
import { createReadStream
|
|
3576
|
+
import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
5253
3577
|
import { Readable } from "stream";
|
|
5254
|
-
import { join as join8, resolve as
|
|
5255
|
-
import { createHash as
|
|
3578
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
3579
|
+
import { createHash as createHash22 } from "crypto";
|
|
5256
3580
|
|
|
5257
3581
|
// ../core/dist/studioPreviewMark.js
|
|
5258
3582
|
var STUDIO_PREVIEW_MARK_META = "hyperframes-studio-preview";
|
|
@@ -5261,46 +3585,46 @@ var STUDIO_PREVIEW_ERRORS = "__hfPreviewErrors";
|
|
|
5261
3585
|
|
|
5262
3586
|
// ../studio-server/dist/index.js
|
|
5263
3587
|
import { readlinkSync as readlinkSync2 } from "fs";
|
|
5264
|
-
import { dirname as
|
|
5265
|
-
import { readFileSync as
|
|
3588
|
+
import { dirname as dirname4, join as join7, parse as parse2, relative as relative3, resolve as resolve32 } from "path";
|
|
3589
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5266
3590
|
import { join as join9 } from "path";
|
|
5267
3591
|
import { streamSSE } from "hono/streaming";
|
|
5268
|
-
import { existsSync as existsSync6, lstatSync as
|
|
3592
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
5269
3593
|
import { join as join10 } from "path";
|
|
5270
3594
|
import { HTTPException } from "hono/http-exception";
|
|
5271
|
-
import { open, stat
|
|
5272
|
-
import { createHash as
|
|
3595
|
+
import { open, stat } from "fs/promises";
|
|
3596
|
+
import { createHash as createHash3 } from "crypto";
|
|
5273
3597
|
import {
|
|
5274
3598
|
closeSync as closeSync22,
|
|
5275
3599
|
fstatSync as fstatSync22,
|
|
5276
3600
|
openSync as openSync22,
|
|
5277
3601
|
readFileSync as readFileSync9,
|
|
5278
|
-
readdirSync as
|
|
5279
|
-
renameSync as
|
|
5280
|
-
rmSync as
|
|
3602
|
+
readdirSync as readdirSync4,
|
|
3603
|
+
renameSync as renameSync3,
|
|
3604
|
+
rmSync as rmSync4,
|
|
5281
3605
|
statSync as statSync6,
|
|
5282
3606
|
unlinkSync as unlinkSync4,
|
|
5283
|
-
writeFileSync as
|
|
3607
|
+
writeFileSync as writeFileSync4
|
|
5284
3608
|
} from "fs";
|
|
5285
3609
|
import { join as join11 } from "path";
|
|
5286
|
-
import { createHash as
|
|
3610
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
5287
3611
|
import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
5288
|
-
import { join as join12, resolve as
|
|
3612
|
+
import { join as join12, resolve as resolve5 } from "path";
|
|
5289
3613
|
import { execFile } from "child_process";
|
|
5290
3614
|
import { readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
|
|
5291
|
-
import { basename as
|
|
3615
|
+
import { basename as basename2, dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
|
|
5292
3616
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
5293
3617
|
import { existsSync as existsSync8 } from "fs";
|
|
5294
3618
|
import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
5295
3619
|
import { join as join14, resolve as resolve6 } from "path";
|
|
5296
3620
|
import { execFile as execFile2, spawn as spawn2 } from "child_process";
|
|
5297
3621
|
import { promisify } from "util";
|
|
5298
|
-
import { closeSync as
|
|
3622
|
+
import { closeSync as closeSync3, constants as constants2, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
|
|
5299
3623
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
5300
3624
|
import { existsSync as existsSync10 } from "fs";
|
|
5301
|
-
import { basename as basename3, dirname as dirname6, extname
|
|
3625
|
+
import { basename as basename3, dirname as dirname6, extname, join as join15, posix } from "path";
|
|
5302
3626
|
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
5303
|
-
import { homedir
|
|
3627
|
+
import { homedir } from "os";
|
|
5304
3628
|
import { join as join16 } from "path";
|
|
5305
3629
|
import { createHash as createHash6 } from "crypto";
|
|
5306
3630
|
import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
|
|
@@ -5310,7 +3634,7 @@ async function filterCompositionFiles(projectDir, files) {
|
|
|
5310
3634
|
const checks = await Promise.all(
|
|
5311
3635
|
htmlFiles.map(async (f) => {
|
|
5312
3636
|
try {
|
|
5313
|
-
const content = await
|
|
3637
|
+
const content = await readFile(join2(projectDir, f), "utf-8");
|
|
5314
3638
|
return isCompositionSource(content);
|
|
5315
3639
|
} catch {
|
|
5316
3640
|
return false;
|
|
@@ -5391,7 +3715,7 @@ function isAudioFile(name) {
|
|
|
5391
3715
|
return (getMimeType(name) ?? "").startsWith("audio/");
|
|
5392
3716
|
}
|
|
5393
3717
|
function rewriteRelativePaths(root, compPath, projectDir) {
|
|
5394
|
-
const assetExists = (path) =>
|
|
3718
|
+
const assetExists = (path) => existsSync(join22(projectDir, path));
|
|
5395
3719
|
rewriteAssetPaths(
|
|
5396
3720
|
root.querySelectorAll("[src], [href]"),
|
|
5397
3721
|
compPath,
|
|
@@ -5491,9 +3815,9 @@ function rootHeadContent(rootHtml) {
|
|
|
5491
3815
|
return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
|
|
5492
3816
|
}
|
|
5493
3817
|
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
|
|
5494
|
-
const compFile =
|
|
5495
|
-
if (!
|
|
5496
|
-
const rawComp = rawOverride ??
|
|
3818
|
+
const compFile = join22(projectDir, compPath);
|
|
3819
|
+
if (!existsSync(compFile)) return null;
|
|
3820
|
+
const rawComp = rawOverride ?? readFileSync3(compFile, "utf-8");
|
|
5497
3821
|
let compHeadContent = "";
|
|
5498
3822
|
let rewrittenContent;
|
|
5499
3823
|
let htmlAttrs = "";
|
|
@@ -5528,9 +3852,9 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
5528
3852
|
deferScriptsUntilFonts(scriptsDoc);
|
|
5529
3853
|
rewrittenContent = scriptsDoc.body.innerHTML;
|
|
5530
3854
|
rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
|
|
5531
|
-
const indexPath =
|
|
3855
|
+
const indexPath = join22(projectDir, "index.html");
|
|
5532
3856
|
let headContent = "";
|
|
5533
|
-
if (
|
|
3857
|
+
if (existsSync(indexPath)) headContent = rootHeadContent(readFileSync3(indexPath, "utf-8"));
|
|
5534
3858
|
if (baseHref && !hasBaseElement(headContent)) {
|
|
5535
3859
|
headContent = `<base href="${baseHref}">
|
|
5536
3860
|
${headContent}`;
|
|
@@ -5592,7 +3916,7 @@ function canonicalProjectPath(projectDir, candidate) {
|
|
|
5592
3916
|
if (folderGone(projectDir)) throw new ProjectRootMissingError(projectDir);
|
|
5593
3917
|
throw new CompositionInsertionError("Composition source escapes the project", 400);
|
|
5594
3918
|
}
|
|
5595
|
-
if (!
|
|
3919
|
+
if (!existsSync2(candidate)) {
|
|
5596
3920
|
throw new CompositionInsertionError("Composition source was not found", 404);
|
|
5597
3921
|
}
|
|
5598
3922
|
const canonical = realpath(candidate);
|
|
@@ -5624,7 +3948,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
|
|
|
5624
3948
|
validateSourcePath(sourcePath);
|
|
5625
3949
|
return canonicalProjectPath(
|
|
5626
3950
|
projectDir,
|
|
5627
|
-
resolveWithinProject(projectDir,
|
|
3951
|
+
resolveWithinProject(projectDir, relative(projectDir, resolve3(dirname2(ownerAbs), sourcePath)))
|
|
5628
3952
|
);
|
|
5629
3953
|
}
|
|
5630
3954
|
function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
|
|
@@ -5697,7 +4021,7 @@ function uniqueHostId(root, base) {
|
|
|
5697
4021
|
return `${base}_${suffix}`;
|
|
5698
4022
|
}
|
|
5699
4023
|
function relativeSourcePath(targetAbs, sourceAbs) {
|
|
5700
|
-
return
|
|
4024
|
+
return relative(dirname2(targetAbs), sourceAbs).split(sep).join("/");
|
|
5701
4025
|
}
|
|
5702
4026
|
function insertCompositionIntoSource(input) {
|
|
5703
4027
|
const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
|
|
@@ -5732,7 +4056,7 @@ function insertCompositionIntoSource(input) {
|
|
|
5732
4056
|
const host = document2.createElement("div");
|
|
5733
4057
|
host.id = hostId;
|
|
5734
4058
|
host.className = "clip";
|
|
5735
|
-
host.setAttribute("data-hf-id", `hf-${
|
|
4059
|
+
host.setAttribute("data-hf-id", `hf-${randomUUID()}`);
|
|
5736
4060
|
host.setAttribute("data-composition-id", hostId);
|
|
5737
4061
|
host.setAttribute("data-composition-src", relativeSourcePath(targetAbs, sourceAbs));
|
|
5738
4062
|
host.setAttribute("data-start", String(Math.round(input.start * 100) / 100));
|
|
@@ -5761,7 +4085,7 @@ function projectReader(projectDir) {
|
|
|
5761
4085
|
const file = resolveWithinProject(projectDir, compPath);
|
|
5762
4086
|
let source = null;
|
|
5763
4087
|
try {
|
|
5764
|
-
source = file ?
|
|
4088
|
+
source = file ? readFileSync2(file, "utf-8") : null;
|
|
5765
4089
|
} catch {
|
|
5766
4090
|
}
|
|
5767
4091
|
sources.set(compPath, source);
|
|
@@ -5813,7 +4137,7 @@ function lengthIsTimeline(projectDir, compPath) {
|
|
|
5813
4137
|
}
|
|
5814
4138
|
var inputSignatures = /* @__PURE__ */ new Map();
|
|
5815
4139
|
var rootMemos = /* @__PURE__ */ new Map();
|
|
5816
|
-
var sha1 = (text2) =>
|
|
4140
|
+
var sha1 = (text2) => createHash2("sha1").update(text2).digest("hex");
|
|
5817
4141
|
function rootHashes(read) {
|
|
5818
4142
|
const source = read(ROOT_COMPOSITION) ?? "";
|
|
5819
4143
|
return { root: sha1(source), head: sha1(rootHeadContent(source)) };
|
|
@@ -5919,36 +4243,36 @@ function decodeAudioPeaks(audioPath) {
|
|
|
5919
4243
|
});
|
|
5920
4244
|
}
|
|
5921
4245
|
async function generateWaveformCache(projectDir, assetPath) {
|
|
5922
|
-
const audioPath =
|
|
5923
|
-
if (!
|
|
5924
|
-
const stats =
|
|
5925
|
-
const cacheDir =
|
|
5926
|
-
const cachePath =
|
|
5927
|
-
if (isWaveformCacheDirectory(cacheDir) &&
|
|
4246
|
+
const audioPath = join3(projectDir, assetPath);
|
|
4247
|
+
if (!existsSync3(audioPath)) return;
|
|
4248
|
+
const stats = statSync(audioPath);
|
|
4249
|
+
const cacheDir = join3(projectDir, ".waveform-cache");
|
|
4250
|
+
const cachePath = join3(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
4251
|
+
if (isWaveformCacheDirectory(cacheDir) && existsSync3(cachePath)) return;
|
|
5928
4252
|
const peaks = await decodeAudioPeaks(audioPath);
|
|
5929
4253
|
writeWaveformCache(cachePath, peaks);
|
|
5930
4254
|
}
|
|
5931
4255
|
function isWaveformCacheDirectory(cacheDir) {
|
|
5932
|
-
return
|
|
4256
|
+
return lstatSync(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
5933
4257
|
}
|
|
5934
4258
|
function writeWaveformCache(cachePath, peaks) {
|
|
5935
|
-
const cacheDir =
|
|
4259
|
+
const cacheDir = dirname22(cachePath);
|
|
5936
4260
|
try {
|
|
5937
|
-
|
|
4261
|
+
mkdirSync(cacheDir, { mode: 448 });
|
|
5938
4262
|
} catch (error) {
|
|
5939
4263
|
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
5940
4264
|
}
|
|
5941
4265
|
if (!isWaveformCacheDirectory(cacheDir)) {
|
|
5942
4266
|
throw new Error("Waveform cache must be a directory, not a symlink");
|
|
5943
4267
|
}
|
|
5944
|
-
const stagingDir = mkdtempSync(
|
|
4268
|
+
const stagingDir = mkdtempSync(join3(cacheDir, ".waveform-"));
|
|
5945
4269
|
try {
|
|
5946
|
-
const stagingPath =
|
|
5947
|
-
|
|
5948
|
-
|
|
4270
|
+
const stagingPath = join3(stagingDir, "peaks.json");
|
|
4271
|
+
writeFileSync2(stagingPath, JSON.stringify(peaks), { flag: "wx" });
|
|
4272
|
+
renameSync(stagingPath, cachePath);
|
|
5949
4273
|
} finally {
|
|
5950
4274
|
try {
|
|
5951
|
-
|
|
4275
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
5952
4276
|
} catch {
|
|
5953
4277
|
}
|
|
5954
4278
|
}
|
|
@@ -6002,13 +4326,13 @@ function checkStreams(stdout, isVideo, isAudio) {
|
|
|
6002
4326
|
}
|
|
6003
4327
|
}
|
|
6004
4328
|
function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
|
|
6005
|
-
const tempDir = mkdtempSync2(
|
|
6006
|
-
const tempPath =
|
|
4329
|
+
const tempDir = mkdtempSync2(join4(tmpdir(), "hyperframes-upload-"));
|
|
4330
|
+
const tempPath = join4(tempDir, basename(fileName));
|
|
6007
4331
|
try {
|
|
6008
|
-
|
|
4332
|
+
writeFileSync22(tempPath, buffer);
|
|
6009
4333
|
return validateUploadedMedia(tempPath, runner);
|
|
6010
4334
|
} finally {
|
|
6011
|
-
|
|
4335
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
6012
4336
|
}
|
|
6013
4337
|
}
|
|
6014
4338
|
var DEFAULT_KEEP_PER_FILE = 10;
|
|
@@ -6020,7 +4344,7 @@ function timestampPrefix() {
|
|
|
6020
4344
|
}
|
|
6021
4345
|
function backupPathForResponse(projectDir, backupPath) {
|
|
6022
4346
|
if (!backupPath) return null;
|
|
6023
|
-
const rel =
|
|
4347
|
+
const rel = relative2(projectDir, backupPath);
|
|
6024
4348
|
if (!rel || rel.startsWith("..")) return null;
|
|
6025
4349
|
return rel.split("\\").join("/");
|
|
6026
4350
|
}
|
|
@@ -6028,12 +4352,12 @@ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
|
|
|
6028
4352
|
if (!isSafePath(projectDir, absPath)) return { backupPath: null };
|
|
6029
4353
|
try {
|
|
6030
4354
|
const content = readFileSync32(absPath);
|
|
6031
|
-
const relativePath =
|
|
6032
|
-
const backupDir =
|
|
4355
|
+
const relativePath = relative2(projectDir, absPath);
|
|
4356
|
+
const backupDir = join5(projectDir, ".hyperframes", "backup");
|
|
6033
4357
|
mkdirWithinProject(projectDir, backupDir);
|
|
6034
4358
|
const backupKey = backupKeyForPath(relativePath);
|
|
6035
4359
|
const backupPath = nextBackupPath(backupDir, backupKey);
|
|
6036
|
-
|
|
4360
|
+
writeFileSync3(backupPath, content);
|
|
6037
4361
|
pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
|
|
6038
4362
|
return { backupPath };
|
|
6039
4363
|
} catch (error) {
|
|
@@ -6046,7 +4370,7 @@ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
|
|
|
6046
4370
|
}
|
|
6047
4371
|
function nextBackupPath(backupDir, backupKey) {
|
|
6048
4372
|
const base = `${timestampPrefix()}-${backupKey}`;
|
|
6049
|
-
let candidate =
|
|
4373
|
+
let candidate = join5(backupDir, base);
|
|
6050
4374
|
let counter = 2;
|
|
6051
4375
|
while (true) {
|
|
6052
4376
|
try {
|
|
@@ -6057,7 +4381,7 @@ function nextBackupPath(backupDir, backupKey) {
|
|
|
6057
4381
|
}
|
|
6058
4382
|
throw error;
|
|
6059
4383
|
}
|
|
6060
|
-
candidate =
|
|
4384
|
+
candidate = join5(backupDir, `${base}-${counter}`);
|
|
6061
4385
|
counter += 1;
|
|
6062
4386
|
}
|
|
6063
4387
|
}
|
|
@@ -6065,7 +4389,7 @@ function pruneBackups(backupDir, backupKey, keepPerFile) {
|
|
|
6065
4389
|
const keep = Math.max(1, Math.floor(keepPerFile));
|
|
6066
4390
|
const suffix = `-${backupKey}`;
|
|
6067
4391
|
const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
|
|
6068
|
-
const matches =
|
|
4392
|
+
const matches = readdirSync(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join5(backupDir, name)).sort((a, b) => {
|
|
6069
4393
|
return b.localeCompare(a);
|
|
6070
4394
|
});
|
|
6071
4395
|
for (const file of matches.slice(keep)) {
|
|
@@ -6082,26 +4406,26 @@ function replaceWithProjectDirMissing(c, hostHeaders) {
|
|
|
6082
4406
|
c.res = void 0;
|
|
6083
4407
|
c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
|
|
6084
4408
|
}
|
|
6085
|
-
function applyFileMutations(projectDir, mutations, requestToken,
|
|
4409
|
+
function applyFileMutations(projectDir, mutations, requestToken, writeFile = (path, content) => replaceFileAtomically(path, content, statSync2(path).mode)) {
|
|
6086
4410
|
const prepared = mutations.map((mutation) => ({
|
|
6087
4411
|
...mutation,
|
|
6088
|
-
before: mutation.before ??
|
|
4412
|
+
before: mutation.before ?? readFileSync4(mutation.absPath, "utf-8")
|
|
6089
4413
|
}));
|
|
6090
4414
|
const results = [];
|
|
6091
4415
|
const attempted = [];
|
|
6092
4416
|
for (const mutation of prepared)
|
|
6093
|
-
assertExpectedVersion(mutation,
|
|
4417
|
+
assertExpectedVersion(mutation, readFileSync4(mutation.absPath, "utf-8"));
|
|
6094
4418
|
try {
|
|
6095
4419
|
for (const mutation of prepared) {
|
|
6096
|
-
results.push(applyOneMutation(projectDir, mutation, requestToken,
|
|
4420
|
+
results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted));
|
|
6097
4421
|
}
|
|
6098
4422
|
return results;
|
|
6099
4423
|
} catch (error) {
|
|
6100
4424
|
const rollbackErrors = [];
|
|
6101
4425
|
for (const mutation of attempted.reverse()) {
|
|
6102
4426
|
try {
|
|
6103
|
-
if (!mutation.written ||
|
|
6104
|
-
|
|
4427
|
+
if (!mutation.written || readFileSync4(mutation.absPath, "utf-8") === mutation.after)
|
|
4428
|
+
writeFile(mutation.absPath, mutation.before, "utf-8");
|
|
6105
4429
|
clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
|
|
6106
4430
|
} catch (rollbackError) {
|
|
6107
4431
|
rollbackErrors.push(rollbackError);
|
|
@@ -6116,8 +4440,8 @@ function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (p
|
|
|
6116
4440
|
throw error;
|
|
6117
4441
|
}
|
|
6118
4442
|
}
|
|
6119
|
-
function applyOneMutation(projectDir, mutation, requestToken,
|
|
6120
|
-
const current =
|
|
4443
|
+
function applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted) {
|
|
4444
|
+
const current = readFileSync4(mutation.absPath, "utf-8");
|
|
6121
4445
|
assertExpectedVersion(mutation, current);
|
|
6122
4446
|
if (mutation.after === mutation.before) {
|
|
6123
4447
|
return {
|
|
@@ -6131,13 +4455,13 @@ function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attemp
|
|
|
6131
4455
|
}
|
|
6132
4456
|
const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
|
|
6133
4457
|
if (backup.error) throw new Error(`backup failed: ${backup.error}`);
|
|
6134
|
-
assertExpectedVersion(mutation,
|
|
4458
|
+
assertExpectedVersion(mutation, readFileSync4(mutation.absPath, "utf-8"));
|
|
6135
4459
|
const before = current;
|
|
6136
4460
|
const version = fileContentVersion(mutation.after);
|
|
6137
4461
|
const writeToken = createWriteToken(requestToken);
|
|
6138
4462
|
const attempt = { ...mutation, before, version, writeToken, written: false };
|
|
6139
4463
|
attempted.push(attempt);
|
|
6140
|
-
|
|
4464
|
+
writeFile(mutation.absPath, mutation.after, "utf-8");
|
|
6141
4465
|
attempt.written = true;
|
|
6142
4466
|
recordFileWriteReceipt(mutation.absPath, {
|
|
6143
4467
|
path: mutation.sourceFile,
|
|
@@ -6258,18 +4582,18 @@ async function loadGsapParser() {
|
|
|
6258
4582
|
return import("./gsapParser-UP3PNPC2.js");
|
|
6259
4583
|
}
|
|
6260
4584
|
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
6261
|
-
if (!isSafePath(projectDir,
|
|
4585
|
+
if (!isSafePath(projectDir, dirname3(lexicalPath))) return false;
|
|
6262
4586
|
let stats;
|
|
6263
4587
|
try {
|
|
6264
|
-
stats =
|
|
4588
|
+
stats = lstatSync2(lexicalPath);
|
|
6265
4589
|
} catch {
|
|
6266
4590
|
return false;
|
|
6267
4591
|
}
|
|
6268
4592
|
if (!stats.isSymbolicLink()) return false;
|
|
6269
|
-
const target =
|
|
4593
|
+
const target = resolve2(dirname3(lexicalPath), readlinkSync(lexicalPath));
|
|
6270
4594
|
if (!isSafePath(projectDir, target)) return false;
|
|
6271
4595
|
try {
|
|
6272
|
-
|
|
4596
|
+
statSync3(lexicalPath);
|
|
6273
4597
|
return false;
|
|
6274
4598
|
} catch {
|
|
6275
4599
|
return true;
|
|
@@ -6290,7 +4614,7 @@ async function resolveProjectPath(c, adapter, route, opts) {
|
|
|
6290
4614
|
}
|
|
6291
4615
|
const absPath = (opts?.pin ? pinWithinProject : resolveWithinProject)(project.dir, filePath);
|
|
6292
4616
|
if (!absPath) {
|
|
6293
|
-
if (isDanglingSymlinkInProject(project.dir,
|
|
4617
|
+
if (isDanglingSymlinkInProject(project.dir, resolve2(project.dir, filePath))) {
|
|
6294
4618
|
return { error: c.json({ error: "not found", why: "dangling_symlink" }, 404) };
|
|
6295
4619
|
}
|
|
6296
4620
|
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
@@ -6361,7 +4685,7 @@ function projectHtmlIds(projectDir) {
|
|
|
6361
4685
|
if (!rel.endsWith(".html") || isInHiddenOrVendorDir(rel)) continue;
|
|
6362
4686
|
let html;
|
|
6363
4687
|
try {
|
|
6364
|
-
html =
|
|
4688
|
+
html = readFileSync5(join6(projectDir, rel), "utf-8");
|
|
6365
4689
|
} catch {
|
|
6366
4690
|
continue;
|
|
6367
4691
|
}
|
|
@@ -6369,10 +4693,10 @@ function projectHtmlIds(projectDir) {
|
|
|
6369
4693
|
}
|
|
6370
4694
|
return ids;
|
|
6371
4695
|
}
|
|
6372
|
-
function commitElementPatchBatches(projectDir, batches,
|
|
4696
|
+
function commitElementPatchBatches(projectDir, batches, writeFile = (path, content) => replaceFileAtomically(path, content, statSync3(path).mode), requestToken) {
|
|
6373
4697
|
for (let attempt = 1; ; attempt += 1) {
|
|
6374
4698
|
try {
|
|
6375
|
-
return foldAndCommitElementPatchBatches(projectDir, batches,
|
|
4699
|
+
return foldAndCommitElementPatchBatches(projectDir, batches, writeFile, requestToken);
|
|
6376
4700
|
} catch (error) {
|
|
6377
4701
|
if (!(error instanceof FileChangedError)) throw error;
|
|
6378
4702
|
if (attempt === PATCH_CONFLICT_ATTEMPTS)
|
|
@@ -6380,7 +4704,7 @@ function commitElementPatchBatches(projectDir, batches, writeFile2 = (path, cont
|
|
|
6380
4704
|
}
|
|
6381
4705
|
}
|
|
6382
4706
|
}
|
|
6383
|
-
function foldAndCommitElementPatchBatches(projectDir, batches,
|
|
4707
|
+
function foldAndCommitElementPatchBatches(projectDir, batches, writeFile, requestToken) {
|
|
6384
4708
|
const resolvedPaths = /* @__PURE__ */ new Set();
|
|
6385
4709
|
const prepared = [];
|
|
6386
4710
|
for (const batch of batches) {
|
|
@@ -6390,7 +4714,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
|
|
|
6390
4714
|
resolvedPaths.add(absPath);
|
|
6391
4715
|
let before;
|
|
6392
4716
|
try {
|
|
6393
|
-
before =
|
|
4717
|
+
before = readFileSync5(absPath, "utf-8");
|
|
6394
4718
|
} catch {
|
|
6395
4719
|
return { error: "not-found", sourceFile: batch.sourceFile };
|
|
6396
4720
|
}
|
|
@@ -6425,7 +4749,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
|
|
|
6425
4749
|
after
|
|
6426
4750
|
})),
|
|
6427
4751
|
requestToken,
|
|
6428
|
-
|
|
4752
|
+
writeFile
|
|
6429
4753
|
);
|
|
6430
4754
|
const files = applied.map((file, index) => ({
|
|
6431
4755
|
sourceFile: file.sourceFile,
|
|
@@ -6446,8 +4770,8 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
|
|
|
6446
4770
|
);
|
|
6447
4771
|
}
|
|
6448
4772
|
function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
6449
|
-
const overwrote =
|
|
6450
|
-
replaceFileAtomically(absPath, html,
|
|
4773
|
+
const overwrote = readFileSync5(absPath);
|
|
4774
|
+
replaceFileAtomically(absPath, html, statSync3(absPath).mode);
|
|
6451
4775
|
const version = fileContentVersion(html);
|
|
6452
4776
|
const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
|
|
6453
4777
|
recordFileWriteReceipt(absPath, { path: filePath, version, writeToken, overwrote });
|
|
@@ -6456,7 +4780,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
|
6456
4780
|
function writeMutationResult(c, projectDir, filePath, absPath, html, original) {
|
|
6457
4781
|
const backup = snapshotBeforeWrite(projectDir, absPath);
|
|
6458
4782
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
6459
|
-
if (
|
|
4783
|
+
if (readFileSync5(absPath, "utf-8") !== original) {
|
|
6460
4784
|
return c.json({ error: "file changed", conflict: true, path: filePath }, 409);
|
|
6461
4785
|
}
|
|
6462
4786
|
const { version } = writeFileWithReceipt(c, filePath, absPath, html);
|
|
@@ -6502,7 +4826,7 @@ async function parseMutationBody(c) {
|
|
|
6502
4826
|
return { target: body.target, body };
|
|
6503
4827
|
}
|
|
6504
4828
|
function ensureDir(projectDir, filePath) {
|
|
6505
|
-
mkdirWithinProject(projectDir,
|
|
4829
|
+
mkdirWithinProject(projectDir, dirname3(filePath));
|
|
6506
4830
|
}
|
|
6507
4831
|
function generateCopyPath(projectDir, originalPath) {
|
|
6508
4832
|
const ext = originalPath.includes(".") ? "." + originalPath.split(".").pop() : "";
|
|
@@ -6511,7 +4835,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6511
4835
|
const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
|
|
6512
4836
|
let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
|
|
6513
4837
|
let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
|
|
6514
|
-
while (existsSync4(
|
|
4838
|
+
while (existsSync4(resolve2(projectDir, candidate))) {
|
|
6515
4839
|
num++;
|
|
6516
4840
|
candidate = `${cleanBase} (copy ${num})${ext}`;
|
|
6517
4841
|
}
|
|
@@ -6520,7 +4844,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
6520
4844
|
function walkFiles(dir, filter) {
|
|
6521
4845
|
const results = [];
|
|
6522
4846
|
for (const entry of readableEntries(dir)) {
|
|
6523
|
-
const full2 =
|
|
4847
|
+
const full2 = join6(dir, entry.name);
|
|
6524
4848
|
if (entry.isDirectory()) {
|
|
6525
4849
|
if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
|
|
6526
4850
|
continue;
|
|
@@ -6537,7 +4861,7 @@ function skippable(error) {
|
|
|
6537
4861
|
}
|
|
6538
4862
|
function readableEntries(dir) {
|
|
6539
4863
|
try {
|
|
6540
|
-
return
|
|
4864
|
+
return readdirSync2(dir, { withFileTypes: true });
|
|
6541
4865
|
} catch (error) {
|
|
6542
4866
|
if (skippable(error)) return [];
|
|
6543
4867
|
throw error;
|
|
@@ -6545,7 +4869,7 @@ function readableEntries(dir) {
|
|
|
6545
4869
|
}
|
|
6546
4870
|
function readableText(file) {
|
|
6547
4871
|
try {
|
|
6548
|
-
return
|
|
4872
|
+
return readFileSync5(file, "utf-8");
|
|
6549
4873
|
} catch (error) {
|
|
6550
4874
|
if (skippable(error)) return null;
|
|
6551
4875
|
throw error;
|
|
@@ -6603,7 +4927,7 @@ function occurrences(text2, part) {
|
|
|
6603
4927
|
}
|
|
6604
4928
|
function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
|
|
6605
4929
|
return readableEntries(dir).flatMap((entry) => {
|
|
6606
|
-
const [path, full2] = [`${prefix}${entry.name}`,
|
|
4930
|
+
const [path, full2] = [`${prefix}${entry.name}`, join6(dir, entry.name)];
|
|
6607
4931
|
const real = entry.name === "node_modules" ? null : directoryReal(entry, full2);
|
|
6608
4932
|
if (real === null || ancestors.has(real) || !isSafePath(root, full2)) return [path];
|
|
6609
4933
|
return [path, ...projectPaths(root, full2, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
|
|
@@ -6611,7 +4935,7 @@ function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */
|
|
|
6611
4935
|
}
|
|
6612
4936
|
function directoryReal(entry, full2) {
|
|
6613
4937
|
try {
|
|
6614
|
-
return entry.isDirectory() || entry.isSymbolicLink() &&
|
|
4938
|
+
return entry.isDirectory() || entry.isSymbolicLink() && statSync3(full2).isDirectory() ? realpathSync(full2) : null;
|
|
6615
4939
|
} catch {
|
|
6616
4940
|
return null;
|
|
6617
4941
|
}
|
|
@@ -6629,7 +4953,7 @@ function updateReferences(projectDir, oldPath, newPath, isDirectory) {
|
|
|
6629
4953
|
if (content === null) continue;
|
|
6630
4954
|
const updated = rewrite(content);
|
|
6631
4955
|
if (updated !== content) {
|
|
6632
|
-
replaceFileAtomically(file, updated,
|
|
4956
|
+
replaceFileAtomically(file, updated, statSync3(file).mode);
|
|
6633
4957
|
updatedCount++;
|
|
6634
4958
|
}
|
|
6635
4959
|
}
|
|
@@ -6911,12 +5235,12 @@ function containsRawGsapExpression(value) {
|
|
|
6911
5235
|
return Object.values(value).some(containsRawGsapExpression);
|
|
6912
5236
|
}
|
|
6913
5237
|
async function prepareGsapMutationScript(c, res, firstMutation) {
|
|
6914
|
-
const beforeHtml =
|
|
5238
|
+
const beforeHtml = readFileSync5(res.absPath, "utf-8");
|
|
6915
5239
|
let html = beforeHtml;
|
|
6916
5240
|
let block = extractGsapScriptBlock(html);
|
|
6917
5241
|
if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
|
|
6918
5242
|
const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
|
|
6919
|
-
const { GSAP_CDN } = await import("./dist-
|
|
5243
|
+
const { GSAP_CDN } = await import("./dist-ROUDWGLO.js");
|
|
6920
5244
|
const bootstrap = [
|
|
6921
5245
|
`<script src="${GSAP_CDN}"></script>`,
|
|
6922
5246
|
"<script>",
|
|
@@ -7028,7 +5352,7 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
7028
5352
|
const changed = block.scriptText !== initialScript;
|
|
7029
5353
|
const newHtml = changed ? block.replaceScript(block.scriptText) : html;
|
|
7030
5354
|
let backupPath = null;
|
|
7031
|
-
if (
|
|
5355
|
+
if (readFileSync5(res.absPath, "utf-8") !== beforeHtml) {
|
|
7032
5356
|
return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
|
|
7033
5357
|
}
|
|
7034
5358
|
if (changed) {
|
|
@@ -7749,7 +6073,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7749
6073
|
skipped.push(name);
|
|
7750
6074
|
continue;
|
|
7751
6075
|
}
|
|
7752
|
-
const destPath =
|
|
6076
|
+
const destPath = resolve2(targetDir, name);
|
|
7753
6077
|
if (!isSafePath(projectDir, destPath)) continue;
|
|
7754
6078
|
let finalPath = destPath;
|
|
7755
6079
|
let finalName = name;
|
|
@@ -7760,13 +6084,13 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7760
6084
|
let n = 1;
|
|
7761
6085
|
if (existsSync4(finalPath)) {
|
|
7762
6086
|
n = 2;
|
|
7763
|
-
while (n < MAX_COPY_INDEX && existsSync4(
|
|
6087
|
+
while (n < MAX_COPY_INDEX && existsSync4(resolve2(targetDir, `${base} (${n})${ext}`))) n++;
|
|
7764
6088
|
if (n >= MAX_COPY_INDEX) {
|
|
7765
6089
|
skipped.push(name);
|
|
7766
6090
|
continue;
|
|
7767
6091
|
}
|
|
7768
6092
|
finalName = `${base} (${n})${ext}`;
|
|
7769
|
-
finalPath =
|
|
6093
|
+
finalPath = resolve2(targetDir, finalName);
|
|
7770
6094
|
}
|
|
7771
6095
|
if (!isSafePath(projectDir, finalPath) || isPrivateProjectFile(projectDir, finalPath)) continue;
|
|
7772
6096
|
const buffer = Buffer.from(await value.arrayBuffer());
|
|
@@ -7787,14 +6111,14 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
7787
6111
|
}
|
|
7788
6112
|
n++;
|
|
7789
6113
|
finalName = `${base} (${n})${ext}`;
|
|
7790
|
-
finalPath =
|
|
6114
|
+
finalPath = resolve2(targetDir, finalName);
|
|
7791
6115
|
}
|
|
7792
6116
|
}
|
|
7793
6117
|
if (!written) {
|
|
7794
6118
|
if (n >= MAX_COPY_INDEX) skipped.push(name);
|
|
7795
6119
|
continue;
|
|
7796
6120
|
}
|
|
7797
|
-
const relativePath = subDir ?
|
|
6121
|
+
const relativePath = subDir ? join6(subDir, finalName) : finalName;
|
|
7798
6122
|
uploaded.push(relativePath);
|
|
7799
6123
|
if (validation.unchecked) unchecked.push({ name: finalName, reason: validation.unchecked });
|
|
7800
6124
|
if (isAudioFile(finalName)) {
|
|
@@ -7812,7 +6136,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7812
6136
|
if ("error" in res) return res.error;
|
|
7813
6137
|
let fd;
|
|
7814
6138
|
try {
|
|
7815
|
-
fd =
|
|
6139
|
+
fd = openSync2(res.absPath, "r");
|
|
7816
6140
|
} catch {
|
|
7817
6141
|
if (c.req.query("optional") === "1") {
|
|
7818
6142
|
return c.json({ filename: res.filePath, content: "", missing: true });
|
|
@@ -7820,14 +6144,14 @@ function registerFileRoutes(api, adapter) {
|
|
|
7820
6144
|
return c.json({ error: "not found" }, 404);
|
|
7821
6145
|
}
|
|
7822
6146
|
try {
|
|
7823
|
-
const
|
|
7824
|
-
if (!
|
|
6147
|
+
const stat2 = fstatSync2(fd);
|
|
6148
|
+
if (!stat2.isFile()) {
|
|
7825
6149
|
return c.json({ error: "not found", why: "not_a_file" }, 404);
|
|
7826
6150
|
}
|
|
7827
|
-
if (
|
|
6151
|
+
if (stat2.size > MAX_TEXT_READ_BYTES) {
|
|
7828
6152
|
return c.json({ error: "too large to read as text", why: "too_large" }, 413);
|
|
7829
6153
|
}
|
|
7830
|
-
const content =
|
|
6154
|
+
const content = readFileSync5(fd);
|
|
7831
6155
|
const version = fileContentVersion(content);
|
|
7832
6156
|
c.header("ETag", version);
|
|
7833
6157
|
if (content.subarray(0, GIT_BINARY_SNIFF_BYTES).includes(0)) {
|
|
@@ -7840,7 +6164,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7840
6164
|
missing: false
|
|
7841
6165
|
});
|
|
7842
6166
|
} finally {
|
|
7843
|
-
|
|
6167
|
+
closeSync2(fd);
|
|
7844
6168
|
}
|
|
7845
6169
|
});
|
|
7846
6170
|
api.put("/projects/:id/files/*", async (c) => {
|
|
@@ -7852,7 +6176,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7852
6176
|
if (expectedVersion === null && !createOnly) {
|
|
7853
6177
|
let currentContent = null;
|
|
7854
6178
|
try {
|
|
7855
|
-
currentContent =
|
|
6179
|
+
currentContent = readFileSync5(res.absPath);
|
|
7856
6180
|
} catch (error) {
|
|
7857
6181
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7858
6182
|
throw error;
|
|
@@ -7878,7 +6202,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7878
6202
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
7879
6203
|
throw error;
|
|
7880
6204
|
}
|
|
7881
|
-
const currentContent =
|
|
6205
|
+
const currentContent = readFileSync5(res.absPath);
|
|
7882
6206
|
return c.json(
|
|
7883
6207
|
{
|
|
7884
6208
|
error: "file conflict",
|
|
@@ -7892,7 +6216,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7892
6216
|
} else {
|
|
7893
6217
|
let fd;
|
|
7894
6218
|
try {
|
|
7895
|
-
fd =
|
|
6219
|
+
fd = openSync2(res.absPath, "r+");
|
|
7896
6220
|
} catch (error) {
|
|
7897
6221
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7898
6222
|
throw error;
|
|
@@ -7908,7 +6232,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7908
6232
|
);
|
|
7909
6233
|
}
|
|
7910
6234
|
try {
|
|
7911
|
-
const currentContent =
|
|
6235
|
+
const currentContent = readFileSync5(fd);
|
|
7912
6236
|
overwrote = currentContent;
|
|
7913
6237
|
const currentVersion = fileContentVersion(currentContent);
|
|
7914
6238
|
if (expectedVersion !== currentVersion) {
|
|
@@ -7925,11 +6249,11 @@ function registerFileRoutes(api, adapter) {
|
|
|
7925
6249
|
backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
7926
6250
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
7927
6251
|
const mode = fstatSync2(fd).mode;
|
|
7928
|
-
|
|
6252
|
+
closeSync2(fd);
|
|
7929
6253
|
fd = null;
|
|
7930
6254
|
replaceFileAtomically(res.absPath, body, mode);
|
|
7931
6255
|
} finally {
|
|
7932
|
-
if (fd !== null)
|
|
6256
|
+
if (fd !== null) closeSync2(fd);
|
|
7933
6257
|
}
|
|
7934
6258
|
}
|
|
7935
6259
|
const version = fileContentVersion(body);
|
|
@@ -7962,10 +6286,10 @@ function registerFileRoutes(api, adapter) {
|
|
|
7962
6286
|
api.delete("/projects/:id/files/*", async (c) => {
|
|
7963
6287
|
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
|
7964
6288
|
if ("error" in res) return res.error;
|
|
7965
|
-
const
|
|
6289
|
+
const stat2 = statSync3(res.absPath);
|
|
7966
6290
|
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
7967
6291
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
7968
|
-
if (
|
|
6292
|
+
if (stat2.isDirectory()) {
|
|
7969
6293
|
rmSync3(res.absPath, { recursive: true });
|
|
7970
6294
|
} else {
|
|
7971
6295
|
unlinkSync2(res.absPath);
|
|
@@ -7984,7 +6308,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
7984
6308
|
}
|
|
7985
6309
|
let before;
|
|
7986
6310
|
try {
|
|
7987
|
-
before =
|
|
6311
|
+
before = readFileSync5(ctx.absPath, "utf-8");
|
|
7988
6312
|
} catch (error) {
|
|
7989
6313
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
|
|
7990
6314
|
throw error;
|
|
@@ -8013,7 +6337,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8013
6337
|
}
|
|
8014
6338
|
const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
|
|
8015
6339
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
8016
|
-
const current =
|
|
6340
|
+
const current = readFileSync5(ctx.absPath, "utf-8");
|
|
8017
6341
|
if (current !== before) {
|
|
8018
6342
|
const currentVersion2 = fileContentVersion(current);
|
|
8019
6343
|
return c.json({ error: "file conflict", currentVersion: currentVersion2, currentContent: current }, 409);
|
|
@@ -8046,7 +6370,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8046
6370
|
}
|
|
8047
6371
|
const parsed = await parseMutationBody(c);
|
|
8048
6372
|
if ("error" in parsed) return parsed.error;
|
|
8049
|
-
const originalContent =
|
|
6373
|
+
const originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8050
6374
|
return writeIfChanged(
|
|
8051
6375
|
c,
|
|
8052
6376
|
ctx.project.dir,
|
|
@@ -8067,7 +6391,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8067
6391
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
8068
6392
|
return c.json({ error: "targets required" }, 400);
|
|
8069
6393
|
}
|
|
8070
|
-
const originalContent =
|
|
6394
|
+
const originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8071
6395
|
return writeIfChanged(
|
|
8072
6396
|
c,
|
|
8073
6397
|
ctx.project.dir,
|
|
@@ -8103,7 +6427,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8103
6427
|
seen.add(absPath);
|
|
8104
6428
|
let before;
|
|
8105
6429
|
try {
|
|
8106
|
-
before =
|
|
6430
|
+
before = readFileSync5(absPath, "utf-8");
|
|
8107
6431
|
} catch {
|
|
8108
6432
|
return c.json({ error: `not found: ${file.path}` }, 404);
|
|
8109
6433
|
}
|
|
@@ -8130,7 +6454,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8130
6454
|
prepared.push(folded);
|
|
8131
6455
|
}
|
|
8132
6456
|
for (const file of prepared) {
|
|
8133
|
-
const current =
|
|
6457
|
+
const current = readFileSync5(file.absPath, "utf-8");
|
|
8134
6458
|
if (current !== file.before) {
|
|
8135
6459
|
return c.json(
|
|
8136
6460
|
{
|
|
@@ -8160,7 +6484,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8160
6484
|
const written = [];
|
|
8161
6485
|
try {
|
|
8162
6486
|
for (const file of prepared) {
|
|
8163
|
-
replaceFileAtomically(file.absPath, file.after,
|
|
6487
|
+
replaceFileAtomically(file.absPath, file.after, statSync3(file.absPath).mode);
|
|
8164
6488
|
written.push(file);
|
|
8165
6489
|
recordFileWriteReceipt(file.absPath, {
|
|
8166
6490
|
path: file.path,
|
|
@@ -8173,12 +6497,12 @@ function registerFileRoutes(api, adapter) {
|
|
|
8173
6497
|
const conflicts = [];
|
|
8174
6498
|
for (const file of written.reverse()) {
|
|
8175
6499
|
try {
|
|
8176
|
-
const current =
|
|
6500
|
+
const current = readFileSync5(file.absPath, "utf-8");
|
|
8177
6501
|
if (current !== file.after) {
|
|
8178
6502
|
conflicts.push(file.path);
|
|
8179
6503
|
continue;
|
|
8180
6504
|
}
|
|
8181
|
-
replaceFileAtomically(file.absPath, file.before,
|
|
6505
|
+
replaceFileAtomically(file.absPath, file.before, statSync3(file.absPath).mode);
|
|
8182
6506
|
recordFileWriteReceipt(file.absPath, {
|
|
8183
6507
|
path: file.path,
|
|
8184
6508
|
version: fileContentVersion(file.before),
|
|
@@ -8221,7 +6545,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8221
6545
|
const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
|
|
8222
6546
|
let originalContent;
|
|
8223
6547
|
try {
|
|
8224
|
-
originalContent =
|
|
6548
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8225
6549
|
} catch {
|
|
8226
6550
|
return c.json({ error: "not found" }, 404);
|
|
8227
6551
|
}
|
|
@@ -8283,7 +6607,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8283
6607
|
for (let attempt = 1; ; attempt += 1) {
|
|
8284
6608
|
let originalContent;
|
|
8285
6609
|
try {
|
|
8286
|
-
originalContent =
|
|
6610
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8287
6611
|
} catch {
|
|
8288
6612
|
return c.json({ error: "not found" }, 404);
|
|
8289
6613
|
}
|
|
@@ -8395,7 +6719,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8395
6719
|
}
|
|
8396
6720
|
let originalContent;
|
|
8397
6721
|
try {
|
|
8398
|
-
originalContent =
|
|
6722
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8399
6723
|
} catch {
|
|
8400
6724
|
return c.json({ error: "not found" }, 404);
|
|
8401
6725
|
}
|
|
@@ -8451,7 +6775,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8451
6775
|
).map((entry) => ({ target: entry.target, track: entry.track }));
|
|
8452
6776
|
let originalContent;
|
|
8453
6777
|
try {
|
|
8454
|
-
originalContent =
|
|
6778
|
+
originalContent = readFileSync5(ctx.absPath, "utf-8");
|
|
8455
6779
|
} catch {
|
|
8456
6780
|
return c.json({ error: "not found" }, 404);
|
|
8457
6781
|
}
|
|
@@ -8480,7 +6804,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8480
6804
|
if ("error" in parsed) return parsed.error;
|
|
8481
6805
|
let content;
|
|
8482
6806
|
try {
|
|
8483
|
-
content =
|
|
6807
|
+
content = readFileSync5(ctx.absPath, "utf-8");
|
|
8484
6808
|
} catch {
|
|
8485
6809
|
return c.json({ exists: false });
|
|
8486
6810
|
}
|
|
@@ -8494,7 +6818,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8494
6818
|
if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
|
|
8495
6819
|
let content;
|
|
8496
6820
|
try {
|
|
8497
|
-
content =
|
|
6821
|
+
content = readFileSync5(ctx.absPath, "utf-8");
|
|
8498
6822
|
} catch {
|
|
8499
6823
|
return c.json({ exists: body.targets.map(() => false) });
|
|
8500
6824
|
}
|
|
@@ -8514,9 +6838,9 @@ function registerFileRoutes(api, adapter) {
|
|
|
8514
6838
|
if (existsSync4(newAbs)) {
|
|
8515
6839
|
return c.json({ error: "already exists" }, 409);
|
|
8516
6840
|
}
|
|
8517
|
-
const isDirectory =
|
|
6841
|
+
const isDirectory = statSync3(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
8518
6842
|
ensureDir(res.project.dir, newAbs);
|
|
8519
|
-
|
|
6843
|
+
renameSync2(res.absPath, newAbs);
|
|
8520
6844
|
const updatedFiles = updateReferences(res.project.dir, res.filePath, body.newPath, isDirectory);
|
|
8521
6845
|
return c.json({ ok: true, path: body.newPath, updatedReferences: updatedFiles });
|
|
8522
6846
|
});
|
|
@@ -8538,7 +6862,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8538
6862
|
}
|
|
8539
6863
|
ensureDir(project.dir, destAbs);
|
|
8540
6864
|
try {
|
|
8541
|
-
createFileAtomically(destAbs,
|
|
6865
|
+
createFileAtomically(destAbs, readFileSync5(srcAbs));
|
|
8542
6866
|
} catch (error) {
|
|
8543
6867
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
8544
6868
|
throw error;
|
|
@@ -8581,7 +6905,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
8581
6905
|
mustExist: true
|
|
8582
6906
|
});
|
|
8583
6907
|
if ("error" in res) return res.error;
|
|
8584
|
-
const html =
|
|
6908
|
+
const html = readFileSync5(res.absPath, "utf-8");
|
|
8585
6909
|
const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
|
|
8586
6910
|
const headers = { ETag: etag, "Cache-Control": "no-cache" };
|
|
8587
6911
|
if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
|
|
@@ -8638,11 +6962,11 @@ function registerFileRoutes(api, adapter) {
|
|
|
8638
6962
|
if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
|
|
8639
6963
|
return c.json({ error: "expected and restore contents required" }, 400);
|
|
8640
6964
|
}
|
|
8641
|
-
const current =
|
|
6965
|
+
const current = readFileSync5(res.absPath, "utf-8");
|
|
8642
6966
|
if (current !== body.expected) {
|
|
8643
6967
|
return c.json({ ok: true, restored: false, conflict: true });
|
|
8644
6968
|
}
|
|
8645
|
-
replaceFileAtomically(res.absPath, body.restore,
|
|
6969
|
+
replaceFileAtomically(res.absPath, body.restore, statSync3(res.absPath).mode);
|
|
8646
6970
|
return c.json({ ok: true, restored: true, conflict: false });
|
|
8647
6971
|
});
|
|
8648
6972
|
}
|
|
@@ -8656,7 +6980,7 @@ var pathKey = process.platform === "darwin" || process.platform === "win32" ? (p
|
|
|
8656
6980
|
var readsByProject = /* @__PURE__ */ new Map();
|
|
8657
6981
|
var builtProjects = /* @__PURE__ */ new Set();
|
|
8658
6982
|
var projectKey = (projectDir) => pathKey(realFilePath(resolve32(projectDir)));
|
|
8659
|
-
var inRealProject = (projectDir, path) => join7(realFilePath(resolve32(projectDir)),
|
|
6983
|
+
var inRealProject = (projectDir, path) => join7(realFilePath(resolve32(projectDir)), relative3(resolve32(projectDir), resolve32(projectDir, path)));
|
|
8660
6984
|
var readLink = (path) => {
|
|
8661
6985
|
try {
|
|
8662
6986
|
return readlinkSync2(path);
|
|
@@ -8672,7 +6996,7 @@ function linksOnTheWay(path) {
|
|
|
8672
6996
|
const part = rest.shift();
|
|
8673
6997
|
if (!part || part === ".") continue;
|
|
8674
6998
|
if (part === "..") {
|
|
8675
|
-
at =
|
|
6999
|
+
at = dirname4(at);
|
|
8676
7000
|
continue;
|
|
8677
7001
|
}
|
|
8678
7002
|
const next = join7(at, part);
|
|
@@ -8699,7 +7023,7 @@ function recordPreviewRead(projectDir, filePath) {
|
|
|
8699
7023
|
const read = inRealProject(projectDir, filePath);
|
|
8700
7024
|
if (read.length > MAX_PREVIEW_PATH_LENGTH) return;
|
|
8701
7025
|
for (const found of [read, ...linksOnTheWay(read)]) {
|
|
8702
|
-
for (let path = pathKey(found); !reads.has(path); path =
|
|
7026
|
+
for (let path = pathKey(found); !reads.has(path); path = dirname4(path)) reads.add(path);
|
|
8703
7027
|
}
|
|
8704
7028
|
}
|
|
8705
7029
|
function recordPreviewReferences(projectDir, html) {
|
|
@@ -8810,7 +7134,7 @@ function readStudioMotionManifestContent(projectDir) {
|
|
|
8810
7134
|
const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
|
|
8811
7135
|
if (!existsSync5(manifestPath)) return "";
|
|
8812
7136
|
try {
|
|
8813
|
-
return
|
|
7137
|
+
return readFileSync6(manifestPath, "utf-8");
|
|
8814
7138
|
} catch {
|
|
8815
7139
|
return "";
|
|
8816
7140
|
}
|
|
@@ -8954,7 +7278,7 @@ function parsePreviewVariablesParam(raw) {
|
|
|
8954
7278
|
}
|
|
8955
7279
|
function variablesEtagSalt(raw) {
|
|
8956
7280
|
if (!raw) return "";
|
|
8957
|
-
return `:vars:${
|
|
7281
|
+
return `:vars:${createHash22("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
8958
7282
|
}
|
|
8959
7283
|
function previewVariablesFromRequest(rawVariables) {
|
|
8960
7284
|
const parse22 = parsePreviewVariablesParam(rawVariables);
|
|
@@ -8999,13 +7323,13 @@ function resolveProjectMainHtml(projectDir, projectId) {
|
|
|
8999
7323
|
recordPreviewRead(projectDir, blockHtmlPath);
|
|
9000
7324
|
if (existsSync5(indexPath)) {
|
|
9001
7325
|
return {
|
|
9002
|
-
html:
|
|
7326
|
+
html: readFileSync6(indexPath, "utf-8"),
|
|
9003
7327
|
compositionPath: "index.html"
|
|
9004
7328
|
};
|
|
9005
7329
|
}
|
|
9006
7330
|
if (existsSync5(blockHtmlPath)) {
|
|
9007
7331
|
return {
|
|
9008
|
-
html:
|
|
7332
|
+
html: readFileSync6(blockHtmlPath, "utf-8"),
|
|
9009
7333
|
compositionPath: `${projectId}.html`
|
|
9010
7334
|
};
|
|
9011
7335
|
}
|
|
@@ -9136,7 +7460,7 @@ ${etag}`;
|
|
|
9136
7460
|
function pinSubCompHfIds(compFile, compPath) {
|
|
9137
7461
|
if (!/\.html?$/i.test(compPath)) return void 0;
|
|
9138
7462
|
try {
|
|
9139
|
-
return ensureHfIds(
|
|
7463
|
+
return ensureHfIds(readFileSync6(compFile, "utf-8"));
|
|
9140
7464
|
} catch {
|
|
9141
7465
|
return null;
|
|
9142
7466
|
}
|
|
@@ -9152,10 +7476,10 @@ ${etag}`;
|
|
|
9152
7476
|
const compFile = resolveWithinProject(project.dir, compPath);
|
|
9153
7477
|
if (compFile) recordPreviewRead(project.dir, compFile);
|
|
9154
7478
|
recordPreviewRead(project.dir, "index.html");
|
|
9155
|
-
if (!compFile || !existsSync5(compFile) || !
|
|
7479
|
+
if (!compFile || !existsSync5(compFile) || !statSync4(compFile).isFile()) {
|
|
9156
7480
|
return c.text("not found", 404);
|
|
9157
7481
|
}
|
|
9158
|
-
const compPathHash =
|
|
7482
|
+
const compPathHash = createHash22("sha1").update(compPath).digest("hex");
|
|
9159
7483
|
const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
|
|
9160
7484
|
const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
|
|
9161
7485
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
@@ -9187,19 +7511,24 @@ ${etag}`;
|
|
|
9187
7511
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
9188
7512
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9189
7513
|
const subPath = requestSubPath(c.req.url, "projects/:id/preview");
|
|
9190
|
-
const candidate =
|
|
7514
|
+
const candidate = resolve4(project.dir, subPath);
|
|
9191
7515
|
const file = isWithinProjectRoot(project.dir, candidate) ? candidate : null;
|
|
9192
7516
|
if (!file || isPrivateProjectFile(project.dir, file)) {
|
|
9193
7517
|
return c.text("not found", 404);
|
|
9194
7518
|
}
|
|
9195
7519
|
recordPreviewRead(project.dir, file);
|
|
9196
|
-
const
|
|
9197
|
-
if (!
|
|
7520
|
+
const stat2 = existsSync5(file) ? statSync4(file) : null;
|
|
7521
|
+
if (!stat2?.isFile()) {
|
|
9198
7522
|
return c.text("not found", 404);
|
|
9199
7523
|
}
|
|
9200
7524
|
const contentType = getMimeType(subPath);
|
|
9201
7525
|
const isText = /\.(html|css|js|json|svg|txt|md|cube)$/i.test(subPath);
|
|
9202
7526
|
const proxyParam = c.req.query("hf-proxy");
|
|
7527
|
+
const boxParam = c.req.query(PREVIEW_PROXY_BOX_PARAM);
|
|
7528
|
+
const proxyBox = boxParam === void 0 ? void 0 : parsePreviewProxyBox(boxParam);
|
|
7529
|
+
if (proxyBox === null || proxyBox && proxyParam === void 0) {
|
|
7530
|
+
return c.text("not found", 404);
|
|
7531
|
+
}
|
|
9203
7532
|
let proxyVariant;
|
|
9204
7533
|
if (proxyParam !== void 0) {
|
|
9205
7534
|
if (!isProxyVariantRequest(proxyParam) || !contentType.startsWith("video/") || !isAutoProxyEnabled(adapter)) {
|
|
@@ -9216,8 +7545,8 @@ ${etag}`;
|
|
|
9216
7545
|
return c.text("media proxy variant does not match asset", 422);
|
|
9217
7546
|
}
|
|
9218
7547
|
}
|
|
9219
|
-
const tag = settledFileTag(
|
|
9220
|
-
const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant)}"`;
|
|
7548
|
+
const tag = settledFileTag(stat2);
|
|
7549
|
+
const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant, proxyBox)}"`;
|
|
9221
7550
|
const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, no-cache", ...etag && { ETag: etag } };
|
|
9222
7551
|
if (!isText && etag) {
|
|
9223
7552
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
@@ -9229,7 +7558,7 @@ ${etag}`;
|
|
|
9229
7558
|
let servedContentType = contentType;
|
|
9230
7559
|
if (proxyVariant !== void 0) {
|
|
9231
7560
|
try {
|
|
9232
|
-
servedPath = await waitForProxy(resolveProxy(project.dir, file, proxyVariant), 0);
|
|
7561
|
+
servedPath = await waitForProxy(resolveProxy(project.dir, file, proxyVariant, proxyBox), 0);
|
|
9233
7562
|
} catch (err) {
|
|
9234
7563
|
if (err instanceof ProxyWaitTimeoutError) {
|
|
9235
7564
|
return c.text("media proxy is being made", 202, {
|
|
@@ -9247,13 +7576,13 @@ ${etag}`;
|
|
|
9247
7576
|
recordProxyRequest();
|
|
9248
7577
|
servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
|
|
9249
7578
|
}
|
|
9250
|
-
const textBuffer = isText ? Buffer.from(
|
|
9251
|
-
const totalSize = textBuffer ? textBuffer.length :
|
|
7579
|
+
const textBuffer = isText ? Buffer.from(readFileSync6(file, "utf-8"), "utf-8") : null;
|
|
7580
|
+
const totalSize = textBuffer ? textBuffer.length : statSync4(servedPath).size;
|
|
9252
7581
|
const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
|
|
9253
7582
|
// Node's web stream type and the DOM one do not overlap for tsc on
|
|
9254
7583
|
// every platform's lib set; the double cast is the documented bridge.
|
|
9255
7584
|
Readable.toWeb(
|
|
9256
|
-
|
|
7585
|
+
createReadStream(servedPath, { start, end })
|
|
9257
7586
|
)
|
|
9258
7587
|
);
|
|
9259
7588
|
const rangeHeader = c.req.header("Range");
|
|
@@ -9306,7 +7635,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
|
|
|
9306
7635
|
const findings = [];
|
|
9307
7636
|
for (const file of htmlFiles) {
|
|
9308
7637
|
if (coveredFiles.has(file)) continue;
|
|
9309
|
-
const content =
|
|
7638
|
+
const content = readFileSync7(join9(projectDir, file), "utf-8");
|
|
9310
7639
|
const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
|
|
9311
7640
|
for (const finding of result?.findings ?? []) {
|
|
9312
7641
|
findings.push({ ...finding, file });
|
|
@@ -9462,7 +7791,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9462
7791
|
const RENDER_EXTENSIONS = Object.keys(RENDER_MIME);
|
|
9463
7792
|
const isPlainFile = (path) => {
|
|
9464
7793
|
try {
|
|
9465
|
-
return
|
|
7794
|
+
return lstatSync3(path).isFile();
|
|
9466
7795
|
} catch {
|
|
9467
7796
|
return false;
|
|
9468
7797
|
}
|
|
@@ -9545,11 +7874,11 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9545
7874
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9546
7875
|
const rendersDir = adapter.rendersDir(project);
|
|
9547
7876
|
if (!existsSync6(rendersDir)) return c.json({ renders: [] });
|
|
9548
|
-
const files =
|
|
7877
|
+
const files = readdirSync3(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov")).flatMap((f) => {
|
|
9549
7878
|
const fp = resolveWithinProject(rendersDir, f);
|
|
9550
7879
|
return fp && !isPrivateProjectFile(project.dir, fp) ? [{ f, fp }] : [];
|
|
9551
7880
|
}).map(({ f, fp }) => {
|
|
9552
|
-
const
|
|
7881
|
+
const stat2 = statSync5(fp);
|
|
9553
7882
|
const rid = f.replace(/\.(mp4|webm|mov)$/, "");
|
|
9554
7883
|
const metaPath = join10(rendersDir, `${rid}.meta.json`);
|
|
9555
7884
|
let status = "complete";
|
|
@@ -9568,8 +7897,8 @@ function registerRenderRoutes(api, adapter) {
|
|
|
9568
7897
|
id: rid,
|
|
9569
7898
|
filename: f,
|
|
9570
7899
|
path: fp,
|
|
9571
|
-
size:
|
|
9572
|
-
createdAt:
|
|
7900
|
+
size: stat2.size,
|
|
7901
|
+
createdAt: stat2.mtimeMs,
|
|
9573
7902
|
status,
|
|
9574
7903
|
durationMs,
|
|
9575
7904
|
...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
|
|
@@ -9740,7 +8069,7 @@ async function resolveSource(adapter, id, url) {
|
|
|
9740
8069
|
if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
|
|
9741
8070
|
const path = pinWithinProject(project.dir, relative5);
|
|
9742
8071
|
if (!path) throw new HTTPException(404);
|
|
9743
|
-
const info = await
|
|
8072
|
+
const info = await stat(path).catch(() => {
|
|
9744
8073
|
throw new HTTPException(404);
|
|
9745
8074
|
});
|
|
9746
8075
|
if (!info.isFile()) throw new HTTPException(404);
|
|
@@ -9766,7 +8095,7 @@ function registerImageThumbnailRoutes(api, adapter) {
|
|
|
9766
8095
|
api.get("/projects/:id/image-thumbnail/*", async (c) => {
|
|
9767
8096
|
try {
|
|
9768
8097
|
const { path, info } = await resolveSource(adapter, c.req.param("id"), c.req.url);
|
|
9769
|
-
const key =
|
|
8098
|
+
const key = createHash3("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
|
|
9770
8099
|
const etag = `"${key}"`;
|
|
9771
8100
|
c.header("ETag", etag);
|
|
9772
8101
|
c.header("Cache-Control", "private, no-cache");
|
|
@@ -9800,7 +8129,7 @@ var prunedCacheDirs = /* @__PURE__ */ new Set();
|
|
|
9800
8129
|
function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
9801
8130
|
let entries;
|
|
9802
8131
|
try {
|
|
9803
|
-
entries =
|
|
8132
|
+
entries = readdirSync4(cacheDir, { withFileTypes: true });
|
|
9804
8133
|
} catch (err) {
|
|
9805
8134
|
if (err.code === "ENOENT") return;
|
|
9806
8135
|
throw err;
|
|
@@ -9818,7 +8147,7 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
9818
8147
|
const retained = [];
|
|
9819
8148
|
for (const file of files) {
|
|
9820
8149
|
if (!protectedPaths.has(file.path) && now - file.mtimeMs > THUMBNAIL_CACHE_MAX_AGE_MS) {
|
|
9821
|
-
|
|
8150
|
+
rmSync4(file.path, { force: true });
|
|
9822
8151
|
} else {
|
|
9823
8152
|
retained.push(file);
|
|
9824
8153
|
}
|
|
@@ -9835,12 +8164,12 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
9835
8164
|
}
|
|
9836
8165
|
}
|
|
9837
8166
|
function writeThumbnailAtomically(path, buffer) {
|
|
9838
|
-
const temporaryPath = `${path}.${process.pid}.${
|
|
8167
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
9839
8168
|
try {
|
|
9840
|
-
|
|
9841
|
-
|
|
8169
|
+
writeFileSync4(temporaryPath, buffer, { flag: "wx" });
|
|
8170
|
+
renameSync3(temporaryPath, path);
|
|
9842
8171
|
} finally {
|
|
9843
|
-
|
|
8172
|
+
rmSync4(temporaryPath, { force: true });
|
|
9844
8173
|
}
|
|
9845
8174
|
}
|
|
9846
8175
|
function readFileOnce(file) {
|
|
@@ -9854,9 +8183,9 @@ function readFileOnce(file) {
|
|
|
9854
8183
|
throw err;
|
|
9855
8184
|
}
|
|
9856
8185
|
try {
|
|
9857
|
-
const
|
|
9858
|
-
if (!
|
|
9859
|
-
return { data: readFileSync9(fd), mtimeMs:
|
|
8186
|
+
const stat2 = fstatSync22(fd);
|
|
8187
|
+
if (!stat2.isFile()) return "not-a-file";
|
|
8188
|
+
return { data: readFileSync9(fd), mtimeMs: stat2.mtimeMs };
|
|
9860
8189
|
} finally {
|
|
9861
8190
|
closeSync22(fd);
|
|
9862
8191
|
}
|
|
@@ -9864,7 +8193,7 @@ function readFileOnce(file) {
|
|
|
9864
8193
|
function manifestKey(file) {
|
|
9865
8194
|
const manifest = readFileOnce(file);
|
|
9866
8195
|
if (typeof manifest === "string") return { key: "", mtimeMs: -Infinity };
|
|
9867
|
-
const hash =
|
|
8196
|
+
const hash = createHash4("sha1").update(manifest.data).digest("hex").slice(0, 16);
|
|
9868
8197
|
return { key: `_${hash}`, mtimeMs: Math.round(manifest.mtimeMs) };
|
|
9869
8198
|
}
|
|
9870
8199
|
function registerThumbnailRoutes(api, adapter) {
|
|
@@ -9901,7 +8230,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
9901
8230
|
let sourceKey = "";
|
|
9902
8231
|
if (source !== "missing") {
|
|
9903
8232
|
const html = source.data.toString("utf-8");
|
|
9904
|
-
sourceKey = `_${
|
|
8233
|
+
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
9905
8234
|
sourceMtime = Math.round(source.mtimeMs);
|
|
9906
8235
|
if (!vpWidth) {
|
|
9907
8236
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
@@ -9917,9 +8246,9 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
9917
8246
|
const previewPath = compPath === "index.html" ? `${projectUrl}/preview` : `${projectUrl}/preview/comp/${compPath.split("/").map(encodeURIComponent).join("/")}`;
|
|
9918
8247
|
const previewUrl = `${previewPath}?${PREVIEW_CAPTURE_PARAM}=1`;
|
|
9919
8248
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
9920
|
-
const selectorKey = selector ? `_${
|
|
8249
|
+
const selectorKey = selector ? `_${createHash4("sha1").update(selector).digest("hex").slice(0, 16)}_${selectorIndex ?? 0}` : "";
|
|
9921
8250
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
9922
|
-
const inputSignatureKey = `_${
|
|
8251
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
9923
8252
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
9924
8253
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
9925
8254
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
@@ -9997,7 +8326,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
9997
8326
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
9998
8327
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
9999
8328
|
const assetPath = requestSubPath(c.req.url, "projects/:id/waveform");
|
|
10000
|
-
const audioPath =
|
|
8329
|
+
const audioPath = resolve5(project.dir, assetPath);
|
|
10001
8330
|
if (!isWithinProjectRoot(project.dir, audioPath)) {
|
|
10002
8331
|
return c.json({ error: "file not found" }, 404);
|
|
10003
8332
|
}
|
|
@@ -10250,20 +8579,20 @@ async function extractThenPublishWhole(extract, mediaPath, mediaTime, imagePath)
|
|
|
10250
8579
|
return { error: `Could not extract the frame: ${reason}`, status: 500 };
|
|
10251
8580
|
}
|
|
10252
8581
|
if (publishStill(imagePath, frame)) return null;
|
|
10253
|
-
return { error: `freeze still already exists: ${
|
|
8582
|
+
return { error: `freeze still already exists: ${basename2(imagePath)}`, status: 409 };
|
|
10254
8583
|
} finally {
|
|
10255
8584
|
rmSync5(unseenByHistory, { force: true });
|
|
10256
8585
|
}
|
|
10257
8586
|
}
|
|
10258
8587
|
async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
10259
|
-
const fileDir =
|
|
8588
|
+
const fileDir = dirname5(absPath);
|
|
10260
8589
|
const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
|
|
10261
8590
|
if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
|
|
10262
8591
|
const freezeDir = join13(projectDir, ...FREEZE_DIR);
|
|
10263
8592
|
const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
|
|
10264
8593
|
mkdirWithinProject(projectDir, freezeDir);
|
|
10265
8594
|
const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
|
|
10266
|
-
if (!imagePath ||
|
|
8595
|
+
if (!imagePath || dirname5(imagePath) !== freezeDir) {
|
|
10267
8596
|
return { error: `forbidden freeze path: ${fileName}`, status: 403 };
|
|
10268
8597
|
}
|
|
10269
8598
|
const failed = await extractThenPublishWhole(
|
|
@@ -10273,7 +8602,7 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
|
|
|
10273
8602
|
imagePath
|
|
10274
8603
|
);
|
|
10275
8604
|
if (failed) return failed;
|
|
10276
|
-
const depth = relative4(projectDir, fileDir).split(
|
|
8605
|
+
const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
|
|
10277
8606
|
const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
|
|
10278
8607
|
return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
|
|
10279
8608
|
}
|
|
@@ -10643,17 +8972,17 @@ function registerFontRoutes(api) {
|
|
|
10643
8972
|
if (!located) return c.json({ error: "font not found" }, 404);
|
|
10644
8973
|
let fd;
|
|
10645
8974
|
try {
|
|
10646
|
-
fd =
|
|
8975
|
+
fd = openSync3(located.path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
10647
8976
|
} catch {
|
|
10648
8977
|
return c.json({ error: "font file not accessible" }, 404);
|
|
10649
8978
|
}
|
|
10650
8979
|
try {
|
|
10651
|
-
const
|
|
10652
|
-
if (
|
|
8980
|
+
const stat2 = fstatSync3(fd);
|
|
8981
|
+
if (stat2.size > SYSTEM_FONT_SIZE_LIMIT) {
|
|
10653
8982
|
return c.json({ error: "font file too large" }, 413);
|
|
10654
8983
|
}
|
|
10655
|
-
const buffer = Buffer.alloc(
|
|
10656
|
-
readSync(fd, buffer, 0,
|
|
8984
|
+
const buffer = Buffer.alloc(stat2.size);
|
|
8985
|
+
readSync(fd, buffer, 0, stat2.size, 0);
|
|
10657
8986
|
const mimeType = located.format === "otf" ? "font/otf" : located.format === "woff2" ? "font/woff2" : located.format === "woff" ? "font/woff" : located.format === "ttc" ? "font/collection" : "font/ttf";
|
|
10658
8987
|
const fileName = `${family.replace(/[^a-zA-Z0-9 -]/g, "")}.${located.format}`;
|
|
10659
8988
|
return new Response(buffer, {
|
|
@@ -10665,7 +8994,7 @@ function registerFontRoutes(api) {
|
|
|
10665
8994
|
} catch {
|
|
10666
8995
|
return c.json({ error: "failed to read font file" }, 500);
|
|
10667
8996
|
} finally {
|
|
10668
|
-
|
|
8997
|
+
closeSync3(fd);
|
|
10669
8998
|
}
|
|
10670
8999
|
});
|
|
10671
9000
|
}
|
|
@@ -10811,10 +9140,10 @@ var VIDEO_OUTPUT_EXTENSIONS = /* @__PURE__ */ new Set([".webm", ".mov"]);
|
|
|
10811
9140
|
var QUALITIES = /* @__PURE__ */ new Set(["fast", "balanced", "best"]);
|
|
10812
9141
|
var DEVICES = /* @__PURE__ */ new Set(["auto", "cpu", "coreml", "cuda"]);
|
|
10813
9142
|
function isVideoPath(path) {
|
|
10814
|
-
return VIDEO_EXTENSIONS.has(
|
|
9143
|
+
return VIDEO_EXTENSIONS.has(extname(path).toLowerCase());
|
|
10815
9144
|
}
|
|
10816
9145
|
function isImagePath(path) {
|
|
10817
|
-
return IMAGE_EXTENSIONS.has(
|
|
9146
|
+
return IMAGE_EXTENSIONS.has(extname(path).toLowerCase());
|
|
10818
9147
|
}
|
|
10819
9148
|
function normalizeProjectAssetPath(path) {
|
|
10820
9149
|
return path.trim().replace(/^[.]\//, "").replace(/[?#].*$/, "");
|
|
@@ -10823,11 +9152,11 @@ function containsNullByte(path) {
|
|
|
10823
9152
|
return path.includes("\0");
|
|
10824
9153
|
}
|
|
10825
9154
|
function slugFileBase(path) {
|
|
10826
|
-
const name = basename3(path,
|
|
9155
|
+
const name = basename3(path, extname(path)).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10827
9156
|
return name || "media";
|
|
10828
9157
|
}
|
|
10829
9158
|
function uniqueAssetPath(projectDir, assetPath, taken) {
|
|
10830
|
-
const ext =
|
|
9159
|
+
const ext = extname(assetPath);
|
|
10831
9160
|
const withoutExt = assetPath.slice(0, -ext.length);
|
|
10832
9161
|
let candidate = assetPath;
|
|
10833
9162
|
for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
|
|
@@ -10930,10 +9259,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
10930
9259
|
const outputAssetPath = requestedOutput ? uniqueAssetPath(project.dir, requestedOutput, taken) : defaultOutputPath(project.dir, inputAssetPath, taken);
|
|
10931
9260
|
const outputPath = pinWithinProject(project.dir, outputAssetPath);
|
|
10932
9261
|
if (!outputPath) return c.json({ error: "forbidden" }, 403);
|
|
10933
|
-
if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(
|
|
9262
|
+
if (inputIsVideo && !VIDEO_OUTPUT_EXTENSIONS.has(extname(outputAssetPath).toLowerCase())) {
|
|
10934
9263
|
return c.json({ error: "video background removal output must be .webm or .mov" }, 400);
|
|
10935
9264
|
}
|
|
10936
|
-
if (inputIsImage &&
|
|
9265
|
+
if (inputIsImage && extname(outputAssetPath).toLowerCase() !== ".png") {
|
|
10937
9266
|
return c.json({ error: "image background removal output must be .png" }, 400);
|
|
10938
9267
|
}
|
|
10939
9268
|
let backgroundOutputAssetPath;
|
|
@@ -11005,7 +9334,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
11005
9334
|
});
|
|
11006
9335
|
});
|
|
11007
9336
|
}
|
|
11008
|
-
function readGlobalAssets(home =
|
|
9337
|
+
function readGlobalAssets(home = homedir()) {
|
|
11009
9338
|
const manifestPath = join16(home, ".media", "manifest.jsonl");
|
|
11010
9339
|
if (!existsSync11(manifestPath)) return [];
|
|
11011
9340
|
const out = [];
|
|
@@ -11342,24 +9671,6 @@ function updateBackgroundRemovalProgress(state, event) {
|
|
|
11342
9671
|
|
|
11343
9672
|
export {
|
|
11344
9673
|
stampProjectHfIds,
|
|
11345
|
-
fileContentVersion,
|
|
11346
|
-
DELETED_VERSION,
|
|
11347
|
-
settledFileTag,
|
|
11348
|
-
identifyFileWrite,
|
|
11349
|
-
consumeFileWriteReceipt,
|
|
11350
|
-
affectsProjectSignature,
|
|
11351
|
-
createProjectSignature,
|
|
11352
|
-
HistoryIdError,
|
|
11353
|
-
HistoryBusyError,
|
|
11354
|
-
listProjectHistories,
|
|
11355
|
-
pruneGoneProjectHistories,
|
|
11356
|
-
START,
|
|
11357
|
-
DEFAULT_HISTORY_ROOT,
|
|
11358
|
-
MAX_WINDOW_IDLE_MS,
|
|
11359
|
-
UNDO_MODES,
|
|
11360
|
-
HistoryClosedError,
|
|
11361
|
-
openProjectHistory,
|
|
11362
|
-
historyCache,
|
|
11363
9674
|
getElementScreenshotClip,
|
|
11364
9675
|
clearElementScreenshotIsolation,
|
|
11365
9676
|
injectMediaCodecMapIntoHtml,
|