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.
Files changed (231) hide show
  1. package/README.md +158 -1
  2. package/build/client/_app/immutable/assets/2.X44Wyac-.css +1 -0
  3. package/build/client/_app/immutable/assets/2.X44Wyac-.css.br +0 -0
  4. package/build/client/_app/immutable/assets/2.X44Wyac-.css.gz +0 -0
  5. package/build/client/_app/immutable/assets/{3.C6xo25uI.css → 3.66PJT3JC.css} +1 -1
  6. package/build/client/_app/immutable/assets/3.66PJT3JC.css.br +0 -0
  7. package/build/client/_app/immutable/assets/3.66PJT3JC.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/{AssetSourceMenu.IjDdjx3o.css → AssetSourceMenu.Buj2UxUg.css} +1 -1
  9. package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.br +0 -0
  10. package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.gz +0 -0
  11. package/build/client/_app/immutable/chunks/{VX-Uq_b9.js → 5fq8ySvR.js} +1 -1
  12. package/build/client/_app/immutable/chunks/5fq8ySvR.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/5fq8ySvR.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/BcVZHIBD.js +1 -0
  15. package/build/client/_app/immutable/chunks/BcVZHIBD.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/BcVZHIBD.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/DXiUbZ9S.js +2 -0
  18. package/build/client/_app/immutable/chunks/DXiUbZ9S.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/DXiUbZ9S.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/H5KnxQLQ.js +108 -0
  21. package/build/client/_app/immutable/chunks/H5KnxQLQ.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/H5KnxQLQ.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/{-8MKoIIe.js → jEVVu5G9.js} +1 -1
  24. package/build/client/_app/immutable/chunks/jEVVu5G9.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/jEVVu5G9.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/{app.CJpgt8H5.js → app.Z9n2k33I.js} +2 -2
  27. package/build/client/_app/immutable/entry/app.Z9n2k33I.js.br +0 -0
  28. package/build/client/_app/immutable/entry/app.Z9n2k33I.js.gz +0 -0
  29. package/build/client/_app/immutable/entry/start.DGo1uhI2.js +1 -0
  30. package/build/client/_app/immutable/entry/start.DGo1uhI2.js.br +0 -0
  31. package/build/client/_app/immutable/entry/start.DGo1uhI2.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js +1 -0
  33. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{1.D6OhgXcC.js → 1.rgoSlmPu.js} +1 -1
  36. package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.br +2 -0
  37. package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js +3 -0
  39. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js +1 -0
  42. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js +2 -0
  45. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.gz +0 -0
  47. package/build/client/_app/version.json +1 -1
  48. package/build/client/_app/version.json.br +0 -0
  49. package/build/client/_app/version.json.gz +0 -0
  50. package/build/handler.js +8 -7
  51. package/build/handler.js.map +1 -1
  52. package/build/index.js +8 -7
  53. package/build/index.js.map +1 -1
  54. package/build/server/chunks/chunks/{context.js-CVuTSPjH.js → context.js-Dh0ibRIF.js} +818 -136
  55. package/build/server/chunks/chunks/context.js-Dh0ibRIF.js.map +1 -0
  56. package/build/server/chunks/chunks/{contract.js-itFN0Gon.js → contract.js-_LTMZBo2.js} +156 -58
  57. package/build/server/chunks/chunks/contract.js-_LTMZBo2.js.map +1 -0
  58. package/build/server/chunks/chunks/{core.js-CABVXpby.js → core.js-CTyKR4bu.js} +145 -156
  59. package/build/server/chunks/chunks/core.js-CTyKR4bu.js.map +1 -0
  60. package/build/server/chunks/chunks/{exports.js-VRV1_yEb.js → exports.js-gz2aNXzz.js} +2 -2
  61. package/build/server/chunks/chunks/{exports.js-VRV1_yEb.js.map → exports.js-gz2aNXzz.js.map} +1 -1
  62. package/build/server/chunks/chunks/{http.js-ChQRRKfZ.js → http.js-BiqUcAuu.js} +3 -2
  63. package/build/server/chunks/chunks/http.js-BiqUcAuu.js.map +1 -0
  64. package/build/server/chunks/chunks/{internal.js-qXxDe7VN.js → internal.js-DoH0cN5N.js} +5 -3
  65. package/build/server/chunks/chunks/internal.js-DoH0cN5N.js.map +1 -0
  66. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  67. package/build/server/chunks/chunks/{manifest-membership.js-BnSECWpJ.js → manifest-membership.js-at1ixQdW.js} +10 -3
  68. package/build/server/chunks/chunks/manifest-membership.js-at1ixQdW.js.map +1 -0
  69. package/build/server/chunks/chunks/{raw-reconciliation.js-ZEOCk_jA.js → raw-reconciliation.js-CUH8IIJx.js} +3 -3
  70. package/build/server/chunks/chunks/raw-reconciliation.js-CUH8IIJx.js.map +1 -0
  71. package/build/server/chunks/chunks/{raw-reconciliation2.js-CscuoBC2.js → raw-reconciliation2.js-z4xl5zw2.js} +2 -2
  72. package/build/server/chunks/chunks/{raw-reconciliation2.js-CscuoBC2.js.map → raw-reconciliation2.js-z4xl5zw2.js.map} +1 -1
  73. package/build/server/chunks/chunks/{shared.js-DnEOHbgT.js → shared.js-CI-ZkPQn.js} +2 -2
  74. package/build/server/chunks/chunks/{shared.js-DnEOHbgT.js.map → shared.js-CI-ZkPQn.js.map} +1 -1
  75. package/build/server/chunks/chunks/{utils.js-lNH0hKSx.js → utils.js-gXYLxRG9.js} +3 -3
  76. package/build/server/chunks/chunks/{utils.js-lNH0hKSx.js.map → utils.js-gXYLxRG9.js.map} +1 -1
  77. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-z988EDp2.js → _server.ts.js-CdDjuW0r.js} +6 -6
  78. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-z988EDp2.js.map → _server.ts.js-CdDjuW0r.js.map} +1 -1
  79. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-C68ru6gS.js → _server.ts.js-OWM1eORG.js} +9 -7
  80. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-C68ru6gS.js.map → _server.ts.js-OWM1eORG.js.map} +1 -1
  81. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-C0tk6RFk.js → _server.ts.js-DoG-7ZpZ.js} +9 -7
  82. 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
  83. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-I03oZuo8.js → _server.ts.js-CAWjVeGd.js} +9 -7
  84. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-I03oZuo8.js.map → _server.ts.js-CAWjVeGd.js.map} +1 -1
  85. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js +38 -0
  86. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js.map +1 -0
  87. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BsQB8YDq.js → _server.ts.js-Bo_EsYXJ.js} +9 -7
  88. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BsQB8YDq.js.map → _server.ts.js-Bo_EsYXJ.js.map} +1 -1
  89. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-CJuFE6Fb.js → _server.ts.js-DW4w0XAr.js} +9 -7
  90. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-CJuFE6Fb.js.map → _server.ts.js-DW4w0XAr.js.map} +1 -1
  91. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DuOZRRiA.js → _server.ts.js-_MiNPnSy.js} +9 -7
  92. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DuOZRRiA.js.map → _server.ts.js-_MiNPnSy.js.map} +1 -1
  93. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DGzYpQbv.js → _server.ts.js-DwbrQnBc.js} +9 -7
  94. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DGzYpQbv.js.map → _server.ts.js-DwbrQnBc.js.map} +1 -1
  95. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-B045QoCA.js → _server.ts.js-CEnHQf3b.js} +9 -7
  96. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-B045QoCA.js.map → _server.ts.js-CEnHQf3b.js.map} +1 -1
  97. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-HUkVdz19.js → _server.ts.js-BZDuJZtF.js} +9 -7
  98. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-HUkVdz19.js.map → _server.ts.js-BZDuJZtF.js.map} +1 -1
  99. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-ry509mom.js → _server.ts.js-DRgdpKKd.js} +9 -7
  100. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-ry509mom.js.map → _server.ts.js-DRgdpKKd.js.map} +1 -1
  101. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DU0J7U25.js → _server.ts.js-CP5QyIhz.js} +9 -7
  102. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-DU0J7U25.js.map → _server.ts.js-CP5QyIhz.js.map} +1 -1
  103. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AZIV2PIn.js → _server.ts.js-BKEvt065.js} +9 -7
  104. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-AZIV2PIn.js.map → _server.ts.js-BKEvt065.js.map} +1 -1
  105. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CDL0hCfM.js → _server.ts.js-C_zihkDS.js} +9 -7
  106. 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
  107. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-B13c17fq.js → _server.ts.js-watkBmy6.js} +9 -7
  108. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-B13c17fq.js.map → _server.ts.js-watkBmy6.js.map} +1 -1
  109. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BCxjyAGC.js → _server.ts.js-YtumLIou.js} +9 -7
  110. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BCxjyAGC.js.map → _server.ts.js-YtumLIou.js.map} +1 -1
  111. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BveeMZc2.js → _server.ts.js-Dkouuu-M.js} +9 -7
  112. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BveeMZc2.js.map → _server.ts.js-Dkouuu-M.js.map} +1 -1
  113. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BZjTd9a0.js → _server.ts.js-DwsEQeuf.js} +9 -7
  114. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BZjTd9a0.js.map → _server.ts.js-DwsEQeuf.js.map} +1 -1
  115. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BbTfPvC9.js → _server.ts.js-CdQxfhV3.js} +9 -7
  116. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BbTfPvC9.js.map → _server.ts.js-CdQxfhV3.js.map} +1 -1
  117. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-8VPJMLBk.js → _server.ts.js-DDdMocWj.js} +6 -6
  118. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-8VPJMLBk.js.map → _server.ts.js-DDdMocWj.js.map} +1 -1
  119. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BqR2J2sB.js → _server.ts.js-DbzjbH32.js} +9 -7
  120. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BqR2J2sB.js.map → _server.ts.js-DbzjbH32.js.map} +1 -1
  121. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-DbA28dCY.js → _server.ts.js-K_JbXZsU.js} +9 -7
  122. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-DbA28dCY.js.map → _server.ts.js-K_JbXZsU.js.map} +1 -1
  123. package/build/server/chunks/entries/{hooks.server.js-9PBc_3Jh.js → hooks.server.js-DgZAVFbQ.js} +8 -6
  124. package/build/server/chunks/entries/{hooks.server.js-9PBc_3Jh.js.map → hooks.server.js-DgZAVFbQ.js.map} +1 -1
  125. package/build/server/chunks/{handler-BHt7eOOX.js → handler-CFkKak8t.js} +5 -5
  126. package/build/server/chunks/{handler-BHt7eOOX.js.map → handler-CFkKak8t.js.map} +1 -1
  127. package/build/server/chunks/{index.js-DcVaL-Ja.js → index.js-B1IroJH5.js} +5 -5
  128. package/build/server/chunks/{index.js-DcVaL-Ja.js.map → index.js-B1IroJH5.js.map} +1 -1
  129. package/build/server/chunks/{manifest.js-DgjSU4wg.js → manifest.js-DNDBsrEU.js} +36 -29
  130. package/build/server/chunks/manifest.js-DNDBsrEU.js.map +1 -0
  131. package/build/server/chunks/nodes/{0.js-BgTB-_Zt.js → 0.js-CZ3mZUZl.js} +2 -2
  132. package/build/server/chunks/nodes/{0.js-BgTB-_Zt.js.map → 0.js-CZ3mZUZl.js.map} +1 -1
  133. package/build/server/chunks/nodes/1.js-BmDzLKxV.js +7 -0
  134. package/build/server/chunks/nodes/{1.js-DNabcIPP.js.map → 1.js-BmDzLKxV.js.map} +1 -1
  135. package/build/server/chunks/nodes/2.js-Dirm-bY-.js +7 -0
  136. package/build/server/chunks/nodes/2.js-Dirm-bY-.js.map +1 -0
  137. package/build/server/chunks/nodes/3.js-DOqHmSWt.js +7 -0
  138. package/build/server/chunks/nodes/3.js-DOqHmSWt.js.map +1 -0
  139. package/build/server/chunks/nodes/4.js-C8pmF8IP.js +7 -0
  140. package/build/server/chunks/nodes/{4.js-BE7ZG9vA.js.map → 4.js-C8pmF8IP.js.map} +1 -1
  141. package/dist/config.d.ts +3 -3
  142. package/dist/config.js +2 -2
  143. package/dist/core/api-types.d.ts +24 -3
  144. package/dist/core/config.d.ts +84 -0
  145. package/dist/core/config.js +9 -0
  146. package/dist/core/index.d.ts +10 -5
  147. package/dist/core/index.js +5 -2
  148. package/dist/core/ingest.d.ts +1 -6
  149. package/dist/core/ingest.js +4 -21
  150. package/dist/core/library-validation.d.ts +13 -0
  151. package/dist/core/library-validation.js +48 -0
  152. package/dist/core/library.d.ts +19 -0
  153. package/dist/core/library.js +4 -4
  154. package/dist/core/manifest-membership.d.ts +2 -2
  155. package/dist/core/package-declaration.d.ts +43 -0
  156. package/dist/core/package-declaration.js +80 -0
  157. package/dist/core/package-merge.d.ts +65 -0
  158. package/dist/core/package-merge.js +173 -0
  159. package/dist/core/propose-asset-id.d.ts +10 -0
  160. package/dist/core/propose-asset-id.js +25 -0
  161. package/dist/core/reference-graph.d.ts +3 -3
  162. package/dist/server/build.js +26 -11
  163. package/dist/server/check.d.ts +13 -3
  164. package/dist/server/check.js +112 -24
  165. package/dist/server/cli-commands.d.ts +2 -2
  166. package/dist/server/cli-commands.js +213 -29
  167. package/dist/server/codegen.d.ts +3 -1
  168. package/dist/server/codegen.js +18 -0
  169. package/dist/server/config-loader.d.ts +36 -1
  170. package/dist/server/config-loader.js +190 -4
  171. package/dist/server/http.js +1 -0
  172. package/dist/server/importers/figma.d.ts +26 -3
  173. package/dist/server/importers/figma.js +114 -1
  174. package/dist/server/library-store.d.ts +20 -1
  175. package/dist/server/library-store.js +121 -60
  176. package/dist/server/operations.d.ts +12 -0
  177. package/dist/server/operations.js +283 -48
  178. package/package.json +1 -1
  179. package/build/client/_app/immutable/assets/2.C-1MfvLQ.css +0 -1
  180. package/build/client/_app/immutable/assets/2.C-1MfvLQ.css.br +0 -0
  181. package/build/client/_app/immutable/assets/2.C-1MfvLQ.css.gz +0 -0
  182. package/build/client/_app/immutable/assets/3.C6xo25uI.css.br +0 -0
  183. package/build/client/_app/immutable/assets/3.C6xo25uI.css.gz +0 -0
  184. package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.br +0 -0
  185. package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.gz +0 -0
  186. package/build/client/_app/immutable/chunks/-8MKoIIe.js.br +0 -0
  187. package/build/client/_app/immutable/chunks/-8MKoIIe.js.gz +0 -0
  188. package/build/client/_app/immutable/chunks/D1wFzWdi.js +0 -108
  189. package/build/client/_app/immutable/chunks/D1wFzWdi.js.br +0 -0
  190. package/build/client/_app/immutable/chunks/D1wFzWdi.js.gz +0 -0
  191. package/build/client/_app/immutable/chunks/DmQb0SN8.js +0 -2
  192. package/build/client/_app/immutable/chunks/DmQb0SN8.js.br +0 -0
  193. package/build/client/_app/immutable/chunks/DmQb0SN8.js.gz +0 -0
  194. package/build/client/_app/immutable/chunks/N1X6Qzcl.js +0 -1
  195. package/build/client/_app/immutable/chunks/N1X6Qzcl.js.br +0 -0
  196. package/build/client/_app/immutable/chunks/N1X6Qzcl.js.gz +0 -0
  197. package/build/client/_app/immutable/chunks/VX-Uq_b9.js.br +0 -1
  198. package/build/client/_app/immutable/chunks/VX-Uq_b9.js.gz +0 -0
  199. package/build/client/_app/immutable/entry/app.CJpgt8H5.js.br +0 -0
  200. package/build/client/_app/immutable/entry/app.CJpgt8H5.js.gz +0 -0
  201. package/build/client/_app/immutable/entry/start.DToPJitz.js +0 -1
  202. package/build/client/_app/immutable/entry/start.DToPJitz.js.br +0 -1
  203. package/build/client/_app/immutable/entry/start.DToPJitz.js.gz +0 -0
  204. package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js +0 -1
  205. package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.br +0 -0
  206. package/build/client/_app/immutable/nodes/0.Co4Hh8k6.js.gz +0 -0
  207. package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.br +0 -3
  208. package/build/client/_app/immutable/nodes/1.D6OhgXcC.js.gz +0 -0
  209. package/build/client/_app/immutable/nodes/2.sBDjyIQu.js +0 -3
  210. package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.br +0 -0
  211. package/build/client/_app/immutable/nodes/2.sBDjyIQu.js.gz +0 -0
  212. package/build/client/_app/immutable/nodes/3.DDt73kMC.js +0 -1
  213. package/build/client/_app/immutable/nodes/3.DDt73kMC.js.br +0 -0
  214. package/build/client/_app/immutable/nodes/3.DDt73kMC.js.gz +0 -0
  215. package/build/client/_app/immutable/nodes/4.FsXXhVSP.js +0 -2
  216. package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.br +0 -0
  217. package/build/client/_app/immutable/nodes/4.FsXXhVSP.js.gz +0 -0
  218. package/build/server/chunks/chunks/context.js-CVuTSPjH.js.map +0 -1
  219. package/build/server/chunks/chunks/contract.js-itFN0Gon.js.map +0 -1
  220. package/build/server/chunks/chunks/core.js-CABVXpby.js.map +0 -1
  221. package/build/server/chunks/chunks/http.js-ChQRRKfZ.js.map +0 -1
  222. package/build/server/chunks/chunks/internal.js-qXxDe7VN.js.map +0 -1
  223. package/build/server/chunks/chunks/manifest-membership.js-BnSECWpJ.js.map +0 -1
  224. package/build/server/chunks/chunks/raw-reconciliation.js-ZEOCk_jA.js.map +0 -1
  225. package/build/server/chunks/manifest.js-DgjSU4wg.js.map +0 -1
  226. package/build/server/chunks/nodes/1.js-DNabcIPP.js +0 -7
  227. package/build/server/chunks/nodes/2.js-CcVBvLSR.js +0 -7
  228. package/build/server/chunks/nodes/2.js-CcVBvLSR.js.map +0 -1
  229. package/build/server/chunks/nodes/3.js-DreG5rig.js +0 -7
  230. package/build/server/chunks/nodes/3.js-DreG5rig.js.map +0 -1
  231. 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, J as proposeAssetId, k as parentFolderPath, u as portableFolderPathKey } from './core.js-CABVXpby.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, c as invalidInput, f as parseLibrary } from './contract.js-itFN0Gon.js';
