@elurjs/kit 2.4.4

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 (255) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1330 -0
  4. package/bin/elur-kit.js +54 -0
  5. package/dist/lib/action/define.d.cts +72 -0
  6. package/dist/lib/action/define.d.ts +72 -0
  7. package/dist/lib/action/error-store.d.cts +29 -0
  8. package/dist/lib/action/error-store.d.ts +29 -0
  9. package/dist/lib/action/index.cjs +1 -0
  10. package/dist/lib/action/index.d.cts +64 -0
  11. package/dist/lib/action/index.d.ts +64 -0
  12. package/dist/lib/action/index.js +2 -0
  13. package/dist/lib/action/origin.d.cts +20 -0
  14. package/dist/lib/action/origin.d.ts +20 -0
  15. package/dist/lib/action/scan.d.cts +28 -0
  16. package/dist/lib/action/scan.d.ts +28 -0
  17. package/dist/lib/action/server.d.cts +30 -0
  18. package/dist/lib/action/server.d.ts +30 -0
  19. package/dist/lib/action-BB6N5yD-.js +111 -0
  20. package/dist/lib/action-BB6N5yD-.js.map +1 -0
  21. package/dist/lib/action-DZHihU3Z.cjs +2 -0
  22. package/dist/lib/action-DZHihU3Z.cjs.map +1 -0
  23. package/dist/lib/adapters/bun.cjs +30 -0
  24. package/dist/lib/adapters/bun.cjs.map +1 -0
  25. package/dist/lib/adapters/bun.d.cts +13 -0
  26. package/dist/lib/adapters/bun.d.ts +13 -0
  27. package/dist/lib/adapters/bun.js +65 -0
  28. package/dist/lib/adapters/bun.js.map +1 -0
  29. package/dist/lib/adapters/index.cjs +0 -0
  30. package/dist/lib/adapters/index.d.cts +30 -0
  31. package/dist/lib/adapters/index.d.ts +30 -0
  32. package/dist/lib/adapters/index.js +0 -0
  33. package/dist/lib/adapters/netlify.cjs +10 -0
  34. package/dist/lib/adapters/netlify.cjs.map +1 -0
  35. package/dist/lib/adapters/netlify.d.cts +13 -0
  36. package/dist/lib/adapters/netlify.d.ts +13 -0
  37. package/dist/lib/adapters/netlify.js +51 -0
  38. package/dist/lib/adapters/netlify.js.map +1 -0
  39. package/dist/lib/adapters/node.cjs +46 -0
  40. package/dist/lib/adapters/node.cjs.map +1 -0
  41. package/dist/lib/adapters/node.d.cts +13 -0
  42. package/dist/lib/adapters/node.d.ts +13 -0
  43. package/dist/lib/adapters/node.js +105 -0
  44. package/dist/lib/adapters/node.js.map +1 -0
  45. package/dist/lib/adapters/shared.d.cts +17 -0
  46. package/dist/lib/adapters/shared.d.ts +17 -0
  47. package/dist/lib/adapters/vercel.cjs +2 -0
  48. package/dist/lib/adapters/vercel.cjs.map +1 -0
  49. package/dist/lib/adapters/vercel.d.cts +9 -0
  50. package/dist/lib/adapters/vercel.d.ts +9 -0
  51. package/dist/lib/adapters/vercel.js +62 -0
  52. package/dist/lib/adapters/vercel.js.map +1 -0
  53. package/dist/lib/build/build.d.cts +83 -0
  54. package/dist/lib/build/build.d.ts +83 -0
  55. package/dist/lib/build/document-shell.d.cts +48 -0
  56. package/dist/lib/build/document-shell.d.ts +48 -0
  57. package/dist/lib/build/transform-source.d.cts +26 -0
  58. package/dist/lib/build/transform-source.d.ts +26 -0
  59. package/dist/lib/build/vite-build.d.cts +78 -0
  60. package/dist/lib/build/vite-build.d.ts +78 -0
  61. package/dist/lib/cache/policy.d.cts +25 -0
  62. package/dist/lib/cache/policy.d.ts +25 -0
  63. package/dist/lib/cache.d.cts +13 -0
  64. package/dist/lib/cache.d.ts +13 -0
  65. package/dist/lib/capabilities-C0TDQoMY.cjs +2 -0
  66. package/dist/lib/capabilities-C0TDQoMY.cjs.map +1 -0
  67. package/dist/lib/capabilities-C7txofmY.js +45 -0
  68. package/dist/lib/capabilities-C7txofmY.js.map +1 -0
  69. package/dist/lib/cli/commands.d.cts +15 -0
  70. package/dist/lib/cli/commands.d.ts +15 -0
  71. package/dist/lib/cli.cjs +5132 -0
  72. package/dist/lib/cli.cjs.map +1 -0
  73. package/dist/lib/cli.d.cts +30 -0
  74. package/dist/lib/cli.d.ts +30 -0
  75. package/dist/lib/cli.js +5131 -0
  76. package/dist/lib/cli.js.map +1 -0
  77. package/dist/lib/collections-Dbc8aRLf.js +233 -0
  78. package/dist/lib/collections-Dbc8aRLf.js.map +1 -0
  79. package/dist/lib/collections-VDy5g9p8.cjs +13 -0
  80. package/dist/lib/collections-VDy5g9p8.cjs.map +1 -0
  81. package/dist/lib/config/index.cjs +2 -0
  82. package/dist/lib/config/index.cjs.map +1 -0
  83. package/dist/lib/config/index.d.cts +100 -0
  84. package/dist/lib/config/index.d.ts +100 -0
  85. package/dist/lib/config/index.js +122 -0
  86. package/dist/lib/config/index.js.map +1 -0
  87. package/dist/lib/content/collections.d.cts +69 -0
  88. package/dist/lib/content/collections.d.ts +69 -0
  89. package/dist/lib/content/frontmatter.d.cts +20 -0
  90. package/dist/lib/content/frontmatter.d.ts +20 -0
  91. package/dist/lib/content/index.cjs +2 -0
  92. package/dist/lib/content/index.cjs.map +1 -0
  93. package/dist/lib/content/index.d.cts +5 -0
  94. package/dist/lib/content/index.d.ts +5 -0
  95. package/dist/lib/content/index.js +28 -0
  96. package/dist/lib/content/index.js.map +1 -0
  97. package/dist/lib/content/markdown.d.cts +6 -0
  98. package/dist/lib/content/markdown.d.ts +6 -0
  99. package/dist/lib/content/raw.d.cts +9 -0
  100. package/dist/lib/content/raw.d.ts +9 -0
  101. package/dist/lib/content/schema.d.cts +14 -0
  102. package/dist/lib/content/schema.d.ts +14 -0
  103. package/dist/lib/errors-0X7pCFNU.cjs +2 -0
  104. package/dist/lib/errors-0X7pCFNU.cjs.map +1 -0
  105. package/dist/lib/errors-ePYxDlhU.js +70 -0
  106. package/dist/lib/errors-ePYxDlhU.js.map +1 -0
  107. package/dist/lib/errors.d.cts +84 -0
  108. package/dist/lib/errors.d.ts +84 -0
  109. package/dist/lib/hydrate-8a3TbQbs.cjs +2 -0
  110. package/dist/lib/hydrate-8a3TbQbs.cjs.map +1 -0
  111. package/dist/lib/hydrate-CgZGrTxF.js +126 -0
  112. package/dist/lib/hydrate-CgZGrTxF.js.map +1 -0
  113. package/dist/lib/image/index.cjs +2 -0
  114. package/dist/lib/image/index.cjs.map +1 -0
  115. package/dist/lib/image/index.d.cts +37 -0
  116. package/dist/lib/image/index.d.ts +37 -0
  117. package/dist/lib/image/index.js +49 -0
  118. package/dist/lib/image/index.js.map +1 -0
  119. package/dist/lib/image/pipeline.d.cts +37 -0
  120. package/dist/lib/image/pipeline.d.ts +37 -0
  121. package/dist/lib/image/registry.cjs +2 -0
  122. package/dist/lib/image/registry.cjs.map +1 -0
  123. package/dist/lib/image/registry.d.cts +26 -0
  124. package/dist/lib/image/registry.d.ts +26 -0
  125. package/dist/lib/image/registry.js +23 -0
  126. package/dist/lib/image/registry.js.map +1 -0
  127. package/dist/lib/image/service.cjs +2 -0
  128. package/dist/lib/image/service.cjs.map +1 -0
  129. package/dist/lib/image/service.d.cts +164 -0
  130. package/dist/lib/image/service.d.ts +164 -0
  131. package/dist/lib/image/service.js +308 -0
  132. package/dist/lib/image/service.js.map +1 -0
  133. package/dist/lib/index.cjs +1 -0
  134. package/dist/lib/index.cjs.map +1 -0
  135. package/dist/lib/index.d.cts +38 -0
  136. package/dist/lib/index.d.ts +38 -0
  137. package/dist/lib/index.js +1 -0
  138. package/dist/lib/index.js.map +1 -0
  139. package/dist/lib/integrations/hooks.d.cts +58 -0
  140. package/dist/lib/integrations/hooks.d.ts +58 -0
  141. package/dist/lib/integrations/index.cjs +1 -0
  142. package/dist/lib/integrations/index.d.cts +18 -0
  143. package/dist/lib/integrations/index.d.ts +18 -0
  144. package/dist/lib/integrations/index.js +2 -0
  145. package/dist/lib/integrations-7dpN1RYB.js +35 -0
  146. package/dist/lib/integrations-7dpN1RYB.js.map +1 -0
  147. package/dist/lib/integrations-DDqfWjvH.cjs +2 -0
  148. package/dist/lib/integrations-DDqfWjvH.cjs.map +1 -0
  149. package/dist/lib/interpolation-plugin-Bt-KRKVY.js +365 -0
  150. package/dist/lib/interpolation-plugin-Bt-KRKVY.js.map +1 -0
  151. package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs +31 -0
  152. package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs.map +1 -0
  153. package/dist/lib/island/generate-entry.d.cts +27 -0
  154. package/dist/lib/island/generate-entry.d.ts +27 -0
  155. package/dist/lib/island/hydrate.d.cts +33 -0
  156. package/dist/lib/island/hydrate.d.ts +33 -0
  157. package/dist/lib/island/index.cjs +1 -0
  158. package/dist/lib/island/index.d.cts +2 -0
  159. package/dist/lib/island/index.d.ts +2 -0
  160. package/dist/lib/island/index.js +1 -0
  161. package/dist/lib/island/island.d.cts +32 -0
  162. package/dist/lib/island/island.d.ts +32 -0
  163. package/dist/lib/island/scan.d.cts +14 -0
  164. package/dist/lib/island/scan.d.ts +14 -0
  165. package/dist/lib/manifest/index.cjs +3 -0
  166. package/dist/lib/manifest/index.cjs.map +1 -0
  167. package/dist/lib/manifest/index.d.cts +18 -0
  168. package/dist/lib/manifest/index.d.ts +18 -0
  169. package/dist/lib/manifest/index.js +110 -0
  170. package/dist/lib/manifest/index.js.map +1 -0
  171. package/dist/lib/middleware/index.d.cts +56 -0
  172. package/dist/lib/middleware/index.d.ts +56 -0
  173. package/dist/lib/middleware/stream-boundary.d.cts +53 -0
  174. package/dist/lib/middleware/stream-boundary.d.ts +53 -0
  175. package/dist/lib/node-http-DRAUhO0c.js +617 -0
  176. package/dist/lib/node-http-DRAUhO0c.js.map +1 -0
  177. package/dist/lib/node-http-DhxguYyz.cjs +13 -0
  178. package/dist/lib/node-http-DhxguYyz.cjs.map +1 -0
  179. package/dist/lib/render/render-to-string.d.cts +12 -0
  180. package/dist/lib/render/render-to-string.d.ts +12 -0
  181. package/dist/lib/render/ssr-flag.d.cts +4 -0
  182. package/dist/lib/render/ssr-flag.d.ts +4 -0
  183. package/dist/lib/route-scanner-JY6YrNH5.cjs +2 -0
  184. package/dist/lib/route-scanner-JY6YrNH5.cjs.map +1 -0
  185. package/dist/lib/route-scanner-XcLId7lK.js +100 -0
  186. package/dist/lib/route-scanner-XcLId7lK.js.map +1 -0
  187. package/dist/lib/router/client.cjs +2 -0
  188. package/dist/lib/router/client.cjs.map +1 -0
  189. package/dist/lib/router/client.d.cts +47 -0
  190. package/dist/lib/router/client.d.ts +47 -0
  191. package/dist/lib/router/client.js +298 -0
  192. package/dist/lib/router/client.js.map +1 -0
  193. package/dist/lib/router/route-scanner.d.cts +50 -0
  194. package/dist/lib/router/route-scanner.d.ts +50 -0
  195. package/dist/lib/runtime/capabilities.d.cts +47 -0
  196. package/dist/lib/runtime/capabilities.d.ts +47 -0
  197. package/dist/lib/runtime/context.d.cts +140 -0
  198. package/dist/lib/runtime/context.d.ts +140 -0
  199. package/dist/lib/runtime/handler.d.cts +39 -0
  200. package/dist/lib/runtime/handler.d.ts +39 -0
  201. package/dist/lib/runtime/index.cjs +1 -0
  202. package/dist/lib/runtime/index.d.cts +6 -0
  203. package/dist/lib/runtime/index.d.ts +6 -0
  204. package/dist/lib/runtime/index.js +4 -0
  205. package/dist/lib/runtime/node-http.d.cts +2 -0
  206. package/dist/lib/runtime/node-http.d.ts +2 -0
  207. package/dist/lib/runtime/security-headers.d.cts +13 -0
  208. package/dist/lib/runtime/security-headers.d.ts +13 -0
  209. package/dist/lib/runtime/static.d.cts +1 -0
  210. package/dist/lib/runtime/static.d.ts +1 -0
  211. package/dist/lib/runtime-CrH5X-Es.cjs +2 -0
  212. package/dist/lib/runtime-CrH5X-Es.cjs.map +1 -0
  213. package/dist/lib/runtime-Ds2-wH6P.js +520 -0
  214. package/dist/lib/runtime-Ds2-wH6P.js.map +1 -0
  215. package/dist/lib/scan-CY2O9ty6.cjs +2 -0
  216. package/dist/lib/scan-CY2O9ty6.cjs.map +1 -0
  217. package/dist/lib/scan-CjMUgnsd.js +22 -0
  218. package/dist/lib/scan-CjMUgnsd.js.map +1 -0
  219. package/dist/lib/scan-DXzYtmRQ.cjs +2 -0
  220. package/dist/lib/scan-DXzYtmRQ.cjs.map +1 -0
  221. package/dist/lib/scan-Dn93ycXG.js +33 -0
  222. package/dist/lib/scan-Dn93ycXG.js.map +1 -0
  223. package/dist/lib/seo/index.cjs +10 -0
  224. package/dist/lib/seo/index.cjs.map +1 -0
  225. package/dist/lib/seo/index.d.cts +99 -0
  226. package/dist/lib/seo/index.d.ts +99 -0
  227. package/dist/lib/seo/index.js +41 -0
  228. package/dist/lib/seo/index.js.map +1 -0
  229. package/dist/lib/shared-CfqOLsKj.cjs +183 -0
  230. package/dist/lib/shared-CfqOLsKj.cjs.map +1 -0
  231. package/dist/lib/shared-DU3iQgr7.js +236 -0
  232. package/dist/lib/shared-DU3iQgr7.js.map +1 -0
  233. package/dist/lib/ssr/match.d.cts +25 -0
  234. package/dist/lib/ssr/match.d.ts +25 -0
  235. package/dist/lib/ssr/render.d.cts +62 -0
  236. package/dist/lib/ssr/render.d.ts +62 -0
  237. package/dist/lib/ssr/server.d.cts +41 -0
  238. package/dist/lib/ssr/server.d.ts +41 -0
  239. package/dist/lib/ssr/stream.d.cts +48 -0
  240. package/dist/lib/ssr/stream.d.ts +48 -0
  241. package/dist/lib/stream-CpOG052V.js +104 -0
  242. package/dist/lib/stream-CpOG052V.js.map +1 -0
  243. package/dist/lib/stream-DmZjYqHm.cjs +17 -0
  244. package/dist/lib/stream-DmZjYqHm.cjs.map +1 -0
  245. package/dist/lib/types.d.cts +97 -0
  246. package/dist/lib/types.d.ts +97 -0
  247. package/dist/lib/vite/index.cjs +2 -0
  248. package/dist/lib/vite/index.cjs.map +1 -0
  249. package/dist/lib/vite/index.d.cts +41 -0
  250. package/dist/lib/vite/index.d.ts +41 -0
  251. package/dist/lib/vite/index.js +229 -0
  252. package/dist/lib/vite/index.js.map +1 -0
  253. package/dist/lib/vite/interpolation-plugin.d.cts +64 -0
  254. package/dist/lib/vite/interpolation-plugin.d.ts +64 -0
  255. package/package.json +292 -0
