fundus 0.0.3 → 0.1.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 (205) hide show
  1. package/README.md +28 -3
  2. package/build/client/_app/immutable/assets/{2.wavcNhPC.css → 2.ohi-TClG.css} +1 -1
  3. package/build/client/_app/immutable/assets/2.ohi-TClG.css.br +0 -0
  4. package/build/client/_app/immutable/assets/2.ohi-TClG.css.gz +0 -0
  5. package/build/client/_app/immutable/assets/3.C-toFWI4.css +1 -0
  6. package/build/client/_app/immutable/assets/3.C-toFWI4.css.br +0 -0
  7. package/build/client/_app/immutable/assets/3.C-toFWI4.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css +1 -0
  9. package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.br +0 -0
  10. package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.gz +0 -0
  11. package/build/client/_app/immutable/chunks/BA62n9FX.js +108 -0
  12. package/build/client/_app/immutable/chunks/BA62n9FX.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/BA62n9FX.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/{BiIto6ft.js → CQsKwm8E.js} +1 -1
  15. package/build/client/_app/immutable/chunks/CQsKwm8E.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/CQsKwm8E.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{DQ8SXFaX.js → D9-155C9.js} +1 -1
  18. package/build/client/_app/immutable/chunks/D9-155C9.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/D9-155C9.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/{CvY6YsFE.js → DVEDrSOn.js} +1 -1
  21. package/build/client/_app/immutable/chunks/DVEDrSOn.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/DVEDrSOn.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/cznBUb-E.js +1 -0
  24. package/build/client/_app/immutable/chunks/cznBUb-E.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/cznBUb-E.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/{app.ubN0v9bN.js → app.DwQWZs1F.js} +2 -2
  27. package/build/client/_app/immutable/entry/app.DwQWZs1F.js.br +0 -0
  28. package/build/client/_app/immutable/entry/app.DwQWZs1F.js.gz +0 -0
  29. package/build/client/_app/immutable/entry/start.DEpAL_8Q.js +1 -0
  30. package/build/client/_app/immutable/entry/start.DEpAL_8Q.js.br +0 -0
  31. package/build/client/_app/immutable/entry/start.DEpAL_8Q.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/{0.Dr5ijCIl.js → 0.Sr1WDgiY.js} +1 -1
  33. package/build/client/_app/immutable/nodes/0.Sr1WDgiY.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/0.Sr1WDgiY.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{1.DiIGIRAb.js → 1.BwHqv728.js} +1 -1
  36. package/build/client/_app/immutable/nodes/1.BwHqv728.js.br +2 -0
  37. package/build/client/_app/immutable/nodes/1.BwHqv728.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/2.DidxYIU-.js +3 -0
  39. package/build/client/_app/immutable/nodes/2.DidxYIU-.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/2.DidxYIU-.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/3.C1AavoTf.js +1 -0
  42. package/build/client/_app/immutable/nodes/3.C1AavoTf.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/3.C1AavoTf.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/4.D8UoOONM.js +2 -0
  45. package/build/client/_app/immutable/nodes/4.D8UoOONM.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/4.D8UoOONM.js.gz +0 -0
  47. package/build/client/_app/version.json +1 -1
  48. package/build/client/_app/version.json.br +0 -0
  49. package/build/client/_app/version.json.gz +0 -0
  50. package/build/handler.js +7 -7
  51. package/build/index.js +7 -7
  52. package/build/server/chunks/chunks/{context.js-Bw4WWLhG.js → context.js-CGD-b_vR.js} +24 -34
  53. package/build/server/chunks/chunks/context.js-CGD-b_vR.js.map +1 -0
  54. package/build/server/chunks/chunks/{core.js-DDntpb72.js → core.js-xfrkaQpS.js} +97 -48
  55. package/build/server/chunks/chunks/core.js-xfrkaQpS.js.map +1 -0
  56. package/build/server/chunks/chunks/{exports.js-CLTzaOxn.js → exports.js-B4E0NJIt.js} +2 -2
  57. package/build/server/chunks/chunks/{exports.js-CLTzaOxn.js.map → exports.js-B4E0NJIt.js.map} +1 -1
  58. package/build/server/chunks/chunks/{http.js-Dxoa-jtP.js → http.js-Bu-Ao0Vk.js} +2 -2
  59. package/build/server/chunks/chunks/{http.js-Dxoa-jtP.js.map → http.js-Bu-Ao0Vk.js.map} +1 -1
  60. package/build/server/chunks/chunks/{internal.js-BDBSzJId.js → internal.js-ClSxY3uX.js} +3 -3
  61. package/build/server/chunks/chunks/{internal.js-BDBSzJId.js.map → internal.js-ClSxY3uX.js.map} +1 -1
  62. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  63. package/build/server/chunks/chunks/{library-store.js-BP8cL-qD.js → library-store.js-CZCDpai0.js} +187 -65
  64. package/build/server/chunks/chunks/library-store.js-CZCDpai0.js.map +1 -0
  65. package/build/server/chunks/chunks/{shared.js-BWXgl1Xf.js → shared.js-CHQOW_GN.js} +2 -2
  66. package/build/server/chunks/chunks/{shared.js-BWXgl1Xf.js.map → shared.js-CHQOW_GN.js.map} +1 -1
  67. package/build/server/chunks/chunks/{utils.js-9oI5Oz0w.js → utils.js-B9L6I-_K.js} +3 -3
  68. package/build/server/chunks/chunks/{utils.js-9oI5Oz0w.js.map → utils.js-B9L6I-_K.js.map} +1 -1
  69. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-WknZVW_S.js → _server.ts.js-PO5RpNzP.js} +5 -5
  70. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-WknZVW_S.js.map → _server.ts.js-PO5RpNzP.js.map} +1 -1
  71. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BrZ__-HZ.js → _server.ts.js-BmgqGEXs.js} +6 -6
  72. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BrZ__-HZ.js.map → _server.ts.js-BmgqGEXs.js.map} +1 -1
  73. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-DnTOlfHs.js +35 -0
  74. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-DnTOlfHs.js.map +1 -0
  75. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-PqADGPXc.js → _server.ts.js-DzdC6OJE.js} +6 -6
  76. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-PqADGPXc.js.map → _server.ts.js-DzdC6OJE.js.map} +1 -1
  77. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-OqoE5PN-.js → _server.ts.js-DXJNoYAs.js} +6 -6
  78. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-OqoE5PN-.js.map → _server.ts.js-DXJNoYAs.js.map} +1 -1
  79. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CJ08e1ho.js → _server.ts.js-CqdKXoZL.js} +6 -6
  80. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CJ08e1ho.js.map → _server.ts.js-CqdKXoZL.js.map} +1 -1
  81. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DdF3eK2I.js → _server.ts.js-DIuY66jG.js} +6 -6
  82. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DdF3eK2I.js.map → _server.ts.js-DIuY66jG.js.map} +1 -1
  83. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-Cxt6jsJ5.js → _server.ts.js-D9p-cdJQ.js} +6 -6
  84. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-Cxt6jsJ5.js.map → _server.ts.js-D9p-cdJQ.js.map} +1 -1
  85. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BWVM2Ntl.js → _server.ts.js-DP1x-xAW.js} +6 -6
  86. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BWVM2Ntl.js.map → _server.ts.js-DP1x-xAW.js.map} +1 -1
  87. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Z2bM-qHM.js → _server.ts.js-r1eNMvPd.js} +6 -6
  88. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Z2bM-qHM.js.map → _server.ts.js-r1eNMvPd.js.map} +1 -1
  89. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BX9H2Z34.js → _server.ts.js-D-m3BwRj.js} +6 -6
  90. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-BX9H2Z34.js.map → _server.ts.js-D-m3BwRj.js.map} +1 -1
  91. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-BWHDZ8mz.js → _server.ts.js-3nyAQ3f0.js} +5 -5
  92. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-BWHDZ8mz.js.map → _server.ts.js-3nyAQ3f0.js.map} +1 -1
  93. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-CR_7mndO.js → _server.ts.js-Ba285FiJ.js} +6 -6
  94. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-CR_7mndO.js.map → _server.ts.js-Ba285FiJ.js.map} +1 -1
  95. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Cw1PDBvb.js → _server.ts.js-X354l2NP.js} +6 -6
  96. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Cw1PDBvb.js.map → _server.ts.js-X354l2NP.js.map} +1 -1
  97. package/build/server/chunks/entries/{hooks.server.js-BHoE0yBi.js → hooks.server.js-eZ7H27L4.js} +5 -5
  98. package/build/server/chunks/entries/{hooks.server.js-BHoE0yBi.js.map → hooks.server.js-eZ7H27L4.js.map} +1 -1
  99. package/build/server/chunks/{handler-10cx4zHn.js → handler-Dcc_-rVh.js} +5 -5
  100. package/build/server/chunks/{handler-10cx4zHn.js.map → handler-Dcc_-rVh.js.map} +1 -1
  101. package/build/server/chunks/{index.js-DvPfiC00.js → index.js-DoVHd4mf.js} +5 -5
  102. package/build/server/chunks/{index.js-DvPfiC00.js.map → index.js-DoVHd4mf.js.map} +1 -1
  103. package/build/server/chunks/{manifest.js-DoPjUnzD.js → manifest.js-C_MsDhaa.js} +27 -20
  104. package/build/server/chunks/manifest.js-C_MsDhaa.js.map +1 -0
  105. package/build/server/chunks/nodes/0.js-BP6hHAK9.js +11 -0
  106. package/build/server/chunks/nodes/{0.js-Di3qgAtW.js.map → 0.js-BP6hHAK9.js.map} +1 -1
  107. package/build/server/chunks/nodes/1.js-DKlcrAQP.js +7 -0
  108. package/build/server/chunks/nodes/{1.js-BlWSFIA8.js.map → 1.js-DKlcrAQP.js.map} +1 -1
  109. package/build/server/chunks/nodes/2.js-B-8TTZH_.js +7 -0
  110. package/build/server/chunks/nodes/2.js-B-8TTZH_.js.map +1 -0
  111. package/build/server/chunks/nodes/3.js-Cf7H6K5p.js +7 -0
  112. package/build/server/chunks/nodes/3.js-Cf7H6K5p.js.map +1 -0
  113. package/build/server/chunks/nodes/4.js-JQQZnHWZ.js +7 -0
  114. package/build/server/chunks/nodes/4.js-JQQZnHWZ.js.map +1 -0
  115. package/dist/cli.js +34 -8
  116. package/dist/core/api-types.d.ts +1 -1
  117. package/dist/core/index.d.ts +1 -1
  118. package/dist/core/index.js +1 -1
  119. package/dist/core/ingest.d.ts +9 -1
  120. package/dist/core/ingest.js +10 -19
  121. package/dist/core/plugins/registry.d.ts +16 -0
  122. package/dist/core/plugins/registry.js +52 -0
  123. package/dist/runtime/kind-handlers.d.ts +0 -6
  124. package/dist/runtime/kind-handlers.js +16 -7
  125. package/dist/server/cli-commands.d.ts +2 -1
  126. package/dist/server/cli-commands.js +42 -1
  127. package/dist/server/cli-init.d.ts +1 -1
  128. package/dist/server/cli-init.js +23 -5
  129. package/dist/server/config-loader.d.ts +3 -2
  130. package/dist/server/config-loader.js +24 -48
  131. package/dist/server/index.d.ts +2 -1
  132. package/dist/server/library-store.d.ts +11 -0
  133. package/dist/server/library-store.js +57 -0
  134. package/dist/server/operations.d.ts +3 -1
  135. package/dist/server/operations.js +5 -1
  136. package/dist/server/plugins/audio.d.ts +2 -1
  137. package/dist/server/plugins/audio.js +15 -1
  138. package/dist/server/plugins/chroma-key-video.js +2 -0
  139. package/dist/server/plugins/config.d.ts +13 -0
  140. package/dist/server/plugins/config.js +32 -0
  141. package/dist/server/plugins/contract.d.ts +17 -3
  142. package/dist/server/plugins/image.d.ts +2 -1
  143. package/dist/server/plugins/image.js +14 -1
  144. package/dist/server/plugins/slice.d.ts +2 -1
  145. package/dist/server/plugins/slice.js +14 -1
  146. package/dist/server/plugins/video.d.ts +2 -1
  147. package/dist/server/plugins/video.js +26 -1
  148. package/dist/server/plugins.d.ts +6 -6
  149. package/dist/server/plugins.js +3 -1
  150. package/dist/server/start.d.ts +15 -1
  151. package/dist/server/start.js +84 -5
  152. package/package.json +3 -1
  153. package/build/client/_app/immutable/assets/2.wavcNhPC.css.br +0 -0
  154. package/build/client/_app/immutable/assets/2.wavcNhPC.css.gz +0 -0
  155. package/build/client/_app/immutable/assets/3.Cjh_fXPY.css +0 -1
  156. package/build/client/_app/immutable/assets/3.Cjh_fXPY.css.br +0 -0
  157. package/build/client/_app/immutable/assets/3.Cjh_fXPY.css.gz +0 -0
  158. package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css +0 -1
  159. package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css.br +0 -0
  160. package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css.gz +0 -0
  161. package/build/client/_app/immutable/chunks/BiIto6ft.js.br +0 -0
  162. package/build/client/_app/immutable/chunks/BiIto6ft.js.gz +0 -0
  163. package/build/client/_app/immutable/chunks/CO4RBJfa.js +0 -1
  164. package/build/client/_app/immutable/chunks/CO4RBJfa.js.br +0 -0
  165. package/build/client/_app/immutable/chunks/CO4RBJfa.js.gz +0 -0
  166. package/build/client/_app/immutable/chunks/CvY6YsFE.js.br +0 -0
  167. package/build/client/_app/immutable/chunks/CvY6YsFE.js.gz +0 -0
  168. package/build/client/_app/immutable/chunks/DL0_39Ys.js +0 -5
  169. package/build/client/_app/immutable/chunks/DL0_39Ys.js.br +0 -0
  170. package/build/client/_app/immutable/chunks/DL0_39Ys.js.gz +0 -0
  171. package/build/client/_app/immutable/chunks/DQ8SXFaX.js.br +0 -0
  172. package/build/client/_app/immutable/chunks/DQ8SXFaX.js.gz +0 -0
  173. package/build/client/_app/immutable/chunks/DjdrdRzT.js +0 -1
  174. package/build/client/_app/immutable/chunks/DjdrdRzT.js.br +0 -0
  175. package/build/client/_app/immutable/chunks/DjdrdRzT.js.gz +0 -0
  176. package/build/client/_app/immutable/entry/app.ubN0v9bN.js.br +0 -0
  177. package/build/client/_app/immutable/entry/app.ubN0v9bN.js.gz +0 -0
  178. package/build/client/_app/immutable/entry/start.BW8dBDh6.js +0 -1
  179. package/build/client/_app/immutable/entry/start.BW8dBDh6.js.br +0 -0
  180. package/build/client/_app/immutable/entry/start.BW8dBDh6.js.gz +0 -0
  181. package/build/client/_app/immutable/nodes/0.Dr5ijCIl.js.br +0 -0
  182. package/build/client/_app/immutable/nodes/0.Dr5ijCIl.js.gz +0 -0
  183. package/build/client/_app/immutable/nodes/1.DiIGIRAb.js.br +0 -3
  184. package/build/client/_app/immutable/nodes/1.DiIGIRAb.js.gz +0 -0
  185. package/build/client/_app/immutable/nodes/2.B8KYmQcV.js +0 -3
  186. package/build/client/_app/immutable/nodes/2.B8KYmQcV.js.br +0 -0
  187. package/build/client/_app/immutable/nodes/2.B8KYmQcV.js.gz +0 -0
  188. package/build/client/_app/immutable/nodes/3.B1jc7arT.js +0 -104
  189. package/build/client/_app/immutable/nodes/3.B1jc7arT.js.br +0 -0
  190. package/build/client/_app/immutable/nodes/3.B1jc7arT.js.gz +0 -0
  191. package/build/client/_app/immutable/nodes/4.BPK9n1mH.js +0 -2
  192. package/build/client/_app/immutable/nodes/4.BPK9n1mH.js.br +0 -0
  193. package/build/client/_app/immutable/nodes/4.BPK9n1mH.js.gz +0 -0
  194. package/build/server/chunks/chunks/context.js-Bw4WWLhG.js.map +0 -1
  195. package/build/server/chunks/chunks/core.js-DDntpb72.js.map +0 -1
  196. package/build/server/chunks/chunks/library-store.js-BP8cL-qD.js.map +0 -1
  197. package/build/server/chunks/manifest.js-DoPjUnzD.js.map +0 -1
  198. package/build/server/chunks/nodes/0.js-Di3qgAtW.js +0 -11
  199. package/build/server/chunks/nodes/1.js-BlWSFIA8.js +0 -7
  200. package/build/server/chunks/nodes/2.js-CO9nSPUN.js +0 -7
  201. package/build/server/chunks/nodes/2.js-CO9nSPUN.js.map +0 -1
  202. package/build/server/chunks/nodes/3.js-C2D6xpEO.js +0 -7
  203. package/build/server/chunks/nodes/3.js-C2D6xpEO.js.map +0 -1
  204. package/build/server/chunks/nodes/4.js-B3IuagJW.js +0 -7
  205. package/build/server/chunks/nodes/4.js-B3IuagJW.js.map +0 -1