3
- import { c as collectAllReferences, a as assetIdError, b as computeLibraryMembership, d as allDeliveredAssetIds } from './manifest-membership.js-BnSECWpJ.js';
4
- import { v as verifyStableRawFile, c as classifyRawDirectory } from './raw-reconciliation2.js-CscuoBC2.js';
5
- import { isAbsolute, resolve, join, extname, relative, sep } from 'node:path';
6
- import { existsSync } from 'node:fs';
7
- import { createJiti } from 'jiti';
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
- return resolveConfig(root, isRecord(module) && "default" in module ? module.default : module);
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 || typeof input.url !== "string") return invalidInput("Figma import requires a URL.");
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 library = await this.#store.createAsset({
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
- for (const manifest of new Set(options.manifests ?? [])) if (!Object.hasOwn(library.manifests, manifest)) throw new SourceImportError("invalid-input", `Unknown manifest "${manifest}".`);
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.#requireAsset(library, id);
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 ordinaryIssues = await this.#store.validate(library);
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, library, this.#registry);
1842
- await generateManifestModules(this.#config, library, report.proxies, this.#registry);
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, 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(library, this.#config.rawDir);
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: record.raw,
1901
- folder: folderOfRawPath(record.raw),
1902
- rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}?v=${record.rawHash.slice(0, 12)}`,
1903
- rawHash: record.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(library, classification);
2565
+ const rawIngestionQueue = this.#buildRawIngestionQueue(host, classification);
1943
2566
  return {
1944
2567
  gitBranch,
1945
2568
  library,
1946
2569
  assets,
1947
- importDefaults: { figma: { scale: this.#config.import.figma.defaults.scale } },
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-CVuTSPjH.js.map
2797
+ //# sourceMappingURL=context.js-Dh0ibRIF.js.map