@@ -0,0 +1,26 @@
1
+ import type { ImageManifest } from "./service.js";
2
+ export type ImageFormat = "webp" | "avif" | "jpeg" | "png";
3
+ export interface RegisteredImage {
4
+ src: string;
5
+ widths: number[];
6
+ formats: ImageFormat[];
7
+ }
8
+ /**
9
+ * Set the active image manifest. Called by the build pipeline after
10
+ * processing, or by the SSR server on startup.
11
+ */
12
+ export declare function setImageManifest(manifest: ImageManifest | null): void;
13
+ /**
14
+ * Returns the currently active image manifest (or null). Used by `image()`
15
+ * to decide between `<picture>` (manifest present) and plain `<img>`.
16
+ */
17
+ export declare function getActiveManifest(): ImageManifest | null;
18
+ /**
19
+ * Returns the images registered during the current render pass and clears
20
+ * the registry. Called by the build pipeline after rendering all pages.
21
+ */
22
+ export declare function consumeImageRegistry(): RegisteredImage[];
23
+ /**
24
+ * Registers an image for build-time processing (if sharp is available).
25
+ */
26
+ export declare function registerImage(src: string, widths: number[]): void;
@@ -0,0 +1,26 @@
1
+ import type { ImageManifest } from "./service.js";
2
+ export type ImageFormat = "webp" | "avif" | "jpeg" | "png";
3
+ export interface RegisteredImage {
4
+ src: string;
5
+ widths: number[];
6
+ formats: ImageFormat[];
7
+ }
8
+ /**
9
+ * Set the active image manifest. Called by the build pipeline after
10
+ * processing, or by the SSR server on startup.
11
+ */
12
+ export declare function setImageManifest(manifest: ImageManifest | null): void;
13
+ /**
14
+ * Returns the currently active image manifest (or null). Used by `image()`
15
+ * to decide between `<picture>` (manifest present) and plain `<img>`.
16
+ */
17
+ export declare function getActiveManifest(): ImageManifest | null;
18
+ /**
19
+ * Returns the images registered during the current render pass and clears
20
+ * the registry. Called by the build pipeline after rendering all pages.
21
+ */
22
+ export declare function consumeImageRegistry(): RegisteredImage[];
23
+ /**
24
+ * Registers an image for build-time processing (if sharp is available).
25
+ */
26
+ export declare function registerImage(src: string, widths: number[]): void;
@@ -0,0 +1,23 @@
1
+ //#region src/image/registry.ts
2
+ var e = [], t = null;
3
+ function n(e) {
4
+ t = e;
5
+ }
6
+ function r() {
7
+ return t;
8
+ }
9
+ function i() {
10
+ let t = [...e];
11
+ return e.length = 0, t;
12
+ }
13
+ function a(t, n) {
14
+ e.find((e) => e.src === t && e.widths.join(",") === n.join(",")) || e.push({
15
+ src: t,
16
+ widths: n,
17
+ formats: ["webp", "avif"]
18
+ });
19
+ }
20
+ //#endregion
21
+ export { i as consumeImageRegistry, r as getActiveManifest, a as registerImage, n as setImageManifest };
22
+
23
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../../src/image/registry.ts"],"sourcesContent":["// --- Shared image registry (build-time singleton) ---\n//\n// This module owns the mutable state the image pipeline relies on:\n// * `renderRegistry` — images registered by `image()` during a render pass.\n// * `activeManifest` — the processed manifest used to emit `<picture>`.\n//\n// It MUST stay in its own chunk (`dist/lib/image/registry.js`) so that both the\n// CLI bundle (`dist/lib/cli.js`) and the library entry (`dist/lib/image/index.js`)\n// import the *same* physical module instance. If the registry state is inlined\n// into the CLI, `consumeImageRegistry()` reads a different (empty) array than\n// the one the app's `image()` calls populated, and the two-pass build silently\n// processes nothing. Both vite configs externalize this chunk to enforce that.\n\nimport type { ImageManifest } from \"./service.js\";\n\nexport type ImageFormat = \"webp\" | \"avif\" | \"jpeg\" | \"png\";\n\nexport interface RegisteredImage {\n src: string;\n widths: number[];\n formats: ImageFormat[];\n}\n\nconst renderRegistry: RegisteredImage[] = [];\n\n/**\n * The active image manifest (if loaded). When set, image() emits real\n * <picture>/<source> markup from the manifest instead of a plain <img>.\n */\nlet activeManifest: ImageManifest | null = null;\n\n/**\n * Set the active image manifest. Called by the build pipeline after\n * processing, or by the SSR server on startup.\n */\nexport function setImageManifest(manifest: ImageManifest | null): void {\n activeManifest = manifest;\n}\n\n/**\n * Returns the currently active image manifest (or null). Used by `image()`\n * to decide between `<picture>` (manifest present) and plain `<img>`.\n */\nexport function getActiveManifest(): ImageManifest | null {\n return activeManifest;\n}\n\n/**\n * Returns the images registered during the current render pass and clears\n * the registry. Called by the build pipeline after rendering all pages.\n */\nexport function consumeImageRegistry(): RegisteredImage[] {\n const items = [...renderRegistry];\n renderRegistry.length = 0;\n return items;\n}\n\n/**\n * Registers an image for build-time processing (if sharp is available).\n */\nexport function registerImage(src: string, widths: number[]): void {\n // Avoid duplicates in the same render pass.\n const existing = renderRegistry.find(\n (r) => r.src === src && r.widths.join(\",\") === widths.join(\",\"),\n );\n if (!existing) {\n renderRegistry.push({ src, widths, formats: [\"webp\", \"avif\"] });\n }\n}\n"],"mappings":";AAuBA,IAAM,IAAoC,CAAC,GAMvC,IAAuC;AAM3C,SAAgB,EAAiB,GAAsC;CACrE,IAAiB;AACnB;AAMA,SAAgB,IAA0C;CACxD,OAAO;AACT;AAMA,SAAgB,IAA0C;CACxD,IAAM,IAAQ,CAAC,GAAG,CAAc;CAEhC,OADA,EAAe,SAAS,GACjB;AACT;AAKA,SAAgB,EAAc,GAAa,GAAwB;CAKjE,AAHiB,EAAe,MAC7B,MAAM,EAAE,QAAQ,KAAO,EAAE,OAAO,KAAK,GAAG,MAAM,EAAO,KAAK,GAAG,CAE3D,KACH,EAAe,KAAK;EAAE;EAAK;EAAQ,SAAS,CAAC,QAAQ,MAAM;CAAE,CAAC;AAElE"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:fs/promises"),t=require("node:path"),n=require("node:crypto");var r=`sharp-1`,i=`v1`,a=12,o=80,s=4,c;async function l(){if(c===null)return null;if(c)return c();try{let e=(await import(`sharp`)).default;return typeof e==`function`?(c=async()=>e,e):(c=null,null)}catch{return c=null,null}}async function u(){return await l()!==null}function d(e,t,o,s){let c=(0,n.createHash)(`sha256`).update(e).digest(`hex`),l=JSON.stringify({width:t,format:o,quality:s,withoutEnlargement:!0});return(0,n.createHash)(`sha256`).update(`${c}|${l}|${r}|${i}`).digest(`hex`).slice(0,a)}function f(e){return e.includes(`\0`)||e.includes(`\\`)||/%[0-9a-f]{2}/i.test(e)?!1:!e.replace(/^\/+/,``).split(`/`).some(e=>e===`..`||e===`.`||e===``)}function p(e,n){return n===e||n.startsWith(`${e}${t.sep}`)}function m(e,n,r){let i=(0,t.resolve)(e),a=(0,t.resolve)(n);if(!p(i,a))throw Error(`[elur-kit] Image ${r} escapes its allowed root (${a}).`)}function h(e){let t=0,n=[],r=()=>new Promise(r=>{t<e?(t++,r()):n.push(()=>{t++,r()})}),i=()=>{t--;let e=n.shift();e?e():t<0&&(t=0)};return{async run(e){await r();try{return await e()}finally{i()}}}}async function g(t,r){let i=`${t}.${process.pid}.${(0,n.randomBytes)(6).toString(`hex`)}.tmp`;try{await(0,e.writeFile)(i,r),await(0,e.rename)(i,t)}catch(t){throw await(0,e.rm)(i,{force:!0}).catch(()=>{}),t}}async function _(t){try{return await(0,e.stat)(t),!0}catch{return!1}}function v(e){return{capabilities:{encoding:!1,remote:!1,runtimeEndpoint:!1,filesystem:!0},async resolve(t,n){return y(t,{...e,publicDir:n.publicDir,outDir:n.outDir})}}}async function y(e,t){let{src:n,alt:r,widths:i,formats:a,quality:o,priority:s,loading:c,decoding:l,class:u,attributes:d={}}=e,f=i?.length?[...i]:[e.width??0],p=a?.length?[...a]:t.formats??[`webp`,`avif`],m=(await b([{src:n,widths:f,formats:p}],{...t,quality:o??t.quality})).manifest.entries[n],h=m?m.variants.map(e=>({url:e.url,width:e.width,height:e.height,format:e.format,size:e.size})):[],g=[];for(let e of p){let t=m?w(m,e):``;t&&g.push({type:e===`jpeg`?`image/jpeg`:`image/${e}`,srcset:t})}return{src:n,width:m?.width,height:m?.height,sources:g,attributes:{alt:r,width:m?.width??e.width,height:m?.height??e.height,loading:s?`eager`:c??`lazy`,decoding:l??`async`,...s?{fetchpriority:`high`}:{},...u?{class:u}:{},...d},generated:h}}async function b(r,i){let a=await l(),{publicDir:c,outDir:u,formats:p=[`webp`,`avif`],quality:v=o,strict:y=!1,concurrency:b=s,base:x=``}=i,C={},w=0,T=h(b),E=new Map,D=new Set,O=(e,t)=>{D.has(e)||(D.add(e),console.warn(`[elur-kit] ${t}`))};if(!a){for(let{src:i}of r){if(C[i])continue;if(!f(i)){if(y)throw Error(`[elur-kit] Invalid image source path: ${i}`);O(`path:${i}`,`Skipping invalid image source path: ${i}`);continue}let r=(0,t.join)(c,i.replace(/^\//,``));m(c,r,`source "${i}"`);try{let t=await(0,e.readFile)(r);C[i]={src:i,width:0,height:0,variants:[],hash:(0,n.createHash)(`sha256`).update(t).digest(`hex`).slice(0,8)}}catch{if(y)throw Error(`[elur-kit] Image source not found: ${i}`);O(`missing:${i}`,`Image source not found: ${i}. Skipping.`)}}let a={version:1,entries:C};return i.manifestPath&&await S(i.manifestPath,a),{manifest:a,count:0,optimized:!1}}for(let{src:i,widths:o,formats:s}of r){if(C[i])continue;if(!f(i)){if(y)throw Error(`[elur-kit] Invalid image source path: ${i}`);O(`path:${i}`,`Skipping invalid image source path: ${i}`);continue}let r=(0,t.join)(c,i.replace(/^\//,``));m(c,r,`source "${i}"`);let l;try{l=await(0,e.readFile)(r)}catch{if(y)throw Error(`[elur-kit] Image source not found: ${i}`);O(`missing:${i}`,`Image not found: ${i}. Skipping.`);continue}let h=(0,t.extname)(i),b=(0,t.basename)(i,h).replace(/[^a-zA-Z0-9._-]+/g,`-`),S=(0,t.dirname)(i),D=s?.length?s:p,k=0,A=0;try{let e=await a(l).metadata();k=e.width??0,A=e.height??0}catch{}let j=[],M=async(n,r)=>{if(k>0&&n>k)return;let i=d(l,n,r,v),o=`${b}.${i}.${n}w.${r}`,s=(0,t.join)(S,o),c=(0,t.join)(u,s.replace(/^\//,``));m(u,c,`variant "${s}"`);let f=`${x.replace(/\/$/,``)}/${s.replace(/\\/g,`/`).replace(/^\//,``)}`;if(await _(c))try{let t=await a(c).metadata();j.push({url:f,width:t.width??n,height:t.height??Math.round((t.height??0)||(A&&k?n*A/k:0)),format:r,size:(await(0,e.stat)(c)).size}),w++;return}catch{}let p=c;if(E.has(p)){await E.get(p),j.push({url:f,width:n,height:Math.round(A&&k?n*A/k:0),format:r,size:(await(0,e.stat)(c)).size}),w++;return}let h=(async()=>{try{let i=await a(l).resize({width:n,withoutEnlargement:!0}).toFormat(r,{quality:v}).toBuffer();await(0,e.mkdir)((0,t.dirname)(c),{recursive:!0}),await g(c,i)}catch(e){if(y)throw Error(`[elur-kit] Failed to generate ${o}: ${e instanceof Error?e.message:String(e)}`);O(`fail:${o}`,`Failed to generate ${o}.`);return}j.push({url:f,width:n,height:Math.round(A&&k?n*A/k:0),format:r,size:(await(0,e.stat)(c)).size}),w++})().finally(()=>E.delete(p));E.set(p,h),await T.run(()=>h)},N=[];for(let e of o)for(let t of D)N.push(M(e,t));await Promise.all(N),C[i]={src:i,width:k,height:A,variants:j,hash:(0,n.createHash)(`sha256`).update(l).digest(`hex`).slice(0,8)}}let k={version:1,entries:C};return i.manifestPath&&await S(i.manifestPath,k),{manifest:k,count:w,optimized:!0}}async function x(t){try{let n=await(0,e.readFile)(t,`utf8`);return JSON.parse(n)}catch{return{version:1,entries:{}}}}async function S(n,r){await(0,e.mkdir)((0,t.dirname)(n),{recursive:!0}),await g(n,JSON.stringify(r,null,2))}function C(e,t){return e.entries[t]}function w(e,t){return e.variants.filter(e=>e.format===t).map(e=>`${e.url} ${e.width}w`).join(`, `)}function T(e,t){let{alt:n,sizes:r,priority:i=!1,class:a,attributes:o={},fallbackSrc:s=e.src,fallbackWidth:c=e.width,fallbackHeight:l=e.height}=t,u=[...new Set(e.variants.map(e=>e.format))],d=i?``:` loading="lazy"`,f=i?` fetchpriority="high"`:``,p=r?` sizes="${D(r)}"`:``,m=a?` class="${D(a)}"`:``,h=Object.entries(o).map(([e,t])=>` ${D(e)}="${D(String(t))}"`).join(``),g=u.map(t=>{let n=w(e,t);if(!n)return``;let r=t===`jpeg`?`image/jpeg`:`image/${t}`;return`<source srcset="${n}"${p} type="${r}" />`}).filter(Boolean).join(``),_=`<img src="${D(s)}" alt="${D(n)}" width="${c}" height="${l}"${d} decoding="async"${f}${m}${h} />`;return g?`<picture>${g}${_}</picture>`:_}async function E(n,r){let i=[];for(let a of Object.values(n.entries))for(let n of a.variants){let a=n.url.replace(/^\/+/,``),o=(0,t.resolve)(r,a);if(!p((0,t.resolve)(r),o)){i.push(n.url);continue}try{await(0,e.stat)(o)}catch{i.push(n.url)}}return i}function D(e){return e.replace(/&/g,`&amp;`).replace(/"/g,`&quot;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`)}exports.buildPictureMarkup=T,exports.buildSrcset=w,exports.createImageService=v,exports.getImage=y,exports.getManifestEntry=C,exports.isSharpAvailable=u,exports.processImageBatch=b,exports.readManifest=x,exports.transformHash=d,exports.validateManifestUrls=E,exports.writeManifest=S;
2
+ //# sourceMappingURL=service.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.cjs","names":[],"sources":["../../../src/image/service.ts"],"sourcesContent":["import { readFile, mkdir, writeFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join, dirname, extname, basename, resolve, sep } from \"node:path\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { ImageFormat } from \"./index.js\";\n\n// --- ImageService: metadata-driven image processing and manifest ---\n//\n// * Reads real image dimensions from the source file (sharp metadata).\n// * Generates hashed variant filenames from a SHA-256 transform key that\n// incorporates: content digest + normalized transform options +\n// encoder/service version + output naming version (§4.2).\n// * Applies path containment for sources and outputs (no traversal, no NUL,\n// no symlink escape, no Unicode/separator tricks).\n// * Writes outputs atomically (temp + rename) with single-flight per\n// transform key and a bounded concurrency pool.\n// * Supports `strict` mode: fails the build on missing sources or failed\n// transforms instead of emitting a partially-written variant.\n// * Falls back gracefully when sharp is not installed.\n\nconst ENCODER_VERSION = \"sharp-1\";\nconst NAMING_VERSION = \"v1\";\nconst HASH_LENGTH = 12;\nconst DEFAULT_QUALITY = 80;\nconst DEFAULT_CONCURRENCY = 4;\n\nexport interface ImageVariant {\n /** URL path relative to the site root, e.g. \"/images/hero.abc123def456.800w.webp\". */\n url: string;\n /** Width in pixels. */\n width: number;\n /** Height in pixels (preserves aspect ratio). */\n height: number;\n /** Format of the variant. */\n format: ImageFormat;\n /** File size in bytes. */\n size: number;\n}\n\nexport interface ImageEntry {\n /** Original source URL, e.g. \"/images/hero.jpg\". */\n src: string;\n /** Intrinsic width of the source. */\n width: number;\n /** Intrinsic height of the source. */\n height: number;\n /** All generated variants. */\n variants: ImageVariant[];\n /** Content hash of the source file. */\n hash: string;\n}\n\nexport interface ImageManifest {\n version: 1;\n entries: Record<string, ImageEntry>;\n}\n\nexport interface ProcessOptions {\n /** Absolute path to the public directory (source images). */\n publicDir: string;\n /** Absolute path to the output directory. */\n outDir: string;\n /** Formats to generate. Defaults to [\"webp\", \"avif\"]. */\n formats?: ImageFormat[];\n /** Quality (1-100). Defaults to 80. */\n quality?: number;\n /** Path to write the manifest JSON. */\n manifestPath?: string;\n /** When true, missing sources or failed transforms fail the build. */\n strict?: boolean;\n /** Max concurrent sharp transforms. Defaults to 4. */\n concurrency?: number;\n /** Optional URL base prefix applied to variant URLs. */\n base?: string;\n}\n\nexport interface ProcessResult {\n manifest: ImageManifest;\n /** Number of variants generated. */\n count: number;\n /** Whether sharp was available. */\n optimized: boolean;\n}\n\nlet sharpLoader: (() => Promise<any>) | null | undefined;\n\nasync function loadSharp(): Promise<any | null> {\n if (sharpLoader === null) return null;\n if (sharpLoader) return sharpLoader();\n try {\n // @ts-ignore — `sharp` is an optional peer dependency.\n const mod = await import(\"sharp\");\n const sharp = mod.default;\n if (typeof sharp !== \"function\") {\n sharpLoader = null;\n return null;\n }\n sharpLoader = async () => sharp;\n return sharp;\n } catch {\n sharpLoader = null;\n return null;\n }\n}\n\nexport async function isSharpAvailable(): Promise<boolean> {\n const sharp = await loadSharp();\n return sharp !== null;\n}\n\n// --- Transform identity (§4.2) ---\n\n/**\n * SHA-256 transform key. Stable for identical content+options and invalidated\n * whenever the source bytes, the effective transform options, the encoder\n * version or the naming scheme change.\n */\nexport function transformHash(sourceBuffer: Buffer, width: number, format: ImageFormat, quality: number): string {\n const contentDigest = createHash(\"sha256\").update(sourceBuffer).digest(\"hex\");\n const normalizedOptions = JSON.stringify({\n width,\n format,\n quality,\n withoutEnlargement: true,\n });\n return createHash(\"sha256\")\n .update(`${contentDigest}|${normalizedOptions}|${ENCODER_VERSION}|${NAMING_VERSION}`)\n .digest(\"hex\")\n .slice(0, HASH_LENGTH);\n}\n\n// --- Path containment (§9.5) ---\n\nfunction isSafeRelativePath(value: string): boolean {\n if (value.includes(\"\\0\") || value.includes(\"\\\\\")) return false;\n if (/%[0-9a-f]{2}/i.test(value)) return false;\n const segments = value.replace(/^\\/+/, \"\").split(\"/\");\n return !segments.some((segment) => segment === \"..\" || segment === \".\" || segment === \"\");\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction assertInside(root: string, candidate: string, label: string): void {\n const resolvedRoot = resolve(root);\n const resolvedCandidate = resolve(candidate);\n if (!isInside(resolvedRoot, resolvedCandidate)) {\n throw new Error(`[elur-kit] Image ${label} escapes its allowed root (${resolvedCandidate}).`);\n }\n}\n\n// --- Concurrency: bounded pool + single-flight ---\n\nfunction createPool(limit: number) {\n let active = 0;\n const waiters: Array<() => void> = [];\n const acquire = () =>\n new Promise<void>((resolve) => {\n if (active < limit) {\n active++;\n resolve();\n } else {\n waiters.push(() => {\n active++;\n resolve();\n });\n }\n });\n const release = () => {\n active--;\n const next = waiters.shift();\n if (next) next();\n else if (active < 0) active = 0;\n };\n return {\n async run<T>(fn: () => Promise<T>): Promise<T> {\n await acquire();\n try {\n return await fn();\n } finally {\n release();\n }\n },\n };\n}\n\n// --- Atomic writes (§9.6) ---\n\nasync function atomicWriteFile(path: string, data: Buffer | string): Promise<void> {\n const temp = `${path}.${process.pid}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n await writeFile(temp, data);\n await rename(temp, path);\n } catch (error) {\n await rm(temp, { force: true }).catch(() => { });\n throw error;\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n// --- Public programmatic API (§3.1 / §3.3) ---\n\nexport interface ImageRequest {\n src: string;\n alt: string;\n widths?: readonly number[];\n formats?: readonly ImageFormat[];\n sizes?: string;\n width?: number;\n height?: number;\n priority?: boolean;\n loading?: \"lazy\" | \"eager\";\n decoding?: \"async\" | \"sync\" | \"auto\";\n quality?: number;\n fit?: string;\n class?: string;\n attributes?: Record<string, unknown>;\n}\n\nexport interface GeneratedImage {\n url: string;\n width: number;\n height: number;\n format: ImageFormat;\n size: number;\n}\n\nexport interface ImageMetadata {\n src: string;\n width?: number;\n height?: number;\n sources: Array<{ type: string; srcset: string }>;\n attributes: Record<string, string | number | boolean | undefined>;\n generated: readonly GeneratedImage[];\n}\n\nexport interface ImageServiceContext {\n publicDir: string;\n outDir: string;\n manifest: ImageManifest;\n}\n\nexport interface ImageServiceCapabilities {\n /** Whether the encoder (sharp) is available. */\n encoding: boolean;\n /** Whether remote images can be fetched. */\n remote: boolean;\n /** Whether a runtime image endpoint exists. */\n runtimeEndpoint: boolean;\n /** Whether the host exposes a writable filesystem. */\n filesystem: boolean;\n}\n\nexport interface ImageService {\n resolve(request: ImageRequest, context: ImageServiceContext): Promise<ImageMetadata>;\n capabilities: ImageServiceCapabilities;\n}\n\n/**\n * Creates a build-time ImageService bound to a public/output directory pair.\n */\nexport function createImageService(options: ProcessOptions): ImageService {\n return {\n capabilities: {\n encoding: false,\n remote: false,\n runtimeEndpoint: false,\n filesystem: true,\n },\n async resolve(request, context) {\n return getImage(request, { ...options, publicDir: context.publicDir, outDir: context.outDir });\n },\n };\n}\n\n/**\n * Programmatic async image API (§3.1). Ensures the requested variants exist on\n * disk (build-time), then returns deterministic metadata (not opaque markup).\n */\nexport async function getImage(\n request: ImageRequest,\n options: ProcessOptions,\n): Promise<ImageMetadata> {\n const { src, alt, widths, formats, quality, priority, loading, decoding, class: className, attributes = {} } = request;\n const targetWidths = widths?.length ? [...widths] : [request.width ?? 0];\n const targetFormats = formats?.length ? [...formats] : options.formats ?? [\"webp\", \"avif\"];\n const result = await processImageBatch(\n [{ src, widths: targetWidths, formats: targetFormats }],\n { ...options, quality: quality ?? options.quality },\n );\n const entry = result.manifest.entries[src];\n const generated: GeneratedImage[] = entry\n ? entry.variants.map((v) => ({ url: v.url, width: v.width, height: v.height, format: v.format, size: v.size }))\n : [];\n\n const sources: ImageMetadata[\"sources\"] = [];\n for (const format of targetFormats) {\n const srcset = entry ? buildSrcset(entry, format) : \"\";\n if (srcset) sources.push({ type: format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`, srcset });\n }\n\n return {\n src,\n width: entry?.width,\n height: entry?.height,\n sources,\n attributes: {\n alt,\n width: entry?.width ?? request.width,\n height: entry?.height ?? request.height,\n loading: priority ? \"eager\" : (loading ?? \"lazy\"),\n decoding: decoding ?? \"async\",\n ...(priority ? { fetchpriority: \"high\" } : {}),\n ...(className ? { class: className } : {}),\n ...attributes,\n },\n generated,\n };\n}\n\n// --- Batch processing ---\n\nexport async function processImageBatch(\n images: { src: string; widths: number[]; formats?: ImageFormat[] }[],\n options: ProcessOptions,\n): Promise<ProcessResult> {\n const sharp = await loadSharp();\n const {\n publicDir,\n outDir,\n formats = [\"webp\", \"avif\"],\n quality = DEFAULT_QUALITY,\n strict = false,\n concurrency = DEFAULT_CONCURRENCY,\n base = \"\",\n } = options;\n const entries: Record<string, ImageEntry> = {};\n let count = 0;\n const pool = createPool(concurrency);\n const inFlight = new Map<string, Promise<void>>();\n\n const warned = new Set<string>();\n const warnOnce = (key: string, message: string): void => {\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[elur-kit] ${message}`);\n };\n\n if (!sharp) {\n // Without sharp, build a manifest with only the original source entries.\n for (const { src } of images) {\n if (entries[src]) continue;\n if (!isSafeRelativePath(src)) {\n if (strict) throw new Error(`[elur-kit] Invalid image source path: ${src}`);\n warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n continue;\n }\n const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n assertInside(publicDir, sourcePath, `source \"${src}\"`);\n try {\n const buffer = await readFile(sourcePath);\n entries[src] = {\n src,\n width: 0,\n height: 0,\n variants: [],\n hash: createHash(\"sha256\").update(buffer).digest(\"hex\").slice(0, 8),\n };\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Image source not found: ${src}`);\n warnOnce(`missing:${src}`, `Image source not found: ${src}. Skipping.`);\n }\n }\n const manifest: ImageManifest = { version: 1, entries };\n if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n return { manifest, count: 0, optimized: false };\n }\n\n for (const { src, widths, formats: imgFormats } of images) {\n if (entries[src]) continue;\n\n if (!isSafeRelativePath(src)) {\n if (strict) throw new Error(`[elur-kit] Invalid image source path: ${src}`);\n warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n continue;\n }\n\n const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n assertInside(publicDir, sourcePath, `source \"${src}\"`);\n\n let sourceBuffer: Buffer;\n try {\n sourceBuffer = await readFile(sourcePath);\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Image source not found: ${src}`);\n warnOnce(`missing:${src}`, `Image not found: ${src}. Skipping.`);\n continue;\n }\n\n const ext = extname(src);\n const safeBase = basename(src, ext).replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n const dir = dirname(src);\n const targetFormats = imgFormats?.length ? imgFormats : formats;\n\n // Read real metadata from the source.\n let sourceWidth = 0;\n let sourceHeight = 0;\n try {\n const meta = await sharp(sourceBuffer).metadata();\n sourceWidth = meta.width ?? 0;\n sourceHeight = meta.height ?? 0;\n } catch {\n // Fallback: no metadata.\n }\n\n const variants: ImageVariant[] = [];\n\n const processVariant = async (width: number, format: ImageFormat): Promise<void> => {\n // Never upscale: skip widths larger than the source.\n if (sourceWidth > 0 && width > sourceWidth) return;\n\n const hash = transformHash(sourceBuffer, width, format, quality);\n const variantName = `${safeBase}.${hash}.${width}w.${format}`;\n const variantRelPath = join(dir, variantName);\n const variantAbsPath = join(outDir, variantRelPath.replace(/^\\//, \"\"));\n assertInside(outDir, variantAbsPath, `variant \"${variantRelPath}\"`);\n const variantUrl = `${base.replace(/\\/$/, \"\")}/${variantRelPath.replace(/\\\\/g, \"/\").replace(/^\\//, \"\")}`;\n\n // Reuse an existing, valid output file (validated, not guessed).\n if (await fileExists(variantAbsPath)) {\n try {\n const info = await sharp(variantAbsPath).metadata();\n variants.push({\n url: variantUrl,\n width: info.width ?? width,\n height: info.height ?? Math.round((info.height ?? 0) || (sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0)),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n return;\n } catch {\n // Existing file invalid — regenerate below.\n }\n }\n\n const key = variantAbsPath;\n if (inFlight.has(key)) {\n await inFlight.get(key);\n variants.push({\n url: variantUrl,\n width,\n height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n return;\n }\n\n const task = (async () => {\n try {\n const buffer = await sharp(sourceBuffer)\n .resize({ width, withoutEnlargement: true })\n .toFormat(format, { quality })\n .toBuffer();\n await mkdir(dirname(variantAbsPath), { recursive: true });\n await atomicWriteFile(variantAbsPath, buffer);\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Failed to generate ${variantName}: ${error instanceof Error ? error.message : String(error)}`);\n warnOnce(`fail:${variantName}`, `Failed to generate ${variantName}.`);\n return;\n }\n variants.push({\n url: variantUrl,\n width,\n height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n })().finally(() => inFlight.delete(key));\n\n inFlight.set(key, task);\n await pool.run(() => task);\n };\n\n const tasks: Promise<void>[] = [];\n for (const width of widths) {\n for (const format of targetFormats) {\n tasks.push(processVariant(width, format));\n }\n }\n await Promise.all(tasks);\n\n entries[src] = {\n src,\n width: sourceWidth,\n height: sourceHeight,\n variants,\n hash: createHash(\"sha256\").update(sourceBuffer).digest(\"hex\").slice(0, 8),\n };\n }\n\n const manifest: ImageManifest = { version: 1, entries };\n if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n return { manifest, count, optimized: true };\n}\n\n/**\n * Read a manifest from disk, or return an empty one if it doesn't exist.\n */\nexport async function readManifest(path: string): Promise<ImageManifest> {\n try {\n const data = await readFile(path, \"utf8\");\n return JSON.parse(data) as ImageManifest;\n } catch {\n return { version: 1, entries: {} };\n }\n}\n\n/**\n * Write a manifest to disk atomically.\n */\nexport async function writeManifest(path: string, manifest: ImageManifest): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await atomicWriteFile(path, JSON.stringify(manifest, null, 2));\n}\n\n/**\n * Look up an image entry in the manifest by its source URL.\n */\nexport function getManifestEntry(manifest: ImageManifest, src: string): ImageEntry | undefined {\n return manifest.entries[src];\n}\n\n/**\n * Build a srcset string from manifest variants of a given format.\n * Returns e.g. \"/images/hero.abc.400w.webp 400w, /images/hero.abc.800w.webp 800w\".\n */\nexport function buildSrcset(entry: ImageEntry, format: ImageFormat): string {\n return entry.variants\n .filter((v) => v.format === format)\n .map((v) => `${v.url} ${v.width}w`)\n .join(\", \");\n}\n\n/**\n * Build the full <picture> markup for an image entry, with <source> per format\n * and a fallback <img>.\n */\nexport function buildPictureMarkup(entry: ImageEntry, opts: {\n alt: string;\n sizes?: string;\n priority?: boolean;\n class?: string;\n attributes?: Record<string, string>;\n fallbackSrc?: string;\n fallbackWidth?: number;\n fallbackHeight?: number;\n}): string {\n const {\n alt,\n sizes,\n priority = false,\n class: className,\n attributes = {},\n fallbackSrc = entry.src,\n fallbackWidth = entry.width,\n fallbackHeight = entry.height,\n } = opts;\n\n const formats = [...new Set(entry.variants.map((v) => v.format))];\n const loadingAttr = priority ? \"\" : ' loading=\"lazy\"';\n const fetchPriorityAttr = priority ? ' fetchpriority=\"high\"' : \"\";\n const sizesAttr = sizes ? ` sizes=\"${escapeAttr(sizes)}\"` : \"\";\n const classAttr = className ? ` class=\"${escapeAttr(className)}\"` : \"\";\n const extraAttrs = Object.entries(attributes)\n .map(([key, value]) => ` ${escapeAttr(key)}=\"${escapeAttr(String(value))}\"`)\n .join(\"\");\n\n const sources = formats\n .map((format) => {\n const srcset = buildSrcset(entry, format);\n if (!srcset) return \"\";\n const type = format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`;\n return `<source srcset=\"${srcset}\"${sizesAttr} type=\"${type}\" />`;\n })\n .filter(Boolean)\n .join(\"\");\n\n const img = `<img src=\"${escapeAttr(fallbackSrc)}\" alt=\"${escapeAttr(alt)}\" width=\"${fallbackWidth}\" height=\"${fallbackHeight}\"${loadingAttr} decoding=\"async\"${fetchPriorityAttr}${classAttr}${extraAttrs} />`;\n\n return sources ? `<picture>${sources}${img}</picture>` : img;\n}\n\n/**\n * Validate that every variant URL in the manifest corresponds to a real file\n * in the output directory. Returns a list of missing URLs.\n */\nexport async function validateManifestUrls(\n manifest: ImageManifest,\n outDir: string,\n): Promise<string[]> {\n const missing: string[] = [];\n for (const entry of Object.values(manifest.entries)) {\n for (const variant of entry.variants) {\n const relative = variant.url.replace(/^\\/+/, \"\");\n const resolved = resolve(outDir, relative);\n if (!isInside(resolve(outDir), resolved)) {\n missing.push(variant.url);\n continue;\n }\n try {\n await stat(resolved);\n } catch {\n missing.push(variant.url);\n }\n }\n }\n return missing;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n"],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ import type { ImageFormat } from "./index.js";
2
+ export interface ImageVariant {
3
+ /** URL path relative to the site root, e.g. "/images/hero.abc123def456.800w.webp". */
4
+ url: string;
5
+ /** Width in pixels. */
6
+ width: number;
7
+ /** Height in pixels (preserves aspect ratio). */
8
+ height: number;
9
+ /** Format of the variant. */
10
+ format: ImageFormat;
11
+ /** File size in bytes. */
12
+ size: number;
13
+ }
14
+ export interface ImageEntry {
15
+ /** Original source URL, e.g. "/images/hero.jpg". */
16
+ src: string;
17
+ /** Intrinsic width of the source. */
18
+ width: number;
19
+ /** Intrinsic height of the source. */
20
+ height: number;
21
+ /** All generated variants. */
22
+ variants: ImageVariant[];
23
+ /** Content hash of the source file. */
24
+ hash: string;
25
+ }
26
+ export interface ImageManifest {
27
+ version: 1;
28
+ entries: Record<string, ImageEntry>;
29
+ }
30
+ export interface ProcessOptions {
31
+ /** Absolute path to the public directory (source images). */
32
+ publicDir: string;
33
+ /** Absolute path to the output directory. */
34
+ outDir: string;
35
+ /** Formats to generate. Defaults to ["webp", "avif"]. */
36
+ formats?: ImageFormat[];
37
+ /** Quality (1-100). Defaults to 80. */
38
+ quality?: number;
39
+ /** Path to write the manifest JSON. */
40
+ manifestPath?: string;
41
+ /** When true, missing sources or failed transforms fail the build. */
42
+ strict?: boolean;
43
+ /** Max concurrent sharp transforms. Defaults to 4. */
44
+ concurrency?: number;
45
+ /** Optional URL base prefix applied to variant URLs. */
46
+ base?: string;
47
+ }
48
+ export interface ProcessResult {
49
+ manifest: ImageManifest;
50
+ /** Number of variants generated. */
51
+ count: number;
52
+ /** Whether sharp was available. */
53
+ optimized: boolean;
54
+ }
55
+ export declare function isSharpAvailable(): Promise<boolean>;
56
+ /**
57
+ * SHA-256 transform key. Stable for identical content+options and invalidated
58
+ * whenever the source bytes, the effective transform options, the encoder
59
+ * version or the naming scheme change.
60
+ */
61
+ export declare function transformHash(sourceBuffer: Buffer, width: number, format: ImageFormat, quality: number): string;
62
+ export interface ImageRequest {
63
+ src: string;
64
+ alt: string;
65
+ widths?: readonly number[];
66
+ formats?: readonly ImageFormat[];
67
+ sizes?: string;
68
+ width?: number;
69
+ height?: number;
70
+ priority?: boolean;
71
+ loading?: "lazy" | "eager";
72
+ decoding?: "async" | "sync" | "auto";
73
+ quality?: number;
74
+ fit?: string;
75
+ class?: string;
76
+ attributes?: Record<string, unknown>;
77
+ }
78
+ export interface GeneratedImage {
79
+ url: string;
80
+ width: number;
81
+ height: number;
82
+ format: ImageFormat;
83
+ size: number;
84
+ }
85
+ export interface ImageMetadata {
86
+ src: string;
87
+ width?: number;
88
+ height?: number;
89
+ sources: Array<{
90
+ type: string;
91
+ srcset: string;
92
+ }>;
93
+ attributes: Record<string, string | number | boolean | undefined>;
94
+ generated: readonly GeneratedImage[];
95
+ }
96
+ export interface ImageServiceContext {
97
+ publicDir: string;
98
+ outDir: string;
99
+ manifest: ImageManifest;
100
+ }
101
+ export interface ImageServiceCapabilities {
102
+ /** Whether the encoder (sharp) is available. */
103
+ encoding: boolean;
104
+ /** Whether remote images can be fetched. */
105
+ remote: boolean;
106
+ /** Whether a runtime image endpoint exists. */
107
+ runtimeEndpoint: boolean;
108
+ /** Whether the host exposes a writable filesystem. */
109
+ filesystem: boolean;
110
+ }
111
+ export interface ImageService {
112
+ resolve(request: ImageRequest, context: ImageServiceContext): Promise<ImageMetadata>;
113
+ capabilities: ImageServiceCapabilities;
114
+ }
115
+ /**
116
+ * Creates a build-time ImageService bound to a public/output directory pair.
117
+ */
118
+ export declare function createImageService(options: ProcessOptions): ImageService;
119
+ /**
120
+ * Programmatic async image API (§3.1). Ensures the requested variants exist on
121
+ * disk (build-time), then returns deterministic metadata (not opaque markup).
122
+ */
123
+ export declare function getImage(request: ImageRequest, options: ProcessOptions): Promise<ImageMetadata>;
124
+ export declare function processImageBatch(images: {
125
+ src: string;
126
+ widths: number[];
127
+ formats?: ImageFormat[];
128
+ }[], options: ProcessOptions): Promise<ProcessResult>;
129
+ /**
130
+ * Read a manifest from disk, or return an empty one if it doesn't exist.
131
+ */
132
+ export declare function readManifest(path: string): Promise<ImageManifest>;
133
+ /**
134
+ * Write a manifest to disk atomically.
135
+ */
136
+ export declare function writeManifest(path: string, manifest: ImageManifest): Promise<void>;
137
+ /**
138
+ * Look up an image entry in the manifest by its source URL.
139
+ */
140
+ export declare function getManifestEntry(manifest: ImageManifest, src: string): ImageEntry | undefined;
141
+ /**
142
+ * Build a srcset string from manifest variants of a given format.
143
+ * Returns e.g. "/images/hero.abc.400w.webp 400w, /images/hero.abc.800w.webp 800w".
144
+ */
145
+ export declare function buildSrcset(entry: ImageEntry, format: ImageFormat): string;
146
+ /**
147
+ * Build the full <picture> markup for an image entry, with <source> per format
148
+ * and a fallback <img>.
149
+ */
150
+ export declare function buildPictureMarkup(entry: ImageEntry, opts: {
151
+ alt: string;
152
+ sizes?: string;
153
+ priority?: boolean;
154
+ class?: string;
155
+ attributes?: Record<string, string>;
156
+ fallbackSrc?: string;
157
+ fallbackWidth?: number;
158
+ fallbackHeight?: number;
159
+ }): string;
160
+ /**
161
+ * Validate that every variant URL in the manifest corresponds to a real file
162
+ * in the output directory. Returns a list of missing URLs.
163
+ */
164
+ export declare function validateManifestUrls(manifest: ImageManifest, outDir: string): Promise<string[]>;
@@ -0,0 +1,164 @@
1
+ import type { ImageFormat } from "./index.js";
2
+ export interface ImageVariant {
3
+ /** URL path relative to the site root, e.g. "/images/hero.abc123def456.800w.webp". */
4
+ url: string;
5
+ /** Width in pixels. */
6
+ width: number;
7
+ /** Height in pixels (preserves aspect ratio). */
8
+ height: number;
9
+ /** Format of the variant. */
10
+ format: ImageFormat;
11
+ /** File size in bytes. */
12
+ size: number;
13
+ }
14
+ export interface ImageEntry {
15
+ /** Original source URL, e.g. "/images/hero.jpg". */
16
+ src: string;
17
+ /** Intrinsic width of the source. */
18
+ width: number;
19
+ /** Intrinsic height of the source. */
20
+ height: number;
21
+ /** All generated variants. */
22
+ variants: ImageVariant[];
23
+ /** Content hash of the source file. */
24
+ hash: string;
25
+ }
26
+ export interface ImageManifest {
27
+ version: 1;
28
+ entries: Record<string, ImageEntry>;
29
+ }
30
+ export interface ProcessOptions {
31
+ /** Absolute path to the public directory (source images). */
32
+ publicDir: string;
33
+ /** Absolute path to the output directory. */
34
+ outDir: string;
35
+ /** Formats to generate. Defaults to ["webp", "avif"]. */
36
+ formats?: ImageFormat[];
37
+ /** Quality (1-100). Defaults to 80. */
38
+ quality?: number;
39
+ /** Path to write the manifest JSON. */
40
+ manifestPath?: string;
41
+ /** When true, missing sources or failed transforms fail the build. */
42
+ strict?: boolean;
43
+ /** Max concurrent sharp transforms. Defaults to 4. */
44
+ concurrency?: number;
45
+ /** Optional URL base prefix applied to variant URLs. */
46
+ base?: string;
47
+ }
48
+ export interface ProcessResult {
49
+ manifest: ImageManifest;
50
+ /** Number of variants generated. */
51
+ count: number;
52
+ /** Whether sharp was available. */
53
+ optimized: boolean;
54
+ }
55
+ export declare function isSharpAvailable(): Promise<boolean>;
56
+ /**
57
+ * SHA-256 transform key. Stable for identical content+options and invalidated
58
+ * whenever the source bytes, the effective transform options, the encoder
59
+ * version or the naming scheme change.
60
+ */
61
+ export declare function transformHash(sourceBuffer: Buffer, width: number, format: ImageFormat, quality: number): string;
62
+ export interface ImageRequest {
63
+ src: string;
64
+ alt: string;
65
+ widths?: readonly number[];
66
+ formats?: readonly ImageFormat[];
67
+ sizes?: string;
68
+ width?: number;
69
+ height?: number;
70
+ priority?: boolean;
71
+ loading?: "lazy" | "eager";
72
+ decoding?: "async" | "sync" | "auto";
73
+ quality?: number;
74
+ fit?: string;
75
+ class?: string;
76
+ attributes?: Record<string, unknown>;
77
+ }
78
+ export interface GeneratedImage {
79
+ url: string;
80
+ width: number;
81
+ height: number;
82
+ format: ImageFormat;
83
+ size: number;
84
+ }
85
+ export interface ImageMetadata {
86
+ src: string;
87
+ width?: number;
88
+ height?: number;
89
+ sources: Array<{
90
+ type: string;
91
+ srcset: string;
92
+ }>;
93
+ attributes: Record<string, string | number | boolean | undefined>;
94
+ generated: readonly GeneratedImage[];
95
+ }
96
+ export interface ImageServiceContext {
97
+ publicDir: string;
98
+ outDir: string;
99
+ manifest: ImageManifest;
100
+ }
101
+ export interface ImageServiceCapabilities {
102
+ /** Whether the encoder (sharp) is available. */
103
+ encoding: boolean;
104
+ /** Whether remote images can be fetched. */
105
+ remote: boolean;
106
+ /** Whether a runtime image endpoint exists. */
107
+ runtimeEndpoint: boolean;
108
+ /** Whether the host exposes a writable filesystem. */
109
+ filesystem: boolean;
110
+ }
111
+ export interface ImageService {
112
+ resolve(request: ImageRequest, context: ImageServiceContext): Promise<ImageMetadata>;
113
+ capabilities: ImageServiceCapabilities;
114
+ }
115
+ /**
116
+ * Creates a build-time ImageService bound to a public/output directory pair.
117
+ */
118
+ export declare function createImageService(options: ProcessOptions): ImageService;
119
+ /**
120
+ * Programmatic async image API (§3.1). Ensures the requested variants exist on
121
+ * disk (build-time), then returns deterministic metadata (not opaque markup).
122
+ */
123
+ export declare function getImage(request: ImageRequest, options: ProcessOptions): Promise<ImageMetadata>;
124
+ export declare function processImageBatch(images: {
125
+ src: string;
126
+ widths: number[];
127
+ formats?: ImageFormat[];
128
+ }[], options: ProcessOptions): Promise<ProcessResult>;
129
+ /**
130
+ * Read a manifest from disk, or return an empty one if it doesn't exist.
131
+ */
132
+ export declare function readManifest(path: string): Promise<ImageManifest>;
133
+ /**
134
+ * Write a manifest to disk atomically.
135
+ */
136
+ export declare function writeManifest(path: string, manifest: ImageManifest): Promise<void>;
137
+ /**
138
+ * Look up an image entry in the manifest by its source URL.
139
+ */
140
+ export declare function getManifestEntry(manifest: ImageManifest, src: string): ImageEntry | undefined;
141
+ /**
142
+ * Build a srcset string from manifest variants of a given format.
143
+ * Returns e.g. "/images/hero.abc.400w.webp 400w, /images/hero.abc.800w.webp 800w".
144
+ */
145
+ export declare function buildSrcset(entry: ImageEntry, format: ImageFormat): string;
146
+ /**
147
+ * Build the full <picture> markup for an image entry, with <source> per format
148
+ * and a fallback <img>.
149
+ */
150
+ export declare function buildPictureMarkup(entry: ImageEntry, opts: {
151
+ alt: string;
152
+ sizes?: string;
153
+ priority?: boolean;
154
+ class?: string;
155
+ attributes?: Record<string, string>;
156
+ fallbackSrc?: string;
157
+ fallbackWidth?: number;
158
+ fallbackHeight?: number;
159
+ }): string;
160
+ /**
161
+ * Validate that every variant URL in the manifest corresponds to a real file
162
+ * in the output directory. Returns a list of missing URLs.
163
+ */
164
+ export declare function validateManifestUrls(manifest: ImageManifest, outDir: string): Promise<string[]>;