fundus 0.2.0 → 0.4.0

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