fundus 0.2.0 → 0.4.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.B5tQm8hR.css +1 -0
- package/build/client/_app/immutable/assets/2.B5tQm8hR.css.br +0 -0
- package/build/client/_app/immutable/assets/2.B5tQm8hR.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/assets/{LibraryContextMenu.BYvU4SY7.css → LibraryContextMenu.CVb-Lqdf.css} +1 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.CVb-Lqdf.css.br +0 -0
- package/build/client/_app/immutable/assets/{LibraryContextMenu.BYvU4SY7.css.gz → LibraryContextMenu.CVb-Lqdf.css.gz} +0 -0
- package/build/client/_app/immutable/chunks/{C8W3KwDq.js → BPiGyn-T.js} +1 -1
- package/build/client/_app/immutable/chunks/BPiGyn-T.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPiGyn-T.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CP-bnOxU.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/{ByVSyIj7.js → CeJsL6hj.js} +1 -1
- package/build/client/_app/immutable/chunks/CeJsL6hj.js.br +0 -0
- package/build/client/_app/immutable/chunks/CeJsL6hj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{Db8LRH6X.js → Cq4Yaq35.js} +3 -3
- package/build/client/_app/immutable/chunks/Cq4Yaq35.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cq4Yaq35.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/entry/{app.nM6SgHKJ.js → app.BQ4TfPHG.js} +2 -2
- package/build/client/_app/immutable/entry/app.BQ4TfPHG.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BQ4TfPHG.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.COP9tIRH.js +1 -0
- package/build/client/_app/immutable/entry/start.COP9tIRH.js.br +0 -0
- package/build/client/_app/immutable/entry/start.COP9tIRH.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CftchwYV.js +2 -0
- package/build/client/_app/immutable/nodes/0.CftchwYV.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CftchwYV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.dMklHTc2.js → 1.C8DOqctV.js} +1 -1
- package/build/client/_app/immutable/nodes/1.C8DOqctV.js.br +1 -0
- package/build/client/_app/immutable/nodes/1.C8DOqctV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BR8OVsS9.js +3 -0
- package/build/client/_app/immutable/nodes/2.BR8OVsS9.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BR8OVsS9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BO5hcxIH.js +1 -0
- package/build/client/_app/immutable/nodes/3.BO5hcxIH.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BO5hcxIH.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.Dgxx52FJ.js → 4.DTaH-k-c.js} +1 -1
- package/build/client/_app/immutable/nodes/4.DTaH-k-c.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.DTaH-k-c.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-CG2t-ufq.js} +568 -25
- package/build/server/chunks/chunks/context.js-CG2t-ufq.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-DvhE7Ffd.js → exports.js-rOfFZumP.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-DvhE7Ffd.js.map → exports.js-rOfFZumP.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-Dn37LrAW.js → internal.js-DnveDsYn.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-Dn37LrAW.js.map → internal.js-DnveDsYn.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-Dob0TlTu.js → shared.js-DtvIbMLj.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-Dob0TlTu.js.map → shared.js-DtvIbMLj.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-B4YimMxv.js → utils.js-bP1j9RdM.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-B4YimMxv.js.map → utils.js-bP1j9RdM.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-BfdsBPWo.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BI3KHSfE.js.map → _server.ts.js-BfdsBPWo.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BlNYUbXX.js → _server.ts.js-D0aKDq87.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BlNYUbXX.js.map → _server.ts.js-D0aKDq87.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/_server.ts.js-DsX_m2gc.js +31 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/_server.ts.js-DsX_m2gc.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-j-_N540j.js → _server.ts.js-nfThjRxs.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-j-_N540j.js.map → _server.ts.js-nfThjRxs.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-BHKobzAg.js → _server.ts.js-CseHmsYB.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-BHKobzAg.js.map → _server.ts.js-CseHmsYB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BFKjIOCv.js → _server.ts.js-CHiICsyC.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BFKjIOCv.js.map → _server.ts.js-CHiICsyC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BxZeVw8y.js → _server.ts.js-Dgf46K5u.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BxZeVw8y.js.map → _server.ts.js-Dgf46K5u.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CO36U_CT.js → _server.ts.js-DwQJUZFa.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CO36U_CT.js.map → _server.ts.js-DwQJUZFa.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/_server.ts.js-BnKS8nVF.js +37 -0
- package/build/server/chunks/entries/endpoints/api/imports/_server.ts.js-BnKS8nVF.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-CBlg1Bkl.js → _server.ts.js-BrDbLcyO.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-CBlg1Bkl.js.map → _server.ts.js-BrDbLcyO.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DcGLCjIs.js → _server.ts.js-DIUiQuyd.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DcGLCjIs.js.map → _server.ts.js-DIUiQuyd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AqhtjOiL.js → _server.ts.js-BrReStaf.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AqhtjOiL.js.map → _server.ts.js-BrReStaf.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CSa6-DcU.js → _server.ts.js-B31iGwJ-.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CSa6-DcU.js.map → _server.ts.js-B31iGwJ-.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DnzZUoAo.js → _server.ts.js-DeZQFNdj.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DnzZUoAo.js.map → _server.ts.js-DeZQFNdj.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-B0KMprKq.js → _server.ts.js-DhDhUU7V.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-B0KMprKq.js.map → _server.ts.js-DhDhUU7V.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-DGkPIMcq.js +80 -0
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-DGkPIMcq.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B1N9o1Ne.js → _server.ts.js-BTQgWDH1.js} +11 -6
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B1N9o1Ne.js.map → _server.ts.js-BTQgWDH1.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-kmc-gejQ.js} +11 -6
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-7wzPyG9v.js.map → _server.ts.js-kmc-gejQ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BWztxYge.js → _server.ts.js-B8EE1DD9.js} +11 -6
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BWztxYge.js.map → _server.ts.js-B8EE1DD9.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-CVKLksJ1.js → hooks.server.js-DbBA5yYQ.js} +10 -5
- package/build/server/chunks/entries/{hooks.server.js-CVKLksJ1.js.map → hooks.server.js-DbBA5yYQ.js.map} +1 -1
- package/build/server/chunks/{handler-DZ2Sps4a.js → handler-J6aKdov3.js} +5 -5
- package/build/server/chunks/{handler-DZ2Sps4a.js.map → handler-J6aKdov3.js.map} +1 -1
- package/build/server/chunks/{index.js-CdmTWQz8.js → index.js-CsvWJ7Qu.js} +5 -5
- package/build/server/chunks/{index.js-CdmTWQz8.js.map → index.js-CsvWJ7Qu.js.map} +1 -1
- package/build/server/chunks/{manifest.js-GhfNr35x.js → manifest.js-wHtZ9vlA.js} +46 -25
- package/build/server/chunks/manifest.js-wHtZ9vlA.js.map +1 -0
- package/build/server/chunks/nodes/0.js-By6-kbk6.js +11 -0
- package/build/server/chunks/nodes/0.js-By6-kbk6.js.map +1 -0
- package/build/server/chunks/nodes/1.js-CrPfmKpN.js +7 -0
- package/build/server/chunks/nodes/{1.js-DQgYreU0.js.map → 1.js-CrPfmKpN.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-C-i3Y5t2.js +7 -0
- package/build/server/chunks/nodes/2.js-C-i3Y5t2.js.map +1 -0
- package/build/server/chunks/nodes/3.js-C1gcIGeV.js +7 -0
- package/build/server/chunks/nodes/3.js-C1gcIGeV.js.map +1 -0
- package/build/server/chunks/nodes/4.js-tgwl3VD3.js +7 -0
- package/build/server/chunks/nodes/{4.js-BebbMVXy.js.map → 4.js-tgwl3VD3.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/file-manager.d.ts +2 -0
- package/dist/server/file-manager.js +17 -0
- 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 +23 -0
- package/dist/server/importers/pinned-https.js +60 -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.ohi-TClG.css +0 -1
- package/build/client/_app/immutable/assets/2.ohi-TClG.css.br +0 -0
- package/build/client/_app/immutable/assets/2.ohi-TClG.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/assets/LibraryContextMenu.BYvU4SY7.css.br +0 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js +0 -1
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.br +0 -1
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.br +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js.br +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.br +0 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js +0 -1
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js +0 -2
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js +0 -3
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js +0 -1
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.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/manifest.js-GhfNr35x.js.map +0 -1
- package/build/server/chunks/nodes/0.js-DGT86_i-.js +0 -11
- package/build/server/chunks/nodes/0.js-DGT86_i-.js.map +0 -1
- package/build/server/chunks/nodes/1.js-DQgYreU0.js +0 -7
- package/build/server/chunks/nodes/2.js-DItreSrX.js +0 -7
- package/build/server/chunks/nodes/2.js-DItreSrX.js.map +0 -1
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js +0 -7
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js.map +0 -1
- package/build/server/chunks/nodes/4.js-BebbMVXy.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,424 @@ function rawSafetyIssues(library, classification, compatibleTypes) {
|
|
|
116
135
|
});
|
|
117
136
|
}
|
|
118
137
|
//#endregion
|
|
138
|
+
//#region src/lib/server/importers/pinned-https.ts
|
|
139
|
+
function lookupError(hostname) {
|
|
140
|
+
const error = /* @__PURE__ */ new Error(`No validated address is available for ${hostname}.`);
|
|
141
|
+
error.code = "ENOTFOUND";
|
|
142
|
+
return error;
|
|
143
|
+
}
|
|
144
|
+
function createPinnedLookup(target) {
|
|
145
|
+
return (hostname, options, callback) => {
|
|
146
|
+
if (hostname !== target.url.hostname) {
|
|
147
|
+
callback(lookupError(hostname), "", 0);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const requestedFamily = options.family === 4 || options.family === 6 ? options.family : 0;
|
|
151
|
+
const addresses = target.addresses.filter((candidate) => requestedFamily === 0 || candidate.family === requestedFamily);
|
|
152
|
+
if (addresses.length === 0) {
|
|
153
|
+
callback(lookupError(hostname), "", 0);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (options.all) {
|
|
157
|
+
callback(null, addresses.map(({ address, family }) => ({
|
|
158
|
+
address,
|
|
159
|
+
family
|
|
160
|
+
})));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
callback(null, addresses[0].address, addresses[0].family);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function responseHeaders(source) {
|
|
167
|
+
const headers = new Headers();
|
|
168
|
+
for (const [name, value] of Object.entries(source)) if (Array.isArray(value)) for (const item of value) headers.append(name, item);
|
|
169
|
+
else if (value !== void 0) headers.set(name, value);
|
|
170
|
+
return headers;
|
|
171
|
+
}
|
|
172
|
+
function createPinnedHttpsTransport(requester = request) {
|
|
173
|
+
return (target, signal) => new Promise((resolve, reject) => {
|
|
174
|
+
const request = requester(target.url, {
|
|
175
|
+
method: "GET",
|
|
176
|
+
agent: false,
|
|
177
|
+
lookup: createPinnedLookup(target),
|
|
178
|
+
servername: target.url.hostname,
|
|
179
|
+
rejectUnauthorized: true,
|
|
180
|
+
signal
|
|
181
|
+
}, (response) => {
|
|
182
|
+
resolve({
|
|
183
|
+
status: response.statusCode ?? 0,
|
|
184
|
+
headers: responseHeaders(response.headers),
|
|
185
|
+
body: response,
|
|
186
|
+
cancel: () => response.destroy()
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
request.once("error", reject);
|
|
190
|
+
request.end();
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
var requestPinnedHttps = createPinnedHttpsTransport();
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/lib/server/importers/figma.ts
|
|
196
|
+
var MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
|
|
197
|
+
var MAX_PIXELS = 32e6;
|
|
198
|
+
var MAX_REDIRECTS = 3;
|
|
199
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
200
|
+
var PNG_SIGNATURE = Uint8Array.from([
|
|
201
|
+
137,
|
|
202
|
+
80,
|
|
203
|
+
78,
|
|
204
|
+
71,
|
|
205
|
+
13,
|
|
206
|
+
10,
|
|
207
|
+
26,
|
|
208
|
+
10
|
|
209
|
+
]);
|
|
210
|
+
function record(value) {
|
|
211
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
212
|
+
}
|
|
213
|
+
function parseFigmaSelectionUrl(value) {
|
|
214
|
+
let url;
|
|
215
|
+
try {
|
|
216
|
+
url = new URL(value);
|
|
217
|
+
} catch {
|
|
218
|
+
return invalidInput("Enter a valid Figma selection link.");
|
|
219
|
+
}
|
|
220
|
+
if (url.protocol !== "https:" || url.hostname !== "figma.com" && url.hostname !== "www.figma.com") return invalidInput("Figma links must use HTTPS on figma.com.");
|
|
221
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
222
|
+
if (![
|
|
223
|
+
"design",
|
|
224
|
+
"file",
|
|
225
|
+
"proto"
|
|
226
|
+
].includes(segments[0] ?? "") || !segments[1]) return invalidInput("The Figma link must point to a design, file, or prototype.");
|
|
227
|
+
const nodeIds = url.searchParams.getAll("node-id");
|
|
228
|
+
if (nodeIds.length !== 1 || !/^\d+[-:]\d+$/.test(nodeIds[0])) return invalidInput("The Figma link must contain exactly one valid node-id.");
|
|
229
|
+
const [left, right] = nodeIds[0].split(/[-:]/);
|
|
230
|
+
return {
|
|
231
|
+
fileKey: segments[1],
|
|
232
|
+
nodeId: `${left}:${right}`
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function validateScale(value) {
|
|
236
|
+
if (value !== 1 && value !== 2 && value !== 3 && value !== 4) return invalidInput("Figma export scale must be 1, 2, 3, or 4.");
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
function validateInput(value) {
|
|
240
|
+
const input = record(value);
|
|
241
|
+
if (!input || typeof input.url !== "string") return invalidInput("Figma import requires a URL.");
|
|
242
|
+
if (input.format !== void 0 && input.format !== "png") return invalidInput("Figma import currently supports PNG only.");
|
|
243
|
+
return {
|
|
244
|
+
url: input.url,
|
|
245
|
+
format: "png",
|
|
246
|
+
...input.scale !== void 0 ? { scale: validateScale(input.scale) } : {}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function validateFigmaSourceParameters(value) {
|
|
250
|
+
const source = record(value);
|
|
251
|
+
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.");
|
|
252
|
+
return {
|
|
253
|
+
fileKey: source.fileKey,
|
|
254
|
+
nodeId: source.nodeId,
|
|
255
|
+
format: "png",
|
|
256
|
+
scale: validateScale(source.scale)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function retryAfter(response) {
|
|
260
|
+
const raw = response.headers.get("retry-after");
|
|
261
|
+
if (!raw || !/^\d+$/.test(raw.trim())) return void 0;
|
|
262
|
+
const seconds = Number(raw);
|
|
263
|
+
return Number.isSafeInteger(seconds) ? seconds : void 0;
|
|
264
|
+
}
|
|
265
|
+
function upstreamStatusError(response) {
|
|
266
|
+
if (response.status === 401) return new SourceImportError("unauthorized", "Figma authentication failed.");
|
|
267
|
+
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.");
|
|
268
|
+
if (response.status === 404) return new SourceImportError("not-found", "The Figma file or node was not found.");
|
|
269
|
+
if (response.status === 429) return new SourceImportError("rate-limited", "Figma rate-limited this request.", retryAfter(response));
|
|
270
|
+
return new SourceImportError("upstream-failed", `Figma export failed with status ${response.status}.`);
|
|
271
|
+
}
|
|
272
|
+
function isForbiddenIpv4(address) {
|
|
273
|
+
const parts = address.split(".").map(Number);
|
|
274
|
+
const [a, b] = parts;
|
|
275
|
+
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;
|
|
276
|
+
}
|
|
277
|
+
function mappedIpv4Address(address) {
|
|
278
|
+
const normalized = address.toLowerCase().split("%")[0];
|
|
279
|
+
const dotted = /^(?:::ffff:|0:0:0:0:0:ffff:)(\d+\.\d+\.\d+\.\d+)$/.exec(normalized);
|
|
280
|
+
if (dotted && isIP(dotted[1]) === 4) return dotted[1];
|
|
281
|
+
const halves = normalized.split("::");
|
|
282
|
+
if (halves.length > 2) return null;
|
|
283
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
284
|
+
const right = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
285
|
+
const missing = 8 - left.length - right.length;
|
|
286
|
+
if (missing < 0 || halves.length === 1 && missing !== 0) return null;
|
|
287
|
+
const parts = [
|
|
288
|
+
...left,
|
|
289
|
+
...Array.from({ length: missing }, () => "0"),
|
|
290
|
+
...right
|
|
291
|
+
];
|
|
292
|
+
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;
|
|
293
|
+
const high = Number.parseInt(parts[6], 16);
|
|
294
|
+
const low = Number.parseInt(parts[7], 16);
|
|
295
|
+
if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 65535 || low < 0 || low > 65535) return null;
|
|
296
|
+
return `${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`;
|
|
297
|
+
}
|
|
298
|
+
function isForbiddenIp(address) {
|
|
299
|
+
if (isIP(address) === 4) return isForbiddenIpv4(address);
|
|
300
|
+
const normalized = address.toLowerCase().split("%")[0];
|
|
301
|
+
const mapped = mappedIpv4Address(normalized);
|
|
302
|
+
if (mapped) return isForbiddenIpv4(mapped);
|
|
303
|
+
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:");
|
|
304
|
+
}
|
|
305
|
+
async function resolveSafeDownloadTarget(value) {
|
|
306
|
+
let url;
|
|
307
|
+
try {
|
|
308
|
+
url = value instanceof URL ? value : new URL(value);
|
|
309
|
+
} catch {
|
|
310
|
+
throw new SourceImportError("unsafe-download-url", "Figma returned an unsafe download target.");
|
|
311
|
+
}
|
|
312
|
+
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.");
|
|
313
|
+
let resolvedAddresses;
|
|
314
|
+
try {
|
|
315
|
+
resolvedAddresses = await lookup(url.hostname, { all: true });
|
|
316
|
+
} catch {
|
|
317
|
+
throw new SourceImportError("unsafe-download-url", "Figma returned an unavailable download target.");
|
|
318
|
+
}
|
|
319
|
+
if (resolvedAddresses.length === 0 || resolvedAddresses.some(({ address }) => isIP(address) === 0 || isForbiddenIp(address))) throw new SourceImportError("unsafe-download-url", "Figma returned an unsafe download target.");
|
|
320
|
+
const addresses = resolvedAddresses.map(({ address }) => ({
|
|
321
|
+
address,
|
|
322
|
+
family: isIP(address)
|
|
323
|
+
}));
|
|
324
|
+
return {
|
|
325
|
+
url,
|
|
326
|
+
addresses
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function fetchWithDeadline(fetcher, url, init, consume) {
|
|
330
|
+
const controller = new AbortController();
|
|
331
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
332
|
+
try {
|
|
333
|
+
const response = await fetcher(url, {
|
|
334
|
+
...init,
|
|
335
|
+
signal: controller.signal
|
|
336
|
+
});
|
|
337
|
+
if (!response.ok) throw upstreamStatusError(response);
|
|
338
|
+
return await consume(response);
|
|
339
|
+
} catch (cause) {
|
|
340
|
+
if (controller.signal.aborted || cause instanceof Error && cause.name === "AbortError") throw new SourceImportError("download-timeout", "Figma export timed out.");
|
|
341
|
+
if (cause instanceof SourceImportError) throw cause;
|
|
342
|
+
throw new SourceImportError("upstream-failed", "Figma export request failed.");
|
|
343
|
+
} finally {
|
|
344
|
+
clearTimeout(timeout);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function readLimitedBody(response, signal) {
|
|
348
|
+
let canceled = false;
|
|
349
|
+
const cancel = () => {
|
|
350
|
+
if (canceled) return;
|
|
351
|
+
canceled = true;
|
|
352
|
+
response.cancel();
|
|
353
|
+
};
|
|
354
|
+
const declared = response.headers.get("content-length");
|
|
355
|
+
if (declared && /^\d+$/.test(declared) && Number(declared) > MAX_DOWNLOAD_BYTES) {
|
|
356
|
+
cancel();
|
|
357
|
+
throw new SourceImportError("download-too-large", "Figma export exceeds the 50 MiB limit.");
|
|
358
|
+
}
|
|
359
|
+
const iterator = response.body[Symbol.asyncIterator]();
|
|
360
|
+
const chunks = [];
|
|
361
|
+
let size = 0;
|
|
362
|
+
let rejectAbort;
|
|
363
|
+
const aborted = new Promise((_, reject) => rejectAbort = reject);
|
|
364
|
+
const onAbort = () => {
|
|
365
|
+
rejectAbort(new SourceImportError("download-timeout", "Figma export download timed out."));
|
|
366
|
+
cancel();
|
|
367
|
+
};
|
|
368
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
369
|
+
try {
|
|
370
|
+
for (;;) {
|
|
371
|
+
const { done, value } = await Promise.race([iterator.next(), aborted]);
|
|
372
|
+
if (signal.aborted) throw new SourceImportError("download-timeout", "Figma export download timed out.");
|
|
373
|
+
if (done) break;
|
|
374
|
+
size += value.byteLength;
|
|
375
|
+
if (size > MAX_DOWNLOAD_BYTES) {
|
|
376
|
+
cancel();
|
|
377
|
+
throw new SourceImportError("download-too-large", "Figma export exceeds the 50 MiB limit.");
|
|
378
|
+
}
|
|
379
|
+
chunks.push(value);
|
|
380
|
+
}
|
|
381
|
+
} catch (cause) {
|
|
382
|
+
cancel();
|
|
383
|
+
throw cause;
|
|
384
|
+
} finally {
|
|
385
|
+
signal.removeEventListener("abort", onAbort);
|
|
386
|
+
}
|
|
387
|
+
const bytes = new Uint8Array(size);
|
|
388
|
+
let offset = 0;
|
|
389
|
+
for (const chunk of chunks) {
|
|
390
|
+
bytes.set(chunk, offset);
|
|
391
|
+
offset += chunk.byteLength;
|
|
392
|
+
}
|
|
393
|
+
return bytes;
|
|
394
|
+
}
|
|
395
|
+
async function downloadExport(transport, initial) {
|
|
396
|
+
let target = await resolveSafeDownloadTarget(initial);
|
|
397
|
+
const controller = new AbortController();
|
|
398
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
399
|
+
try {
|
|
400
|
+
for (let redirects = 0;; redirects++) {
|
|
401
|
+
let response;
|
|
402
|
+
try {
|
|
403
|
+
response = await transport(target, controller.signal);
|
|
404
|
+
} catch (cause) {
|
|
405
|
+
if (controller.signal.aborted || cause instanceof Error && cause.name === "AbortError") throw new SourceImportError("download-timeout", "Figma export download timed out.");
|
|
406
|
+
throw new SourceImportError("upstream-failed", "Figma export download failed.");
|
|
407
|
+
}
|
|
408
|
+
if (response.status >= 300 && response.status < 400) {
|
|
409
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
410
|
+
response.cancel();
|
|
411
|
+
throw new SourceImportError("unsafe-download-url", "Figma export redirected too many times.");
|
|
412
|
+
}
|
|
413
|
+
const location = response.headers.get("location");
|
|
414
|
+
if (!location) {
|
|
415
|
+
response.cancel();
|
|
416
|
+
throw new SourceImportError("invalid-response", "Figma export redirect was invalid.");
|
|
417
|
+
}
|
|
418
|
+
response.cancel();
|
|
419
|
+
target = await resolveSafeDownloadTarget(new URL(location, target.url));
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (response.status < 200 || response.status >= 300) {
|
|
423
|
+
response.cancel();
|
|
424
|
+
throw upstreamStatusError(response);
|
|
425
|
+
}
|
|
426
|
+
return await readLimitedBody(response, controller.signal);
|
|
427
|
+
}
|
|
428
|
+
} finally {
|
|
429
|
+
clearTimeout(timeout);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function validatePng(bytes) {
|
|
433
|
+
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.");
|
|
434
|
+
let metadata;
|
|
435
|
+
try {
|
|
436
|
+
metadata = await sharp(bytes).metadata();
|
|
437
|
+
} catch {
|
|
438
|
+
throw new SourceImportError("invalid-response", "Figma export is not a readable PNG.");
|
|
439
|
+
}
|
|
440
|
+
if (!metadata.width || !metadata.height) throw new SourceImportError("invalid-response", "Figma export has invalid dimensions.");
|
|
441
|
+
if (metadata.width * metadata.height > MAX_PIXELS) throw new SourceImportError("image-too-large", "Figma export exceeds the 32 megapixel limit.");
|
|
442
|
+
}
|
|
443
|
+
async function materialize(source, context) {
|
|
444
|
+
const token = context.config.import.figma.token;
|
|
445
|
+
if (!token) throw new SourceImportError("not-configured", "Figma access is not configured.");
|
|
446
|
+
const endpoint = new URL(`https://api.figma.com/v1/images/${encodeURIComponent(source.fileKey)}`);
|
|
447
|
+
endpoint.searchParams.set("ids", source.nodeId);
|
|
448
|
+
endpoint.searchParams.set("format", source.format);
|
|
449
|
+
endpoint.searchParams.set("scale", String(source.scale));
|
|
450
|
+
const jsonBytes = await fetchWithDeadline(context.fetch, endpoint, {
|
|
451
|
+
redirect: "error",
|
|
452
|
+
headers: { "X-Figma-Token": token }
|
|
453
|
+
}, async (response) => new Uint8Array(await response.arrayBuffer()));
|
|
454
|
+
let payload;
|
|
455
|
+
try {
|
|
456
|
+
payload = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
457
|
+
} catch {
|
|
458
|
+
throw new SourceImportError("invalid-response", "Figma returned an invalid response.");
|
|
459
|
+
}
|
|
460
|
+
const body = record(payload);
|
|
461
|
+
if (!body || body.err || typeof body.status === "number" && body.status >= 400) throw new SourceImportError("invalid-response", "Figma could not render this node.");
|
|
462
|
+
const images = record(body.images);
|
|
463
|
+
if (!images || !Object.hasOwn(images, source.nodeId)) throw new SourceImportError("invalid-response", "Figma did not return the requested node.");
|
|
464
|
+
const imageUrl = images[source.nodeId];
|
|
465
|
+
if (typeof imageUrl !== "string" || imageUrl.length === 0) throw new SourceImportError("invalid-response", "Figma could not render the requested node.");
|
|
466
|
+
const bytes = await downloadExport(context.httpsTransport ?? requestPinnedHttps, imageUrl);
|
|
467
|
+
await validatePng(bytes);
|
|
468
|
+
return {
|
|
469
|
+
bytes,
|
|
470
|
+
extension: "png",
|
|
471
|
+
importSource: {
|
|
472
|
+
importer: "figma",
|
|
473
|
+
parameters: source
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
var figmaSourceImporter = {
|
|
478
|
+
kind: "figma",
|
|
479
|
+
label: "Figma",
|
|
480
|
+
validateInput,
|
|
481
|
+
async importRaw(input, context) {
|
|
482
|
+
const parsed = parseFigmaSelectionUrl(input.url);
|
|
483
|
+
return materialize({
|
|
484
|
+
fileKey: parsed.fileKey,
|
|
485
|
+
nodeId: parsed.nodeId,
|
|
486
|
+
format: "png",
|
|
487
|
+
scale: input.scale ?? context.config.import.figma.defaults.scale
|
|
488
|
+
}, context);
|
|
489
|
+
},
|
|
490
|
+
validateSourceParameters: validateFigmaSourceParameters,
|
|
491
|
+
async reimportRaw(source, context) {
|
|
492
|
+
return materialize(source, context);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/lib/server/importers/file.ts
|
|
497
|
+
function validate(value) {
|
|
498
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SourceImportError("invalid-input", "File import input must be an object.");
|
|
499
|
+
const input = value;
|
|
500
|
+
if (typeof input.fileName !== "string" || input.fileName.length === 0) throw new SourceImportError("invalid-input", "File import requires a file name.");
|
|
501
|
+
if (!(input.bytes instanceof Uint8Array)) throw new SourceImportError("invalid-input", "File import requires byte data.");
|
|
502
|
+
return {
|
|
503
|
+
fileName: input.fileName,
|
|
504
|
+
bytes: input.bytes
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
var fileSourceImporter = {
|
|
508
|
+
kind: "file",
|
|
509
|
+
label: "File",
|
|
510
|
+
validateInput: validate,
|
|
511
|
+
async importRaw(input) {
|
|
512
|
+
const extension = extname(input.fileName).slice(1).toLowerCase();
|
|
513
|
+
if (!extension) throw new SourceImportError("invalid-input", "The file needs an extension.");
|
|
514
|
+
return {
|
|
515
|
+
bytes: input.bytes,
|
|
516
|
+
extension,
|
|
517
|
+
suggestedFileName: input.fileName
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
validateSourceParameters() {
|
|
521
|
+
throw new SourceImportError("invalid-input", "File imports cannot be re-imported.");
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/lib/server/importers/registry.ts
|
|
526
|
+
var SourceImporterRegistry = class {
|
|
527
|
+
#byKind = /* @__PURE__ */ new Map();
|
|
528
|
+
constructor(importers) {
|
|
529
|
+
for (const importer of importers) {
|
|
530
|
+
if (!importer.kind) throw new Error("Source importer kind must not be empty.");
|
|
531
|
+
if (this.#byKind.has(importer.kind)) throw new Error(`Duplicate source importer kind "${importer.kind}".`);
|
|
532
|
+
this.#byKind.set(importer.kind, importer);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
require(kind) {
|
|
536
|
+
const importer = this.#byKind.get(kind);
|
|
537
|
+
if (!importer) throw new SourceImportError("invalid-input", `Unknown source importer "${kind}".`);
|
|
538
|
+
return importer;
|
|
539
|
+
}
|
|
540
|
+
has(kind) {
|
|
541
|
+
return this.#byKind.has(kind);
|
|
542
|
+
}
|
|
543
|
+
requireReimport(source) {
|
|
544
|
+
const importer = this.require(source.importer);
|
|
545
|
+
if (!importer.reimportRaw) throw new SourceImportError("invalid-input", `Source importer "${source.importer}" does not support re-import.`);
|
|
546
|
+
return {
|
|
547
|
+
importer,
|
|
548
|
+
parameters: importer.validateSourceParameters(source.parameters)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/lib/server/importers.ts
|
|
554
|
+
var serverImporters = new SourceImporterRegistry([fileSourceImporter, figmaSourceImporter]);
|
|
555
|
+
//#endregion
|
|
119
556
|
//#region src/lib/server/operations.ts
|
|
120
557
|
/**
|
|
121
558
|
* The one operations layer: builds the state payload and runs
|
|
@@ -142,12 +579,14 @@ var FundusOperations = class {
|
|
|
142
579
|
#config;
|
|
143
580
|
#registry;
|
|
144
581
|
#store;
|
|
582
|
+
#importers;
|
|
145
583
|
#operationQueue = Promise.resolve();
|
|
146
584
|
#pipelineQueue = Promise.resolve();
|
|
147
|
-
constructor(config, registry = serverPluginsByType) {
|
|
585
|
+
constructor(config, registry = serverPluginsByType, importerRegistry = serverImporters) {
|
|
148
586
|
this.#config = config;
|
|
149
587
|
this.#registry = registry;
|
|
150
588
|
this.#store = new LibraryStore(config, registry);
|
|
589
|
+
this.#importers = importerRegistry;
|
|
151
590
|
}
|
|
152
591
|
get config() {
|
|
153
592
|
return this.#config;
|
|
@@ -274,6 +713,7 @@ var FundusOperations = class {
|
|
|
274
713
|
record.type = type;
|
|
275
714
|
record.rawHash = edited.hash;
|
|
276
715
|
record.parameters = parameters;
|
|
716
|
+
delete record.importSource;
|
|
277
717
|
return edited.assetId;
|
|
278
718
|
});
|
|
279
719
|
await this.#store.deleteProxiesOf(result.value);
|
|
@@ -290,6 +730,7 @@ var FundusOperations = class {
|
|
|
290
730
|
const record = current.assets[edited.assetId];
|
|
291
731
|
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
732
|
candidate.assets[edited.assetId].rawHash = edited.hash;
|
|
733
|
+
delete candidate.assets[edited.assetId].importSource;
|
|
293
734
|
return edited.assetId;
|
|
294
735
|
});
|
|
295
736
|
await this.#store.deleteProxiesOf(result.value);
|
|
@@ -306,32 +747,130 @@ var FundusOperations = class {
|
|
|
306
747
|
* rolls the whole ingest back, so a failed ingest leaves nothing behind.
|
|
307
748
|
*/
|
|
308
749
|
ingest(options) {
|
|
309
|
-
return this.#enqueueOperation(() =>
|
|
750
|
+
return this.#enqueueOperation(async () => {
|
|
751
|
+
const importer = this.#importers.require("file");
|
|
752
|
+
const input = importer.validateInput({
|
|
753
|
+
fileName: options.fileName,
|
|
754
|
+
bytes: options.bytes
|
|
755
|
+
});
|
|
756
|
+
const raw = await importer.importRaw(input, {
|
|
757
|
+
config: this.#config,
|
|
758
|
+
fetch: globalThis.fetch
|
|
759
|
+
});
|
|
760
|
+
return this.#createImportedAsset(raw, options);
|
|
761
|
+
});
|
|
310
762
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
763
|
+
/** Materialize a source outside the operation queue, then atomically create the asset. */
|
|
764
|
+
async importAsset(options) {
|
|
765
|
+
const preflight = await this.#enqueueOperation(async () => {
|
|
766
|
+
const library = await this.#store.read();
|
|
767
|
+
this.#validateImportTarget(options, library);
|
|
768
|
+
if (Object.hasOwn(library.assets, options.id)) throw new SourceImportError("invalid-input", `An asset with id "${options.id}" already exists.`);
|
|
769
|
+
const importer = this.#importers.require(options.importer);
|
|
770
|
+
return {
|
|
771
|
+
importer,
|
|
772
|
+
input: importer.validateInput(options.input)
|
|
773
|
+
};
|
|
774
|
+
});
|
|
775
|
+
const raw = await preflight.importer.importRaw(preflight.input, {
|
|
776
|
+
config: this.#config,
|
|
777
|
+
fetch: globalThis.fetch
|
|
778
|
+
});
|
|
779
|
+
return this.#enqueueOperation(async () => {
|
|
780
|
+
this.#validateImportedRaw(raw, options.importer);
|
|
781
|
+
return this.#createImportedAsset(raw, options);
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
/** Re-materialize a persisted source with record and on-disk CAS protection. */
|
|
785
|
+
async reimportAsset(id, input) {
|
|
786
|
+
const snapshot = await this.#enqueueOperation(async () => {
|
|
787
|
+
const library = await this.#store.read();
|
|
788
|
+
this.#requireAsset(library, id);
|
|
789
|
+
const record = library.assets[id];
|
|
790
|
+
if (!record.importSource) throw new SourceImportError("invalid-input", `Asset "${id}" has no repeatable import source.`);
|
|
791
|
+
const resolved = this.#importers.requireReimport(record.importSource);
|
|
792
|
+
try {
|
|
793
|
+
await verifyStableRawFile(join(this.#config.rawDir, record.raw), record.rawHash);
|
|
794
|
+
} catch {
|
|
795
|
+
throw new SourceImportError("stale-import", "The local raw file changed. Apply or discard that edit before re-importing.");
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
importer: resolved.importer,
|
|
799
|
+
parameters: resolved.parameters,
|
|
800
|
+
input: input === void 0 ? void 0 : resolved.importer.validateInput(input),
|
|
801
|
+
expected: {
|
|
802
|
+
raw: record.raw,
|
|
803
|
+
rawHash: record.rawHash,
|
|
804
|
+
type: record.type,
|
|
805
|
+
canonicalSource: canonicalizeParameters(record.importSource)
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
const context = {
|
|
810
|
+
config: this.#config,
|
|
811
|
+
fetch: globalThis.fetch
|
|
812
|
+
};
|
|
813
|
+
const raw = snapshot.input === void 0 ? await snapshot.importer.reimportRaw(snapshot.parameters, context) : await snapshot.importer.importRaw(snapshot.input, context);
|
|
814
|
+
if (snapshot.input !== void 0 && !raw.importSource) throw new SourceImportError("invalid-response", "The source importer did not return provenance for the updated source.");
|
|
815
|
+
return this.#enqueueOperation(async () => {
|
|
816
|
+
this.#validateImportedRaw(raw, snapshot.importer.kind);
|
|
817
|
+
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.`);
|
|
818
|
+
let library;
|
|
819
|
+
try {
|
|
820
|
+
library = await this.#store.replaceRawFromImportSource(id, raw.bytes, snapshot.expected, raw.importSource);
|
|
821
|
+
} catch (cause) {
|
|
822
|
+
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.");
|
|
823
|
+
throw cause;
|
|
824
|
+
}
|
|
825
|
+
return this.#afterMutation(library);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
async #createImportedAsset(raw, options) {
|
|
829
|
+
this.#validateImportedRaw(raw);
|
|
830
|
+
const fileName = raw.suggestedFileName ?? `${options.id}.${raw.extension}`;
|
|
831
|
+
const plugin = serverPluginForType(options.type, this.#registry);
|
|
832
|
+
const library = await this.#store.createAsset({
|
|
833
|
+
fileName,
|
|
834
|
+
bytes: raw.bytes,
|
|
835
|
+
type: options.type,
|
|
836
|
+
id: options.id,
|
|
837
|
+
folder: options.folder,
|
|
838
|
+
manifests: options.manifests,
|
|
839
|
+
importSource: raw.importSource
|
|
840
|
+
}, async ({ rawPath, rawFileName, parameters }) => {
|
|
841
|
+
let next = parameters;
|
|
315
842
|
if (plugin.seedIngestParameters) {
|
|
316
|
-
const record = library.assets[options.id];
|
|
317
843
|
const seeded = await plugin.seedIngestParameters({
|
|
318
|
-
rawPath
|
|
319
|
-
rawFileName
|
|
320
|
-
parameters:
|
|
844
|
+
rawPath,
|
|
845
|
+
rawFileName,
|
|
846
|
+
parameters: next,
|
|
321
847
|
config: this.#config
|
|
322
848
|
});
|
|
323
|
-
if (seeded !== void 0)
|
|
849
|
+
if (seeded !== void 0) next = seeded;
|
|
324
850
|
}
|
|
325
|
-
if (options.parametersPatch !== void 0
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
329
|
-
} catch (cause) {
|
|
330
|
-
await this.#store.deleteAsset(options.id);
|
|
331
|
-
throw cause;
|
|
332
|
-
}
|
|
851
|
+
if (options.parametersPatch !== void 0) next = mergeParameterPatch(next, options.parametersPatch);
|
|
852
|
+
return plugin.descriptor.validateParameters(next);
|
|
853
|
+
});
|
|
333
854
|
return this.#afterMutation(library);
|
|
334
855
|
}
|
|
856
|
+
#validateImportTarget(options, library) {
|
|
857
|
+
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.");
|
|
858
|
+
const problem = assetIdError(options.id);
|
|
859
|
+
if (problem) throw new SourceImportError("invalid-input", problem);
|
|
860
|
+
serverPluginForType(options.type, this.#registry);
|
|
861
|
+
if (options.folder !== void 0 && typeof options.folder !== "string") throw new SourceImportError("invalid-input", "Import folder must be a string.");
|
|
862
|
+
const folder = normalizeFolderPath(options.folder ?? "");
|
|
863
|
+
if (folder !== "" && !library.folders.includes(folder)) throw new SourceImportError("invalid-input", `Unknown folder "${folder}".`);
|
|
864
|
+
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.");
|
|
865
|
+
for (const manifest of new Set(options.manifests ?? [])) if (!Object.hasOwn(library.manifests, manifest)) throw new SourceImportError("invalid-input", `Unknown manifest "${manifest}".`);
|
|
866
|
+
}
|
|
867
|
+
#validateImportedRaw(raw, importerKind) {
|
|
868
|
+
if (!(raw.bytes instanceof Uint8Array) || !/^[a-z0-9]+$/.test(raw.extension)) throw new SourceImportError("invalid-response", "The source importer returned invalid raw data.");
|
|
869
|
+
if (raw.importSource) {
|
|
870
|
+
if (importerKind && raw.importSource.importer !== importerKind) throw new SourceImportError("invalid-response", "The source importer returned mismatched provenance.");
|
|
871
|
+
this.#importers.require(raw.importSource.importer).validateSourceParameters(raw.importSource.parameters);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
335
874
|
/** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
|
|
336
875
|
patchAsset(id, body) {
|
|
337
876
|
return this.#enqueueOperation(() => this.#patchAsset(id, body));
|
|
@@ -583,6 +1122,7 @@ var FundusOperations = class {
|
|
|
583
1122
|
const warnings = [];
|
|
584
1123
|
for (const issue of issues) if (issue.assetId === assetId) warnings.push(issue.message);
|
|
585
1124
|
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.");
|
|
1125
|
+
if (record.importSource && !this.#importers.has(record.importSource.importer)) warnings.push(`Importer "${record.importSource.importer}" is unavailable; the local raw file remains usable.`);
|
|
586
1126
|
let proxy = null;
|
|
587
1127
|
if (delivered.has(assetId)) try {
|
|
588
1128
|
const currentProxy = await readCurrentProxy(assetId, record, this.#config, this.#registry);
|
|
@@ -602,6 +1142,7 @@ var FundusOperations = class {
|
|
|
602
1142
|
folder: folderOfRawPath(record.raw),
|
|
603
1143
|
rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}?v=${record.rawHash.slice(0, 12)}`,
|
|
604
1144
|
rawHash: record.rawHash,
|
|
1145
|
+
importSource: record.importSource,
|
|
605
1146
|
parameters: record.parameters,
|
|
606
1147
|
manifests: record.manifests,
|
|
607
1148
|
passiveManifests: passiveManifestsByAsset.get(assetId) ?? [],
|
|
@@ -631,14 +1172,16 @@ var FundusOperations = class {
|
|
|
631
1172
|
}
|
|
632
1173
|
const presetNames = {};
|
|
633
1174
|
for (const [type, plugin] of Object.entries(this.#registry)) presetNames[type] = Object.keys(plugin.presets(this.#config)).sort();
|
|
1175
|
+
const rawIngestionQueue = this.#buildRawIngestionQueue(library, classification);
|
|
634
1176
|
return {
|
|
635
1177
|
library,
|
|
636
1178
|
assets,
|
|
1179
|
+
importDefaults: { figma: { scale: this.#config.import.figma.defaults.scale } },
|
|
637
1180
|
issues,
|
|
638
1181
|
presetNames,
|
|
639
1182
|
manifestMemberships,
|
|
640
1183
|
manifestDeliveredBytes,
|
|
641
|
-
rawIngestionQueue
|
|
1184
|
+
rawIngestionQueue
|
|
642
1185
|
};
|
|
643
1186
|
}
|
|
644
1187
|
#compatibleTypes(path) {
|
|
@@ -800,4 +1343,4 @@ async function createFundusContext() {
|
|
|
800
1343
|
}
|
|
801
1344
|
|
|
802
1345
|
export { getFundusContext as g, initializeFundusContext as i };
|
|
803
|
-
//# sourceMappingURL=context.js-
|
|
1346
|
+
//# sourceMappingURL=context.js-CG2t-ufq.js.map
|