@@ -202,6 +202,7 @@ Subcommands:
202
202
  list list assets (--manifest, --type, --warnings-only)
203
203
  show one asset in detail
204
204
  ingest copy a file into the raw dir as a typed asset
205
+ replace replace an asset's raw file while preserving its metadata
205
206
  set merge parameters and/or replace manifest membership
206
207
  rename rename an asset id
207
208
  move move an asset to a folder or root
@@ -285,6 +286,22 @@ Example:
285
286
  fundus asset ingest ./exports/navigation_panel@2x.png --type slice \\
286
287
  --manifests mainCatalog --json
287
288
  `;
289
+ export const ASSET_REPLACE_HELP = `fundus asset replace — replace an asset's raw file
290
+
291
+ Usage: fundus asset replace <id> <file> [--json]
292
+
293
+ Replaces the stored raw file and content hash while preserving the asset's
294
+ id, type, folder, parameters, references, and manifest memberships. The new
295
+ file must have an extension accepted by the existing asset type. Runs the full
296
+ autosave chain, so delivered assets are reprocessed and manifests regenerated.
297
+
298
+ Flags:
299
+ --json print {"ok": true, "asset": <AssetStateDto>, "issues": […]}
300
+ --help this help
301
+
302
+ Example:
303
+ fundus asset replace navigationPanel ./exports/navigation_panel_v2.png --json
304
+ `;
288
305
  export const ASSET_SET_HELP = `fundus asset set — update an asset's parameters and/or memberships
