fundus 0.3.0 → 0.5.0
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/README.md +67 -2
- package/build/client/_app/immutable/assets/2.DLP0pEak.css +1 -0
- package/build/client/_app/immutable/assets/2.DLP0pEak.css.br +0 -0
- package/build/client/_app/immutable/assets/2.DLP0pEak.css.gz +0 -0
- package/build/client/_app/immutable/assets/{3.ChSpCG-T.css → 3.CmXD8k9d.css} +1 -1
- package/build/client/_app/immutable/assets/3.CmXD8k9d.css.br +0 -0
- package/build/client/_app/immutable/assets/3.CmXD8k9d.css.gz +0 -0
- package/build/client/_app/immutable/chunks/B1UabfW7.js +1 -0
- package/build/client/_app/immutable/chunks/B1UabfW7.js.br +0 -0
- package/build/client/_app/immutable/chunks/B1UabfW7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BmCZn5E-.js +108 -0
- package/build/client/_app/immutable/chunks/BmCZn5E-.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmCZn5E-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BPJdVAz5.js → BoEHtxM8.js} +1 -1
- package/build/client/_app/immutable/chunks/BoEHtxM8.js.br +0 -0
- package/build/client/_app/immutable/chunks/BoEHtxM8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C-T1_s5P.js +1 -0
- package/build/client/_app/immutable/chunks/C-T1_s5P.js.br +0 -0
- package/build/client/_app/immutable/chunks/C-T1_s5P.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CLcR3DTB.js → CN-G-m-B.js} +1 -1
- package/build/client/_app/immutable/chunks/CN-G-m-B.js.br +4 -0
- package/build/client/_app/immutable/chunks/CN-G-m-B.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D5sQSsUv.js +1 -0
- package/build/client/_app/immutable/chunks/D5sQSsUv.js.br +0 -0
- package/build/client/_app/immutable/chunks/D5sQSsUv.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D6VoU_x4.js +1 -0
- package/build/client/_app/immutable/chunks/D6VoU_x4.js.br +0 -0
- package/build/client/_app/immutable/chunks/D6VoU_x4.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.xrZU0FIS.js → app.fVDDVV_y.js} +2 -2
- package/build/client/_app/immutable/entry/app.fVDDVV_y.js.br +0 -0
- package/build/client/_app/immutable/entry/app.fVDDVV_y.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BiWgLLju.js +1 -0
- package/build/client/_app/immutable/entry/start.BiWgLLju.js.br +1 -0
- package/build/client/_app/immutable/entry/start.BiWgLLju.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BLnSe7Ya.js +2 -0
- package/build/client/_app/immutable/nodes/0.BLnSe7Ya.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BLnSe7Ya.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.0KLmHZC4.js → 1.DEw3d5yL.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DEw3d5yL.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DEw3d5yL.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DnVTXPVk.js +3 -0
- package/build/client/_app/immutable/nodes/2.DnVTXPVk.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DnVTXPVk.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.1Hco6zU7.js +1 -0
- package/build/client/_app/immutable/nodes/3.1Hco6zU7.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.1Hco6zU7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.CsuRmAsC.js → 4.CKbcoBw2.js} +2 -2
- package/build/client/_app/immutable/nodes/4.CKbcoBw2.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CKbcoBw2.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/asset-id.js-CFsW8jeL.js +48 -0
- package/build/server/chunks/chunks/asset-id.js-CFsW8jeL.js.map +1 -0
- package/build/server/chunks/chunks/{context.js-B77ZAVLi.js → context.js-bXe5hzjI.js} +573 -25
- package/build/server/chunks/chunks/context.js-bXe5hzjI.js.map +1 -0
- package/build/server/chunks/chunks/{library-store.js-DJ-JRE-g.js → contract.js-sHny3b-s.js} +133 -359
- package/build/server/chunks/chunks/contract.js-sHny3b-s.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-EA_ysb1J.js → core.js-Clg1CHb6.js} +2 -48
- package/build/server/chunks/chunks/core.js-Clg1CHb6.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-ScnpVC8o.js → exports.js-FRj9eAnR.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-ScnpVC8o.js.map → exports.js-FRj9eAnR.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-DyoR9XRI.js → http.js--6sDZFfm.js} +35 -3
- package/build/server/chunks/chunks/http.js--6sDZFfm.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-D_k3d8Qh.js → internal.js-D2Tk-vi1.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-D_k3d8Qh.js.map → internal.js-D2Tk-vi1.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/raw-reconciliation.js-DOFtjkgu.js +343 -0
- package/build/server/chunks/chunks/raw-reconciliation.js-DOFtjkgu.js.map +1 -0
- package/build/server/chunks/chunks/raw-reconciliation2.js-DFwky2ou.js +11 -0
- package/build/server/chunks/chunks/raw-reconciliation2.js-DFwky2ou.js.map +1 -0
- package/build/server/chunks/chunks/{shared.js-MLULcRRJ.js → shared.js-hvokB2mY.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-MLULcRRJ.js.map → shared.js-hvokB2mY.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-D64X9Sx8.js → utils.js-VGlYdWt0.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-D64X9Sx8.js.map → utils.js-VGlYdWt0.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-H4Pq1KAE.js → _server.ts.js-CkSErIdC.js} +7 -5
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-H4Pq1KAE.js.map → _server.ts.js-CkSErIdC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BI3KHSfE.js → _server.ts.js-Csnt5ISN.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BI3KHSfE.js.map → _server.ts.js-Csnt5ISN.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BlNYUbXX.js → _server.ts.js-BVEQaFEu.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BlNYUbXX.js.map → _server.ts.js-BVEQaFEu.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/_server.ts.js-DIaT5h8R.js +31 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/_server.ts.js-DIaT5h8R.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-j-_N540j.js → _server.ts.js-CgCr-jFC.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-j-_N540j.js.map → _server.ts.js-CgCr-jFC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-BHKobzAg.js → _server.ts.js-BCvv4wq4.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-BHKobzAg.js.map → _server.ts.js-BCvv4wq4.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BFKjIOCv.js → _server.ts.js-GbxbM9uI.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BFKjIOCv.js.map → _server.ts.js-GbxbM9uI.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BxZeVw8y.js → _server.ts.js-BGjOvpRm.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BxZeVw8y.js.map → _server.ts.js-BGjOvpRm.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CO36U_CT.js → _server.ts.js-CJuzJXPs.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CO36U_CT.js.map → _server.ts.js-CJuzJXPs.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/_server.ts.js-BYp42p6V.js +37 -0
- package/build/server/chunks/entries/endpoints/api/imports/_server.ts.js-BYp42p6V.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-CBlg1Bkl.js → _server.ts.js-l_tkhnQ-.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-CBlg1Bkl.js.map → _server.ts.js-l_tkhnQ-.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DcGLCjIs.js → _server.ts.js-B-VXO3Xr.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DcGLCjIs.js.map → _server.ts.js-B-VXO3Xr.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AqhtjOiL.js → _server.ts.js-DV7s20GV.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AqhtjOiL.js.map → _server.ts.js-DV7s20GV.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CSa6-DcU.js → _server.ts.js-CYgOpWYC.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CSa6-DcU.js.map → _server.ts.js-CYgOpWYC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DnzZUoAo.js → _server.ts.js-7loll6HR.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DnzZUoAo.js.map → _server.ts.js-7loll6HR.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-B0KMprKq.js → _server.ts.js-CdxeTMZj.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-B0KMprKq.js.map → _server.ts.js-CdxeTMZj.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-Dhj5O9Lv.js → _server.ts.js-CZWIskM4.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-CZWIskM4.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B1N9o1Ne.js → _server.ts.js-CZFfpJkl.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B1N9o1Ne.js.map → _server.ts.js-CZFfpJkl.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-B6HbnRqq.js → _server.ts.js-DB3i4juR.js} +7 -5
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-B6HbnRqq.js.map → _server.ts.js-DB3i4juR.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-7wzPyG9v.js → _server.ts.js-BqarqenW.js} +11 -6
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-7wzPyG9v.js.map → _server.ts.js-BqarqenW.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BWztxYge.js → _server.ts.js-CXbrUCmh.js} +11 -6
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BWztxYge.js.map → _server.ts.js-CXbrUCmh.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-CVKLksJ1.js → hooks.server.js-6Yf-GF0J.js} +10 -5
- package/build/server/chunks/entries/{hooks.server.js-CVKLksJ1.js.map → hooks.server.js-6Yf-GF0J.js.map} +1 -1
- package/build/server/chunks/{handler-C22elTeC.js → handler-DHAu99Rv.js} +5 -5
- package/build/server/chunks/{handler-C22elTeC.js.map → handler-DHAu99Rv.js.map} +1 -1
- package/build/server/chunks/{index.js-CqN1-wfe.js → index.js-Bm1XIAPD.js} +5 -5
- package/build/server/chunks/{index.js-CqN1-wfe.js.map → index.js-Bm1XIAPD.js.map} +1 -1
- package/build/server/chunks/{manifest.js-5SY_cfDE.js → manifest.js-CCc26-hb.js} +40 -26
- package/build/server/chunks/manifest.js-CCc26-hb.js.map +1 -0
- package/build/server/chunks/nodes/0.js-Bpzcq6LZ.js +11 -0
- package/build/server/chunks/nodes/0.js-Bpzcq6LZ.js.map +1 -0
- package/build/server/chunks/nodes/1.js-CXgbNa8h.js +7 -0
- package/build/server/chunks/nodes/{1.js-B92oHDrL.js.map → 1.js-CXgbNa8h.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-D56ryWAu.js +7 -0
- package/build/server/chunks/nodes/2.js-D56ryWAu.js.map +1 -0
- package/build/server/chunks/nodes/3.js-DMaZRwrN.js +7 -0
- package/build/server/chunks/nodes/{3.js-C1yCh8Tq.js.map → 3.js-DMaZRwrN.js.map} +1 -1
- package/build/server/chunks/nodes/4.js-BUgaRCEY.js +7 -0
- package/build/server/chunks/nodes/{4.js-B9BEDKwq.js.map → 4.js-BUgaRCEY.js.map} +1 -1
- package/dist/cli.js +2 -0
- package/dist/config.d.ts +1 -1
- package/dist/core/api-types.d.ts +12 -1
- package/dist/core/config.d.ts +12 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/library.d.ts +8 -1
- package/dist/core/library.js +30 -6
- package/dist/server/cli-commands.d.ts +2 -1
- package/dist/server/cli-commands.js +155 -34
- package/dist/server/config-loader.d.ts +9 -1
- package/dist/server/config-loader.js +26 -1
- package/dist/server/http.d.ts +1 -0
- package/dist/server/http.js +37 -0
- package/dist/server/importers/contract.d.ts +29 -0
- package/dist/server/importers/contract.js +13 -0
- package/dist/server/importers/figma.d.ts +21 -0
- package/dist/server/importers/figma.js +386 -0
- package/dist/server/importers/file.d.ts +6 -0
- package/dist/server/importers/file.js +33 -0
- package/dist/server/importers/pinned-https.d.ts +21 -0
- package/dist/server/importers/pinned-https.js +70 -0
- package/dist/server/importers/registry.d.ts +12 -0
- package/dist/server/importers/registry.js +31 -0
- package/dist/server/importers.d.ts +2 -0
- package/dist/server/importers.js +7 -0
- package/dist/server/library-store.d.ts +19 -1
- package/dist/server/library-store.js +110 -14
- package/dist/server/operations.d.ts +13 -1
- package/dist/server/operations.js +159 -23
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css +0 -1
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css.br +0 -0
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.br +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BPJdVAz5.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPJdVAz5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CLZr1L0m.js +0 -108
- package/build/client/_app/immutable/chunks/CLZr1L0m.js.br +0 -0
- package/build/client/_app/immutable/chunks/CLZr1L0m.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CLcR3DTB.js.br +0 -4
- package/build/client/_app/immutable/chunks/CLcR3DTB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkK6dXFi.js +0 -1
- package/build/client/_app/immutable/chunks/DkK6dXFi.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkK6dXFi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/RVmTMq_g.js +0 -1
- package/build/client/_app/immutable/chunks/RVmTMq_g.js.br +0 -0
- package/build/client/_app/immutable/chunks/RVmTMq_g.js.gz +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js +0 -1
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.br +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.xrZU0FIS.js.br +0 -0
- package/build/client/_app/immutable/entry/app.xrZU0FIS.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BT85omvY.js +0 -1
- package/build/client/_app/immutable/entry/start.BT85omvY.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BT85omvY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js +0 -2
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.0KLmHZC4.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.0KLmHZC4.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js +0 -3
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js +0 -1
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CsuRmAsC.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CsuRmAsC.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-B77ZAVLi.js.map +0 -1
- package/build/server/chunks/chunks/core.js-EA_ysb1J.js.map +0 -1
- package/build/server/chunks/chunks/http.js-DyoR9XRI.js.map +0 -1
- package/build/server/chunks/chunks/library-store.js-DJ-JRE-g.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-Dhj5O9Lv.js.map +0 -1
- package/build/server/chunks/manifest.js-5SY_cfDE.js.map +0 -1
- package/build/server/chunks/nodes/0.js-CoD1zSiQ.js +0 -11
- package/build/server/chunks/nodes/0.js-CoD1zSiQ.js.map +0 -1
- package/build/server/chunks/nodes/1.js-B92oHDrL.js +0 -7
- package/build/server/chunks/nodes/2.js-COlJ22cI.js +0 -7
- package/build/server/chunks/nodes/2.js-COlJ22cI.js.map +0 -1
- package/build/server/chunks/nodes/3.js-C1yCh8Tq.js +0 -7
- package/build/server/chunks/nodes/4.js-B9BEDKwq.js +0 -7
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { s as serverPluginsByType, i as isRecord, L as LibraryStore, a as serverPluginForType,
|
|
3
|
-
import {
|
|
1
|
+
import { e as folderOfRawPath, g as rawFileNameOfPath, n as normalizeFolderPath, z as typesForFileName, B as proposeAssetId, h as parentFolderPath, q as portableFolderPathKey } from './core.js-Clg1CHb6.js';
|
|
2
|
+
import { s as serverPluginsByType, i as isRecord, L as LibraryStore, a as serverPluginForType, S as SourceImportError, p as processAssets, g as generateManifestModules, c as collectAllReferences, d as descriptorsByType, b as computeLibraryMembership, e as allDeliveredAssetIds, r as readCurrentProxy, f as encodeRelativePathForUrl, h as invalidInput } from './contract.js-sHny3b-s.js';
|
|
3
|
+
import { a as assetIdError } from './asset-id.js-CFsW8jeL.js';
|
|
4
|
+
import { v as verifyStableRawFile, c as canonicalizeParameters, a as compareCodeUnits, b as classifyRawDirectory } from './raw-reconciliation.js-DOFtjkgu.js';
|
|
5
|
+
import { isAbsolute, resolve, join, extname } from 'node:path';
|
|
4
6
|
import { existsSync } from 'node:fs';
|
|
5
7
|
import { createJiti } from 'jiti';
|
|
8
|
+
import sharp from 'sharp';
|
|
9
|
+
import { lookup } from 'node:dns/promises';
|
|
10
|
+
import { isIP } from 'node:net';
|
|
11
|
+
import { request } from 'node:https';
|
|
6
12
|
|
|
7
13
|
//#region src/lib/server/config-loader.ts
|
|
8
14
|
/**
|
|
@@ -39,13 +45,26 @@ function resolveConfig(projectRoot, value, registry = serverPluginsByType) {
|
|
|
39
45
|
projectRoot,
|
|
40
46
|
value: plugins[definition.namespace]
|
|
41
47
|
});
|
|
48
|
+
const importConfig = value.import === void 0 ? {} : value.import;
|
|
49
|
+
if (!isRecord(importConfig)) throw new Error("fundus.config: \"import\" must be an object.");
|
|
50
|
+
const figma = importConfig.figma === void 0 ? {} : importConfig.figma;
|
|
51
|
+
if (!isRecord(figma)) throw new Error("fundus.config: \"import.figma\" must be an object.");
|
|
52
|
+
if (figma.token !== void 0 && (typeof figma.token !== "string" || figma.token.length === 0)) throw new Error("fundus.config: \"import.figma.token\" must be a non-empty string.");
|
|
53
|
+
const defaults = figma.defaults === void 0 ? {} : figma.defaults;
|
|
54
|
+
if (!isRecord(defaults)) throw new Error("fundus.config: \"import.figma.defaults\" must be an object.");
|
|
55
|
+
const scale = defaults.scale === void 0 ? 2 : defaults.scale;
|
|
56
|
+
if (scale !== 1 && scale !== 2 && scale !== 3 && scale !== 4) throw new Error("fundus.config: \"import.figma.defaults.scale\" must be 1, 2, 3, or 4.");
|
|
42
57
|
return {
|
|
43
58
|
rawDir: resolvePath(projectRoot, requiredString(value, "rawDir")),
|
|
44
59
|
proxyDir: resolvePath(projectRoot, requiredString(value, "proxyDir")),
|
|
45
60
|
library: resolvePath(projectRoot, requiredString(value, "library")),
|
|
46
61
|
manifestsDir: resolvePath(projectRoot, requiredString(value, "manifestsDir")),
|
|
47
62
|
proxyBasePath,
|
|
48
|
-
plugins: resolvedPlugins
|
|
63
|
+
plugins: resolvedPlugins,
|
|
64
|
+
import: { figma: {
|
|
65
|
+
...figma.token !== void 0 ? { token: figma.token } : {},
|
|
66
|
+
defaults: { scale }
|
|
67
|
+
} }
|
|
49
68
|
};
|
|
50
69
|
}
|
|
51
70
|
/** Load `fundus.config.ts` from the host project root. */
|
|
@@ -116,6 +135,429 @@ function rawSafetyIssues(library, classification, compatibleTypes) {
|
|
|
116
135
|
});
|
|
117
136
|
}
|
|
118
137
|
//#endregion
|
|
138
|
+
//#region src/lib/server/importers/pinned-https.ts
|
|
139
|
+
function pinnedUrl(url, address) {
|
|
140
|
+
const pinned = new URL(url);
|
|
141
|
+
pinned.hostname = address.family === 6 ? `[${address.address}]` : address.address;
|
|
142
|
+
return pinned;
|
|
143
|
+
}
|
|
144
|
+
function responseHeaders(source) {
|
|
145
|
+
const headers = new Headers();
|
|
146
|
+
for (const [name, value] of Object.entries(source)) if (Array.isArray(value)) for (const item of value) headers.append(name, item);
|
|
147
|
+
else if (value !== void 0) headers.set(name, value);
|
|
148
|
+
return headers;
|
|
149
|
+
}
|
|
150
|
+
function createPinnedHttpsTransport(requester = request) {
|
|
151
|
+
return (target, signal) => new Promise((resolve, reject) => {
|
|
152
|
+
let addressIndex = 0;
|
|
153
|
+
let lastError;
|
|
154
|
+
let settled = false;
|
|
155
|
+
const attempt = () => {
|
|
156
|
+
if (signal.aborted) {
|
|
157
|
+
reject(signal.reason);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const address = target.addresses[addressIndex++];
|
|
161
|
+
if (!address) {
|
|
162
|
+
reject(lastError ?? /* @__PURE__ */ new Error(`No validated address is available for ${target.url.hostname}.`));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let clientRequest;
|
|
166
|
+
try {
|
|
167
|
+
clientRequest = requester(pinnedUrl(target.url, address), {
|
|
168
|
+
method: "GET",
|
|
169
|
+
agent: false,
|
|
170
|
+
headers: { host: target.url.host },
|
|
171
|
+
servername: target.url.hostname,
|
|
172
|
+
rejectUnauthorized: true,
|
|
173
|
+
signal
|
|
174
|
+
}, (response) => {
|
|
175
|
+
settled = true;
|
|
176
|
+
resolve({
|
|
177
|
+
status: response.statusCode ?? 0,
|
|
178
|
+
headers: responseHeaders(response.headers),
|
|
179
|
+
body: response,
|
|
180
|
+
cancel: () => response.destroy()
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
} catch (cause) {
|
|
184
|
+
lastError = cause;
|
|
185
|
+
attempt();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
clientRequest.once("error", (cause) => {
|
|
189
|
+
if (settled) return;
|
|
190
|
+
lastError = cause;
|
|
191
|
+
attempt();
|
|
192
|
+
});
|
|
193
|
+
clientRequest.end();
|
|
194
|
+
};
|
|
195
|
+
attempt();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
var requestPinnedHttps = createPinnedHttpsTransport();
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/lib/server/importers/figma.ts
|
|
201
|
+
var MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
|
|
202
|
+
var MAX_PIXELS = 32e6;
|
|
203
|
+
var MAX_REDIRECTS = 3;
|
|
204
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
205
|
+
var PNG_SIGNATURE = Uint8Array.from([
|
|
206
|
+
137,
|
|
207
|
+
80,
|
|
208
|
+
78,
|
|
209
|
+
71,
|
|
210
|
+
13,
|
|
211
|
+
10,
|
|
212
|
+
26,
|
|
213
|
+
10
|
|
214
|
+
]);
|
|
215
|
+
function record(value) {
|
|
216
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
217
|
+
}
|
|
218
|
+
function parseFigmaSelectionUrl(value) {
|
|
219
|
+
let url;
|
|
220
|
+
try {
|
|
221
|
+
url = new URL(value);
|
|
222
|
+
} catch {
|
|
223
|
+
return invalidInput("Enter a valid Figma selection link.");
|
|
224
|
+
}
|
|
225
|
+
if (url.protocol !== "https:" || url.hostname !== "figma.com" && url.hostname !== "www.figma.com") return invalidInput("Figma links must use HTTPS on figma.com.");
|
|
226
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
227
|
+
if (![
|
|
228
|
+
"design",
|
|
229
|
+
"file",
|
|
230
|
+
"proto"
|
|
231
|
+
].includes(segments[0] ?? "") || !segments[1]) return invalidInput("The Figma link must point to a design, file, or prototype.");
|
|
232
|
+
const nodeIds = url.searchParams.getAll("node-id");
|
|
233
|
+
if (nodeIds.length !== 1 || !/^\d+[-:]\d+$/.test(nodeIds[0])) return invalidInput("The Figma link must contain exactly one valid node-id.");
|
|
234
|
+
const [left, right] = nodeIds[0].split(/[-:]/);
|
|
235
|
+
return {
|
|
236
|
+
fileKey: segments[1],
|
|
237
|
+
nodeId: `${left}:${right}`
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function validateScale(value) {
|
|
241
|
+
if (value !== 1 && value !== 2 && value !== 3 && value !== 4) return invalidInput("Figma export scale must be 1, 2, 3, or 4.");
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
function validateInput(value) {
|
|
245
|
+
const input = record(value);
|
|
246
|
+
if (!input || typeof input.url !== "string") return invalidInput("Figma import requires a URL.");
|
|
247
|
+
if (input.format !== void 0 && input.format !== "png") return invalidInput("Figma import currently supports PNG only.");
|
|
248
|
+
return {
|
|
249
|
+
url: input.url,
|
|
250
|
+
format: "png",
|
|
251
|
+
...input.scale !== void 0 ? { scale: validateScale(input.scale) } : {}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function validateFigmaSourceParameters(value) {
|
|
255
|
+
const source = record(value);
|
|
256
|
+
if (!source || typeof source.fileKey !== "string" || source.fileKey.length === 0 || typeof source.nodeId !== "string" || !/^\d+:\d+$/.test(source.nodeId) || source.format !== "png") return invalidInput("Stored Figma source parameters are invalid.");
|
|
257
|
+
return {
|
|
258
|
+
fileKey: source.fileKey,
|
|
259
|
+
nodeId: source.nodeId,
|
|
260
|
+
format: "png",
|
|
261
|
+
scale: validateScale(source.scale)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function retryAfter(response) {
|
|
265
|
+
const raw = response.headers.get("retry-after");
|
|
266
|
+
if (!raw || !/^\d+$/.test(raw.trim())) return void 0;
|
|
267
|
+
const seconds = Number(raw);
|
|
268
|
+
return Number.isSafeInteger(seconds) ? seconds : void 0;
|
|
269
|
+
}
|
|
270
|
+
function upstreamStatusError(response) {
|
|
271
|
+
if (response.status === 401) return new SourceImportError("unauthorized", "Figma authentication failed.");
|
|
272
|
+
if (response.status === 403) return new SourceImportError("forbidden", "Figma authentication or access failed. The token may be invalid or expired, or it may not have access to this file.");
|
|
273
|
+
if (response.status === 404) return new SourceImportError("not-found", "The Figma file or node was not found.");
|
|
274
|
+
if (response.status === 429) return new SourceImportError("rate-limited", "Figma rate-limited this request.", retryAfter(response));
|
|
275
|
+
return new SourceImportError("upstream-failed", `Figma export failed with status ${response.status}.`);
|
|
276
|
+
}
|
|
277
|
+
function isForbiddenIpv4(address) {
|
|
278
|
+
const parts = address.split(".").map(Number);
|
|
279
|
+
const [a, b] = parts;
|
|
280
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 || a === 192 && b === 0 && parts[2] === 2 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a === 198 && b === 51 && parts[2] === 100 || a === 203 && b === 0 && parts[2] === 113 || a >= 224;
|
|
281
|
+
}
|
|
282
|
+
function mappedIpv4Address(address) {
|
|
283
|
+
const normalized = address.toLowerCase().split("%")[0];
|
|
284
|
+
const dotted = /^(?:::ffff:|0:0:0:0:0:ffff:)(\d+\.\d+\.\d+\.\d+)$/.exec(normalized);
|
|
285
|
+
if (dotted && isIP(dotted[1]) === 4) return dotted[1];
|
|
286
|
+
const halves = normalized.split("::");
|
|
287
|
+
if (halves.length > 2) return null;
|
|
288
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
289
|
+
const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
290
|
+
const missing = 8 - left.length - right.length;
|
|
291
|
+
if (missing < 0 || halves.length === 1 && missing !== 0) return null;
|
|
292
|
+
const parts = [
|
|
293
|
+
...left,
|
|
294
|
+
...Array.from({ length: missing }, () => "0"),
|
|
295
|
+
...right
|
|
296
|
+
];
|
|
297
|
+
if (parts.length !== 8 || parts.slice(0, 5).some((part) => Number.parseInt(part || "0", 16) !== 0) || Number.parseInt(parts[5] || "0", 16) !== 65535) return null;
|
|
298
|
+
const high = Number.parseInt(parts[6], 16);
|
|
299
|
+
const low = Number.parseInt(parts[7], 16);
|
|
300
|
+
if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 65535 || low < 0 || low > 65535) return null;
|
|
301
|
+
return `${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`;
|
|
302
|
+
}
|
|
303
|
+
function isForbiddenIp(address) {
|
|
304
|
+
if (isIP(address) === 4) return isForbiddenIpv4(address);
|
|
305
|
+
const normalized = address.toLowerCase().split("%")[0];
|
|
306
|
+
const mapped = mappedIpv4Address(normalized);
|
|
307
|
+
if (mapped) return isForbiddenIpv4(mapped);
|
|
308
|
+
return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb") || normalized.startsWith("ff") || normalized.startsWith("2001:db8:");
|
|
309
|
+
}
|
|
310
|
+
async function resolveSafeDownloadTarget(value) {
|
|
311
|
+
let url;
|
|
312
|
+
try {
|
|
313
|
+
url = value instanceof URL ? value : new URL(value);
|
|
314
|
+
} catch {
|
|
315
|
+
throw new SourceImportError("unsafe-download-url", "Figma returned an unsafe download target.");
|
|
316
|
+
}
|
|
317
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" && url.port !== "443" || url.hostname === "" || url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname.endsWith(".local") || url.hostname.startsWith("[") || isIP(url.hostname) !== 0) throw new SourceImportError("unsafe-download-url", "Figma returned an unsafe download target.");
|
|
318
|
+
let resolvedAddresses;
|
|
319
|
+
try {
|
|
320
|
+
resolvedAddresses = await lookup(url.hostname, { all: true });
|
|
321
|
+
} catch {
|
|
322
|
+
throw new SourceImportError("unsafe-download-url", "Figma returned an unavailable download target.");
|
|
323
|
+
}
|
|
324
|
+
if (resolvedAddresses.length === 0 || resolvedAddresses.some(({ address }) => isIP(address) === 0 || isForbiddenIp(address))) throw new SourceImportError("unsafe-download-url", "Figma returned an unsafe download target.");
|
|
325
|
+
const addresses = resolvedAddresses.map(({ address }) => ({
|
|
326
|
+
address,
|
|
327
|
+
family: isIP(address)
|
|
328
|
+
}));
|
|
329
|
+
return {
|
|
330
|
+
url,
|
|
331
|
+
addresses
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function fetchWithDeadline(fetcher, url, init, consume) {
|
|
335
|
+
const controller = new AbortController();
|
|
336
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
337
|
+
try {
|
|
338
|
+
const response = await fetcher(url, {
|
|
339
|
+
...init,
|
|
340
|
+
signal: controller.signal
|
|
341
|
+
});
|
|
342
|
+
if (!response.ok) throw upstreamStatusError(response);
|
|
343
|
+
return await consume(response);
|
|
344
|
+
} catch (cause) {
|
|
345
|
+
if (controller.signal.aborted || cause instanceof Error && cause.name === "AbortError") throw new SourceImportError("download-timeout", "Figma export timed out.");
|
|
346
|
+
if (cause instanceof SourceImportError) throw cause;
|
|
347
|
+
throw new SourceImportError("upstream-failed", "Figma export request failed.");
|
|
348
|
+
} finally {
|
|
349
|
+
clearTimeout(timeout);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function readLimitedBody(response, signal) {
|
|
353
|
+
let canceled = false;
|
|
354
|
+
const cancel = () => {
|
|
355
|
+
if (canceled) return;
|
|
356
|
+
canceled = true;
|
|
357
|
+
response.cancel();
|
|
358
|
+
};
|
|
359
|
+
const declared = response.headers.get("content-length");
|
|
360
|
+
if (declared && /^\d+$/.test(declared) && Number(declared) > MAX_DOWNLOAD_BYTES) {
|
|
361
|
+
cancel();
|
|
362
|
+
throw new SourceImportError("download-too-large", "Figma export exceeds the 50 MiB limit.");
|
|
363
|
+
}
|
|
364
|
+
const iterator = response.body[Symbol.asyncIterator]();
|
|
365
|
+
const chunks = [];
|
|
366
|
+
let size = 0;
|
|
367
|
+
let rejectAbort;
|
|
368
|
+
const aborted = new Promise((_, reject) => rejectAbort = reject);
|
|
369
|
+
const onAbort = () => {
|
|
370
|
+
rejectAbort(new SourceImportError("download-timeout", "Figma export download timed out."));
|
|
371
|
+
cancel();
|
|
372
|
+
};
|
|
373
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
374
|
+
try {
|
|
375
|
+
for (;;) {
|
|
376
|
+
const { done, value } = await Promise.race([iterator.next(), aborted]);
|
|
377
|
+
if (signal.aborted) throw new SourceImportError("download-timeout", "Figma export download timed out.");
|
|
378
|
+
if (done) break;
|
|
379
|
+
size += value.byteLength;
|
|
380
|
+
if (size > MAX_DOWNLOAD_BYTES) {
|
|
381
|
+
cancel();
|
|
382
|
+
throw new SourceImportError("download-too-large", "Figma export exceeds the 50 MiB limit.");
|
|
383
|
+
}
|
|
384
|
+
chunks.push(value);
|
|
385
|
+
}
|
|
386
|
+
} catch (cause) {
|
|
387
|
+
cancel();
|
|
388
|
+
throw cause;
|
|
389
|
+
} finally {
|
|
390
|
+
signal.removeEventListener("abort", onAbort);
|
|
391
|
+
}
|
|
392
|
+
const bytes = new Uint8Array(size);
|
|
393
|
+
let offset = 0;
|
|
394
|
+
for (const chunk of chunks) {
|
|
395
|
+
bytes.set(chunk, offset);
|
|
396
|
+
offset += chunk.byteLength;
|
|
397
|
+
}
|
|
398
|
+
return bytes;
|
|
399
|
+
}
|
|
400
|
+
async function downloadExport(transport, initial) {
|
|
401
|
+
let target = await resolveSafeDownloadTarget(initial);
|
|
402
|
+
const controller = new AbortController();
|
|
403
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
404
|
+
try {
|
|
405
|
+
for (let redirects = 0;; redirects++) {
|
|
406
|
+
let response;
|
|
407
|
+
try {
|
|
408
|
+
response = await transport(target, controller.signal);
|
|
409
|
+
} catch (cause) {
|
|
410
|
+
if (controller.signal.aborted || cause instanceof Error && cause.name === "AbortError") throw new SourceImportError("download-timeout", "Figma export download timed out.");
|
|
411
|
+
throw new SourceImportError("upstream-failed", "Figma export download failed.");
|
|
412
|
+
}
|
|
413
|
+
if (response.status >= 300 && response.status < 400) {
|
|
414
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
415
|
+
response.cancel();
|
|
416
|
+
throw new SourceImportError("unsafe-download-url", "Figma export redirected too many times.");
|
|
417
|
+
}
|
|
418
|
+
const location = response.headers.get("location");
|
|
419
|
+
if (!location) {
|
|
420
|
+
response.cancel();
|
|
421
|
+
throw new SourceImportError("invalid-response", "Figma export redirect was invalid.");
|
|
422
|
+
}
|
|
423
|
+
response.cancel();
|
|
424
|
+
target = await resolveSafeDownloadTarget(new URL(location, target.url));
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (response.status < 200 || response.status >= 300) {
|
|
428
|
+
response.cancel();
|
|
429
|
+
throw upstreamStatusError(response);
|
|
430
|
+
}
|
|
431
|
+
return await readLimitedBody(response, controller.signal);
|
|
432
|
+
}
|
|
433
|
+
} finally {
|
|
434
|
+
clearTimeout(timeout);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function validatePng(bytes) {
|
|
438
|
+
if (bytes.byteLength < PNG_SIGNATURE.byteLength || PNG_SIGNATURE.some((value, index) => bytes[index] !== value)) throw new SourceImportError("invalid-response", "Figma export is not a valid PNG.");
|
|
439
|
+
let metadata;
|
|
440
|
+
try {
|
|
441
|
+
metadata = await sharp(bytes).metadata();
|
|
442
|
+
} catch {
|
|
443
|
+
throw new SourceImportError("invalid-response", "Figma export is not a readable PNG.");
|
|
444
|
+
}
|
|
445
|
+
if (!metadata.width || !metadata.height) throw new SourceImportError("invalid-response", "Figma export has invalid dimensions.");
|
|
446
|
+
if (metadata.width * metadata.height > MAX_PIXELS) throw new SourceImportError("image-too-large", "Figma export exceeds the 32 megapixel limit.");
|
|
447
|
+
}
|
|
448
|
+
async function materialize(source, context) {
|
|
449
|
+
const token = context.config.import.figma.token;
|
|
450
|
+
if (!token) throw new SourceImportError("not-configured", "Figma access is not configured.");
|
|
451
|
+
const endpoint = new URL(`https://api.figma.com/v1/images/${encodeURIComponent(source.fileKey)}`);
|
|
452
|
+
endpoint.searchParams.set("ids", source.nodeId);
|
|
453
|
+
endpoint.searchParams.set("format", source.format);
|
|
454
|
+
endpoint.searchParams.set("scale", String(source.scale));
|
|
455
|
+
const jsonBytes = await fetchWithDeadline(context.fetch, endpoint, {
|
|
456
|
+
redirect: "error",
|
|
457
|
+
headers: { "X-Figma-Token": token }
|
|
458
|
+
}, async (response) => new Uint8Array(await response.arrayBuffer()));
|
|
459
|
+
let payload;
|
|
460
|
+
try {
|
|
461
|
+
payload = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
462
|
+
} catch {
|
|
463
|
+
throw new SourceImportError("invalid-response", "Figma returned an invalid response.");
|
|
464
|
+
}
|
|
465
|
+
const body = record(payload);
|
|
466
|
+
if (!body || body.err || typeof body.status === "number" && body.status >= 400) throw new SourceImportError("invalid-response", "Figma could not render this node.");
|
|
467
|
+
const images = record(body.images);
|
|
468
|
+
if (!images || !Object.hasOwn(images, source.nodeId)) throw new SourceImportError("invalid-response", "Figma did not return the requested node.");
|
|
469
|
+
const imageUrl = images[source.nodeId];
|
|
470
|
+
if (typeof imageUrl !== "string" || imageUrl.length === 0) throw new SourceImportError("invalid-response", "Figma could not render the requested node.");
|
|
471
|
+
const bytes = await downloadExport(context.httpsTransport ?? requestPinnedHttps, imageUrl);
|
|
472
|
+
await validatePng(bytes);
|
|
473
|
+
return {
|
|
474
|
+
bytes,
|
|
475
|
+
extension: "png",
|
|
476
|
+
importSource: {
|
|
477
|
+
importer: "figma",
|
|
478
|
+
parameters: source
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
var figmaSourceImporter = {
|
|
483
|
+
kind: "figma",
|
|
484
|
+
label: "Figma",
|
|
485
|
+
validateInput,
|
|
486
|
+
async importRaw(input, context) {
|
|
487
|
+
const parsed = parseFigmaSelectionUrl(input.url);
|
|
488
|
+
return materialize({
|
|
489
|
+
fileKey: parsed.fileKey,
|
|
490
|
+
nodeId: parsed.nodeId,
|
|
491
|
+
format: "png",
|
|
492
|
+
scale: input.scale ?? context.config.import.figma.defaults.scale
|
|
493
|
+
}, context);
|
|
494
|
+
},
|
|
495
|
+
validateSourceParameters: validateFigmaSourceParameters,
|
|
496
|
+
async reimportRaw(source, context) {
|
|
497
|
+
return materialize(source, context);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/lib/server/importers/file.ts
|
|
502
|
+
function validate(value) {
|
|
503
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SourceImportError("invalid-input", "File import input must be an object.");
|
|
504
|
+
const input = value;
|
|
505
|
+
if (typeof input.fileName !== "string" || input.fileName.length === 0) throw new SourceImportError("invalid-input", "File import requires a file name.");
|
|
506
|
+
if (!(input.bytes instanceof Uint8Array)) throw new SourceImportError("invalid-input", "File import requires byte data.");
|
|
507
|
+
return {
|
|
508
|
+
fileName: input.fileName,
|
|
509
|
+
bytes: input.bytes
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
var fileSourceImporter = {
|
|
513
|
+
kind: "file",
|
|
514
|
+
label: "File",
|
|
515
|
+
validateInput: validate,
|
|
516
|
+
async importRaw(input) {
|
|
517
|
+
const extension = extname(input.fileName).slice(1).toLowerCase();
|
|
518
|
+
if (!extension) throw new SourceImportError("invalid-input", "The file needs an extension.");
|
|
519
|
+
return {
|
|
520
|
+
bytes: input.bytes,
|
|
521
|
+
extension,
|
|
522
|
+
suggestedFileName: input.fileName
|
|
523
|
+
};
|
|
524
|
+
},
|
|
525
|
+
validateSourceParameters() {
|
|
526
|
+
throw new SourceImportError("invalid-input", "File imports cannot be re-imported.");
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/lib/server/importers/registry.ts
|
|
531
|
+
var SourceImporterRegistry = class {
|
|
532
|
+
#byKind = /* @__PURE__ */ new Map();
|
|
533
|
+
constructor(importers) {
|
|
534
|
+
for (const importer of importers) {
|
|
535
|
+
if (!importer.kind) throw new Error("Source importer kind must not be empty.");
|
|
536
|
+
if (this.#byKind.has(importer.kind)) throw new Error(`Duplicate source importer kind "${importer.kind}".`);
|
|
537
|
+
this.#byKind.set(importer.kind, importer);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
require(kind) {
|
|
541
|
+
const importer = this.#byKind.get(kind);
|
|
542
|
+
if (!importer) throw new SourceImportError("invalid-input", `Unknown source importer "${kind}".`);
|
|
543
|
+
return importer;
|
|
544
|
+
}
|
|
545
|
+
has(kind) {
|
|
546
|
+
return this.#byKind.has(kind);
|
|
547
|
+
}
|
|
548
|
+
requireReimport(source) {
|
|
549
|
+
const importer = this.require(source.importer);
|
|
550
|
+
if (!importer.reimportRaw) throw new SourceImportError("invalid-input", `Source importer "${source.importer}" does not support re-import.`);
|
|
551
|
+
return {
|
|
552
|
+
importer,
|
|
553
|
+
parameters: importer.validateSourceParameters(source.parameters)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/lib/server/importers.ts
|
|
559
|
+
var serverImporters = new SourceImporterRegistry([fileSourceImporter, figmaSourceImporter]);
|
|
560
|
+
//#endregion
|
|
119
561
|
//#region src/lib/server/operations.ts
|
|
120
562
|
/**
|
|
121
563
|
* The one operations layer: builds the state payload and runs
|
|
@@ -142,12 +584,14 @@ var FundusOperations = class {
|
|
|
142
584
|
#config;
|
|
143
585
|
#registry;
|
|
144
586
|
#store;
|
|
587
|
+
#importers;
|
|
145
588
|
#operationQueue = Promise.resolve();
|
|
146
589
|
#pipelineQueue = Promise.resolve();
|
|
147
|
-
constructor(config, registry = serverPluginsByType) {
|
|
590
|
+
constructor(config, registry = serverPluginsByType, importerRegistry = serverImporters) {
|
|
148
591
|
this.#config = config;
|
|
149
592
|
this.#registry = registry;
|
|
150
593
|
this.#store = new LibraryStore(config, registry);
|
|
594
|
+
this.#importers = importerRegistry;
|
|
151
595
|
}
|
|
152
596
|
get config() {
|
|
153
597
|
return this.#config;
|
|
@@ -274,6 +718,7 @@ var FundusOperations = class {
|
|
|
274
718
|
record.type = type;
|
|
275
719
|
record.rawHash = edited.hash;
|
|
276
720
|
record.parameters = parameters;
|
|
721
|
+
delete record.importSource;
|
|
277
722
|
return edited.assetId;
|
|
278
723
|
});
|
|
279
724
|
await this.#store.deleteProxiesOf(result.value);
|
|
@@ -290,6 +735,7 @@ var FundusOperations = class {
|
|
|
290
735
|
const record = current.assets[edited.assetId];
|
|
291
736
|
if (!this.#compatibleTypes(edited.path).includes(record.type)) throw new Error(`The moved file "${edited.path}" is not compatible with its current ${record.type} type; choose a compatible type and process it.`);
|
|
292
737
|
candidate.assets[edited.assetId].rawHash = edited.hash;
|
|
738
|
+
delete candidate.assets[edited.assetId].importSource;
|
|
293
739
|
return edited.assetId;
|
|
294
740
|
});
|
|
295
741
|
await this.#store.deleteProxiesOf(result.value);
|
|
@@ -306,32 +752,130 @@ var FundusOperations = class {
|
|
|
306
752
|
* rolls the whole ingest back, so a failed ingest leaves nothing behind.
|
|
307
753
|
*/
|
|
308
754
|
ingest(options) {
|
|
309
|
-
return this.#enqueueOperation(() =>
|
|
755
|
+
return this.#enqueueOperation(async () => {
|
|
756
|
+
const importer = this.#importers.require("file");
|
|
757
|
+
const input = importer.validateInput({
|
|
758
|
+
fileName: options.fileName,
|
|
759
|
+
bytes: options.bytes
|
|
760
|
+
});
|
|
761
|
+
const raw = await importer.importRaw(input, {
|
|
762
|
+
config: this.#config,
|
|
763
|
+
fetch: globalThis.fetch
|
|
764
|
+
});
|
|
765
|
+
return this.#createImportedAsset(raw, options);
|
|
766
|
+
});
|
|
310
767
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
768
|
+
/** Materialize a source outside the operation queue, then atomically create the asset. */
|
|
769
|
+
async importAsset(options) {
|
|
770
|
+
const preflight = await this.#enqueueOperation(async () => {
|
|
771
|
+
const library = await this.#store.read();
|
|
772
|
+
this.#validateImportTarget(options, library);
|
|
773
|
+
if (Object.hasOwn(library.assets, options.id)) throw new SourceImportError("invalid-input", `An asset with id "${options.id}" already exists.`);
|
|
774
|
+
const importer = this.#importers.require(options.importer);
|
|
775
|
+
return {
|
|
776
|
+
importer,
|
|
777
|
+
input: importer.validateInput(options.input)
|
|
778
|
+
};
|
|
779
|
+
});
|
|
780
|
+
const raw = await preflight.importer.importRaw(preflight.input, {
|
|
781
|
+
config: this.#config,
|
|
782
|
+
fetch: globalThis.fetch
|
|
783
|
+
});
|
|
784
|
+
return this.#enqueueOperation(async () => {
|
|
785
|
+
this.#validateImportedRaw(raw, options.importer);
|
|
786
|
+
return this.#createImportedAsset(raw, options);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
/** Re-materialize a persisted source with record and on-disk CAS protection. */
|
|
790
|
+
async reimportAsset(id, input) {
|
|
791
|
+
const snapshot = await this.#enqueueOperation(async () => {
|
|
792
|
+
const library = await this.#store.read();
|
|
793
|
+
this.#requireAsset(library, id);
|
|
794
|
+
const record = library.assets[id];
|
|
795
|
+
if (!record.importSource) throw new SourceImportError("invalid-input", `Asset "${id}" has no repeatable import source.`);
|
|
796
|
+
const resolved = this.#importers.requireReimport(record.importSource);
|
|
797
|
+
try {
|
|
798
|
+
await verifyStableRawFile(join(this.#config.rawDir, record.raw), record.rawHash);
|
|
799
|
+
} catch {
|
|
800
|
+
throw new SourceImportError("stale-import", "The local raw file changed. Apply or discard that edit before re-importing.");
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
importer: resolved.importer,
|
|
804
|
+
parameters: resolved.parameters,
|
|
805
|
+
input: input === void 0 ? void 0 : resolved.importer.validateInput(input),
|
|
806
|
+
expected: {
|
|
807
|
+
raw: record.raw,
|
|
808
|
+
rawHash: record.rawHash,
|
|
809
|
+
type: record.type,
|
|
810
|
+
canonicalSource: canonicalizeParameters(record.importSource)
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
});
|
|
814
|
+
const context = {
|
|
815
|
+
config: this.#config,
|
|
816
|
+
fetch: globalThis.fetch
|
|
817
|
+
};
|
|
818
|
+
const raw = snapshot.input === void 0 ? await snapshot.importer.reimportRaw(snapshot.parameters, context) : await snapshot.importer.importRaw(snapshot.input, context);
|
|
819
|
+
if (snapshot.input !== void 0 && !raw.importSource) throw new SourceImportError("invalid-response", "The source importer did not return provenance for the updated source.");
|
|
820
|
+
return this.#enqueueOperation(async () => {
|
|
821
|
+
this.#validateImportedRaw(raw, snapshot.importer.kind);
|
|
822
|
+
if (!serverPluginForType(snapshot.expected.type, this.#registry).descriptor.accepts.includes(raw.extension)) throw new SourceImportError("invalid-response", `The ${snapshot.expected.type} type does not accept ".${raw.extension}" files.`);
|
|
823
|
+
let library;
|
|
824
|
+
try {
|
|
825
|
+
library = await this.#store.replaceRawFromImportSource(id, raw.bytes, snapshot.expected, raw.importSource);
|
|
826
|
+
} catch (cause) {
|
|
827
|
+
if (cause instanceof Error && cause.message === "stale-import") throw new SourceImportError("stale-import", "The asset or local raw file changed while Figma was exporting. Nothing was replaced.");
|
|
828
|
+
throw cause;
|
|
829
|
+
}
|
|
830
|
+
return this.#afterMutation(library);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
async #createImportedAsset(raw, options) {
|
|
834
|
+
this.#validateImportedRaw(raw);
|
|
835
|
+
const fileName = raw.suggestedFileName ?? `${options.id}.${raw.extension}`;
|
|
836
|
+
const plugin = serverPluginForType(options.type, this.#registry);
|
|
837
|
+
const library = await this.#store.createAsset({
|
|
838
|
+
fileName,
|
|
839
|
+
bytes: raw.bytes,
|
|
840
|
+
type: options.type,
|
|
841
|
+
id: options.id,
|
|
842
|
+
folder: options.folder,
|
|
843
|
+
manifests: options.manifests,
|
|
844
|
+
importSource: raw.importSource
|
|
845
|
+
}, async ({ rawPath, rawFileName, parameters }) => {
|
|
846
|
+
let next = parameters;
|
|
315
847
|
if (plugin.seedIngestParameters) {
|
|
316
|
-
const record = library.assets[options.id];
|
|
317
848
|
const seeded = await plugin.seedIngestParameters({
|
|
318
|
-
rawPath
|
|
319
|
-
rawFileName
|
|
320
|
-
parameters:
|
|
849
|
+
rawPath,
|
|
850
|
+
rawFileName,
|
|
851
|
+
parameters: next,
|
|
321
852
|
config: this.#config
|
|
322
853
|
});
|
|
323
|
-
if (seeded !== void 0)
|
|
854
|
+
if (seeded !== void 0) next = seeded;
|
|
324
855
|
}
|
|
325
|
-
if (options.parametersPatch !== void 0
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
329
|
-
} catch (cause) {
|
|
330
|
-
await this.#store.deleteAsset(options.id);
|
|
331
|
-
throw cause;
|
|
332
|
-
}
|
|
856
|
+
if (options.parametersPatch !== void 0) next = mergeParameterPatch(next, options.parametersPatch);
|
|
857
|
+
return plugin.descriptor.validateParameters(next);
|
|
858
|
+
});
|
|
333
859
|
return this.#afterMutation(library);
|
|
334
860
|
}
|
|
861
|
+
#validateImportTarget(options, library) {
|
|
862
|
+
if (typeof options.importer !== "string" || typeof options.id !== "string" || typeof options.type !== "string") throw new SourceImportError("invalid-input", "Import requires importer, type, and id strings.");
|
|
863
|
+
const problem = assetIdError(options.id);
|
|
864
|
+
if (problem) throw new SourceImportError("invalid-input", problem);
|
|
865
|
+
serverPluginForType(options.type, this.#registry);
|
|
866
|
+
if (options.folder !== void 0 && typeof options.folder !== "string") throw new SourceImportError("invalid-input", "Import folder must be a string.");
|
|
867
|
+
const folder = normalizeFolderPath(options.folder ?? "");
|
|
868
|
+
if (folder !== "" && !library.folders.includes(folder)) throw new SourceImportError("invalid-input", `Unknown folder "${folder}".`);
|
|
869
|
+
if (options.manifests !== void 0 && (!Array.isArray(options.manifests) || options.manifests.some((value) => typeof value !== "string"))) throw new SourceImportError("invalid-input", "Import manifests must be an array of strings.");
|
|
870
|
+
for (const manifest of new Set(options.manifests ?? [])) if (!Object.hasOwn(library.manifests, manifest)) throw new SourceImportError("invalid-input", `Unknown manifest "${manifest}".`);
|
|
871
|
+
}
|
|
872
|
+
#validateImportedRaw(raw, importerKind) {
|
|
873
|
+
if (!(raw.bytes instanceof Uint8Array) || !/^[a-z0-9]+$/.test(raw.extension)) throw new SourceImportError("invalid-response", "The source importer returned invalid raw data.");
|
|
874
|
+
if (raw.importSource) {
|
|
875
|
+
if (importerKind && raw.importSource.importer !== importerKind) throw new SourceImportError("invalid-response", "The source importer returned mismatched provenance.");
|
|
876
|
+
this.#importers.require(raw.importSource.importer).validateSourceParameters(raw.importSource.parameters);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
335
879
|
/** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
|
|
336
880
|
patchAsset(id, body) {
|
|
337
881
|
return this.#enqueueOperation(() => this.#patchAsset(id, body));
|
|
@@ -583,6 +1127,7 @@ var FundusOperations = class {
|
|
|
583
1127
|
const warnings = [];
|
|
584
1128
|
for (const issue of issues) if (issue.assetId === assetId) warnings.push(issue.message);
|
|
585
1129
|
if (record.manifests.length === 0 && !delivered.has(assetId)) warnings.push("In no manifest and not referenced by any delivered asset — nothing is processed or generated for it.");
|
|
1130
|
+
if (record.importSource && !this.#importers.has(record.importSource.importer)) warnings.push(`Importer "${record.importSource.importer}" is unavailable; the local raw file remains usable.`);
|
|
586
1131
|
let proxy = null;
|
|
587
1132
|
if (delivered.has(assetId)) try {
|
|
588
1133
|
const currentProxy = await readCurrentProxy(assetId, record, this.#config, this.#registry);
|
|
@@ -602,6 +1147,7 @@ var FundusOperations = class {
|
|
|
602
1147
|
folder: folderOfRawPath(record.raw),
|
|
603
1148
|
rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}?v=${record.rawHash.slice(0, 12)}`,
|
|
604
1149
|
rawHash: record.rawHash,
|
|
1150
|
+
importSource: record.importSource,
|
|
605
1151
|
parameters: record.parameters,
|
|
606
1152
|
manifests: record.manifests,
|
|
607
1153
|
passiveManifests: passiveManifestsByAsset.get(assetId) ?? [],
|
|
@@ -631,14 +1177,16 @@ var FundusOperations = class {
|
|
|
631
1177
|
}
|
|
632
1178
|
const presetNames = {};
|
|
633
1179
|
for (const [type, plugin] of Object.entries(this.#registry)) presetNames[type] = Object.keys(plugin.presets(this.#config)).sort();
|
|
1180
|
+
const rawIngestionQueue = this.#buildRawIngestionQueue(library, classification);
|
|
634
1181
|
return {
|
|
635
1182
|
library,
|
|
636
1183
|
assets,
|
|
1184
|
+
importDefaults: { figma: { scale: this.#config.import.figma.defaults.scale } },
|
|
637
1185
|
issues,
|
|
638
1186
|
presetNames,
|
|
639
1187
|
manifestMemberships,
|
|
640
1188
|
manifestDeliveredBytes,
|
|
641
|
-
rawIngestionQueue
|
|
1189
|
+
rawIngestionQueue
|
|
642
1190
|
};
|
|
643
1191
|
}
|
|
644
1192
|
#compatibleTypes(path) {
|
|
@@ -800,4 +1348,4 @@ async function createFundusContext() {
|
|
|
800
1348
|
}
|
|
801
1349
|
|
|
802
1350
|
export { getFundusContext as g, initializeFundusContext as i };
|
|
803
|
-
//# sourceMappingURL=context.js-
|
|
1351
|
+
//# sourceMappingURL=context.js-bXe5hzjI.js.map
|