fundus 0.3.0 → 0.5.0

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