fundus 0.15.0 → 0.17.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 +158 -1
- package/build/client/_app/immutable/assets/2.X44Wyac-.css +1 -0
- package/build/client/_app/immutable/assets/2.X44Wyac-.css.br +0 -0
- package/build/client/_app/immutable/assets/2.X44Wyac-.css.gz +0 -0
- package/build/client/_app/immutable/assets/{3.C6xo25uI.css → 3.66PJT3JC.css} +1 -1
- package/build/client/_app/immutable/assets/3.66PJT3JC.css.br +0 -0
- package/build/client/_app/immutable/assets/3.66PJT3JC.css.gz +0 -0
- package/build/client/_app/immutable/assets/{AssetSourceMenu.IjDdjx3o.css → AssetSourceMenu.Buj2UxUg.css} +1 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{VX-Uq_b9.js → 5fq8ySvR.js} +1 -1
- package/build/client/_app/immutable/chunks/5fq8ySvR.js.br +0 -0
- package/build/client/_app/immutable/chunks/5fq8ySvR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js +1 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js.br +0 -0
- package/build/client/_app/immutable/chunks/BcVZHIBD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js +2 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js.br +0 -0
- package/build/client/_app/immutable/chunks/DXiUbZ9S.js.gz +0 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js +108 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/H5KnxQLQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{-8MKoIIe.js → jEVVu5G9.js} +1 -1
- package/build/client/_app/immutable/chunks/jEVVu5G9.js.br +0 -0
- package/build/client/_app/immutable/chunks/jEVVu5G9.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CJpgt8H5.js → app.Z9n2k33I.js} +2 -2
- package/build/client/_app/immutable/entry/app.Z9n2k33I.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Z9n2k33I.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js +1 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DGo1uhI2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js +1 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.D6OhgXcC.js → 1.rgoSlmPu.js} +1 -1
- package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js +3 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js +1 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.js +2 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.4oRGPYr4.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 +8 -7
- package/build/handler.js.map +1 -1
- package/build/index.js +8 -7
- package/build/index.js.map +1 -1
- package/build/server/chunks/chunks/{context.js-CVuTSPjH.js → context.js-Dh0ibRIF.js} +818 -136
- package/build/server/chunks/chunks/context.js-Dh0ibRIF.js.map +1 -0
- package/build/server/chunks/chunks/{contract.js-itFN0Gon.js → contract.js-_LTMZBo2.js} +156 -58
- package/build/server/chunks/chunks/contract.js-_LTMZBo2.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-CABVXpby.js → core.js-CTyKR4bu.js} +145 -156
- package/build/server/chunks/chunks/core.js-CTyKR4bu.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-VRV1_yEb.js → exports.js-gz2aNXzz.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-VRV1_yEb.js.map → exports.js-gz2aNXzz.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-ChQRRKfZ.js → http.js-BiqUcAuu.js} +3 -2
- package/build/server/chunks/chunks/http.js-BiqUcAuu.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-qXxDe7VN.js → internal.js-DoH0cN5N.js} +5 -3
- package/build/server/chunks/chunks/internal.js-DoH0cN5N.js.map +1 -0
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{manifest-membership.js-BnSECWpJ.js → manifest-membership.js-at1ixQdW.js} +10 -3
- package/build/server/chunks/chunks/manifest-membership.js-at1ixQdW.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation.js-ZEOCk_jA.js → raw-reconciliation.js-CUH8IIJx.js} +3 -3
- package/build/server/chunks/chunks/raw-reconciliation.js-CUH8IIJx.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation2.js-CscuoBC2.js → raw-reconciliation2.js-z4xl5zw2.js} +2 -2
- package/build/server/chunks/chunks/{raw-reconciliation2.js-CscuoBC2.js.map → raw-reconciliation2.js-z4xl5zw2.js.map} +1 -1
- package/build/server/chunks/chunks/{shared.js-DnEOHbgT.js → shared.js-CI-ZkPQn.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-DnEOHbgT.js.map → shared.js-CI-ZkPQn.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-lNH0hKSx.js → utils.js-gXYLxRG9.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-lNH0hKSx.js.map → utils.js-gXYLxRG9.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-z988EDp2.js → _server.ts.js-CdDjuW0r.js} +6 -6
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-z988EDp2.js.map → _server.ts.js-CdDjuW0r.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-C68ru6gS.js → _server.ts.js-OWM1eORG.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-C68ru6gS.js.map → _server.ts.js-OWM1eORG.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-C0tk6RFk.js → _server.ts.js-DoG-7ZpZ.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-C0tk6RFk.js.map → _server.ts.js-DoG-7ZpZ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-I03oZuo8.js → _server.ts.js-CAWjVeGd.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-I03oZuo8.js.map → _server.ts.js-CAWjVeGd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js +38 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BsQB8YDq.js → _server.ts.js-Bo_EsYXJ.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BsQB8YDq.js.map → _server.ts.js-Bo_EsYXJ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-CJuFE6Fb.js → _server.ts.js-DW4w0XAr.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-CJuFE6Fb.js.map → _server.ts.js-DW4w0XAr.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DuOZRRiA.js → _server.ts.js-_MiNPnSy.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DuOZRRiA.js.map → _server.ts.js-_MiNPnSy.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DGzYpQbv.js → _server.ts.js-DwbrQnBc.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DGzYpQbv.js.map → _server.ts.js-DwbrQnBc.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-B045QoCA.js → _server.ts.js-CEnHQf3b.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-B045QoCA.js.map → _server.ts.js-CEnHQf3b.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-HUkVdz19.js → _server.ts.js-BZDuJZtF.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-HUkVdz19.js.map → _server.ts.js-BZDuJZtF.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-ry509mom.js → _server.ts.js-DRgdpKKd.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-ry509mom.js.map → _server.ts.js-DRgdpKKd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DU0J7U25.js → _server.ts.js-CP5QyIhz.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DU0J7U25.js.map → _server.ts.js-CP5QyIhz.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AZIV2PIn.js → _server.ts.js-BKEvt065.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AZIV2PIn.js.map → _server.ts.js-BKEvt065.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CDL0hCfM.js → _server.ts.js-C_zihkDS.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CDL0hCfM.js.map → _server.ts.js-C_zihkDS.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-B13c17fq.js → _server.ts.js-watkBmy6.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-B13c17fq.js.map → _server.ts.js-watkBmy6.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BCxjyAGC.js → _server.ts.js-YtumLIou.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BCxjyAGC.js.map → _server.ts.js-YtumLIou.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BveeMZc2.js → _server.ts.js-Dkouuu-M.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BveeMZc2.js.map → _server.ts.js-Dkouuu-M.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BZjTd9a0.js → _server.ts.js-DwsEQeuf.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BZjTd9a0.js.map → _server.ts.js-DwsEQeuf.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BbTfPvC9.js → _server.ts.js-CdQxfhV3.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BbTfPvC9.js.map → _server.ts.js-CdQxfhV3.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-8VPJMLBk.js → _server.ts.js-DDdMocWj.js} +6 -6
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-8VPJMLBk.js.map → _server.ts.js-DDdMocWj.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BqR2J2sB.js → _server.ts.js-DbzjbH32.js} +9 -7
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BqR2J2sB.js.map → _server.ts.js-DbzjbH32.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-DbA28dCY.js → _server.ts.js-K_JbXZsU.js} +9 -7
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-DbA28dCY.js.map → _server.ts.js-K_JbXZsU.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-9PBc_3Jh.js → hooks.server.js-DgZAVFbQ.js} +8 -6
- package/build/server/chunks/entries/{hooks.server.js-9PBc_3Jh.js.map → hooks.server.js-DgZAVFbQ.js.map} +1 -1
- package/build/server/chunks/{handler-BHt7eOOX.js → handler-CFkKak8t.js} +5 -5
- package/build/server/chunks/{handler-BHt7eOOX.js.map → handler-CFkKak8t.js.map} +1 -1
- package/build/server/chunks/{index.js-DcVaL-Ja.js → index.js-B1IroJH5.js} +5 -5
- package/build/server/chunks/{index.js-DcVaL-Ja.js.map → index.js-B1IroJH5.js.map} +1 -1
- package/build/server/chunks/{manifest.js-DgjSU4wg.js → manifest.js-DNDBsrEU.js} +36 -29
- package/build/server/chunks/manifest.js-DNDBsrEU.js.map +1 -0
- package/build/server/chunks/nodes/{0.js-BgTB-_Zt.js → 0.js-CZ3mZUZl.js} +2 -2
- package/build/server/chunks/nodes/{0.js-BgTB-_Zt.js.map → 0.js-CZ3mZUZl.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-BmDzLKxV.js +7 -0
- package/build/server/chunks/nodes/{1.js-DNabcIPP.js.map → 1.js-BmDzLKxV.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-Dirm-bY-.js +7 -0
- package/build/server/chunks/nodes/2.js-Dirm-bY-.js.map +1 -0
- package/build/server/chunks/nodes/3.js-DOqHmSWt.js +7 -0
- package/build/server/chunks/nodes/3.js-DOqHmSWt.js.map +1 -0
- package/build/server/chunks/nodes/4.js-C8pmF8IP.js +7 -0
- package/build/server/chunks/nodes/{4.js-BE7ZG9vA.js.map → 4.js-C8pmF8IP.js.map} +1 -1
- package/dist/config.d.ts +3 -3
- package/dist/config.js +2 -2
- package/dist/core/api-types.d.ts +24 -3
- package/dist/core/config.d.ts +84 -0
- package/dist/core/config.js +9 -0
- package/dist/core/index.d.ts +10 -5
- package/dist/core/index.js +5 -2
- package/dist/core/ingest.d.ts +1 -6
- package/dist/core/ingest.js +4 -21
- package/dist/core/library-validation.d.ts +13 -0
- package/dist/core/library-validation.js +48 -0
- package/dist/core/library.d.ts +19 -0
- package/dist/core/library.js +4 -4
- package/dist/core/manifest-membership.d.ts +2 -2
- package/dist/core/package-declaration.d.ts +43 -0
- package/dist/core/package-declaration.js +80 -0
- package/dist/core/package-merge.d.ts +65 -0
- package/dist/core/package-merge.js +173 -0
- package/dist/core/propose-asset-id.d.ts +10 -0
- package/dist/core/propose-asset-id.js +25 -0
- package/dist/core/reference-graph.d.ts +3 -3
- package/dist/server/build.js +26 -11
- package/dist/server/check.d.ts +13 -3
- package/dist/server/check.js +112 -24
- package/dist/server/cli-commands.d.ts +2 -2
- package/dist/server/cli-commands.js +213 -29
- package/dist/server/codegen.d.ts +3 -1
- package/dist/server/codegen.js +18 -0
- package/dist/server/config-loader.d.ts +36 -1
- package/dist/server/config-loader.js +190 -4
- package/dist/server/http.js +1 -0
- package/dist/server/importers/figma.d.ts +26 -3
- package/dist/server/importers/figma.js +114 -1
- package/dist/server/library-store.d.ts +20 -1
- package/dist/server/library-store.js +121 -60
- package/dist/server/operations.d.ts +12 -0
- package/dist/server/operations.js +283 -48
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/2.C-1MfvLQ.css +0 -1
- package/build/client/_app/immutable/assets/2.C-1MfvLQ.css.br +0 -0
- package/build/client/_app/immutable/assets/2.C-1MfvLQ.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.C6xo25uI.css.br +0 -0
- package/build/client/_app/immutable/assets/3.C6xo25uI.css.gz +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.gz +0 -0
- package/build/client/_app/immutable/chunks/-8MKoIIe.js.br +0 -0
- package/build/client/_app/immutable/chunks/-8MKoIIe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D1wFzWdi.js +0 -108
- package/build/client/_app/immutable/chunks/D1wFzWdi.js.br +0 -0
- package/build/client/_app/immutable/chunks/D1wFzWdi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DmQb0SN8.js +0 -2
- package/build/client/_app/immutable/chunks/DmQb0SN8.js.br +0 -0
- package/build/client/_app/immutable/chunks/DmQb0SN8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js +0 -1
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js.br +0 -0
- package/build/client/_app/immutable/chunks/N1X6Qzcl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/VX-Uq_b9.js.br +0 -1
- package/build/client/_app/immutable/chunks/VX-Uq_b9.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CJpgt8H5.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CJpgt8H5.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DToPJitz.js +0 -1
- package/build/client/_app/immutable/entry/start.DToPJitz.js.br +0 -1
- package/build/client/_app/immutable/entry/start.DToPJitz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js +0 -1
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.br +0 -3
- package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js +0 -3
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js +0 -1
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DDt73kMC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js +0 -2
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-CVuTSPjH.js.map +0 -1
- package/build/server/chunks/chunks/contract.js-itFN0Gon.js.map +0 -1
- package/build/server/chunks/chunks/core.js-CABVXpby.js.map +0 -1
- package/build/server/chunks/chunks/http.js-ChQRRKfZ.js.map +0 -1
- package/build/server/chunks/chunks/internal.js-qXxDe7VN.js.map +0 -1
- package/build/server/chunks/chunks/manifest-membership.js-BnSECWpJ.js.map +0 -1
- package/build/server/chunks/chunks/raw-reconciliation.js-ZEOCk_jA.js.map +0 -1
- package/build/server/chunks/manifest.js-DgjSU4wg.js.map +0 -1
- package/build/server/chunks/nodes/1.js-DNabcIPP.js +0 -7
- package/build/server/chunks/nodes/2.js-CcVBvLSR.js +0 -7
- package/build/server/chunks/nodes/2.js-CcVBvLSR.js.map +0 -1
- package/build/server/chunks/nodes/3.js-DreG5rig.js +0 -7
- package/build/server/chunks/nodes/3.js-DreG5rig.js.map +0 -1
- package/build/server/chunks/nodes/4.js-BE7ZG9vA.js +0 -7
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { c as compareCodeUnits, h as folderOfRawPath, j as rawFileNameOfPath, g as canonicalizeParameters, n as normalizeFolderPath, H as typesForFileName,
|
|
2
|
-
import { s as serverPluginsByType, i as isRecord, L as LibraryStore, d as descriptorsByType, m as manifestModuleFileName, l as locateGeneratedAssetDeclaration, a as serializeLibrary, b as serverPluginForType, S as SourceImportError, p as processAssets, g as generateManifestModules, r as readCurrentProxy, e as encodeRelativePathForUrl, c as invalidInput,
|
|
3
|
-
import { c as collectAllReferences, a as assetIdError, b as computeLibraryMembership, d as allDeliveredAssetIds } from './manifest-membership.js-
|
|
4
|
-
import { v as verifyStableRawFile, c as classifyRawDirectory } from './raw-reconciliation2.js-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { c as compareCodeUnits, h as folderOfRawPath, j as rawFileNameOfPath, g as canonicalizeParameters, n as normalizeFolderPath, H as typesForFileName, k as parentFolderPath, u as portableFolderPathKey, B as isAssetReference } from './core.js-CTyKR4bu.js';
|
|
2
|
+
import { s as serverPluginsByType, i as isRecord, L as LibraryStore, d as descriptorsByType, m as manifestModuleFileName, l as locateGeneratedAssetDeclaration, a as serializeLibrary, b as serverPluginForType, S as SourceImportError, p as processAssets, g as generateManifestModules, r as readCurrentProxy, e as encodeRelativePathForUrl, f as flatLibraryIssues, c as manifestModuleCollisionIssues, h as invalidInput, j as parseLibrary, k as isRecord$1, n as assertSafeKey, o as parseManifestRecord, q as parseImportSource } from './contract.js-_LTMZBo2.js';
|
|
3
|
+
import { c as collectAllReferences, a as assetIdError, b as computeLibraryMembership, d as allDeliveredAssetIds, i as isValidAssetId } from './manifest-membership.js-at1ixQdW.js';
|
|
4
|
+
import { v as verifyStableRawFile, c as classifyRawDirectory } from './raw-reconciliation2.js-z4xl5zw2.js';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { isAbsolute, resolve, join, dirname, extname, relative, sep } from 'node:path';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { readFile, realpath } from 'node:fs/promises';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { createJiti } from 'jiti';
|
|
9
11
|
import { execFile } from 'node:child_process';
|
|
10
12
|
import { isDeepStrictEqual, promisify } from 'node:util';
|
|
11
13
|
import sharp from 'sharp';
|
|
@@ -14,6 +16,242 @@ import { lookup } from 'node:dns/promises';
|
|
|
14
16
|
import { isIP } from 'node:net';
|
|
15
17
|
import { request } from 'node:https';
|
|
16
18
|
|
|
19
|
+
//#region src/lib/core/package-declaration.ts
|
|
20
|
+
function parsePackageAsset(origin, id, value) {
|
|
21
|
+
if (!isRecord$1(value)) throw new Error(`Package "${origin}" asset "${id}" must be an object.`);
|
|
22
|
+
if (typeof value.type !== "string" || value.type.length === 0) throw new Error(`Package "${origin}" asset "${id}" is missing a string "type".`);
|
|
23
|
+
if ("raw" in value || "rawHash" in value) throw new Error(`Package "${origin}" asset "${id}" declares "raw"/"rawHash"; Packages ship byte-less slots only.`);
|
|
24
|
+
if (!Array.isArray(value.manifests) || value.manifests.some((entry) => typeof entry !== "string")) throw new Error(`Package "${origin}" asset "${id}" must have a "manifests" array of strings.`);
|
|
25
|
+
const record = {
|
|
26
|
+
type: value.type,
|
|
27
|
+
parameters: "parameters" in value ? value.parameters : {},
|
|
28
|
+
manifests: value.manifests
|
|
29
|
+
};
|
|
30
|
+
if (value.importSource !== void 0) record.importSource = parseImportSource(id, value.importSource);
|
|
31
|
+
return record;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse and structurally validate a `fundus.package.json` (JSON text). Delegates
|
|
35
|
+
* to {@link validatePackageDeclaration} after `JSON.parse`.
|
|
36
|
+
*/
|
|
37
|
+
function parsePackageDeclaration(json, origin) {
|
|
38
|
+
let value;
|
|
39
|
+
try {
|
|
40
|
+
value = JSON.parse(json);
|
|
41
|
+
} catch (cause) {
|
|
42
|
+
throw new Error(`Package "${origin}" declaration is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
43
|
+
}
|
|
44
|
+
return validatePackageDeclaration(value, origin);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Structurally validate an already-parsed package declaration — the shared core of
|
|
48
|
+
* both the JSON path ({@link parsePackageDeclaration}) and the TypeScript-module path
|
|
49
|
+
* (a `fundus.package.ts` default export loaded via jiti). Throws with a clear,
|
|
50
|
+
* origin-tagged message on any malformed part. Plugin parameter validation and
|
|
51
|
+
* id/manifest-name validity are separate steps that run over the merged
|
|
52
|
+
* (effective) library, not here — mirroring `parseLibrary`. `version` defaults to 1.
|
|
53
|
+
*/
|
|
54
|
+
function validatePackageDeclaration(value, origin) {
|
|
55
|
+
if (!isRecord$1(value)) throw new Error(`Package "${origin}" declaration must be an object.`);
|
|
56
|
+
if (value.version !== void 0 && value.version !== 1) throw new Error(`Package "${origin}" declaration has unsupported version ${JSON.stringify(value.version)} (expected 1).`);
|
|
57
|
+
if (!isRecord$1(value.assets)) throw new Error(`Package "${origin}" declaration "assets" must be an object.`);
|
|
58
|
+
if (value.manifests !== void 0 && !isRecord$1(value.manifests)) throw new Error(`Package "${origin}" declaration "manifests" must be an object.`);
|
|
59
|
+
const assets = {};
|
|
60
|
+
for (const [id, record] of Object.entries(value.assets)) {
|
|
61
|
+
assertSafeKey("asset", id);
|
|
62
|
+
assets[id] = parsePackageAsset(origin, id, record);
|
|
63
|
+
}
|
|
64
|
+
const manifests = {};
|
|
65
|
+
if (isRecord$1(value.manifests)) for (const [name, record] of Object.entries(value.manifests)) {
|
|
66
|
+
assertSafeKey("manifest", name);
|
|
67
|
+
if (!isRecord$1(record)) throw new Error(`Package "${origin}" manifest "${name}" must be an object.`);
|
|
68
|
+
manifests[name] = parseManifestRecord(name, record);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
version: 1,
|
|
72
|
+
assets,
|
|
73
|
+
manifests
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/lib/core/package-merge.ts
|
|
78
|
+
/**
|
|
79
|
+
* The pure Package merge: fold package-shipped partial libraries **under** the
|
|
80
|
+
* host library into one in-memory *effective* library. Host wins per-field
|
|
81
|
+
* (deep). No disk, no config — testable like `resolveConfig`. The effective
|
|
82
|
+
* library is never serialized (a host `Library` always carries `raw`/`rawHash`);
|
|
83
|
+
* `materializeLibrary` drops raw-less inert slots to hand a plain `Library` to
|
|
84
|
+
* processing/codegen once the slot/validation gates have passed.
|
|
85
|
+
*/
|
|
86
|
+
function sortDedupManifests(names) {
|
|
87
|
+
return [...new Set(names)].sort(compareCodeUnits);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Deep-merge plugin `parameters` — overlay (host) wins. Recurses into plain
|
|
91
|
+
* objects only; arrays are replaced wholesale (host-replace) and `{ $asset }`
|
|
92
|
+
* reference markers are treated as atomic (never merged field-by-field). Applied
|
|
93
|
+
* to `parameters` only — the record-level `manifests` array is unioned by the
|
|
94
|
+
* caller, never routed through here.
|
|
95
|
+
*/
|
|
96
|
+
function deepMergeParameters(base, overlay) {
|
|
97
|
+
if (isRecord$1(base) && isRecord$1(overlay) && !isAssetReference(base) && !isAssetReference(overlay)) {
|
|
98
|
+
const result = { ...base };
|
|
99
|
+
for (const [key, value] of Object.entries(overlay)) result[key] = key in base ? deepMergeParameters(base[key], value) : value;
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
return overlay;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Merge `packages` under `host` (host wins, per-field deep). Returns the merged
|
|
106
|
+
* effective library, per-asset provenance, and merge findings. `host` is an
|
|
107
|
+
* `EffectiveLibrary` so a raw-less host override branch stays reachable/testable
|
|
108
|
+
* and v2-ready; a plain `Library` is assignable.
|
|
109
|
+
*/
|
|
110
|
+
function mergePackages(host, packages) {
|
|
111
|
+
const findings = [];
|
|
112
|
+
const pkgAssets = /* @__PURE__ */ new Map();
|
|
113
|
+
const pkgManifests = /* @__PURE__ */ new Map();
|
|
114
|
+
for (const { origin, declaration } of packages) {
|
|
115
|
+
for (const [id, decl] of Object.entries(declaration.assets)) {
|
|
116
|
+
const existing = pkgAssets.get(id);
|
|
117
|
+
if (existing) {
|
|
118
|
+
findings.push({
|
|
119
|
+
kind: "package-conflict",
|
|
120
|
+
assetId: id,
|
|
121
|
+
origins: [existing.origin, origin],
|
|
122
|
+
message: `Asset "${id}" is declared by two Packages ("${existing.origin}" and "${origin}"); Package ids must be unique.`
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
pkgAssets.set(id, {
|
|
127
|
+
origin,
|
|
128
|
+
decl
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
for (const [name, record] of Object.entries(declaration.manifests)) if (!pkgManifests.has(name)) pkgManifests.set(name, record);
|
|
132
|
+
}
|
|
133
|
+
const assets = {};
|
|
134
|
+
const provenance = {};
|
|
135
|
+
for (const [id, hostRecord] of Object.entries(host.assets)) {
|
|
136
|
+
const pkg = pkgAssets.get(id);
|
|
137
|
+
if (!pkg) {
|
|
138
|
+
assets[id] = { ...hostRecord };
|
|
139
|
+
provenance[id] = { source: "host" };
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (pkg.decl.type !== hostRecord.type) {
|
|
143
|
+
findings.push({
|
|
144
|
+
kind: "package-type-mismatch",
|
|
145
|
+
assetId: id,
|
|
146
|
+
origin: pkg.origin,
|
|
147
|
+
message: `Asset "${id}" overrides Package "${pkg.origin}" but changes its type from "${pkg.decl.type}" to "${hostRecord.type}"; changing the type is a different asset, not an override.`
|
|
148
|
+
});
|
|
149
|
+
assets[id] = { ...hostRecord };
|
|
150
|
+
provenance[id] = {
|
|
151
|
+
source: "override",
|
|
152
|
+
origin: pkg.origin
|
|
153
|
+
};
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const merged = {
|
|
157
|
+
type: hostRecord.type,
|
|
158
|
+
parameters: deepMergeParameters(pkg.decl.parameters, hostRecord.parameters),
|
|
159
|
+
manifests: sortDedupManifests([...pkg.decl.manifests, ...hostRecord.manifests])
|
|
160
|
+
};
|
|
161
|
+
if (hostRecord.raw !== void 0 || hostRecord.importSource !== void 0) {
|
|
162
|
+
if (hostRecord.raw !== void 0) merged.raw = hostRecord.raw;
|
|
163
|
+
if (hostRecord.rawHash !== void 0) merged.rawHash = hostRecord.rawHash;
|
|
164
|
+
if (hostRecord.importSource !== void 0) merged.importSource = hostRecord.importSource;
|
|
165
|
+
} else if (pkg.decl.importSource !== void 0) merged.importSource = pkg.decl.importSource;
|
|
166
|
+
assets[id] = merged;
|
|
167
|
+
provenance[id] = {
|
|
168
|
+
source: "override",
|
|
169
|
+
origin: pkg.origin
|
|
170
|
+
};
|
|
171
|
+
findings.push({
|
|
172
|
+
kind: "package-overridden",
|
|
173
|
+
assetId: id,
|
|
174
|
+
origin: pkg.origin,
|
|
175
|
+
message: `Asset "${id}" from Package "${pkg.origin}" is overridden by the host library.`
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
for (const [id, { origin, decl }] of pkgAssets) {
|
|
179
|
+
if (Object.hasOwn(host.assets, id)) continue;
|
|
180
|
+
const record = {
|
|
181
|
+
type: decl.type,
|
|
182
|
+
parameters: decl.parameters,
|
|
183
|
+
manifests: sortDedupManifests(decl.manifests)
|
|
184
|
+
};
|
|
185
|
+
if (decl.importSource !== void 0) record.importSource = decl.importSource;
|
|
186
|
+
assets[id] = record;
|
|
187
|
+
provenance[id] = {
|
|
188
|
+
source: "package",
|
|
189
|
+
origin
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const manifests = {};
|
|
193
|
+
for (const [name, record] of pkgManifests) manifests[name] = { ...record };
|
|
194
|
+
for (const [name, record] of Object.entries(host.manifests)) manifests[name] = {
|
|
195
|
+
...manifests[name],
|
|
196
|
+
...record
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
library: {
|
|
200
|
+
version: Object.values(assets).some((record) => record.importSource !== void 0) ? 2 : host.version,
|
|
201
|
+
folders: host.folders,
|
|
202
|
+
assets,
|
|
203
|
+
manifests
|
|
204
|
+
},
|
|
205
|
+
provenance,
|
|
206
|
+
findings
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Drop raw-less (unfilled) slots from an effective library, yielding a plain
|
|
211
|
+
* `Library` for processing/codegen. Safe only after the slot/validation gates
|
|
212
|
+
* pass — every remaining delivered asset then has `raw`; dropped slots are
|
|
213
|
+
* provably inert (not delivered).
|
|
214
|
+
*/
|
|
215
|
+
function materializeLibrary(effective) {
|
|
216
|
+
const assets = {};
|
|
217
|
+
for (const [id, record] of Object.entries(effective.assets)) {
|
|
218
|
+
if (record.raw === void 0 || record.rawHash === void 0) continue;
|
|
219
|
+
const materialized = {
|
|
220
|
+
type: record.type,
|
|
221
|
+
raw: record.raw,
|
|
222
|
+
rawHash: record.rawHash,
|
|
223
|
+
parameters: record.parameters,
|
|
224
|
+
manifests: record.manifests
|
|
225
|
+
};
|
|
226
|
+
if (record.importSource !== void 0) materialized.importSource = record.importSource;
|
|
227
|
+
assets[id] = materialized;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
version: effective.version,
|
|
231
|
+
folders: effective.folders,
|
|
232
|
+
assets,
|
|
233
|
+
manifests: effective.manifests
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/lib/core/propose-asset-id.ts
|
|
238
|
+
/**
|
|
239
|
+
* Propose a camelCase asset id from a file (or node) name: split on
|
|
240
|
+
* non-alphanumeric runs and case boundaries, drop a `@2x`-style scale suffix,
|
|
241
|
+
* lowercase the first word, capitalize the rest, strip leading digits.
|
|
242
|
+
*
|
|
243
|
+
* Pure and dependency-free so it can be shared everywhere — including the Figma
|
|
244
|
+
* plugin bundle, which must not pull in the plugin registry that `ingest.ts`
|
|
245
|
+
* imports.
|
|
246
|
+
*/
|
|
247
|
+
function proposeAssetId(fileName) {
|
|
248
|
+
const words = fileName.replace(/\.[^.]+$/, "").replace(/(?:@|[-_])\dx$/i, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
249
|
+
if (words.length === 0) return "asset";
|
|
250
|
+
const withoutLeadingDigits = words.map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/^[0-9]+/, "");
|
|
251
|
+
if (!withoutLeadingDigits) return "asset";
|
|
252
|
+
return withoutLeadingDigits.charAt(0).toLowerCase() + withoutLeadingDigits.slice(1);
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
17
255
|
//#region src/lib/server/config-loader.ts
|
|
18
256
|
/**
|
|
19
257
|
* Loads and resolves the host's `fundus.config.ts` through jiti, validates
|
|
@@ -37,6 +275,70 @@ function configDefinitionsFor(registry) {
|
|
|
37
275
|
}
|
|
38
276
|
return [...byNamespace.values()];
|
|
39
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* A string `packages` entry is a *path* when it looks like one (starts with
|
|
280
|
+
* `.`/`/`, is absolute, or ends `.json`), or a bare relative path with a
|
|
281
|
+
* separator; otherwise it is a package *name*. A scoped name (`@scope/pkg`)
|
|
282
|
+
* contains a `/` but is a name.
|
|
283
|
+
*/
|
|
284
|
+
function classifyPackageSpec(spec) {
|
|
285
|
+
if (spec.startsWith(".") || spec.startsWith("/") || isAbsolute(spec)) return "path";
|
|
286
|
+
if (DECLARATION_FILE_RE.test(spec)) return "path";
|
|
287
|
+
if (spec.startsWith("@")) return "name";
|
|
288
|
+
if (spec.includes("/") || spec.includes("\\")) return "path";
|
|
289
|
+
return "name";
|
|
290
|
+
}
|
|
291
|
+
/** A declaration file is JSON or a JS/TS module (the latter loaded via jiti). */
|
|
292
|
+
var DECLARATION_FILE_RE = /\.(json|[mc]?[jt]s)$/;
|
|
293
|
+
/**
|
|
294
|
+
* Read a package declaration from its resolved file: a JSON `fundus.package.json`
|
|
295
|
+
* (parsed as text) or a JS/TS module (`fundus.package.ts`) whose default export is
|
|
296
|
+
* the declaration — loaded through the same jiti path as `fundus.config.ts`, which
|
|
297
|
+
* keeps the resource `type`s literal for the package's `PackageAssets<…>` derivation.
|
|
298
|
+
*/
|
|
299
|
+
async function loadPackageDeclaration(file, origin) {
|
|
300
|
+
if (/\.[mc]?[jt]s$/.test(file)) {
|
|
301
|
+
let module;
|
|
302
|
+
try {
|
|
303
|
+
module = await createJiti(import.meta.url, { interopDefault: false }).import(file);
|
|
304
|
+
} catch (cause) {
|
|
305
|
+
throw new Error(`Cannot load fundus package "${origin}" (${file}): ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
306
|
+
}
|
|
307
|
+
return validatePackageDeclaration(isRecord(module) && "default" in module ? module.default : module, origin);
|
|
308
|
+
}
|
|
309
|
+
let text;
|
|
310
|
+
try {
|
|
311
|
+
text = await readFile(file, "utf8");
|
|
312
|
+
} catch (cause) {
|
|
313
|
+
throw new Error(`Cannot read fundus package "${origin}" (${file}): ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
314
|
+
}
|
|
315
|
+
return parsePackageDeclaration(text, origin);
|
|
316
|
+
}
|
|
317
|
+
function normalizePackageSource(projectRoot, entry) {
|
|
318
|
+
let spec;
|
|
319
|
+
let rawDirOverride;
|
|
320
|
+
if (typeof entry === "string") spec = entry;
|
|
321
|
+
else if (isRecord(entry) && typeof entry.package === "string" && entry.package.length > 0) {
|
|
322
|
+
spec = entry.package;
|
|
323
|
+
if (entry.rawDir !== void 0) {
|
|
324
|
+
if (typeof entry.rawDir !== "string" || entry.rawDir.length === 0) throw new Error("fundus.config: a \"packages\" entry \"rawDir\" must be a non-empty string.");
|
|
325
|
+
rawDirOverride = entry.rawDir;
|
|
326
|
+
}
|
|
327
|
+
} else throw new Error("fundus.config: each \"packages\" entry must be a package name, a path string, or { package: string; rawDir?: string }.");
|
|
328
|
+
const kind = classifyPackageSpec(spec);
|
|
329
|
+
const reference = kind === "path" ? resolve(projectRoot, spec) : spec;
|
|
330
|
+
return {
|
|
331
|
+
origin: spec,
|
|
332
|
+
kind,
|
|
333
|
+
reference,
|
|
334
|
+
...rawDirOverride !== void 0 ? { rawDirOverride } : {}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function resolvePackageSources(projectRoot, value) {
|
|
338
|
+
if (value === void 0) return [];
|
|
339
|
+
if (!Array.isArray(value)) throw new Error("fundus.config: \"packages\" must be an array.");
|
|
340
|
+
return value.map((entry) => normalizePackageSource(projectRoot, entry));
|
|
341
|
+
}
|
|
40
342
|
/** Pure resolution step (testable without a config file on disk). */
|
|
41
343
|
function resolveConfig(projectRoot, value, registry = serverPluginsByType) {
|
|
42
344
|
if (!isRecord(value)) throw new Error("fundus.config must default-export a configuration object.");
|
|
@@ -54,6 +356,7 @@ function resolveConfig(projectRoot, value, registry = serverPluginsByType) {
|
|
|
54
356
|
const figma = importConfig.figma === void 0 ? {} : importConfig.figma;
|
|
55
357
|
if (!isRecord(figma)) throw new Error("fundus.config: \"import.figma\" must be an object.");
|
|
56
358
|
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.");
|
|
359
|
+
if (figma.defaultFileKey !== void 0 && (typeof figma.defaultFileKey !== "string" || figma.defaultFileKey.length === 0)) throw new Error("fundus.config: \"import.figma.defaultFileKey\" must be a non-empty string.");
|
|
57
360
|
const defaults = figma.defaults === void 0 ? {} : figma.defaults;
|
|
58
361
|
if (!isRecord(defaults)) throw new Error("fundus.config: \"import.figma.defaults\" must be an object.");
|
|
59
362
|
const scale = defaults.scale === void 0 ? 2 : defaults.scale;
|
|
@@ -72,11 +375,91 @@ function resolveConfig(projectRoot, value, registry = serverPluginsByType) {
|
|
|
72
375
|
plugins: resolvedPlugins,
|
|
73
376
|
import: { figma: {
|
|
74
377
|
...figma.token !== void 0 ? { token: figma.token } : {},
|
|
378
|
+
...figma.defaultFileKey !== void 0 ? { defaultFileKey: figma.defaultFileKey } : {},
|
|
75
379
|
defaults: { scale }
|
|
76
380
|
} },
|
|
77
|
-
usage: usage === void 0 ? null : usage
|
|
381
|
+
usage: usage === void 0 ? null : usage,
|
|
382
|
+
packageSources: resolvePackageSources(projectRoot, value.packages),
|
|
383
|
+
packages: []
|
|
78
384
|
};
|
|
79
385
|
}
|
|
386
|
+
function nearestNamedPackageJson(entryPath, name) {
|
|
387
|
+
let dir = dirname(entryPath);
|
|
388
|
+
for (;;) {
|
|
389
|
+
const candidate = join(dir, "package.json");
|
|
390
|
+
if (existsSync(candidate)) try {
|
|
391
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
392
|
+
if (isRecord(pkg) && pkg.name === name) return {
|
|
393
|
+
dir,
|
|
394
|
+
pkg
|
|
395
|
+
};
|
|
396
|
+
} catch {}
|
|
397
|
+
const parent = dirname(dir);
|
|
398
|
+
if (parent === dir) break;
|
|
399
|
+
dir = parent;
|
|
400
|
+
}
|
|
401
|
+
throw new Error(`Cannot find the package.json for fundus package "${name}" (resolved to ${entryPath}).`);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Resolve a name-form Package to its root directory + parsed `package.json`. A
|
|
405
|
+
* declaration-only Package ships no JS entry module, so resolving the bare name
|
|
406
|
+
* would throw `MODULE_NOT_FOUND`; resolve its `package.json` subpath first and
|
|
407
|
+
* only fall back to entry resolution when an `exports` map hides it.
|
|
408
|
+
*/
|
|
409
|
+
function resolvePackageRoot(require, reference, origin) {
|
|
410
|
+
try {
|
|
411
|
+
const manifestPath = require.resolve(`${reference}/package.json`);
|
|
412
|
+
const pkg = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
413
|
+
if (isRecord(pkg)) return {
|
|
414
|
+
dir: dirname(manifestPath),
|
|
415
|
+
pkg
|
|
416
|
+
};
|
|
417
|
+
} catch {}
|
|
418
|
+
let entry;
|
|
419
|
+
try {
|
|
420
|
+
entry = require.resolve(reference);
|
|
421
|
+
} catch {
|
|
422
|
+
throw new Error(`Cannot resolve fundus package "${origin}": no installed package declares it — is it a dependency?`);
|
|
423
|
+
}
|
|
424
|
+
return nearestNamedPackageJson(entry, reference);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Read and parse every declared Package from disk. Name entries resolve through
|
|
428
|
+
* the host's module resolver (respecting `exports`), then walk up to the nearest
|
|
429
|
+
* `package.json` that declares the name and read its `"fundus"` field. Path
|
|
430
|
+
* entries point directly at a `fundus.package.json`. Throws a clear,
|
|
431
|
+
* origin-tagged error on any resolution/read/parse failure — Package assembly
|
|
432
|
+
* failures are symmetric with a missing `fundus.config.ts`.
|
|
433
|
+
*/
|
|
434
|
+
async function loadPackages(projectRoot, sources) {
|
|
435
|
+
const require = createRequire(pathToFileURL(join(projectRoot, "fundus.config.ts")).href);
|
|
436
|
+
const packages = [];
|
|
437
|
+
for (const source of sources) {
|
|
438
|
+
let file;
|
|
439
|
+
let defaultRawDir;
|
|
440
|
+
if (source.kind === "path") {
|
|
441
|
+
file = source.reference;
|
|
442
|
+
if (!existsSync(file)) throw new Error(`Fundus package file not found: ${file} (from "${source.origin}").`);
|
|
443
|
+
defaultRawDir = dirname(file);
|
|
444
|
+
} else {
|
|
445
|
+
const { dir, pkg } = resolvePackageRoot(require, source.reference, source.origin);
|
|
446
|
+
const fundusField = pkg.fundus;
|
|
447
|
+
if (!isRecord(fundusField) || typeof fundusField.package !== "string" || fundusField.package.length === 0) throw new Error(`Fundus package "${source.origin}" is missing a "fundus.package" field in its package.json.`);
|
|
448
|
+
file = resolve(dir, fundusField.package);
|
|
449
|
+
if (!existsSync(file)) throw new Error(`Fundus package "${source.origin}" declaration not found: ${file}.`);
|
|
450
|
+
defaultRawDir = typeof fundusField.rawDir === "string" && fundusField.rawDir.length > 0 ? resolve(dir, fundusField.rawDir) : dir;
|
|
451
|
+
}
|
|
452
|
+
const declaration = await loadPackageDeclaration(file, source.origin);
|
|
453
|
+
const rawDir = source.rawDirOverride !== void 0 ? resolve(projectRoot, source.rawDirOverride) : defaultRawDir;
|
|
454
|
+
packages.push({
|
|
455
|
+
origin: source.origin,
|
|
456
|
+
file,
|
|
457
|
+
rawDir,
|
|
458
|
+
declaration
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return packages;
|
|
462
|
+
}
|
|
80
463
|
/** Load `fundus.config.ts` from the host project root. */
|
|
81
464
|
async function loadConfig(projectRoot) {
|
|
82
465
|
const root = resolve(projectRoot);
|
|
@@ -88,7 +471,152 @@ async function loadConfig(projectRoot) {
|
|
|
88
471
|
} catch (cause) {
|
|
89
472
|
throw new Error(`Cannot load fundus.config.ts: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
90
473
|
}
|
|
91
|
-
|
|
474
|
+
const config = resolveConfig(root, isRecord(module) && "default" in module ? module.default : module);
|
|
475
|
+
return {
|
|
476
|
+
...config,
|
|
477
|
+
packages: await loadPackages(root, config.packageSources)
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function imageFingerprint(filePath, hashSize) {
|
|
481
|
+
const width = hashSize + 1;
|
|
482
|
+
const pixels = await sharp(filePath).rotate().trim({
|
|
483
|
+
background: {
|
|
484
|
+
r: 0,
|
|
485
|
+
g: 0,
|
|
486
|
+
b: 0,
|
|
487
|
+
alpha: 0
|
|
488
|
+
},
|
|
489
|
+
threshold: 0
|
|
490
|
+
}).resize({
|
|
491
|
+
width,
|
|
492
|
+
height: hashSize,
|
|
493
|
+
fit: "fill"
|
|
494
|
+
}).ensureAlpha().toColourspace("srgb").raw().toBuffer();
|
|
495
|
+
const hash = new Uint8Array(Math.ceil(hashSize * hashSize / 8));
|
|
496
|
+
const alphaHash = new Uint8Array(hash.length);
|
|
497
|
+
const colorTotals = [
|
|
498
|
+
0,
|
|
499
|
+
0,
|
|
500
|
+
0
|
|
501
|
+
];
|
|
502
|
+
let alphaTotal = 0;
|
|
503
|
+
let bit = 0;
|
|
504
|
+
for (let row = 0; row < hashSize; row++) for (let column = 0; column < hashSize; column++) {
|
|
505
|
+
const leftOffset = (row * width + column) * 4;
|
|
506
|
+
const rightOffset = leftOffset + 4;
|
|
507
|
+
const leftAlpha = pixels[leftOffset + 3] / 255;
|
|
508
|
+
const rightAlpha = pixels[rightOffset + 3] / 255;
|
|
509
|
+
if (visibleLuminance(pixels, leftOffset, leftAlpha) < visibleLuminance(pixels, rightOffset, rightAlpha)) hash[bit >> 3] |= 1 << 7 - (bit & 7);
|
|
510
|
+
if (leftAlpha < rightAlpha) alphaHash[bit >> 3] |= 1 << 7 - (bit & 7);
|
|
511
|
+
for (let channel = 0; channel < 3; channel++) colorTotals[channel] += pixels[leftOffset + channel] * leftAlpha;
|
|
512
|
+
alphaTotal += leftAlpha;
|
|
513
|
+
bit += 1;
|
|
514
|
+
}
|
|
515
|
+
const pixelCount = hashSize * hashSize;
|
|
516
|
+
return {
|
|
517
|
+
hash,
|
|
518
|
+
alphaHash,
|
|
519
|
+
meanColor: colorTotals.map((total) => alphaTotal === 0 ? 0 : total / alphaTotal),
|
|
520
|
+
meanAlpha: alphaTotal / pixelCount
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function visibleLuminance(pixels, offset, alpha) {
|
|
524
|
+
return (pixels[offset] * .2126 + pixels[offset + 1] * .7152 + pixels[offset + 2] * .0722) * alpha + 127 * (1 - alpha);
|
|
525
|
+
}
|
|
526
|
+
function colorDistance(left, right) {
|
|
527
|
+
return Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]);
|
|
528
|
+
}
|
|
529
|
+
/** Count differing bits between two equally sized perceptual hashes. */
|
|
530
|
+
function hammingDistance(left, right) {
|
|
531
|
+
if (left.length !== right.length) throw new Error("Image hashes must have the same length.");
|
|
532
|
+
let distance = 0;
|
|
533
|
+
for (let index = 0; index < left.length; index++) {
|
|
534
|
+
let value = left[index] ^ right[index];
|
|
535
|
+
while (value !== 0) {
|
|
536
|
+
value &= value - 1;
|
|
537
|
+
distance += 1;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return distance;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Stateful so the editor can reuse fingerprints by content hash between state
|
|
544
|
+
* snapshots. Unreadable raws are omitted: duplicate detection is advisory and
|
|
545
|
+
* must not hide the editor or replace the pipeline's canonical diagnostics.
|
|
546
|
+
*/
|
|
547
|
+
var ImageDuplicateDetector = class {
|
|
548
|
+
#fingerprints = /* @__PURE__ */ new Map();
|
|
549
|
+
async find(library, rawDir) {
|
|
550
|
+
const images = Object.entries(library.assets).filter(([, record]) => record.type === "image").sort(([left], [right]) => compareCodeUnits(left, right));
|
|
551
|
+
const activeHashes = new Set(images.map(([, record]) => record.rawHash));
|
|
552
|
+
for (const hash of this.#fingerprints.keys()) if (!activeHashes.has(hash)) this.#fingerprints.delete(hash);
|
|
553
|
+
const readable = [];
|
|
554
|
+
for (const [id, record] of images) {
|
|
555
|
+
let fingerprint = this.#fingerprints.get(record.rawHash);
|
|
556
|
+
if (!fingerprint) {
|
|
557
|
+
fingerprint = imageFingerprint(join(rawDir, record.raw), 8);
|
|
558
|
+
this.#fingerprints.set(record.rawHash, fingerprint);
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
readable.push({
|
|
562
|
+
id,
|
|
563
|
+
fingerprint: await fingerprint
|
|
564
|
+
});
|
|
565
|
+
} catch {
|
|
566
|
+
this.#fingerprints.delete(record.rawHash);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const duplicates = /* @__PURE__ */ new Map();
|
|
570
|
+
for (let leftIndex = 0; leftIndex < readable.length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < readable.length; rightIndex++) {
|
|
571
|
+
const left = readable[leftIndex];
|
|
572
|
+
const right = readable[rightIndex];
|
|
573
|
+
if (hammingDistance(left.fingerprint.hash, right.fingerprint.hash) > 5 || hammingDistance(left.fingerprint.alphaHash, right.fingerprint.alphaHash) > 5 || Math.abs(left.fingerprint.meanAlpha - right.fingerprint.meanAlpha) > .125 || colorDistance(left.fingerprint.meanColor, right.fingerprint.meanColor) > 32) continue;
|
|
574
|
+
const leftDuplicates = duplicates.get(left.id) ?? [];
|
|
575
|
+
leftDuplicates.push(right.id);
|
|
576
|
+
duplicates.set(left.id, leftDuplicates);
|
|
577
|
+
const rightDuplicates = duplicates.get(right.id) ?? [];
|
|
578
|
+
rightDuplicates.push(left.id);
|
|
579
|
+
duplicates.set(right.id, rightDuplicates);
|
|
580
|
+
}
|
|
581
|
+
return duplicates;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/lib/server/check.ts
|
|
586
|
+
/**
|
|
587
|
+
* The delivered-slot contract: a Package slot that is delivered (explicit member
|
|
588
|
+
* or in a delivered manifest's reference closure) but has no host-supplied bytes
|
|
589
|
+
* must be filled. A slot with an `importSource` is `slot-unmaterialized` (run
|
|
590
|
+
* import); a slot with nothing is `slot-unfilled`. Inert (undelivered) slots
|
|
591
|
+
* produce no finding.
|
|
592
|
+
*/
|
|
593
|
+
function packageSlotFindings(library, provenance, membership) {
|
|
594
|
+
const deliveringManifests = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const [name, manifestMembership] of Object.entries(membership)) for (const assetId of manifestMembership.deliveredAssetIds) {
|
|
596
|
+
const names = deliveringManifests.get(assetId) ?? [];
|
|
597
|
+
names.push(name);
|
|
598
|
+
deliveringManifests.set(assetId, names);
|
|
599
|
+
}
|
|
600
|
+
const findings = [];
|
|
601
|
+
for (const [assetId, record] of Object.entries(library.assets)) {
|
|
602
|
+
if (record.raw !== void 0) continue;
|
|
603
|
+
const names = deliveringManifests.get(assetId);
|
|
604
|
+
if (!names || names.length === 0) continue;
|
|
605
|
+
const originRecord = provenance[assetId];
|
|
606
|
+
const origin = originRecord && originRecord.source !== "host" ? originRecord.origin : "a package";
|
|
607
|
+
const deliveredBy = [...names].sort(compareCodeUnits).map((name) => `"${name}"`).join(", ");
|
|
608
|
+
if (record.importSource !== void 0) findings.push({
|
|
609
|
+
kind: "slot-unmaterialized",
|
|
610
|
+
assetId,
|
|
611
|
+
message: `Slot "${assetId}" (from ${origin}) has an import source but no bytes yet — run import to fill it. Delivered by: ${deliveredBy}.`
|
|
612
|
+
});
|
|
613
|
+
else findings.push({
|
|
614
|
+
kind: "slot-unfilled",
|
|
615
|
+
assetId,
|
|
616
|
+
message: `Slot "${assetId}" (from ${origin}) has no bytes, import source, or host override. Delivered by: ${deliveredBy}.`
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return findings;
|
|
92
620
|
}
|
|
93
621
|
//#endregion
|
|
94
622
|
//#region src/lib/server/raw-safety.ts
|
|
@@ -253,9 +781,22 @@ function validateScale(value) {
|
|
|
253
781
|
}
|
|
254
782
|
function validateInput(value) {
|
|
255
783
|
const input = record(value);
|
|
256
|
-
if (!input
|
|
784
|
+
if (!input) return invalidInput("Figma import requires a URL.");
|
|
257
785
|
if (input.format !== void 0 && input.format !== "png") return invalidInput("Figma import currently supports PNG only.");
|
|
786
|
+
if (input.mode === "tag") {
|
|
787
|
+
if (typeof input.tag !== "string" || !isValidAssetId(input.tag)) return invalidInput("Figma tag import requires a valid Fundus asset id as the tag.");
|
|
788
|
+
if (input.fileKey !== void 0 && (typeof input.fileKey !== "string" || input.fileKey.length === 0)) return invalidInput("Figma tag import fileKey must be a non-empty string.");
|
|
789
|
+
return {
|
|
790
|
+
mode: "tag",
|
|
791
|
+
tag: input.tag,
|
|
792
|
+
format: "png",
|
|
793
|
+
...input.fileKey !== void 0 ? { fileKey: input.fileKey } : {},
|
|
794
|
+
...input.scale !== void 0 ? { scale: validateScale(input.scale) } : {}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
if (typeof input.url !== "string") return invalidInput("Figma import requires a URL.");
|
|
258
798
|
return {
|
|
799
|
+
mode: "node",
|
|
259
800
|
url: input.url,
|
|
260
801
|
format: "png",
|
|
261
802
|
...input.scale !== void 0 ? { scale: validateScale(input.scale) } : {}
|
|
@@ -264,6 +805,17 @@ function validateInput(value) {
|
|
|
264
805
|
function validateFigmaSourceParameters(value) {
|
|
265
806
|
const source = record(value);
|
|
266
807
|
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.");
|
|
808
|
+
if (source.mode === "tag") {
|
|
809
|
+
if (typeof source.fundusTag !== "string" || !isValidAssetId(source.fundusTag)) return invalidInput("Stored Figma source parameters are invalid.");
|
|
810
|
+
return {
|
|
811
|
+
mode: "tag",
|
|
812
|
+
fileKey: source.fileKey,
|
|
813
|
+
fundusTag: source.fundusTag,
|
|
814
|
+
nodeId: source.nodeId,
|
|
815
|
+
format: "png",
|
|
816
|
+
scale: validateScale(source.scale)
|
|
817
|
+
};
|
|
818
|
+
}
|
|
267
819
|
return {
|
|
268
820
|
fileKey: source.fileKey,
|
|
269
821
|
nodeId: source.nodeId,
|
|
@@ -455,6 +1007,37 @@ async function validatePng(bytes) {
|
|
|
455
1007
|
if (!metadata.width || !metadata.height) throw new SourceImportError("invalid-response", "Figma export has invalid dimensions.");
|
|
456
1008
|
if (metadata.width * metadata.height > MAX_PIXELS) throw new SourceImportError("image-too-large", "Figma export exceeds the 32 megapixel limit.");
|
|
457
1009
|
}
|
|
1010
|
+
function collectTaggedNodeIds(node, tag, matches) {
|
|
1011
|
+
const current = record(node);
|
|
1012
|
+
if (!current) return;
|
|
1013
|
+
const shared = record(current.sharedPluginData);
|
|
1014
|
+
const fundus = shared ? record(shared.fundus) : null;
|
|
1015
|
+
if (fundus && fundus.assetId === tag && typeof current.id === "string") matches.push(current.id);
|
|
1016
|
+
if (Array.isArray(current.children)) for (const child of current.children) collectTaggedNodeIds(child, tag, matches);
|
|
1017
|
+
}
|
|
1018
|
+
async function resolveTagToNode(fileKey, tag, context) {
|
|
1019
|
+
const token = context.config.import.figma.token;
|
|
1020
|
+
if (!token) throw new SourceImportError("not-configured", "Figma access is not configured.");
|
|
1021
|
+
const endpoint = new URL(`https://api.figma.com/v1/files/${encodeURIComponent(fileKey)}`);
|
|
1022
|
+
endpoint.searchParams.set("plugin_data", "shared");
|
|
1023
|
+
const jsonBytes = await fetchWithDeadline(context.fetch, endpoint, {
|
|
1024
|
+
redirect: "error",
|
|
1025
|
+
headers: { "X-Figma-Token": token }
|
|
1026
|
+
}, async (response) => new Uint8Array(await response.arrayBuffer()));
|
|
1027
|
+
let payload;
|
|
1028
|
+
try {
|
|
1029
|
+
payload = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
1030
|
+
} catch {
|
|
1031
|
+
throw new SourceImportError("invalid-response", "Figma returned an invalid response.");
|
|
1032
|
+
}
|
|
1033
|
+
const body = record(payload);
|
|
1034
|
+
if (!body || body.err || typeof body.status === "number" && body.status >= 400) throw new SourceImportError("invalid-response", "Figma returned an invalid response.");
|
|
1035
|
+
const matches = [];
|
|
1036
|
+
collectTaggedNodeIds(body.document, tag, matches);
|
|
1037
|
+
if (matches.length === 0) throw new SourceImportError("not-found", `No Figma node is tagged "${tag}" in this file.`);
|
|
1038
|
+
if (matches.length > 1) throw new SourceImportError("ambiguous-tag", `More than one Figma node is tagged "${tag}" in this file.`);
|
|
1039
|
+
return matches[0];
|
|
1040
|
+
}
|
|
458
1041
|
async function materialize(source, context) {
|
|
459
1042
|
const token = context.config.import.figma.token;
|
|
460
1043
|
if (!token) throw new SourceImportError("not-configured", "Figma access is not configured.");
|
|
@@ -494,6 +1077,19 @@ var figmaSourceImporter = {
|
|
|
494
1077
|
label: "Figma",
|
|
495
1078
|
validateInput,
|
|
496
1079
|
async importRaw(input, context) {
|
|
1080
|
+
if (input.mode === "tag") {
|
|
1081
|
+
const fileKey = input.fileKey ?? context.config.import.figma.defaultFileKey;
|
|
1082
|
+
if (!fileKey) throw new SourceImportError("not-configured", "Figma tag import needs a fileKey or import.figma.defaultFileKey in fundus.config.");
|
|
1083
|
+
const nodeId = await resolveTagToNode(fileKey, input.tag, context);
|
|
1084
|
+
return materialize({
|
|
1085
|
+
mode: "tag",
|
|
1086
|
+
fileKey,
|
|
1087
|
+
fundusTag: input.tag,
|
|
1088
|
+
nodeId,
|
|
1089
|
+
format: "png",
|
|
1090
|
+
scale: input.scale ?? context.config.import.figma.defaults.scale
|
|
1091
|
+
}, context);
|
|
1092
|
+
}
|
|
497
1093
|
const parsed = parseFigmaSelectionUrl(input.url);
|
|
498
1094
|
return materialize({
|
|
499
1095
|
fileKey: parsed.fileKey,
|
|
@@ -504,6 +1100,13 @@ var figmaSourceImporter = {
|
|
|
504
1100
|
},
|
|
505
1101
|
validateSourceParameters: validateFigmaSourceParameters,
|
|
506
1102
|
async reimportRaw(source, context) {
|
|
1103
|
+
if (source.mode === "tag") {
|
|
1104
|
+
const nodeId = await resolveTagToNode(source.fileKey, source.fundusTag, context);
|
|
1105
|
+
return materialize({
|
|
1106
|
+
...source,
|
|
1107
|
+
nodeId
|
|
1108
|
+
}, context);
|
|
1109
|
+
}
|
|
507
1110
|
return materialize(source, context);
|
|
508
1111
|
}
|
|
509
1112
|
};
|
|
@@ -1075,110 +1678,6 @@ function pathsFromRoot(root, target, referencesByAsset, countMemo = /* @__PURE__
|
|
|
1075
1678
|
function usageSnapshotRevision(parts) {
|
|
1076
1679
|
return createHash("sha256").update(parts.join("\0")).digest("hex");
|
|
1077
1680
|
}
|
|
1078
|
-
async function imageFingerprint(filePath, hashSize) {
|
|
1079
|
-
const width = hashSize + 1;
|
|
1080
|
-
const pixels = await sharp(filePath).rotate().trim({
|
|
1081
|
-
background: {
|
|
1082
|
-
r: 0,
|
|
1083
|
-
g: 0,
|
|
1084
|
-
b: 0,
|
|
1085
|
-
alpha: 0
|
|
1086
|
-
},
|
|
1087
|
-
threshold: 0
|
|
1088
|
-
}).resize({
|
|
1089
|
-
width,
|
|
1090
|
-
height: hashSize,
|
|
1091
|
-
fit: "fill"
|
|
1092
|
-
}).ensureAlpha().toColourspace("srgb").raw().toBuffer();
|
|
1093
|
-
const hash = new Uint8Array(Math.ceil(hashSize * hashSize / 8));
|
|
1094
|
-
const alphaHash = new Uint8Array(hash.length);
|
|
1095
|
-
const colorTotals = [
|
|
1096
|
-
0,
|
|
1097
|
-
0,
|
|
1098
|
-
0
|
|
1099
|
-
];
|
|
1100
|
-
let alphaTotal = 0;
|
|
1101
|
-
let bit = 0;
|
|
1102
|
-
for (let row = 0; row < hashSize; row++) for (let column = 0; column < hashSize; column++) {
|
|
1103
|
-
const leftOffset = (row * width + column) * 4;
|
|
1104
|
-
const rightOffset = leftOffset + 4;
|
|
1105
|
-
const leftAlpha = pixels[leftOffset + 3] / 255;
|
|
1106
|
-
const rightAlpha = pixels[rightOffset + 3] / 255;
|
|
1107
|
-
if (visibleLuminance(pixels, leftOffset, leftAlpha) < visibleLuminance(pixels, rightOffset, rightAlpha)) hash[bit >> 3] |= 1 << 7 - (bit & 7);
|
|
1108
|
-
if (leftAlpha < rightAlpha) alphaHash[bit >> 3] |= 1 << 7 - (bit & 7);
|
|
1109
|
-
for (let channel = 0; channel < 3; channel++) colorTotals[channel] += pixels[leftOffset + channel] * leftAlpha;
|
|
1110
|
-
alphaTotal += leftAlpha;
|
|
1111
|
-
bit += 1;
|
|
1112
|
-
}
|
|
1113
|
-
const pixelCount = hashSize * hashSize;
|
|
1114
|
-
return {
|
|
1115
|
-
hash,
|
|
1116
|
-
alphaHash,
|
|
1117
|
-
meanColor: colorTotals.map((total) => alphaTotal === 0 ? 0 : total / alphaTotal),
|
|
1118
|
-
meanAlpha: alphaTotal / pixelCount
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
function visibleLuminance(pixels, offset, alpha) {
|
|
1122
|
-
return (pixels[offset] * .2126 + pixels[offset + 1] * .7152 + pixels[offset + 2] * .0722) * alpha + 127 * (1 - alpha);
|
|
1123
|
-
}
|
|
1124
|
-
function colorDistance(left, right) {
|
|
1125
|
-
return Math.hypot(left[0] - right[0], left[1] - right[1], left[2] - right[2]);
|
|
1126
|
-
}
|
|
1127
|
-
/** Count differing bits between two equally sized perceptual hashes. */
|
|
1128
|
-
function hammingDistance(left, right) {
|
|
1129
|
-
if (left.length !== right.length) throw new Error("Image hashes must have the same length.");
|
|
1130
|
-
let distance = 0;
|
|
1131
|
-
for (let index = 0; index < left.length; index++) {
|
|
1132
|
-
let value = left[index] ^ right[index];
|
|
1133
|
-
while (value !== 0) {
|
|
1134
|
-
value &= value - 1;
|
|
1135
|
-
distance += 1;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
return distance;
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Stateful so the editor can reuse fingerprints by content hash between state
|
|
1142
|
-
* snapshots. Unreadable raws are omitted: duplicate detection is advisory and
|
|
1143
|
-
* must not hide the editor or replace the pipeline's canonical diagnostics.
|
|
1144
|
-
*/
|
|
1145
|
-
var ImageDuplicateDetector = class {
|
|
1146
|
-
#fingerprints = /* @__PURE__ */ new Map();
|
|
1147
|
-
async find(library, rawDir) {
|
|
1148
|
-
const images = Object.entries(library.assets).filter(([, record]) => record.type === "image").sort(([left], [right]) => compareCodeUnits(left, right));
|
|
1149
|
-
const activeHashes = new Set(images.map(([, record]) => record.rawHash));
|
|
1150
|
-
for (const hash of this.#fingerprints.keys()) if (!activeHashes.has(hash)) this.#fingerprints.delete(hash);
|
|
1151
|
-
const readable = [];
|
|
1152
|
-
for (const [id, record] of images) {
|
|
1153
|
-
let fingerprint = this.#fingerprints.get(record.rawHash);
|
|
1154
|
-
if (!fingerprint) {
|
|
1155
|
-
fingerprint = imageFingerprint(join(rawDir, record.raw), 8);
|
|
1156
|
-
this.#fingerprints.set(record.rawHash, fingerprint);
|
|
1157
|
-
}
|
|
1158
|
-
try {
|
|
1159
|
-
readable.push({
|
|
1160
|
-
id,
|
|
1161
|
-
fingerprint: await fingerprint
|
|
1162
|
-
});
|
|
1163
|
-
} catch {
|
|
1164
|
-
this.#fingerprints.delete(record.rawHash);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
const duplicates = /* @__PURE__ */ new Map();
|
|
1168
|
-
for (let leftIndex = 0; leftIndex < readable.length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < readable.length; rightIndex++) {
|
|
1169
|
-
const left = readable[leftIndex];
|
|
1170
|
-
const right = readable[rightIndex];
|
|
1171
|
-
if (hammingDistance(left.fingerprint.hash, right.fingerprint.hash) > 5 || hammingDistance(left.fingerprint.alphaHash, right.fingerprint.alphaHash) > 5 || Math.abs(left.fingerprint.meanAlpha - right.fingerprint.meanAlpha) > .125 || colorDistance(left.fingerprint.meanColor, right.fingerprint.meanColor) > 32) continue;
|
|
1172
|
-
const leftDuplicates = duplicates.get(left.id) ?? [];
|
|
1173
|
-
leftDuplicates.push(right.id);
|
|
1174
|
-
duplicates.set(left.id, leftDuplicates);
|
|
1175
|
-
const rightDuplicates = duplicates.get(right.id) ?? [];
|
|
1176
|
-
rightDuplicates.push(left.id);
|
|
1177
|
-
duplicates.set(right.id, rightDuplicates);
|
|
1178
|
-
}
|
|
1179
|
-
return duplicates;
|
|
1180
|
-
}
|
|
1181
|
-
};
|
|
1182
1681
|
//#endregion
|
|
1183
1682
|
//#region src/lib/server/operations.ts
|
|
1184
1683
|
/**
|
|
@@ -1206,6 +1705,17 @@ function figmaSlicePixelRatio(raw, type) {
|
|
|
1206
1705
|
if (type !== "slice" || raw.importSource?.importer !== "figma") return void 0;
|
|
1207
1706
|
return validateFigmaSourceParameters(raw.importSource.parameters).scale;
|
|
1208
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* The Fundus tag of a Figma tag-mode import source, else null. In tag mode the
|
|
1710
|
+
* tag IS the asset id, so this is the single seam that ties provenance to
|
|
1711
|
+
* identity — used to assert id equality on import/reimport and to forbid
|
|
1712
|
+
* renaming a tag-referenced asset. Only Figma sources are inspected.
|
|
1713
|
+
*/
|
|
1714
|
+
function figmaTagOf(importSource) {
|
|
1715
|
+
if (importSource?.importer !== "figma") return null;
|
|
1716
|
+
const params = validateFigmaSourceParameters(importSource.parameters);
|
|
1717
|
+
return params.mode === "tag" ? params.fundusTag : null;
|
|
1718
|
+
}
|
|
1209
1719
|
var FundusOperations = class {
|
|
1210
1720
|
#config;
|
|
1211
1721
|
#registry;
|
|
@@ -1477,6 +1987,8 @@ var FundusOperations = class {
|
|
|
1477
1987
|
});
|
|
1478
1988
|
return this.#enqueueOperation(async () => {
|
|
1479
1989
|
this.#validateImportedRaw(raw, options.importer);
|
|
1990
|
+
const tag = figmaTagOf(raw.importSource);
|
|
1991
|
+
if (tag !== null && tag !== options.id) throw new SourceImportError("invalid-input", `A Figma tag import must use its tag as the asset id: the tag is "${tag}", not "${options.id}".`);
|
|
1480
1992
|
return this.#createImportedAsset(raw, options);
|
|
1481
1993
|
});
|
|
1482
1994
|
}
|
|
@@ -1513,6 +2025,8 @@ var FundusOperations = class {
|
|
|
1513
2025
|
if (snapshot.input !== void 0 && !raw.importSource) throw new SourceImportError("invalid-response", "The source importer did not return provenance for the updated source.");
|
|
1514
2026
|
return this.#enqueueOperation(async () => {
|
|
1515
2027
|
this.#validateImportedRaw(raw, snapshot.importer.kind);
|
|
2028
|
+
const tag = figmaTagOf(raw.importSource);
|
|
2029
|
+
if (tag !== null && tag !== id) throw new SourceImportError("invalid-input", `A Figma tag reimport may not retarget asset "${id}" to a different tag "${tag}".`);
|
|
1516
2030
|
const plugin = serverPluginForType(snapshot.expected.type, this.#registry);
|
|
1517
2031
|
if (!plugin.descriptor.accepts.includes(raw.extension)) throw new SourceImportError("invalid-response", `The ${snapshot.expected.type} type does not accept ".${raw.extension}" files.`);
|
|
1518
2032
|
const pixelRatio = snapshot.input === void 0 ? void 0 : figmaSlicePixelRatio(raw, snapshot.expected.type);
|
|
@@ -1559,6 +2073,8 @@ var FundusOperations = class {
|
|
|
1559
2073
|
const importSource = raw.importSource;
|
|
1560
2074
|
return this.#enqueueOperation(async () => {
|
|
1561
2075
|
this.#validateImportedRaw(raw, preflight.importer.kind);
|
|
2076
|
+
const tag = figmaTagOf(importSource);
|
|
2077
|
+
if (tag !== null && tag !== id) throw new SourceImportError("invalid-input", `A Figma tag replacement must use its own tag as the asset id: the tag is "${tag}", not "${id}".`);
|
|
1562
2078
|
const plugin = serverPluginForType(preflight.expected.type, this.#registry);
|
|
1563
2079
|
if (!plugin.descriptor.accepts.includes(raw.extension)) throw new SourceImportError("invalid-response", `The ${preflight.expected.type} type does not accept ".${raw.extension}" files.`);
|
|
1564
2080
|
const previousName = rawFileNameOfPath(preflight.expected.raw);
|
|
@@ -1580,11 +2096,101 @@ var FundusOperations = class {
|
|
|
1580
2096
|
}
|
|
1581
2097
|
});
|
|
1582
2098
|
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Edit a Figma tag asset's tag, fileKey, and/or scale in place. Because in tag
|
|
2101
|
+
* mode the tag IS the asset id, changing the tag renames the asset (moves the
|
|
2102
|
+
* library key); changing only fileKey/scale is an in-place re-point. Mirrors
|
|
2103
|
+
* `reimportAsset`'s three phases: preflight in the queue, materialize outside
|
|
2104
|
+
* it, then commit atomically through `store.repointTagAsset`.
|
|
2105
|
+
*/
|
|
2106
|
+
async retagFigmaAsset(id, input) {
|
|
2107
|
+
const snapshot = await this.#enqueueOperation(async () => {
|
|
2108
|
+
const library = await this.#store.read();
|
|
2109
|
+
this.#requireAsset(library, id);
|
|
2110
|
+
const record = library.assets[id];
|
|
2111
|
+
const currentTag = figmaTagOf(record.importSource);
|
|
2112
|
+
if (currentTag === null || !record.importSource) throw new SourceImportError("invalid-input", `Asset "${id}" is not a Figma tag import.`);
|
|
2113
|
+
const current = validateFigmaSourceParameters(record.importSource.parameters);
|
|
2114
|
+
const newTag = input.tag ?? currentTag;
|
|
2115
|
+
const fileKey = input.fileKey ?? current.fileKey;
|
|
2116
|
+
const scale = input.scale ?? current.scale;
|
|
2117
|
+
if (newTag !== id) {
|
|
2118
|
+
const problem = assetIdError(newTag);
|
|
2119
|
+
if (problem) throw new SourceImportError("invalid-input", problem);
|
|
2120
|
+
if (Object.hasOwn(library.assets, newTag)) throw new SourceImportError("invalid-input", `An asset with id "${newTag}" already exists.`);
|
|
2121
|
+
const candidate = structuredClone(library);
|
|
2122
|
+
candidate.assets[newTag] = candidate.assets[id];
|
|
2123
|
+
delete candidate.assets[id];
|
|
2124
|
+
await this.#assertNoIntroducedIssues(library, candidate);
|
|
2125
|
+
}
|
|
2126
|
+
try {
|
|
2127
|
+
await verifyStableRawFile(join(this.#config.rawDir, record.raw), record.rawHash);
|
|
2128
|
+
} catch {
|
|
2129
|
+
throw new SourceImportError("stale-import", "The local raw file changed. Apply or discard that edit before retagging.");
|
|
2130
|
+
}
|
|
2131
|
+
const importer = this.#importers.require("figma");
|
|
2132
|
+
return {
|
|
2133
|
+
importer,
|
|
2134
|
+
input: importer.validateInput({
|
|
2135
|
+
mode: "tag",
|
|
2136
|
+
tag: newTag,
|
|
2137
|
+
fileKey,
|
|
2138
|
+
scale,
|
|
2139
|
+
format: "png"
|
|
2140
|
+
}),
|
|
2141
|
+
newTag,
|
|
2142
|
+
expected: {
|
|
2143
|
+
raw: record.raw,
|
|
2144
|
+
rawHash: record.rawHash,
|
|
2145
|
+
type: record.type,
|
|
2146
|
+
canonicalSource: canonicalizeParameters(record.importSource)
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
});
|
|
2150
|
+
const raw = await snapshot.importer.importRaw(snapshot.input, {
|
|
2151
|
+
config: this.#config,
|
|
2152
|
+
fetch: globalThis.fetch
|
|
2153
|
+
});
|
|
2154
|
+
if (!raw.importSource) throw new SourceImportError("invalid-response", "The Figma importer did not return provenance for the retagged source.");
|
|
2155
|
+
const importSource = raw.importSource;
|
|
2156
|
+
return this.#enqueueOperation(async () => {
|
|
2157
|
+
this.#validateImportedRaw(raw, snapshot.importer.kind);
|
|
2158
|
+
if (figmaTagOf(importSource) !== snapshot.newTag) throw new SourceImportError("invalid-response", `The Figma importer resolved a different tag than "${snapshot.newTag}".`);
|
|
2159
|
+
const plugin = serverPluginForType(snapshot.expected.type, this.#registry);
|
|
2160
|
+
if (!plugin.descriptor.accepts.includes(raw.extension)) throw new SourceImportError("invalid-response", `The ${snapshot.expected.type} type does not accept ".${raw.extension}" files.`);
|
|
2161
|
+
const pixelRatio = figmaSlicePixelRatio(raw, snapshot.expected.type);
|
|
2162
|
+
let library;
|
|
2163
|
+
try {
|
|
2164
|
+
library = await this.#store.repointTagAsset(id, snapshot.newTag, raw.bytes, snapshot.expected, importSource, pixelRatio === void 0 ? void 0 : (parameters) => plugin.descriptor.validateParameters(mergeParameterPatch(parameters, { pixelRatio })));
|
|
2165
|
+
} catch (cause) {
|
|
2166
|
+
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 changed.");
|
|
2167
|
+
throw cause;
|
|
2168
|
+
}
|
|
2169
|
+
return this.#afterMutation(library);
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
1583
2172
|
async #createImportedAsset(raw, options) {
|
|
1584
2173
|
this.#validateImportedRaw(raw);
|
|
1585
2174
|
const fileName = raw.suggestedFileName ?? `${options.id}.${raw.extension}`;
|
|
1586
2175
|
const plugin = serverPluginForType(options.type, this.#registry);
|
|
1587
|
-
const
|
|
2176
|
+
const packageBase = this.#packageBase(await this.#store.read(), options.id);
|
|
2177
|
+
if (packageBase && packageBase.type !== options.type) throw new SourceImportError("invalid-input", `Filling slot "${options.id}" requires the package type "${packageBase.type}", not "${options.type}".`);
|
|
2178
|
+
let slotOverride = {};
|
|
2179
|
+
if (packageBase) {
|
|
2180
|
+
const pixelRatio = figmaSlicePixelRatio(raw, options.type);
|
|
2181
|
+
if (pixelRatio !== void 0) slotOverride = mergeParameterPatch(slotOverride, { pixelRatio });
|
|
2182
|
+
if (options.parametersPatch !== void 0) slotOverride = mergeParameterPatch(slotOverride, options.parametersPatch);
|
|
2183
|
+
}
|
|
2184
|
+
const library = packageBase ? await this.#store.createAsset({
|
|
2185
|
+
fileName,
|
|
2186
|
+
bytes: raw.bytes,
|
|
2187
|
+
type: options.type,
|
|
2188
|
+
id: options.id,
|
|
2189
|
+
folder: options.folder,
|
|
2190
|
+
manifests: options.manifests,
|
|
2191
|
+
importSource: raw.importSource,
|
|
2192
|
+
parametersOverride: slotOverride
|
|
2193
|
+
}) : await this.#store.createAsset({
|
|
1588
2194
|
fileName,
|
|
1589
2195
|
bytes: raw.bytes,
|
|
1590
2196
|
type: options.type,
|
|
@@ -1619,7 +2225,8 @@ var FundusOperations = class {
|
|
|
1619
2225
|
const folder = normalizeFolderPath(options.folder ?? "");
|
|
1620
2226
|
if (folder !== "" && !library.folders.includes(folder)) throw new SourceImportError("invalid-input", `Unknown folder "${folder}".`);
|
|
1621
2227
|
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.");
|
|
1622
|
-
|
|
2228
|
+
const validManifests = this.#effectiveOf(library).effective.manifests;
|
|
2229
|
+
for (const manifest of new Set(options.manifests ?? [])) if (!Object.hasOwn(validManifests, manifest)) throw new SourceImportError("invalid-input", `Unknown manifest "${manifest}".`);
|
|
1623
2230
|
}
|
|
1624
2231
|
#validateImportedRaw(raw, importerKind) {
|
|
1625
2232
|
if (!(raw.bytes instanceof Uint8Array) || !/^[a-z0-9]+$/.test(raw.extension)) throw new SourceImportError("invalid-response", "The source importer returned invalid raw data.");
|
|
@@ -1634,7 +2241,7 @@ var FundusOperations = class {
|
|
|
1634
2241
|
}
|
|
1635
2242
|
async #patchAsset(id, body) {
|
|
1636
2243
|
let library = await this.#store.read();
|
|
1637
|
-
this.#
|
|
2244
|
+
this.#assertHostAsset(library, id, "editing it");
|
|
1638
2245
|
if (body.parameters !== void 0) library = await this.#store.updateParameters(id, body.parameters);
|
|
1639
2246
|
if (body.manifests !== void 0) {
|
|
1640
2247
|
if (!Array.isArray(body.manifests) || body.manifests.some((name) => typeof name !== "string")) throw new Error("\"manifests\" must be an array of manifest names.");
|
|
@@ -1742,6 +2349,7 @@ var FundusOperations = class {
|
|
|
1742
2349
|
async #renameAsset(id, newId) {
|
|
1743
2350
|
const library = await this.#store.read();
|
|
1744
2351
|
this.#requireAsset(library, id);
|
|
2352
|
+
if (figmaTagOf(library.assets[id].importSource) !== null) throw new SourceImportError("invalid-input", "This asset is referenced by a Figma tag and cannot be renamed; re-import with a new tag instead.");
|
|
1745
2353
|
if (newId !== id && !Object.hasOwn(library.assets, newId)) {
|
|
1746
2354
|
const candidate = structuredClone(library);
|
|
1747
2355
|
candidate.assets[newId] = candidate.assets[id];
|
|
@@ -1835,11 +2443,13 @@ var FundusOperations = class {
|
|
|
1835
2443
|
async #runAfterMutation(library) {
|
|
1836
2444
|
const pipelineIssues = [];
|
|
1837
2445
|
const classification = await this.#classifyRawDirectory(library);
|
|
1838
|
-
const
|
|
2446
|
+
const { effective, provenance, mergeFindings } = this.#effectiveOf(library);
|
|
2447
|
+
const materialized = materializeLibrary(effective);
|
|
2448
|
+
const ordinaryIssues = [...await this.#store.validate(library, { flatSpace: false }), ...this.#flatIssues(effective, provenance, mergeFindings)];
|
|
1839
2449
|
const safetyIssues = this.#rawSafetyIssues(library, classification);
|
|
1840
2450
|
if (ordinaryIssues.length === 0 && safetyIssues.length === 0) try {
|
|
1841
|
-
const report = await processAssets(this.#config,
|
|
1842
|
-
await generateManifestModules(this.#config,
|
|
2451
|
+
const report = await processAssets(this.#config, materialized, this.#registry);
|
|
2452
|
+
await generateManifestModules(this.#config, materialized, report.proxies, this.#registry);
|
|
1843
2453
|
} catch (cause) {
|
|
1844
2454
|
pipelineIssues.push({ message: `Processing failed: ${cause instanceof Error ? cause.message : String(cause)}` });
|
|
1845
2455
|
}
|
|
@@ -1851,10 +2461,10 @@ var FundusOperations = class {
|
|
|
1851
2461
|
state: await this.#snapshotState(latestLibrary, latestClassification, pipelineIssues)
|
|
1852
2462
|
};
|
|
1853
2463
|
}
|
|
1854
|
-
async #buildState(library, issues, classification) {
|
|
1855
|
-
const [gitBranch, changedGitAssets] = await Promise.all([currentGitBranch(this.#config.projectRoot), gitAssetChanges(this.#config.projectRoot, this.#config.library,
|
|
2464
|
+
async #buildState(host, library, provenance, issues, classification) {
|
|
2465
|
+
const [gitBranch, changedGitAssets] = await Promise.all([currentGitBranch(this.#config.projectRoot), gitAssetChanges(this.#config.projectRoot, this.#config.library, host)]);
|
|
1856
2466
|
const referencesByAsset = collectAllReferences(library, descriptorsByType(this.#registry));
|
|
1857
|
-
const duplicatesByAsset = await this.#duplicateDetector.find(
|
|
2467
|
+
const duplicatesByAsset = await this.#duplicateDetector.find(host, this.#config.rawDir);
|
|
1858
2468
|
const membershipByManifest = computeLibraryMembership(library, referencesByAsset);
|
|
1859
2469
|
const delivered = allDeliveredAssetIds(membershipByManifest);
|
|
1860
2470
|
const passiveManifestsByAsset = /* @__PURE__ */ new Map();
|
|
@@ -1878,12 +2488,21 @@ var FundusOperations = class {
|
|
|
1878
2488
|
const assets = [];
|
|
1879
2489
|
for (const assetId of Object.keys(library.assets).sort(compareCodeUnits)) {
|
|
1880
2490
|
const record = library.assets[assetId];
|
|
2491
|
+
const prov = provenance[assetId] ?? { source: "host" };
|
|
2492
|
+
const origin = prov.source === "host" ? { kind: "host" } : prov.source === "package" ? {
|
|
2493
|
+
kind: "package",
|
|
2494
|
+
origin: prov.origin
|
|
2495
|
+
} : {
|
|
2496
|
+
kind: "override",
|
|
2497
|
+
origin: prov.origin
|
|
2498
|
+
};
|
|
2499
|
+
const isSlot = record.raw === void 0;
|
|
1881
2500
|
const warnings = [];
|
|
1882
2501
|
for (const issue of issues) if (issue.assetId === assetId) warnings.push(issue.message);
|
|
1883
|
-
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.");
|
|
2502
|
+
if (record.manifests.length === 0 && !delivered.has(assetId) && !isSlot) warnings.push("In no manifest and not referenced by any delivered asset — nothing is processed or generated for it.");
|
|
1884
2503
|
if (record.importSource && !this.#importers.has(record.importSource.importer)) warnings.push(`Importer "${record.importSource.importer}" is unavailable; the local raw file remains usable.`);
|
|
1885
2504
|
let proxy = null;
|
|
1886
|
-
if (delivered.has(assetId)) try {
|
|
2505
|
+
if (delivered.has(assetId) && !isSlot) try {
|
|
1887
2506
|
const currentProxy = await readCurrentProxy(assetId, record, this.#config, this.#registry);
|
|
1888
2507
|
if (currentProxy) proxy = {
|
|
1889
2508
|
url: `/files/proxy/${encodeRelativePathForUrl(currentProxy.relativePath)}`,
|
|
@@ -1894,13 +2513,15 @@ var FundusOperations = class {
|
|
|
1894
2513
|
} catch (cause) {
|
|
1895
2514
|
warnings.push(cause instanceof Error ? cause.message : String(cause));
|
|
1896
2515
|
}
|
|
2516
|
+
const raw = record.raw ?? "";
|
|
2517
|
+
const rawHash = record.rawHash ?? "";
|
|
1897
2518
|
assets.push({
|
|
1898
2519
|
id: assetId,
|
|
1899
2520
|
type: record.type,
|
|
1900
|
-
raw
|
|
1901
|
-
folder: folderOfRawPath(
|
|
1902
|
-
rawUrl: `/files/raw/${encodeRelativePathForUrl(
|
|
1903
|
-
rawHash
|
|
2521
|
+
raw,
|
|
2522
|
+
folder: folderOfRawPath(raw),
|
|
2523
|
+
rawUrl: isSlot ? "" : `/files/raw/${encodeRelativePathForUrl(raw)}?v=${rawHash.slice(0, 12)}`,
|
|
2524
|
+
rawHash,
|
|
1904
2525
|
gitStatus: changedGitAssets.get(assetId) ?? null,
|
|
1905
2526
|
importSource: record.importSource,
|
|
1906
2527
|
parameters: record.parameters,
|
|
@@ -1915,7 +2536,9 @@ var FundusOperations = class {
|
|
|
1915
2536
|
assetId: reference.assetId
|
|
1916
2537
|
})),
|
|
1917
2538
|
incoming: incomingByAsset.get(assetId) ?? []
|
|
1918
|
-
}
|
|
2539
|
+
},
|
|
2540
|
+
origin,
|
|
2541
|
+
overridden: origin.kind === "override"
|
|
1919
2542
|
});
|
|
1920
2543
|
}
|
|
1921
2544
|
const proxySizeByAssetId = new Map(assets.filter((asset) => asset.proxy).map((asset) => [asset.id, asset.proxy?.fileSize ?? 0]));
|
|
@@ -1939,18 +2562,22 @@ var FundusOperations = class {
|
|
|
1939
2562
|
presetNames[type] = names;
|
|
1940
2563
|
presetCapabilities[type] = Object.fromEntries(names.map((name) => [name, plugin.presetCapabilities?.(presets[name]) ?? {}]));
|
|
1941
2564
|
}
|
|
1942
|
-
const rawIngestionQueue = this.#buildRawIngestionQueue(
|
|
2565
|
+
const rawIngestionQueue = this.#buildRawIngestionQueue(host, classification);
|
|
1943
2566
|
return {
|
|
1944
2567
|
gitBranch,
|
|
1945
2568
|
library,
|
|
1946
2569
|
assets,
|
|
1947
|
-
importDefaults: { figma: {
|
|
2570
|
+
importDefaults: { figma: {
|
|
2571
|
+
scale: this.#config.import.figma.defaults.scale,
|
|
2572
|
+
...this.#config.import.figma.defaultFileKey !== void 0 ? { defaultFileKey: this.#config.import.figma.defaultFileKey } : {}
|
|
2573
|
+
} },
|
|
1948
2574
|
issues,
|
|
1949
2575
|
presetNames,
|
|
1950
2576
|
presetCapabilities,
|
|
1951
2577
|
manifestMemberships,
|
|
1952
2578
|
manifestDeliveredBytes,
|
|
1953
|
-
rawIngestionQueue
|
|
2579
|
+
rawIngestionQueue,
|
|
2580
|
+
packageOrigins: this.#config.packages.map((pkg) => pkg.origin).sort(compareCodeUnits)
|
|
1954
2581
|
};
|
|
1955
2582
|
}
|
|
1956
2583
|
#compatibleTypes(path) {
|
|
@@ -1963,8 +2590,10 @@ var FundusOperations = class {
|
|
|
1963
2590
|
return rawSafetyIssues(library, classification, (path) => this.#compatibleTypes(path));
|
|
1964
2591
|
}
|
|
1965
2592
|
async #snapshotState(library, classification, extraIssues = []) {
|
|
2593
|
+
const { effective, provenance, mergeFindings } = this.#effectiveOf(library);
|
|
1966
2594
|
const issues = [
|
|
1967
|
-
...await this.#store.validate(library),
|
|
2595
|
+
...await this.#store.validate(library, { flatSpace: false }),
|
|
2596
|
+
...this.#flatIssues(effective, provenance, mergeFindings),
|
|
1968
2597
|
...this.#rawSafetyIssues(library, classification),
|
|
1969
2598
|
...extraIssues
|
|
1970
2599
|
];
|
|
@@ -1975,7 +2604,7 @@ var FundusOperations = class {
|
|
|
1975
2604
|
seen.add(key);
|
|
1976
2605
|
return true;
|
|
1977
2606
|
});
|
|
1978
|
-
return this.#buildState(library, uniqueIssues, classification);
|
|
2607
|
+
return this.#buildState(library, effective, provenance, uniqueIssues, classification);
|
|
1979
2608
|
}
|
|
1980
2609
|
#canAutoReconcile(classification, retry = false) {
|
|
1981
2610
|
return classification.complete && classification.blockers.length === 0 && classification.moves.length + classification.deletions.length > 0 && (!retry || classification.edited.length === 0 && classification.unmanaged.length === 0);
|
|
@@ -2064,6 +2693,59 @@ var FundusOperations = class {
|
|
|
2064
2693
|
#requireAsset(library, id) {
|
|
2065
2694
|
if (!Object.hasOwn(library.assets, id)) throw new Error(`Unknown asset "${id}".`);
|
|
2066
2695
|
}
|
|
2696
|
+
/** Merge the configured Packages under the host library (host wins). */
|
|
2697
|
+
#effectiveOf(host) {
|
|
2698
|
+
const { library, provenance, findings } = mergePackages(host, this.#config.packages ?? []);
|
|
2699
|
+
return {
|
|
2700
|
+
effective: library,
|
|
2701
|
+
provenance,
|
|
2702
|
+
mergeFindings: findings
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* The Package base record for `id` when `id` is a read-only Package asset the
|
|
2707
|
+
* host has not yet filled — i.e. this create is a *slot fill*. Returns null
|
|
2708
|
+
* for a plain new host asset.
|
|
2709
|
+
*/
|
|
2710
|
+
#packageBase(host, id) {
|
|
2711
|
+
if (Object.hasOwn(host.assets, id)) return null;
|
|
2712
|
+
const { effective, provenance } = this.#effectiveOf(host);
|
|
2713
|
+
if (provenance[id]?.source === "package") return { type: effective.assets[id].type };
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Flat-space validation over the effective library: merge errors, id/type/
|
|
2718
|
+
* parameter/membership/appearance/reference checks, module collisions, and
|
|
2719
|
+
* delivered-slot findings. Disk/host checks stay in `store.validate(host,
|
|
2720
|
+
* { flatSpace: false })`.
|
|
2721
|
+
*/
|
|
2722
|
+
#flatIssues(effective, provenance, mergeFindings) {
|
|
2723
|
+
const membership = computeLibraryMembership(effective, collectAllReferences(effective, descriptorsByType(this.#registry)));
|
|
2724
|
+
return [
|
|
2725
|
+
...mergeFindings.filter((finding) => finding.kind !== "package-overridden").map((finding) => ({
|
|
2726
|
+
assetId: finding.assetId,
|
|
2727
|
+
message: finding.message
|
|
2728
|
+
})),
|
|
2729
|
+
...flatLibraryIssues(effective, descriptorsByType(this.#registry)),
|
|
2730
|
+
...manifestModuleCollisionIssues(effective.manifests),
|
|
2731
|
+
...packageSlotFindings(effective, provenance, membership).map((finding) => ({
|
|
2732
|
+
assetId: finding.assetId,
|
|
2733
|
+
message: finding.message
|
|
2734
|
+
}))
|
|
2735
|
+
];
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Whether an id is a read-only Package asset the host has not overridden.
|
|
2739
|
+
* Such ids can be filled/overridden (write a host record) but not mutated,
|
|
2740
|
+
* renamed, moved, or deleted in place.
|
|
2741
|
+
*/
|
|
2742
|
+
#assertHostAsset(host, id, action) {
|
|
2743
|
+
if (Object.hasOwn(host.assets, id)) return;
|
|
2744
|
+
const { provenance } = this.#effectiveOf(host);
|
|
2745
|
+
const prov = provenance[id];
|
|
2746
|
+
if (prov && prov.source !== "host") throw new Error(`Asset "${id}" is provided by package "${prov.origin}"; ${action} is not available. Override it by importing or uploading bytes with this id.`);
|
|
2747
|
+
throw new Error(`Unknown asset "${id}".`);
|
|
2748
|
+
}
|
|
2067
2749
|
/**
|
|
2068
2750
|
* Apply a CLI asset change to the store, validating the resulting library
|
|
2069
2751
|
* on an in-memory candidate first — nothing is persisted when the change
|
|
@@ -2112,4 +2794,4 @@ async function createFundusContext() {
|
|
|
2112
2794
|
}
|
|
2113
2795
|
|
|
2114
2796
|
export { getFundusContext as g, initializeFundusContext as i };
|
|
2115
|
-
//# sourceMappingURL=context.js-
|
|
2797
|
+
//# sourceMappingURL=context.js-Dh0ibRIF.js.map
|