289
306
 
290
307
  Usage: fundus asset set <id> [--parameters <json>] [--manifests <a,b>] [--json]
@@ -368,6 +385,7 @@ const ASSET_SUBCOMMAND_HELP = {
368
385
  list: ASSET_LIST_HELP,
369
386
  show: ASSET_SHOW_HELP,
370
387
  ingest: ASSET_INGEST_HELP,
388
+ replace: ASSET_REPLACE_HELP,
371
389
  set: ASSET_SET_HELP,
372
390
  rename: ASSET_RENAME_HELP,
373
391
  move: ASSET_MOVE_HELP,
@@ -490,6 +508,27 @@ async function runAssetIngest(argumentList) {
490
508
  }
491
509
  printMutationResult(`Ingested "${fileName}" as ${type} asset "${id}".`, asset, state);
492
510
  }
511
+ async function runAssetReplace(argumentList) {
512
+ const parsed = parseArguments(argumentList, COMMON_FLAGS, 'fundus asset replace');
513
+ const [id, filePath] = requirePositionals(parsed, ['<id>', '<file>'], 'fundus asset replace');
514
+ const absolutePath = resolve(process.cwd(), filePath);
515
+ let bytes;
516
+ try {
517
+ bytes = new Uint8Array(await readFile(absolutePath));
518
+ }
519
+ catch {
520
+ throw new Error(`Cannot read file "${filePath}".`);
521
+ }
522
+ const fileName = basename(absolutePath);
523
+ const operations = await createOperations();
524
+ const state = await operations.replaceRawFile(id, { fileName, bytes });
525
+ const asset = assetById(state, id);
526
+ if (parsed.flags.json) {
527
+ printJsonDocument({ ok: true, asset, issues: state.issues });
528
+ return;
529
+ }
530
+ printMutationResult(`Replaced raw file for asset "${id}" with "${fileName}".`, asset, state);
531
+ }
493
532
  async function runAssetSet(argumentList) {
494
533
  const parsed = parseArguments(argumentList, { ...COMMON_FLAGS, parameters: { takesValue: true }, manifests: { takesValue: true } }, 'fundus asset set');
495
534
  const [id] = requirePositionals(parsed, ['<id>'], 'fundus asset set');
@@ -554,7 +593,7 @@ export async function runAssetCommand(argumentList) {
554
593
  if (subcommand === undefined || subcommand === '--help') {
555
594
  console.log(ASSET_HELP);
556
595
  if (subcommand === undefined) {
557
- throw new CliUsageError('`fundus asset` needs a subcommand: list, show, ingest, set, rename, move, delete.');
596
+ throw new CliUsageError('`fundus asset` needs a subcommand: list, show, ingest, replace, set, rename, move, delete.');
558
597
  }
559
598
  return;
560
599
  }
@@ -570,6 +609,8 @@ export async function runAssetCommand(argumentList) {
570
609
  return runAssetShow(rest);
571
610
  case 'ingest':
572
611
  return runAssetIngest(rest);
612
+ case 'replace':
613
+ return runAssetReplace(rest);
573
614
  case 'set':
574
615
  return runAssetSet(rest);
575
616
  case 'rename':
@@ -1,3 +1,3 @@
1
1
  export declare const FUNDUS_CONFIG_TEMPLATE = "import { defineConfig } from 'fundus/config';\n\nexport default defineConfig({\n\trawDir: './assets/raw',\n\tproxyDir: './static/fundus',\n\tproxyBasePath: '/fundus/',\n\tlibrary: './assets/fundus.library.json',\n\tmanifestsDir: './src/lib/fundus'\n});\n";
2
- export declare const INIT_HELP = "fundus init \u2014 create a Fundus configuration file\n\nUsage: fundus init [--json]\n\nCreates a template fundus.config.ts in the current directory. The command\nrefuses to overwrite an existing configuration file.\n\nFlags:\n --json print {\"ok\": true, \"path\": \"fundus.config.ts\"} on stdout\n --help this help\n\nExample:\n cd my-host-app && npx fundus init\n";
2
+ export declare const INIT_HELP = "fundus init \u2014 create a Fundus configuration file\n\nUsage: fundus init [--json]\n\nCreates a template fundus.config.ts and a library with a default \"main\"\nmanifest in the current directory. The command refuses to initialize a project\nthat already has a Fundus configuration file.\n\nFlags:\n --json print {\"ok\": true, \"path\": \"fundus.config.ts\"} on stdout\n --help this help\n\nExample:\n cd my-host-app && npx fundus init\n";
3
3
  export declare function runInitCommand(argumentList: string[]): Promise<void>;
@@ -1,5 +1,6 @@
1
- import { writeFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
1
+ import { mkdir, unlink, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { createEmptyLibrary, serializeLibrary } from '../core/index.js';
3
4
  import { parseArguments, printJsonDocument, requirePositionals } from './cli-io.js';
4
5
  export const FUNDUS_CONFIG_TEMPLATE = `import { defineConfig } from 'fundus/config';
5
6
 
@@ -15,8 +16,9 @@ export const INIT_HELP = `fundus init — create a Fundus configuration file
15
16
 
16
17
  Usage: fundus init [--json]
17
18
 
18
- Creates a template fundus.config.ts in the current directory. The command
19
- refuses to overwrite an existing configuration file.
19
+ Creates a template fundus.config.ts and a library with a default "main"
20
+ manifest in the current directory. The command refuses to initialize a project
21
+ that already has a Fundus configuration file.
20
22
 
21
23
  Flags:
22
24
  --json print {"ok": true, "path": "fundus.config.ts"} on stdout
@@ -45,9 +47,25 @@ export async function runInitCommand(argumentList) {
45
47
  }
46
48
  throw new Error(`Cannot create fundus.config.ts: ${cause instanceof Error ? cause.message : String(cause)}`);
47
49
  }
50
+ const libraryPath = resolve(process.cwd(), 'assets/fundus.library.json');
51
+ const library = createEmptyLibrary();
52
+ library.manifests.main = {};
53
+ try {
54
+ await mkdir(dirname(libraryPath), { recursive: true });
55
+ await writeFile(libraryPath, serializeLibrary(library), { encoding: 'utf8', flag: 'wx' });
56
+ }
57
+ catch (cause) {
58
+ // The config was created by this invocation. Remove it so a failed
59
+ // library scaffold does not make the project look initialized.
60
+ await unlink(configPath).catch(() => { });
61
+ if (cause.code === 'EEXIST') {
62
+ throw new Error(`assets/fundus.library.json already exists in ${process.cwd()}.`);
63
+ }
64
+ throw new Error(`Cannot create assets/fundus.library.json: ${cause instanceof Error ? cause.message : String(cause)}`);
65
+ }
48
66
  if (parsed.flags.json) {
49
67
  printJsonDocument({ ok: true, path: 'fundus.config.ts' });
50
68
  return;
51
69
  }
52
- console.log('Created fundus.config.ts.');
70
+ console.log('Created fundus.config.ts and default "main" manifest.');
53
71
  }
@@ -1,4 +1,5 @@
1
- import { type AudioPreset, type ImagePreset, type VideoPreset } from '../core/index.js';
1
+ import type { AudioPreset, ImagePreset, VideoPreset } from '../core/index.js';
2
+ import type { ServerPluginRegistry } from './plugins/contract.js';
2
3
  export interface ResolvedPresetPluginConfig {
3
4
  presets: Record<string, ImagePreset>;
4
5
  }
@@ -29,6 +30,6 @@ export interface ResolvedFundusConfig {
29
30
  };
30
31
  }
31
32
  /** Pure resolution step (testable without a config file on disk). */
32
- export declare function resolveConfig(projectRoot: string, value: unknown): ResolvedFundusConfig;
33
+ export declare function resolveConfig(projectRoot: string, value: unknown, registry?: ServerPluginRegistry): ResolvedFundusConfig;
33
34
  /** Load `fundus.config.ts` from the host project root. */
34
35
  export declare function loadConfig(projectRoot: string): Promise<ResolvedFundusConfig>;
@@ -4,12 +4,10 @@
4
4
  * absolute, and merges the built-in plugin preset defaults under the host's.
5
5
  */
6
6
  import { existsSync } from 'node:fs';
7
- import { isAbsolute, resolve } from 'node:path';
7
+ import { resolve } from 'node:path';
8
8
  import { createJiti } from 'jiti';
9
- import { AUDIO_BUILT_IN_PRESETS, IMAGE_BUILT_IN_PRESETS, SLICE_BUILT_IN_PRESETS, VIDEO_BUILT_IN_PRESETS, validateAudioPreset, validateImagePreset, validateVideoPreset } from '../core/index.js';
10
- function isRecord(value) {
11
- return typeof value === 'object' && value !== null && !Array.isArray(value);
12
- }
9
+ import { serverPluginsByType } from './plugins.js';
10
+ import { isRecord } from './plugins/config.js';
13
11
  function requiredString(config, key) {
14
12
  const value = config[key];
15
13
  if (typeof value !== 'string' || value.length === 0) {
@@ -18,37 +16,23 @@ function requiredString(config, key) {
18
16
  return value;
19
17
  }
20
18
  function resolvePath(projectRoot, value) {
21
- return isAbsolute(value) ? value : resolve(projectRoot, value);
22
- }
23
- function mergePresets(label, builtIn, pluginConfig, validatePreset) {
24
- const merged = { ...builtIn };
25
- if (pluginConfig === undefined)
26
- return merged;
27
- if (!isRecord(pluginConfig)) {
28
- throw new Error(`fundus.config: "plugins.${label}" must be an object.`);
29
- }
30
- if (pluginConfig.presets === undefined)
31
- return merged;
32
- if (!isRecord(pluginConfig.presets)) {
33
- throw new Error(`fundus.config: "plugins.${label}.presets" must be an object.`);
34
- }
35
- for (const [name, preset] of Object.entries(pluginConfig.presets)) {
36
- merged[name] = validatePreset(name, preset);
37
- }
38
- return merged;
19
+ return resolve(projectRoot, value);
39
20
  }
40
- /** `plugins.video.ffmpegPath` / `ffprobePath`: optional system-binary overrides. */
41
- function binaryPathOverride(projectRoot, pluginConfig, key) {
42
- if (!isRecord(pluginConfig) || pluginConfig[key] === undefined)
43
- return null;
44
- const value = pluginConfig[key];
45
- if (typeof value !== 'string' || value.length === 0) {
46
- throw new Error(`fundus.config: "plugins.video.${key}" must be a non-empty string.`);
21
+ function configDefinitionsFor(registry) {
22
+ const byNamespace = new Map();
23
+ for (const plugin of Object.values(registry)) {
24
+ for (const definition of plugin.configDefinitions) {
25
+ const existing = byNamespace.get(definition.namespace);
26
+ if (existing && existing !== definition) {
27
+ throw new Error(`Plugins provide conflicting config definitions for "plugins.${definition.namespace}".`);
28
+ }
29
+ byNamespace.set(definition.namespace, definition);
30
+ }
47
31
  }
48
- return resolvePath(projectRoot, value);
32
+ return [...byNamespace.values()];
49
33
  }
50
34
  /** Pure resolution step (testable without a config file on disk). */
51
- export function resolveConfig(projectRoot, value) {
35
+ export function resolveConfig(projectRoot, value, registry = serverPluginsByType) {
52
36
  if (!isRecord(value)) {
53
37
  throw new Error('fundus.config must default-export a configuration object.');
54
38
  }
@@ -60,28 +44,20 @@ export function resolveConfig(projectRoot, value) {
60
44
  if (!isRecord(plugins)) {
61
45
  throw new Error('fundus.config: "plugins" must be an object.');
62
46
  }
47
+ const resolvedPlugins = {};
48
+ for (const definition of configDefinitionsFor(registry)) {
49
+ resolvedPlugins[definition.namespace] = definition.resolve({
50
+ projectRoot,
51
+ value: plugins[definition.namespace]
52
+ });
53
+ }
63
54
  return {
64
55
  rawDir: resolvePath(projectRoot, requiredString(value, 'rawDir')),
65
56
  proxyDir: resolvePath(projectRoot, requiredString(value, 'proxyDir')),
66
57
  library: resolvePath(projectRoot, requiredString(value, 'library')),
67
58
  manifestsDir: resolvePath(projectRoot, requiredString(value, 'manifestsDir')),
68
59
  proxyBasePath,
69
- plugins: {
70
- image: {
71
- presets: mergePresets('image', IMAGE_BUILT_IN_PRESETS, plugins.image, validateImagePreset)
72
- },
73
- slice: {
74
- presets: mergePresets('slice', SLICE_BUILT_IN_PRESETS, plugins.slice, validateImagePreset)
75
- },
76
- video: {
77
- presets: mergePresets('video', VIDEO_BUILT_IN_PRESETS, plugins.video, validateVideoPreset),
78
- ffmpegPath: binaryPathOverride(projectRoot, plugins.video, 'ffmpegPath'),
79
- ffprobePath: binaryPathOverride(projectRoot, plugins.video, 'ffprobePath')
80
- },
81
- audio: {
82
- presets: mergePresets('audio', AUDIO_BUILT_IN_PRESETS, plugins.audio, validateAudioPreset)
83
- }
84
- }
60
+ plugins: resolvedPlugins
85
61
  };
86
62
  }
87
63
  /** Load `fundus.config.ts` from the host project root. */
@@ -1,7 +1,7 @@
1
1
  export type { ResolvedFundusConfig, ResolvedPresetPluginConfig, ResolvedVideoPluginConfig, ResolvedAudioPluginConfig } from './config-loader.js';
2
2
  export { loadConfig, resolveConfig } from './config-loader.js';
3
3
  export { LibraryStore } from './library-store.js';
4
- export type { IngestOptions } from './library-store.js';
4
+ export type { IngestOptions, ReplaceRawFileOptions } from './library-store.js';
5
5
  export type { AssetProxyInfo, ProxyOutputInfo, ProcessingReport } from './processing/processor.js';
6
6
  export { processAssets, expectedProxyFileName, expectedProxyRelativePath, deliveredAssetIds, isProxyFileName, readProxyOutputInfo } from './processing/processor.js';
7
7
  export { ffmpegBinaryPath, runToolchainBinary } from './processing/ffmpeg.js';
@@ -13,4 +13,5 @@ export type { CheckFinding, CheckReport } from './check.js';
13
13
  export { formatCheckFinding, runCheck } from './check.js';
14
14
  export type { AssetChangeOptions } from './operations.js';
15
15
  export { FundusOperations } from './operations.js';
16
+ export type { RunStartOptions } from './start.js';
16
17
  export { runStart } from './start.js';
@@ -23,6 +23,11 @@ export interface IngestOptions {
23
23
  /** Registered destination folder; empty string means the implicit root. */
24
24
  folder?: string;
25
25
  }
26
+ export interface ReplaceRawFileOptions {
27
+ /** Original file name of the replacement (sanitized before storing). */
28
+ fileName: string;
29
+ bytes: Uint8Array;
30
+ }
26
31
  export declare class LibraryStore {
27
32
  #private;
28
33
  constructor(config: ResolvedFundusConfig, registry?: ServerPluginRegistry);
@@ -37,6 +42,12 @@ export declare class LibraryStore {
37
42
  * the owning plugin's default parameters. Returns the updated library.
38
43
  */
39
44
  ingest(options: IngestOptions): Promise<Library>;
45
+ /**
46
+ * Replace one asset's raw file while preserving its identity and authored
47
+ * metadata. The replacement stays in the current folder, receives a safe
48
+ * non-colliding name, and must be accepted by the asset's existing type.
49
+ */
50
+ replaceRawFile(id: string, options: ReplaceRawFileOptions): Promise<Library>;
40
51
  /** Replace an asset's parameters (validated by the owning plugin). */
41
52
  updateParameters(id: string, parameters: unknown): Promise<Library>;
42
53
  /** Replace an asset's manifest membership (every name must exist). */
@@ -136,6 +136,63 @@ export class LibraryStore {
136
136
  await this.write(library);
137
137
  return library;
138
138
  }
139
+ /**
140
+ * Replace one asset's raw file while preserving its identity and authored
141
+ * metadata. The replacement stays in the current folder, receives a safe
142
+ * non-colliding name, and must be accepted by the asset's existing type.
143
+ */
144
+ async replaceRawFile(id, options) {
145
+ return this.#enqueue(() => this.#replaceRawFile(id, options));
146
+ }
147
+ async #replaceRawFile(id, options) {
148
+ const library = await this.read();
149
+ const record = this.#requireAsset(library, id);
150
+ const sanitizedName = sanitizeFileName(options.fileName);
151
+ const extension = extname(sanitizedName).slice(1).toLowerCase();
152
+ const plugin = serverPluginForType(record.type, this.#registry);
153
+ if (!plugin.descriptor.accepts.includes(extension)) {
154
+ throw new Error(`The ${record.type} type does not accept ".${extension}" files (accepts: ${plugin.descriptor.accepts.join(', ')}).`);
155
+ }
156
+ const previousRaw = record.raw;
157
+ const previousHash = record.rawHash;
158
+ const folder = folderOfRawPath(previousRaw);
159
+ const previousName = rawFileNameOfPath(previousRaw);
160
+ const storedName = sanitizedName === previousName ? previousName : this.#uniqueRawName(sanitizedName, folder);
161
+ const nextRaw = folder === '' ? storedName : `${folder}/${storedName}`;
162
+ const previousPath = join(this.#config.rawDir, previousRaw);
163
+ const nextPath = join(this.#config.rawDir, nextRaw);
164
+ const token = crypto.randomUUID();
165
+ const temporaryPath = join(this.#config.rawDir, folder, `.fundus-replace-${token}.tmp`);
166
+ const backupPath = `${previousPath}.fundus-replace-${token}`;
167
+ let previousStaged = false;
168
+ let replacementInstalled = false;
169
+ await mkdir(dirname(temporaryPath), { recursive: true });
170
+ try {
171
+ await writeFile(temporaryPath, options.bytes);
172
+ if (existsSync(previousPath)) {
173
+ await rename(previousPath, backupPath);
174
+ previousStaged = true;
175
+ }
176
+ await rename(temporaryPath, nextPath);
177
+ replacementInstalled = true;
178
+ record.raw = nextRaw;
179
+ record.rawHash = sha256Hex(options.bytes);
180
+ await this.write(library);
181
+ }
182
+ catch (cause) {
183
+ record.raw = previousRaw;
184
+ record.rawHash = previousHash;
185
+ if (replacementInstalled)
186
+ await unlink(nextPath).catch(() => { });
187
+ if (previousStaged)
188
+ await rename(backupPath, previousPath);
189
+ await unlink(temporaryPath).catch(() => { });
190
+ throw cause;
191
+ }
192
+ if (previousStaged)
193
+ await unlink(backupPath).catch(() => { });
194
+ return library;
195
+ }
139
196
  /** Replace an asset's parameters (validated by the owning plugin). */
140
197
  async updateParameters(id, parameters) {
141
198
  return this.#enqueue(() => this.#updateParameters(id, parameters));
@@ -1,6 +1,6 @@
1
1
  import { type FundusStateDto, type PatchAssetRequestDto, type PatchManifestRequestDto } from '../core/index.js';
2
2
  import type { ResolvedFundusConfig } from './config-loader.js';
3
- import { LibraryStore, type IngestOptions } from './library-store.js';
3
+ import { LibraryStore, type IngestOptions, type ReplaceRawFileOptions } from './library-store.js';
4
4
  import type { ServerPluginRegistry } from './plugins/contract.js';
5
5
  /**
6
6
  * The CLI's `--parameters` merge semantics (M7): a SHALLOW merge at the top
@@ -37,6 +37,8 @@ export declare class FundusOperations {
37
37
  /** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
38
38
  patchAsset(id: string, body: PatchAssetRequestDto): Promise<FundusStateDto>;
39
39
  moveAsset(id: string, folder: string): Promise<FundusStateDto>;
40
+ /** Replace the raw bytes/name and run the normal processing + codegen tail. */
41
+ replaceRawFile(id: string, options: ReplaceRawFileOptions): Promise<FundusStateDto>;
40
42
  /** Move a selected asset set as one editor action and roll back on any failure. */
41
43
  moveAssets(assetIds: string[], folder: string): Promise<FundusStateDto>;
42
44
  createFolder(path: string): Promise<FundusStateDto>;
@@ -125,6 +125,10 @@ export class FundusOperations {
125
125
  async moveAsset(id, folder) {
126
126
  return this.#afterMutation(await this.#store.moveAsset(id, folder));
127
127
  }
128
+ /** Replace the raw bytes/name and run the normal processing + codegen tail. */
129
+ async replaceRawFile(id, options) {
130
+ return this.#afterMutation(await this.#store.replaceRawFile(id, options));
131
+ }
128
132
  /** Move a selected asset set as one editor action and roll back on any failure. */
129
133
  async moveAssets(assetIds, folder) {
130
134
  const uniqueIds = [...new Set(assetIds)];
@@ -334,7 +338,7 @@ export class FundusOperations {
334
338
  type: record.type,
335
339
  raw: record.raw,
336
340
  folder: folderOfRawPath(record.raw),
337
- rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}`,
341
+ rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}?v=${record.rawHash.slice(0, 12)}`,
338
342
  rawHash: record.rawHash,
339
343
  parameters: record.parameters,
340
344
  manifests: record.manifests,
@@ -3,5 +3,6 @@
3
3
  * No dimensions — the proxy's only output metadata is its duration.
4
4
  */
5
5
  import { type AudioParameters, type AudioPreset } from '../../core/index.js';
6
- import { type ServerAssetTypePlugin } from './contract.js';
6
+ import { type ServerAssetTypePlugin, type ServerPluginConfigDefinition } from './contract.js';
7
+ export declare const audioConfigDefinition: ServerPluginConfigDefinition;
7
8
  export declare const audioServerPlugin: ServerAssetTypePlugin<AudioParameters, AudioPreset>;
@@ -2,12 +2,26 @@
2
2
  * Audio plugin (server part): one ffmpeg transcode per preset.
3
3
  * No dimensions — the proxy's only output metadata is its duration.
4
4
  */
5
- import { audioPlugin } from '../../core/index.js';
5
+ import { AUDIO_BUILT_IN_PRESETS, audioPlugin, validateAudioPreset } from '../../core/index.js';
6
6
  import { processAudioFile } from '../processing/audio-processing.js';
7
7
  import { probeMedia } from '../processing/media-probe.js';
8
+ import { mergePresets } from './config.js';
8
9
  import { requireProxyField, resolveNamedPreset } from './contract.js';
10
+ import { videoConfigDefinition } from './video.js';
11
+ export const audioConfigDefinition = {
12
+ namespace: 'audio',
13
+ resolve: ({ value }) => ({
14
+ presets: mergePresets({
15
+ label: 'audio',
16
+ builtIn: AUDIO_BUILT_IN_PRESETS,
17
+ pluginConfig: value,
18
+ validatePreset: validateAudioPreset
19
+ })
20
+ })
21
+ };
9
22
  export const audioServerPlugin = {
10
23
  descriptor: audioPlugin,
24
+ configDefinitions: [audioConfigDefinition, videoConfigDefinition],
11
25
  pluginConfig: (config) => config.plugins.audio,
12
26
  presets: (config) => config.plugins.audio.presets,
13
27
  resolvePreset: (parameters, config) => resolveNamedPreset('audio', parameters.preset, config.plugins.audio.presets),
@@ -8,8 +8,10 @@ import { chromaKeyVideoPlugin } from '../../core/index.js';
8
8
  import { probeMedia } from '../processing/media-probe.js';
9
9
  import { processVideoFile } from '../processing/video-processing.js';
10
10
  import { requireProxyField, resolveNamedPreset } from './contract.js';
11
+ import { videoConfigDefinition } from './video.js';
11
12
  export const chromaKeyVideoServerPlugin = {
12
13
  descriptor: chromaKeyVideoPlugin,
14
+ configDefinitions: [videoConfigDefinition],
13
15
  // One preset namespace with the video plugin — the config slice,
14
16
  // the preset map, and the resolution label are video's.
15
17
  pluginConfig: (config) => config.plugins.video,
@@ -0,0 +1,13 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ export declare function mergePresets<TPreset>(options: {
3
+ label: string;
4
+ builtIn: Record<string, TPreset>;
5
+ pluginConfig: unknown;
6
+ validatePreset: (name: string, value: unknown) => TPreset;
7
+ }): Record<string, TPreset>;
8
+ export declare function binaryPathOverride(options: {
9
+ projectRoot: string;
10
+ pluginConfig: unknown;
11
+ label: string;
12
+ key: string;
13
+ }): string | null;
@@ -0,0 +1,32 @@
1
+ import { isAbsolute, resolve } from 'node:path';
2
+ export function isRecord(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ }
5
+ export function mergePresets(options) {
6
+ const { label, builtIn, pluginConfig, validatePreset } = options;
7
+ const merged = { ...builtIn };
8
+ if (pluginConfig === undefined)
9
+ return merged;
10
+ if (!isRecord(pluginConfig)) {
11
+ throw new Error(`fundus.config: "plugins.${label}" must be an object.`);
12
+ }
13
+ if (pluginConfig.presets === undefined)
14
+ return merged;
15
+ if (!isRecord(pluginConfig.presets)) {
16
+ throw new Error(`fundus.config: "plugins.${label}.presets" must be an object.`);
17
+ }
18
+ for (const [name, preset] of Object.entries(pluginConfig.presets)) {
19
+ merged[name] = validatePreset(name, preset);
20
+ }
21
+ return merged;
22
+ }
23
+ export function binaryPathOverride(options) {
24
+ const { projectRoot, pluginConfig, label, key } = options;
25
+ if (!isRecord(pluginConfig) || pluginConfig[key] === undefined)
26
+ return null;
27
+ const value = pluginConfig[key];
28
+ if (typeof value !== 'string' || value.length === 0) {
29
+ throw new Error(`fundus.config: "plugins.${label}.${key}" must be a non-empty string.`);
30
+ }
31
+ return isAbsolute(value) ? value : resolve(projectRoot, value);
32
+ }
@@ -4,8 +4,7 @@
4
4
  * config slice, preset resolution, the processing step (sharp/ffmpeg), proxy
5
5
  * probing, and the manifest-entry builder for codegen. The orchestrators
6
6
  * (processor, codegen, check, store, operations) are type-agnostic and consult
7
- * only the registry (plugins.ts), so a new asset type is: a core descriptor +
8
- * one `ServerAssetTypePlugin` + one registry entry — nothing else server-side.
7
+ * only the projected server registry (plugins.ts).
9
8
  */
10
9
  import type { AssetTypePluginDescriptor, ManifestEntryData } from '../../core/index.js';
11
10
  import type { ResolvedFundusConfig } from '../config-loader.js';
@@ -47,9 +46,24 @@ export interface ManifestEntryInput<TParameters> {
47
46
  src: string;
48
47
  proxy: AssetProxyInfo;
49
48
  }
49
+ /** One host-config namespace required by a server plugin. */
50
+ export interface ServerPluginConfigDefinition {
51
+ /** Key below `plugins` in fundus.config.ts. */
52
+ namespace: string;
53
+ /** Validate and resolve that namespace's host value. */
54
+ resolve(context: {
55
+ projectRoot: string;
56
+ value: unknown;
57
+ }): unknown;
58
+ }
50
59
  export interface ServerAssetTypePlugin<TParameters = unknown, TPreset = unknown> {
51
60
  /** The environment-free half of the plugin (type key, ingestion, validation). */
52
61
  descriptor: AssetTypePluginDescriptor<TParameters, unknown>;
62
+ /**
63
+ * Config namespaces this plugin needs. Shared definitions are deduplicated
64
+ * by identity (video, chroma-key-video, and audio share the video toolchain).
65
+ */
66
+ configDefinitions: readonly ServerPluginConfigDefinition[];
53
67
  /**
54
68
  * The plugin's `plugins.<namespace>` config slice — handed to the
55
69
  * descriptor's `defaultParameters` on ingest. Types may share a namespace
@@ -92,7 +106,7 @@ export interface ServerAssetTypePlugin<TParameters = unknown, TPreset = unknown>
92
106
  */
93
107
  buildManifestEntry(input: ManifestEntryInput<TParameters>): ManifestEntryData;
94
108
  }
95
- /** The one server-side touch point for a new asset type: type key → plugin. */
109
+ /** The server projection consumed by type-agnostic orchestrators and tests. */
96
110
  export type ServerPluginRegistry = Record<string, ServerAssetTypePlugin>;
97
111
  /** Resolve a named preset out of a plugin's merged preset map, or throw listing the known names. */
98
112
  export declare function resolveNamedPreset<TPreset>(label: string, presetName: string, presets: Record<string, TPreset>): TPreset;
@@ -2,7 +2,8 @@
2
2
  * Image plugin (server part): sharp scale + convert per preset.
3
3
  */
4
4
  import { type ImageFormat, type ImageParameters, type ImagePreset } from '../../core/index.js';
5
- import { type ServerAssetTypePlugin } from './contract.js';
5
+ import { type ServerAssetTypePlugin, type ServerPluginConfigDefinition } from './contract.js';
6
+ export declare const imageConfigDefinition: ServerPluginConfigDefinition;
6
7
  /** The delivered file extension of an image-preset format. */
7
8
  export declare function extensionForImageFormat(format: ImageFormat): string;
8
9
  export declare const imageServerPlugin: ServerAssetTypePlugin<ImageParameters, ImagePreset>;
@@ -1,15 +1,28 @@
1
1
  /**
2
2
  * Image plugin (server part): sharp scale + convert per preset.
3
3
  */
4
- import { imagePlugin } from '../../core/index.js';
4
+ import { IMAGE_BUILT_IN_PRESETS, imagePlugin, validateImagePreset } from '../../core/index.js';
5
5
  import { processImageFile, readImageDimensions } from '../processing/image-processing.js';
6
+ import { mergePresets } from './config.js';
6
7
  import { requireProxyField, resolveNamedPreset } from './contract.js';
8
+ export const imageConfigDefinition = {
9
+ namespace: 'image',
10
+ resolve: ({ value }) => ({
11
+ presets: mergePresets({
12
+ label: 'image',
13
+ builtIn: IMAGE_BUILT_IN_PRESETS,
14
+ pluginConfig: value,
15
+ validatePreset: validateImagePreset
16
+ })
17
+ })
18
+ };
7
19
  /** The delivered file extension of an image-preset format. */
8
20
  export function extensionForImageFormat(format) {
9
21
  return format === 'jpeg' ? 'jpg' : format;
10
22
  }
11
23
  export const imageServerPlugin = {
12
24
  descriptor: imagePlugin,
25
+ configDefinitions: [imageConfigDefinition],
13
26
  pluginConfig: (config) => config.plugins.image,
14
27
  presets: (config) => config.plugins.image.presets,
15
28
  resolvePreset: (parameters, config) => resolveNamedPreset('image', parameters.preset, config.plugins.image.presets),
@@ -4,7 +4,8 @@
4
4
  * keeps the source dimensions. The slicing data is entry-only.
5
5
  */
6
6
  import { type ImagePreset, type SliceParameters } from '../../core/index.js';
7
- import { type ServerAssetTypePlugin } from './contract.js';
7
+ import { type ServerAssetTypePlugin, type ServerPluginConfigDefinition } from './contract.js';
8
+ export declare const sliceConfigDefinition: ServerPluginConfigDefinition;
8
9
  /**
9
10
  * Best-effort guess of the asset's export scale from a `@2x` / `@3x` suffix in
10
11
  * the file name (also tolerates `-`/`_` separators, e.g. `dialog-3x.png`).
@@ -3,10 +3,22 @@
3
3
  * never resized — insets are authored in source pixels, so the delivered proxy
4
4
  * keeps the source dimensions. The slicing data is entry-only.
5
5
  */
6
- import { defaultInsetsForSize, slicePlugin } from '../../core/index.js';
6
+ import { SLICE_BUILT_IN_PRESETS, defaultInsetsForSize, slicePlugin, validateImagePreset } from '../../core/index.js';
7
7
  import { processImageFile, readImageDimensions } from '../processing/image-processing.js';
8
8
  import { extensionForImageFormat } from './image.js';
9
+ import { mergePresets } from './config.js';
9
10
  import { requireProxyField, resolveNamedPreset } from './contract.js';
11
+ export const sliceConfigDefinition = {
12
+ namespace: 'slice',
13
+ resolve: ({ value }) => ({
14
+ presets: mergePresets({
15
+ label: 'slice',
16
+ builtIn: SLICE_BUILT_IN_PRESETS,
17
+ pluginConfig: value,
18
+ validatePreset: validateImagePreset
19
+ })
20
+ })
21
+ };
10
22
  function hasAnySide(insets) {
11
23
  if (!insets)
12
24
  return false;
@@ -24,6 +36,7 @@ export function detectPixelRatio(fileName) {
24
36
  }
25
37
  export const sliceServerPlugin = {
26
38
  descriptor: slicePlugin,
39
+ configDefinitions: [sliceConfigDefinition],
27
40
  pluginConfig: (config) => config.plugins.slice,
28
41
  presets: (config) => config.plugins.slice.presets,
29
42
  resolvePreset: (parameters, config) => resolveNamedPreset('slice', parameters.preset, config.plugins.slice.presets),
@@ -2,5 +2,6 @@
2
2
  * Video plugin (server part): one ffmpeg transcode per preset.
3
3
  */
4
4
  import { type VideoParameters, type VideoPreset } from '../../core/index.js';
5
- import { type ServerAssetTypePlugin } from './contract.js';
5
+ import { type ServerAssetTypePlugin, type ServerPluginConfigDefinition } from './contract.js';
6
+ export declare const videoConfigDefinition: ServerPluginConfigDefinition;
6
7
  export declare const videoServerPlugin: ServerAssetTypePlugin<VideoParameters, VideoPreset>;