@stacksjs/stx 0.2.320 → 0.2.322

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 (169) hide show
  1. package/dist/builtins/image-alpha.d.ts +26 -0
  2. package/dist/builtins/image-alpha.js +57 -0
  3. package/dist/builtins/image-delivery.d.ts +33 -6
  4. package/dist/builtins/image-delivery.js +55 -51
  5. package/dist/builtins/image-placeholder.d.ts +9 -0
  6. package/dist/builtins/image-placeholder.js +26 -4
  7. package/dist/builtins/image-warmup-worker.d.ts +1 -0
  8. package/dist/builtins/image-warmup-worker.js +18 -0
  9. package/dist/builtins/image-worker.d.ts +40 -0
  10. package/dist/builtins/image-worker.js +82 -0
  11. package/dist/chunk-0050wktp.js +2 -0
  12. package/dist/{chunk-fkvg0m44.js → chunk-02hjbe0p.js} +1 -1
  13. package/dist/{chunk-g786azs5.js → chunk-0aqtbma7.js} +1 -1
  14. package/dist/{chunk-kz7rzjfd.js → chunk-0fkvezw3.js} +1 -1
  15. package/dist/{chunk-7vbwm0fg.js → chunk-0xw3atfx.js} +1 -1
  16. package/dist/chunk-1yxtx5y2.js +2 -0
  17. package/dist/{chunk-vc506z79.js → chunk-24r2ctta.js} +1 -1
  18. package/dist/{chunk-2cey22m2.js → chunk-2astj1fq.js} +346 -346
  19. package/dist/{chunk-naxteb5j.js → chunk-2dk3ym1g.js} +1 -1
  20. package/dist/{chunk-pj9zybjf.js → chunk-2hyxkzpc.js} +1 -1
  21. package/dist/chunk-2k9qqkx2.js +2 -0
  22. package/dist/chunk-2qk3qwmp.js +2 -0
  23. package/dist/{chunk-c9m4a26z.js → chunk-308r399e.js} +1 -1
  24. package/dist/{chunk-jx6qn384.js → chunk-3vydymb5.js} +1 -1
  25. package/dist/{chunk-aqzs1rqe.js → chunk-3yn8ghtz.js} +1 -1
  26. package/dist/{chunk-kpj1tk19.js → chunk-54mnf7n2.js} +1 -1
  27. package/dist/chunk-59fcj7ym.js +2 -0
  28. package/dist/{chunk-3meekwv6.js → chunk-5az2jb8y.js} +2 -2
  29. package/dist/chunk-5n2ps25k.js +2 -0
  30. package/dist/{chunk-456x7xb4.js → chunk-5zqec01h.js} +1 -1
  31. package/dist/chunk-62s55rv0.js +2 -0
  32. package/dist/{chunk-aspvxqzg.js → chunk-69knrrh9.js} +1 -1
  33. package/dist/{chunk-fvhg6b2b.js → chunk-70zb3560.js} +1 -1
  34. package/dist/{chunk-j03sjtfs.js → chunk-7bmqjfft.js} +1 -1
  35. package/dist/chunk-7dd808wv.js +2 -0
  36. package/dist/{chunk-hrxdza7v.js → chunk-7jqh58k9.js} +1 -1
  37. package/dist/{chunk-2rca15ge.js → chunk-88kr475r.js} +1 -1
  38. package/dist/chunk-8cwenbw3.js +2 -0
  39. package/dist/{chunk-9enf3e5m.js → chunk-8n0mjaf6.js} +1 -1
  40. package/dist/{chunk-k1jq596t.js → chunk-8r0yf1q7.js} +1 -1
  41. package/dist/{chunk-cy6rz73m.js → chunk-90ma145g.js} +1 -1
  42. package/dist/{chunk-ws1s0ar4.js → chunk-9e6pyjwz.js} +2 -2
  43. package/dist/{chunk-73wmd3vp.js → chunk-9q9z1027.js} +1 -1
  44. package/dist/{chunk-dt03j5ff.js → chunk-akrxtrh1.js} +1 -1
  45. package/dist/chunk-asf7qyyc.js +2 -0
  46. package/dist/{chunk-nevmgfc7.js → chunk-avpsqyaq.js} +1 -1
  47. package/dist/{chunk-qs9bq14j.js → chunk-b0wm1c6x.js} +1 -1
  48. package/dist/{chunk-gvjkh5mv.js → chunk-b1h7pyhs.js} +1 -1
  49. package/dist/{chunk-s17bnxws.js → chunk-bdr8dz57.js} +2 -2
  50. package/dist/chunk-bgwcrkg8.js +2 -0
  51. package/dist/{chunk-tvvqcwa5.js → chunk-brb3g571.js} +1 -1
  52. package/dist/chunk-bw0488z7.js +2 -0
  53. package/dist/chunk-c3pg3yx4.js +2 -0
  54. package/dist/chunk-cevmy9cn.js +2 -0
  55. package/dist/chunk-df5wrw50.js +3 -0
  56. package/dist/{chunk-dc6z4asx.js → chunk-dgrayagx.js} +1 -1
  57. package/dist/{chunk-kbf0xkp1.js → chunk-dsqcfv2k.js} +1 -1
  58. package/dist/chunk-dvszm8ay.js +2 -0
  59. package/dist/{chunk-3745eg0d.js → chunk-e0369ngw.js} +1 -1
  60. package/dist/{chunk-j6f8bq12.js → chunk-e10pfrds.js} +1 -1
  61. package/dist/{chunk-a9az6pen.js → chunk-e9dtt2cw.js} +2 -2
  62. package/dist/{chunk-j2d5v745.js → chunk-ecbqmesm.js} +1 -1
  63. package/dist/chunk-eqsqs6yj.js +2 -0
  64. package/dist/{chunk-8s9x1kcg.js → chunk-exrnsgtc.js} +1 -1
  65. package/dist/{chunk-k29v4m21.js → chunk-ez93xc1z.js} +1 -1
  66. package/dist/{chunk-2q30mata.js → chunk-f75yj981.js} +1 -1
  67. package/dist/chunk-fps56kyx.js +2 -0
  68. package/dist/{chunk-b8hj1ena.js → chunk-g2pjef44.js} +1 -1
  69. package/dist/{chunk-nsavhgg0.js → chunk-gctdc94b.js} +1 -1
  70. package/dist/chunk-gg74g03k.js +2 -0
  71. package/dist/chunk-gyprd56b.js +2 -0
  72. package/dist/{chunk-2f0kykg4.js → chunk-hrs2pepp.js} +1 -1
  73. package/dist/chunk-k0ht65ts.js +2 -0
  74. package/dist/{chunk-rsjbw0qt.js → chunk-k3w1ctbb.js} +1 -1
  75. package/dist/chunk-kb040j0d.js +2 -0
  76. package/dist/{chunk-273xtqav.js → chunk-kwrj745h.js} +3 -3
  77. package/dist/{chunk-f1hfbnxv.js → chunk-m4wwqznv.js} +1 -1
  78. package/dist/{chunk-ctdmn0az.js → chunk-mczfzq2y.js} +1 -1
  79. package/dist/{chunk-c84gtc0v.js → chunk-mp8nekfh.js} +1 -1
  80. package/dist/chunk-mr33j493.js +2 -0
  81. package/dist/{chunk-1zckawsv.js → chunk-mz9twa1f.js} +1 -1
  82. package/dist/{chunk-nyfqbqd2.js → chunk-namj6apv.js} +2 -2
  83. package/dist/{chunk-fe0pxn8d.js → chunk-nh2ze1a7.js} +1 -1
  84. package/dist/chunk-nkpww053.js +2 -0
  85. package/dist/{chunk-5mg4ddbx.js → chunk-nksce8wc.js} +1 -1
  86. package/dist/{chunk-fbb6eaw3.js → chunk-nq0ec0w1.js} +1 -1
  87. package/dist/{chunk-45e1wffb.js → chunk-nq19m0ja.js} +1 -1
  88. package/dist/{chunk-rhk3w1hf.js → chunk-nytw8ek5.js} +1 -1
  89. package/dist/chunk-pc70paa2.js +2 -0
  90. package/dist/chunk-pgafdv9x.js +2 -0
  91. package/dist/{chunk-brfcence.js → chunk-pty8ywaj.js} +1 -1
  92. package/dist/chunk-pwwhcdh3.js +2 -0
  93. package/dist/{chunk-49129rm8.js → chunk-qhzvf0nf.js} +1 -1
  94. package/dist/{chunk-vvds14af.js → chunk-qrkxyb5p.js} +1 -1
  95. package/dist/{chunk-z57k43ad.js → chunk-qsxtjtvv.js} +2 -2
  96. package/dist/{chunk-sh9m91w0.js → chunk-r05ekvht.js} +1 -1
  97. package/dist/{chunk-7z2wtxxj.js → chunk-r1g0bnpv.js} +1 -1
  98. package/dist/chunk-r8scz4g7.js +2 -0
  99. package/dist/{chunk-rwcnfdj5.js → chunk-rcvnh2p7.js} +1 -1
  100. package/dist/chunk-rw93qdw8.js +2 -0
  101. package/dist/{chunk-k9qd0v7e.js → chunk-sn1edmp8.js} +1 -1
  102. package/dist/{chunk-ebk6h629.js → chunk-tpvs4bzw.js} +1 -1
  103. package/dist/{chunk-qc63mkgk.js → chunk-v1jcfb07.js} +1 -1
  104. package/dist/{chunk-y3m051g6.js → chunk-v3taty7e.js} +1 -1
  105. package/dist/{chunk-9znajpe5.js → chunk-vtn57jw8.js} +10 -10
  106. package/dist/{chunk-xsd936j5.js → chunk-vy2nvpbw.js} +2 -2
  107. package/dist/{chunk-k4417q3k.js → chunk-wf3pcr37.js} +1 -1
  108. package/dist/{chunk-daxm49bn.js → chunk-ws4kh6et.js} +1 -1
  109. package/dist/{chunk-pytndzd2.js → chunk-x5xee26w.js} +1 -1
  110. package/dist/{chunk-szyc050e.js → chunk-xgrh0461.js} +1 -1
  111. package/dist/{chunk-kep0d46b.js → chunk-xsrb2heb.js} +1 -1
  112. package/dist/{chunk-hbvxhwvt.js → chunk-y1zwvdn5.js} +1 -1
  113. package/dist/chunk-y3egkbc2.js +2 -0
  114. package/dist/{chunk-rra7n04f.js → chunk-yc7fptr2.js} +2 -2
  115. package/dist/chunk-ycjjd5js.js +2 -0
  116. package/dist/{chunk-b70m51nc.js → chunk-yms0xgy0.js} +1 -1
  117. package/dist/{chunk-19yw8p2h.js → chunk-yyq7rpw2.js} +1 -1
  118. package/dist/{chunk-b810yw1a.js → chunk-z698hwht.js} +1 -1
  119. package/dist/chunk-zcea9z8j.js +2 -0
  120. package/dist/{chunk-n9bmagzv.js → chunk-zgbwr604.js} +1 -1
  121. package/dist/{chunk-sqwqyxzx.js → chunk-zsapb653.js} +1 -1
  122. package/dist/{chunk-jk6xwdfb.js → chunk-zwkzwymf.js} +1 -1
  123. package/dist/cli.js +14 -14
  124. package/dist/craft-entry.js +1 -1
  125. package/dist/craft.js +1 -1
  126. package/dist/database.js +1 -1
  127. package/dist/first-script-tag.d.ts +24 -0
  128. package/dist/first-script-tag.js +44 -0
  129. package/dist/includes.js +3 -2
  130. package/dist/loops.d.ts +15 -1
  131. package/dist/loops.js +18 -4
  132. package/dist/process.js +4 -29
  133. package/dist/pwa.js +2 -2
  134. package/dist/runtime-injection.js +2 -1
  135. package/dist/ssg.js +2 -2
  136. package/dist/visual-testing.js +1 -1
  137. package/package.json +6 -6
  138. package/dist/chunk-08809py9.js +0 -2
  139. package/dist/chunk-273pkk23.js +0 -2
  140. package/dist/chunk-2zpf8z9f.js +0 -2
  141. package/dist/chunk-3m34hq6t.js +0 -2
  142. package/dist/chunk-3rajeek7.js +0 -2
  143. package/dist/chunk-5wep1h8r.js +0 -2
  144. package/dist/chunk-61bmp7rb.js +0 -2
  145. package/dist/chunk-6py5b6p4.js +0 -2
  146. package/dist/chunk-9ef162n1.js +0 -2
  147. package/dist/chunk-abefheag.js +0 -2
  148. package/dist/chunk-bbad74mr.js +0 -2
  149. package/dist/chunk-bjy23bjm.js +0 -2
  150. package/dist/chunk-bx9mxc6z.js +0 -2
  151. package/dist/chunk-dfewnpk4.js +0 -2
  152. package/dist/chunk-f1yazmb2.js +0 -2
  153. package/dist/chunk-fchtzkft.js +0 -2
  154. package/dist/chunk-ghvqy1n9.js +0 -2
  155. package/dist/chunk-gvyaeytx.js +0 -3
  156. package/dist/chunk-hxk2wkmr.js +0 -2
  157. package/dist/chunk-j9vkpac1.js +0 -2
  158. package/dist/chunk-k0rrma2q.js +0 -2
  159. package/dist/chunk-k4tbashh.js +0 -2
  160. package/dist/chunk-nwsnjyhg.js +0 -2
  161. package/dist/chunk-pgbhyh3z.js +0 -2
  162. package/dist/chunk-pnp60knr.js +0 -2
  163. package/dist/chunk-qvc2ddqn.js +0 -2
  164. package/dist/chunk-s4c6vvs3.js +0 -2
  165. package/dist/chunk-v4m87pv6.js +0 -2
  166. package/dist/chunk-v6kvq7ww.js +0 -2
  167. package/dist/chunk-vawmst0v.js +0 -2
  168. package/dist/chunk-x89acpn9.js +0 -2
  169. package/dist/chunk-y2htmh35.js +0 -2
@@ -0,0 +1,26 @@
1
+ /**
2
+ * - `none`: the format or header rules transparency out entirely (JPEG, a PNG
3
+ * with no alpha channel and no `tRNS`, a WebP that declares no alpha).
4
+ * - `possible`: the header declares an alpha channel. Every pixel may still be
5
+ * opaque; only a decode can say.
6
+ * - `unknown`: the header does not settle it (AVIF, anything unrecognised).
7
+ */
8
+ export type SourceAlpha = 'none' | 'possible' | 'unknown';
9
+ export declare function readAt(fd: number, position: number, length: number): Buffer;
10
+ /**
11
+ * Whether a WebP header declares an alpha channel.
12
+ *
13
+ * A simple `VP8 ` frame cannot hold one at all; `VP8X` says so in its flags and
14
+ * `VP8L` in the `alpha_is_used` bit after the dimensions.
15
+ */
16
+ export declare function webpHeaderHasAlpha(header: Uint8Array): boolean;
17
+ /**
18
+ * Classify a source file's transparency from its header alone.
19
+ *
20
+ * PNG says so with its colour type or a `tRNS` chunk (which must precede the
21
+ * first `IDAT`, so the walk stops there and never reads pixel data). A WebP
22
+ * `VP8 ` frame cannot carry alpha and a `VP8X` without the alpha flag declares
23
+ * none; `VP8L`'s `alpha_is_used` bit is only a hint, so without it the answer
24
+ * stays `unknown`. JPEG cannot carry alpha at all.
25
+ */
26
+ export declare function sourceAlpha(file: string): SourceAlpha;
@@ -0,0 +1,57 @@
1
+ import fs from "node:fs";
2
+ export function readAt(fd, position, length) {
3
+ const buffer = Buffer.alloc(length), read = fs.readSync(fd, buffer, 0, length, position);
4
+ return buffer.subarray(0, read);
5
+ }
6
+ export function webpHeaderHasAlpha(header) {
7
+ if (header.length < 25)
8
+ return !1;
9
+ const text = (start, end) => String.fromCharCode(...header.subarray(start, end));
10
+ if (text(0, 4) !== "RIFF" || text(8, 12) !== "WEBP")
11
+ return !1;
12
+ const chunk = text(12, 16);
13
+ if (chunk === "VP8X")
14
+ return (header[20] & 16) !== 0;
15
+ if (chunk === "VP8L")
16
+ return ((header[21] | header[22] << 8 | header[23] << 16 | header[24] << 24) >>> 28 & 1) === 1;
17
+ return !1;
18
+ }
19
+ export function sourceAlpha(file) {
20
+ let fd;
21
+ try {
22
+ fd = fs.openSync(file, "r");
23
+ const head = readAt(fd, 0, 33);
24
+ if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255)
25
+ return "none";
26
+ if (head.length >= 26 && head[0] === 137 && head.toString("latin1", 1, 4) === "PNG") {
27
+ const colorType = head[25];
28
+ if (colorType === 4 || colorType === 6)
29
+ return "possible";
30
+ let offset = 8;
31
+ for (let i = 0;i < 64; i++) {
32
+ const chunk = readAt(fd, offset, 8);
33
+ if (chunk.length < 8)
34
+ return "unknown";
35
+ const type = chunk.toString("latin1", 4, 8);
36
+ if (type === "tRNS")
37
+ return "possible";
38
+ if (type === "IDAT" || type === "IEND")
39
+ return "none";
40
+ offset += 12 + chunk.readUInt32BE(0);
41
+ }
42
+ return "unknown";
43
+ }
44
+ if (head.length >= 25 && head.toString("latin1", 0, 4) === "RIFF" && head.toString("latin1", 8, 12) === "WEBP") {
45
+ if (webpHeaderHasAlpha(head))
46
+ return "possible";
47
+ const chunk = head.toString("latin1", 12, 16);
48
+ return chunk === "VP8 " || chunk === "VP8X" ? "none" : "unknown";
49
+ }
50
+ return "unknown";
51
+ } catch {
52
+ return "unknown";
53
+ } finally {
54
+ if (fd !== void 0)
55
+ fs.closeSync(fd);
56
+ }
57
+ }
@@ -1,11 +1,20 @@
1
1
  import type { ImageDeliveryManifest } from 'ts-images/delivery';
2
+ import { webpHeaderHasAlpha } from './image-alpha';
3
+ export { webpHeaderHasAlpha };
2
4
  /**
3
- * Whether a WebP header declares an alpha channel.
4
- *
5
- * A simple `VP8 ` frame cannot hold one at all; `VP8X` says so in its flags and
6
- * `VP8L` in the `alpha_is_used` bit after the dimensions.
5
+ * Which encoders this process writes variants with, e.g.
6
+ * `ts-webp@0.1.6+ts-avif@0.1.4`. Resolved the way ts-images resolves them,
7
+ * so a vendored or hoisted copy reports the copy that actually runs. An
8
+ * encoder that cannot be identified reads as `unknown`, which is still a
9
+ * namespace no older release wrote into.
10
+ */
11
+ /**
12
+ * Override {@link imageEncoderIdentity}, or with no argument go back to
13
+ * resolving it. For tests that need a variant cache written by another
14
+ * encoder than the one installed.
7
15
  */
8
- export declare function webpHeaderHasAlpha(header: Uint8Array): boolean;
16
+ export declare function setImageEncoderIdentity(identity?: string): void;
17
+ export declare function imageEncoderIdentity(): string;
9
18
  /** Return build-time delivery metadata for one public image URL. */
10
19
  export declare function getImageDelivery(src: string): ImageDeliveryManifest | undefined;
11
20
  /**
@@ -17,6 +26,24 @@ export declare function getImageDelivery(src: string): ImageDeliveryManifest | u
17
26
  export declare function isTransparentImage(src: string): boolean;
18
27
  /** Clear process-global delivery state between builds and tests. */
19
28
  export declare function clearImageDeliveryCatalog(): void;
29
+ /** The catalog as plain data, for a worker to post back to the thread that serves. */
30
+ export interface ImageDeliverySnapshot {
31
+ entries: Array<[string, ImageDeliveryManifest]>;
32
+ transparent: string[];
33
+ }
34
+ /** What this thread's catalog holds, in a form `postMessage` can carry. */
35
+ export declare function snapshotImageDelivery(): ImageDeliverySnapshot;
36
+ /** Swap in a catalog built elsewhere, whole, as the in-thread pass does. */
37
+ export declare function installImageDelivery(snapshot: ImageDeliverySnapshot): void;
38
+ export interface PrepareImageDeliveryOptions {
39
+ /**
40
+ * Decode and encode on a worker thread, and install the finished catalog
41
+ * here. For a long-lived server, whose event loop has requests to answer
42
+ * while a cold cache re-encodes every raster. Same files, same names, same
43
+ * catalog; falls back to running in-thread where no worker can start.
44
+ */
45
+ offThread?: boolean;
46
+ }
20
47
  /**
21
48
  * Optimize every raster in the public directory before templates render.
22
49
  *
@@ -24,7 +51,7 @@ export declare function clearImageDeliveryCatalog(): void;
24
51
  * catalog. Its content fingerprint is folded into the SSG cache key to ensure
25
52
  * changing image bytes can never reuse HTML pointing at an older asset hash.
26
53
  */
27
- export declare function prepareImageDelivery(publicDir: string, outputDir: string): Promise<{
54
+ export declare function prepareImageDelivery(publicDir: string, outputDir: string, options?: PrepareImageDeliveryOptions): Promise<{
28
55
  count: number;
29
56
  fingerprint: string;
30
57
  }>;
@@ -3,7 +3,44 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { decode, generateThumbHash } from "ts-images";
5
5
  import { createImageDeliveryCatalog } from "ts-images/delivery";
6
- const RASTER_EXTENSIONS = new Set([".avif", ".jpeg", ".jpg", ".png", ".webp"]), DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920], DELIVERY_URL = "/_stx/images", ALPHA_NAMESPACE = `stx:${DELIVERY_URL}:alpha-1`;
6
+ import { readAt, sourceAlpha, webpHeaderHasAlpha } from "./image-alpha.js";
7
+ import { runImageTask, WORKER_UNAVAILABLE } from "./image-worker.js";
8
+
9
+ export { webpHeaderHasAlpha };
10
+ const RASTER_EXTENSIONS = new Set([".avif", ".jpeg", ".jpg", ".png", ".webp"]), DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920], DELIVERY_URL = "/_stx/images", ALPHA_NAMESPACE = `stx:${DELIVERY_URL}:alpha-1`, ENCODER_PACKAGES = ["@stacksjs/ts-webp", "@stacksjs/ts-avif"];
11
+ let encoderIdentity;
12
+ function packageVersion(specifier, fromDir) {
13
+ let dir = path.dirname(Bun.resolveSync(specifier, fromDir));
14
+ while (dir !== path.dirname(dir)) {
15
+ const manifest = path.join(dir, "package.json");
16
+ if (fs.existsSync(manifest)) {
17
+ const pkg = JSON.parse(fs.readFileSync(manifest, "utf8"));
18
+ if (pkg.name === specifier)
19
+ return pkg.version;
20
+ }
21
+ dir = path.dirname(dir);
22
+ }
23
+ return;
24
+ }
25
+ export function setImageEncoderIdentity(identity) {
26
+ encoderIdentity = identity;
27
+ }
28
+ export function imageEncoderIdentity() {
29
+ if (encoderIdentity !== void 0)
30
+ return encoderIdentity;
31
+ let from;
32
+ try {
33
+ from = path.dirname(Bun.resolveSync("ts-images", import.meta.dir));
34
+ } catch {}
35
+ encoderIdentity = ENCODER_PACKAGES.map((specifier) => {
36
+ let version;
37
+ try {
38
+ version = from ? packageVersion(specifier, from) : void 0;
39
+ } catch {}
40
+ return `${specifier.replace("@stacksjs/", "")}@${version ?? "unknown"}`;
41
+ }).join("+");
42
+ return encoderIdentity;
43
+ }
7
44
  let deliveryCatalog = new Map, transparentSources = new Set;
8
45
  const warnedOpaqueWebp = new Set;
9
46
  function publicUrl(relativePath) {
@@ -61,55 +98,8 @@ function deliveryEntries(files, alphaStorage) {
61
98
  ...mayHaveAlpha(file.absolutePath) ? { options: { storage: alphaStorage } } : {}
62
99
  }));
63
100
  }
64
- function readAt(fd, position, length) {
65
- const buffer = Buffer.alloc(length), read = fs.readSync(fd, buffer, 0, length, position);
66
- return buffer.subarray(0, read);
67
- }
68
- export function webpHeaderHasAlpha(header) {
69
- if (header.length < 25)
70
- return !1;
71
- const text = (start, end) => String.fromCharCode(...header.subarray(start, end));
72
- if (text(0, 4) !== "RIFF" || text(8, 12) !== "WEBP")
73
- return !1;
74
- const chunk = text(12, 16);
75
- if (chunk === "VP8X")
76
- return (header[20] & 16) !== 0;
77
- if (chunk === "VP8L")
78
- return ((header[21] | header[22] << 8 | header[23] << 16 | header[24] << 24) >>> 28 & 1) === 1;
79
- return !1;
80
- }
81
101
  function mayHaveAlpha(file) {
82
- let fd;
83
- try {
84
- fd = fs.openSync(file, "r");
85
- const head = readAt(fd, 0, 33);
86
- if (head.length >= 26 && head[0] === 137 && head.toString("latin1", 1, 4) === "PNG") {
87
- const colorType = head[25];
88
- if (colorType === 4 || colorType === 6)
89
- return !0;
90
- let offset = 8;
91
- for (let i = 0;i < 64; i++) {
92
- const chunk = readAt(fd, offset, 8);
93
- if (chunk.length < 8)
94
- return !1;
95
- const type = chunk.toString("latin1", 4, 8);
96
- if (type === "tRNS")
97
- return !0;
98
- if (type === "IDAT" || type === "IEND")
99
- return !1;
100
- offset += 12 + chunk.readUInt32BE(0);
101
- }
102
- return !1;
103
- }
104
- if (head.length >= 25 && head.toString("latin1", 0, 4) === "RIFF")
105
- return webpHeaderHasAlpha(head);
106
- return !1;
107
- } catch {
108
- return !1;
109
- } finally {
110
- if (fd !== void 0)
111
- fs.closeSync(fd);
112
- }
102
+ return sourceAlpha(file) === "possible";
113
103
  }
114
104
  function hasVisibleAlpha(rgba) {
115
105
  for (let i = 3;i < rgba.length; i += 4)
@@ -226,11 +216,25 @@ export function clearImageDeliveryCatalog() {
226
216
  deliveryCatalog = new Map;
227
217
  transparentSources = new Set;
228
218
  }
229
- export async function prepareImageDelivery(publicDir, outputDir) {
219
+ export function snapshotImageDelivery() {
220
+ return { entries: [...deliveryCatalog], transparent: [...transparentSources] };
221
+ }
222
+ export function installImageDelivery(snapshot) {
223
+ deliveryCatalog = new Map(snapshot.entries);
224
+ transparentSources = new Set(snapshot.transparent);
225
+ }
226
+ export async function prepareImageDelivery(publicDir, outputDir, options = {}) {
230
227
  if (!publicDir || !fs.existsSync(publicDir)) {
231
228
  clearImageDeliveryCatalog();
232
229
  return { count: 0, fingerprint: "" };
233
230
  }
231
+ if (options.offThread) {
232
+ const reply = await runImageTask("delivery", { publicDir, outputDir });
233
+ if (reply !== WORKER_UNAVAILABLE) {
234
+ installImageDelivery(reply.state);
235
+ return reply.result;
236
+ }
237
+ }
234
238
  const allFiles = await collectRasterImages(publicDir);
235
239
  if (allFiles.length === 0) {
236
240
  clearImageDeliveryCatalog();
@@ -247,7 +251,7 @@ export async function prepareImageDelivery(publicDir, outputDir) {
247
251
  clearImageDeliveryCatalog();
248
252
  return { count: 0, fingerprint: "" };
249
253
  }
250
- const alphaStorage = deliveryStorage(path.join(outputDir, "_stx", "images"), ALPHA_NAMESPACE), catalogOptions = {
254
+ const alphaStorage = deliveryStorage(path.join(outputDir, "_stx", "images"), `${ALPHA_NAMESPACE}:${imageEncoderIdentity()}`), catalogOptions = {
251
255
  outDir: path.join(outputDir, "_stx", "images"),
252
256
  storage: deliveryStorage(path.join(outputDir, "_stx", "images")),
253
257
  baseUrl: DELIVERY_URL,
@@ -46,6 +46,8 @@ export declare function getImagePlaceholder(src: string): ImagePlaceholder | und
46
46
  export declare function placeholdersWarmed(): boolean;
47
47
  /** Drop everything, for tests and for a watch-mode rebuild. */
48
48
  export declare function clearImagePlaceholders(): void;
49
+ /** Every placeholder this thread holds, in a form `postMessage` can carry. */
50
+ export declare function snapshotImagePlaceholders(): Array<[string, ImagePlaceholder]>;
49
51
  /**
50
52
  * Register a placeholder directly. Used by the warm below, and by tests that
51
53
  * would rather not decode a real JPEG to check the markup.
@@ -69,6 +71,13 @@ export interface WarmOptions {
69
71
  maxBytes?: number;
70
72
  /** Stop after this many images, so an enormous asset tree cannot stall a build. */
71
73
  limit?: number;
74
+ /**
75
+ * Decode on a worker thread and merge the results here. For a long-lived
76
+ * server, whose event loop has requests to answer while a cold cache
77
+ * decodes every image. Same placeholders, same cache file; falls back to
78
+ * running in-thread where no worker can start.
79
+ */
80
+ offThread?: boolean;
72
81
  }
73
82
  /**
74
83
  * Derive placeholders for every image under `publicDir` and hold them for
@@ -1,5 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { sourceAlpha } from "./image-alpha.js";
4
+ import { runImageTask, WORKER_UNAVAILABLE } from "./image-worker.js";
3
5
  const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]), placeholders = new Map;
4
6
  let warmed = !1;
5
7
  export function getImagePlaceholder(src) {
@@ -15,6 +17,9 @@ export function clearImagePlaceholders() {
15
17
  placeholders.clear();
16
18
  warmed = !1;
17
19
  }
20
+ export function snapshotImagePlaceholders() {
21
+ return [...placeholders];
22
+ }
18
23
  export function setImagePlaceholder(urlPath, placeholder) {
19
24
  placeholders.set(urlPath, placeholder);
20
25
  warmed = !0;
@@ -93,11 +98,20 @@ function collectImages(dir, baseUrl, out = []) {
93
98
  return out;
94
99
  }
95
100
  export async function warmImagePlaceholders(publicDir, options = {}) {
96
- const { cachePath, maxBytes = 33554432, limit = 2000 } = options;
101
+ const { cachePath, maxBytes = 33554432, limit = 2000, offThread } = options;
97
102
  if (!publicDir || !fs.existsSync(publicDir)) {
98
103
  warmed = !0;
99
104
  return placeholders.size;
100
105
  }
106
+ if (offThread) {
107
+ const reply = await runImageTask("placeholders", { publicDir, options: { cachePath, maxBytes, limit } });
108
+ if (reply !== WORKER_UNAVAILABLE) {
109
+ for (const [urlPath, placeholder] of reply.placeholders)
110
+ placeholders.set(urlPath, placeholder);
111
+ warmed = !0;
112
+ return placeholders.size;
113
+ }
114
+ }
101
115
  let tsImages;
102
116
  try {
103
117
  tsImages = await import("ts-images");
@@ -117,9 +131,17 @@ export async function warmImagePlaceholders(publicDir, options = {}) {
117
131
  if (stat.size > maxBytes)
118
132
  return;
119
133
  const cached = cache[urlPath];
120
- if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size && typeof cached.transparent === "boolean") {
121
- placeholders.set(urlPath, { dataUrl: cached.dataUrl, color: cached.color, transparent: cached.transparent });
122
- return;
134
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
135
+ let transparent = cached.transparent;
136
+ if (typeof transparent !== "boolean" && sourceAlpha(filePath) === "none") {
137
+ transparent = !1;
138
+ cache[urlPath] = { ...cached, transparent };
139
+ cacheDirty = !0;
140
+ }
141
+ if (typeof transparent === "boolean") {
142
+ placeholders.set(urlPath, { dataUrl: cached.dataUrl, color: cached.color, transparent });
143
+ return;
144
+ }
123
145
  }
124
146
  try {
125
147
  const { hash } = await tsImages.generateThumbHash(filePath);
@@ -0,0 +1,18 @@
1
+ import { prepareImageDelivery, snapshotImageDelivery } from "./image-delivery.js";
2
+ import { snapshotImagePlaceholders, warmImagePlaceholders } from "./image-placeholder.js";
3
+ self.addEventListener("message", async (event) => {
4
+ const { task, args } = event.data;
5
+ try {
6
+ if (task === "delivery") {
7
+ const result = await prepareImageDelivery(args.publicDir, args.outputDir);
8
+ self.postMessage({ type: "result", value: { result, state: snapshotImageDelivery() } });
9
+ } else {
10
+ await warmImagePlaceholders(args.publicDir, args.options);
11
+ self.postMessage({ type: "result", value: { placeholders: snapshotImagePlaceholders() } });
12
+ }
13
+ } catch (error) {
14
+ const err = error instanceof Error ? error : Error(String(error));
15
+ self.postMessage({ type: "error", error: { name: err.name, message: err.message, stack: err.stack } });
16
+ }
17
+ });
18
+ self.postMessage({ type: "ready" });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Runs the image pass on a worker thread.
3
+ *
4
+ * Decoding and encoding (AVIF, WebP, thumbhash) is pure TypeScript and fully
5
+ * synchronous per image. On the thread that also answers requests, a cold pass
6
+ * over a photo-heavy `public/` held the event loop for most of its minutes:
7
+ * every `await` a render made queued behind the next encode, so a first page
8
+ * took 14-18s and a proxy in front of the server saw empty responses. On a
9
+ * worker the same code runs to the same results, and the serving thread only
10
+ * receives the finished catalog.
11
+ *
12
+ * The worker is the mechanism, not a requirement. Where one cannot start (no
13
+ * `Worker` in the runtime, or the entry file missing because stx was bundled
14
+ * into something else), {@link runImageTask} reports that and the caller runs
15
+ * the pass in-thread exactly as before.
16
+ *
17
+ * @module builtins/image-worker
18
+ */
19
+ /** Tasks the worker entry knows how to run. */
20
+ export type ImageTask = 'delivery' | 'placeholders';
21
+ /** Returned instead of a result when no worker could be started. */
22
+ export declare const WORKER_UNAVAILABLE: unique symbol;
23
+ /**
24
+ * How many image workers may run at once.
25
+ *
26
+ * Serving needs a core of its own, so on a two-core box the placeholder and
27
+ * delivery passes take turns in one worker instead of claiming both cores.
28
+ * Two at most: the passes are the only callers, and a third would have
29
+ * nothing to do.
30
+ */
31
+ export declare function imageWorkerSlots(cores?: number): number;
32
+ /**
33
+ * Run `task` with `args` on a worker thread and resolve with what it posts
34
+ * back, or with {@link WORKER_UNAVAILABLE} when no worker could be started.
35
+ *
36
+ * A failure inside the task rejects, as the in-thread call would have thrown.
37
+ * Only a worker that never became ready counts as unavailable, so a crash
38
+ * mid-pass is never silently re-run on the serving thread.
39
+ */
40
+ export declare function runImageTask<T>(task: ImageTask, args: unknown): Promise<T | typeof WORKER_UNAVAILABLE>;
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import { fileURLToPath } from "node:url";
4
+ export const WORKER_UNAVAILABLE = Symbol("stx.image-worker.unavailable");
5
+ export function imageWorkerSlots(cores = typeof os.availableParallelism === "function" ? os.availableParallelism() : os.cpus().length) {
6
+ return Math.max(1, Math.min(2, cores - 1));
7
+ }
8
+ let active = 0;
9
+ const waiting = [];
10
+ async function acquire() {
11
+ if (active < imageWorkerSlots()) {
12
+ active++;
13
+ return;
14
+ }
15
+ await new Promise((resolve) => waiting.push(resolve));
16
+ active++;
17
+ }
18
+ function release() {
19
+ active--;
20
+ waiting.shift()?.();
21
+ }
22
+ function workerEntry() {
23
+ const extension = import.meta.url.endsWith(".ts") ? "ts" : "js", url = new URL(`./image-warmup-worker.${extension}`, import.meta.url);
24
+ try {
25
+ return url.protocol === "file:" && fs.existsSync(fileURLToPath(url)) ? url : void 0;
26
+ } catch {
27
+ return;
28
+ }
29
+ }
30
+ export async function runImageTask(task, args) {
31
+ if (typeof Worker > "u")
32
+ return WORKER_UNAVAILABLE;
33
+ const entry = workerEntry();
34
+ if (!entry)
35
+ return WORKER_UNAVAILABLE;
36
+ await acquire();
37
+ try {
38
+ return await new Promise((resolve, reject) => {
39
+ let worker;
40
+ try {
41
+ worker = new Worker(entry);
42
+ } catch {
43
+ resolve(WORKER_UNAVAILABLE);
44
+ return;
45
+ }
46
+ let ready = !1, settled = !1;
47
+ const finish = (settle) => {
48
+ if (settled)
49
+ return;
50
+ settled = !0;
51
+ settle();
52
+ worker.terminate();
53
+ };
54
+ worker.addEventListener("message", (event) => {
55
+ const reply = event.data;
56
+ if (reply.type === "ready") {
57
+ ready = !0;
58
+ worker.postMessage({ task, args });
59
+ return;
60
+ }
61
+ if (reply.type === "result") {
62
+ finish(() => resolve(reply.value));
63
+ return;
64
+ }
65
+ const error = Error(reply.error?.message ?? "image worker failed");
66
+ if (reply.error?.name)
67
+ error.name = reply.error.name;
68
+ if (reply.error?.stack)
69
+ error.stack = reply.error.stack;
70
+ finish(() => reject(error));
71
+ });
72
+ worker.addEventListener("error", (event) => {
73
+ finish(() => ready ? reject(event.error instanceof Error ? event.error : Error(event.message || "image worker crashed")) : resolve(WORKER_UNAVAILABLE));
74
+ });
75
+ worker.addEventListener("close", () => {
76
+ finish(() => ready ? reject(Error("image worker exited before it finished")) : resolve(WORKER_UNAVAILABLE));
77
+ });
78
+ });
79
+ } finally {
80
+ release();
81
+ }
82
+ }
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ import{tk as a,uk as b,vk as c,wk as d}from"./chunk-k3w1ctbb.js";import"./chunk-vy2nvpbw.js";import"./chunk-2astj1fq.js";import"./chunk-9e6pyjwz.js";import"./chunk-z698hwht.js";import"./chunk-70zb3560.js";import"./chunk-zsapb653.js";import"./chunk-kwrj745h.js";import"./chunk-df5wrw50.js";import"./chunk-e10pfrds.js";import"./chunk-ecbqmesm.js";import"./chunk-namj6apv.js";import"./chunk-dgrayagx.js";import"./chunk-2hyxkzpc.js";import"./chunk-r1g0bnpv.js";import"./chunk-2dk3ym1g.js";import"./chunk-8n0mjaf6.js";import"./chunk-5az2jb8y.js";import"./chunk-rcvnh2p7.js";import"./chunk-qrkxyb5p.js";export{b as getAvailableTargets,c as getAvailableFormats,d as default,a as buildNative};
@@ -1,3 +1,3 @@
1
1
  // @bun
2
- import{Nf as T}from"./chunk-n9bmagzv.js";import{hw as I}from"./chunk-vvds14af.js";import E from"fs";import X from"path";import U from"crypto";var _=new Set(["webp","avif","jpeg","png"]),F=new Set(["cover","contain","fill","inside","outside"]),A=new Set([".jpg",".jpeg",".png",".webp",".avif",".gif"]),S=4096,y=80,D="cover";async function g(k,G){let J=G.routePath??"/_stx/img",B=new URL(k.url);if(B.pathname!==J)return null;let $=q(B,G);if(!$.ok)return new Response($.error,{status:$.status,headers:{"Content-Type":"text/plain"}});let{srcFsPath:j,srcUrl:K,width:H,height:v,format:Z,quality:Y,fit:z}=$,C;try{C=E.statSync(j)}catch{return new Response(`Source image not found: ${K}`,{status:404,headers:{"Content-Type":"text/plain"}})}if(!C.isFile())return new Response("Not a file",{status:404,headers:{"Content-Type":"text/plain"}});let N=G.cacheDir??".stx/cache/img";await E.promises.mkdir(N,{recursive:!0});let V=`${j}:${C.mtimeMs}:${C.size}`,O=`${H}x${v}:${Z}:${Y}:${z}`,b=U.createHash("sha256").update(`${V}|${O}`).digest("hex").slice(0,16),Q=X.join(N,`${b}.${Z}`),W={"Content-Type":T(Z),"Cache-Control":"public, max-age=31536000, immutable",ETag:`"${b}"`,"X-Stx-Image-Cache":"HIT"};if(k.headers.get("if-none-match")===`"${b}"`)return new Response(null,{status:304,headers:W});let L=Bun.file(Q);if(await L.exists())return new Response(L,{headers:W});try{let R=Buffer.from(await Bun.file(j).arrayBuffer()),{resizeAndConvert:M}=await P(),w=await M(R,{width:H,height:v,format:Z,quality:Y,fit:z});return await Bun.write(Q,w),W["X-Stx-Image-Cache"]="MISS",new Response(new Uint8Array(w),{headers:W})}catch(R){let M=R.message;return new Response(`Image processing failed: ${M}`,{status:500,headers:{"Content-Type":"text/plain"}})}}function l(k){return async(G)=>{return await g(G,k)??new Response("Not Found",{status:404,headers:{"Content-Type":"text/plain"}})}}function q(k,G){let J=k.searchParams.get("src");if(!J)return{ok:!1,error:"Missing required param: src",status:400};if(!J.startsWith("/"))return{ok:!1,error:"src must be an absolute URL path (start with /)",status:400};if(J.startsWith("//"))return{ok:!1,error:"src must be a local path",status:400};let B=X.extname(J).toLowerCase();if(!A.has(B))return{ok:!1,error:`Unsupported source extension: ${B}`,status:400};let $=X.resolve(G.publicDir),j=X.resolve($,J.slice(1));if(!j.startsWith(`${$}${X.sep}`)&&j!==$)return{ok:!1,error:"src is outside publicDir",status:400};let K=G.maxWidth??S,H=G.maxHeight??S,v=k.searchParams.get("w"),Z=k.searchParams.get("h"),Y=v?Number.parseInt(v,10):0,z=Z?Number.parseInt(Z,10):0;if(v&&(!Number.isFinite(Y)||Y<=0))return{ok:!1,error:"Invalid w (must be positive integer)",status:400};if(Z&&(!Number.isFinite(z)||z<=0))return{ok:!1,error:"Invalid h (must be positive integer)",status:400};if(!v&&!Z)return{ok:!1,error:"At least one of w or h is required",status:400};let C=Math.min(Y||K,K),N=Math.min(z||H,H),V=k.searchParams.get("f")||"webp";if(!_.has(V))return{ok:!1,error:`Invalid f (must be one of: ${[..._].join(", ")})`,status:400};let O=V,b=k.searchParams.get("q"),Q=b?Number.parseInt(b,10):G.defaultQuality??y;if(!Number.isFinite(Q)||Q<1||Q>100)return{ok:!1,error:"Invalid q (must be 1-100)",status:400};let x=k.searchParams.get("fit")||G.defaultFit||D;if(!F.has(x))return{ok:!1,error:`Invalid fit (must be one of: ${[...F].join(", ")})`,status:400};return{ok:!0,srcFsPath:j,srcUrl:J,width:C,height:N,format:O,quality:Q,fit:x}}async function P(){return await import("./chunk-aqzs1rqe.js")}
2
+ import{Nf as T}from"./chunk-zgbwr604.js";import{pw as I}from"./chunk-qrkxyb5p.js";import E from"fs";import X from"path";import U from"crypto";var _=new Set(["webp","avif","jpeg","png"]),F=new Set(["cover","contain","fill","inside","outside"]),A=new Set([".jpg",".jpeg",".png",".webp",".avif",".gif"]),S=4096,y=80,D="cover";async function g(k,G){let J=G.routePath??"/_stx/img",B=new URL(k.url);if(B.pathname!==J)return null;let $=q(B,G);if(!$.ok)return new Response($.error,{status:$.status,headers:{"Content-Type":"text/plain"}});let{srcFsPath:j,srcUrl:K,width:H,height:v,format:Z,quality:Y,fit:z}=$,C;try{C=E.statSync(j)}catch{return new Response(`Source image not found: ${K}`,{status:404,headers:{"Content-Type":"text/plain"}})}if(!C.isFile())return new Response("Not a file",{status:404,headers:{"Content-Type":"text/plain"}});let N=G.cacheDir??".stx/cache/img";await E.promises.mkdir(N,{recursive:!0});let V=`${j}:${C.mtimeMs}:${C.size}`,O=`${H}x${v}:${Z}:${Y}:${z}`,b=U.createHash("sha256").update(`${V}|${O}`).digest("hex").slice(0,16),Q=X.join(N,`${b}.${Z}`),W={"Content-Type":T(Z),"Cache-Control":"public, max-age=31536000, immutable",ETag:`"${b}"`,"X-Stx-Image-Cache":"HIT"};if(k.headers.get("if-none-match")===`"${b}"`)return new Response(null,{status:304,headers:W});let L=Bun.file(Q);if(await L.exists())return new Response(L,{headers:W});try{let R=Buffer.from(await Bun.file(j).arrayBuffer()),{resizeAndConvert:M}=await P(),w=await M(R,{width:H,height:v,format:Z,quality:Y,fit:z});return await Bun.write(Q,w),W["X-Stx-Image-Cache"]="MISS",new Response(new Uint8Array(w),{headers:W})}catch(R){let M=R.message;return new Response(`Image processing failed: ${M}`,{status:500,headers:{"Content-Type":"text/plain"}})}}function l(k){return async(G)=>{return await g(G,k)??new Response("Not Found",{status:404,headers:{"Content-Type":"text/plain"}})}}function q(k,G){let J=k.searchParams.get("src");if(!J)return{ok:!1,error:"Missing required param: src",status:400};if(!J.startsWith("/"))return{ok:!1,error:"src must be an absolute URL path (start with /)",status:400};if(J.startsWith("//"))return{ok:!1,error:"src must be a local path",status:400};let B=X.extname(J).toLowerCase();if(!A.has(B))return{ok:!1,error:`Unsupported source extension: ${B}`,status:400};let $=X.resolve(G.publicDir),j=X.resolve($,J.slice(1));if(!j.startsWith(`${$}${X.sep}`)&&j!==$)return{ok:!1,error:"src is outside publicDir",status:400};let K=G.maxWidth??S,H=G.maxHeight??S,v=k.searchParams.get("w"),Z=k.searchParams.get("h"),Y=v?Number.parseInt(v,10):0,z=Z?Number.parseInt(Z,10):0;if(v&&(!Number.isFinite(Y)||Y<=0))return{ok:!1,error:"Invalid w (must be positive integer)",status:400};if(Z&&(!Number.isFinite(z)||z<=0))return{ok:!1,error:"Invalid h (must be positive integer)",status:400};if(!v&&!Z)return{ok:!1,error:"At least one of w or h is required",status:400};let C=Math.min(Y||K,K),N=Math.min(z||H,H),V=k.searchParams.get("f")||"webp";if(!_.has(V))return{ok:!1,error:`Invalid f (must be one of: ${[..._].join(", ")})`,status:400};let O=V,b=k.searchParams.get("q"),Q=b?Number.parseInt(b,10):G.defaultQuality??y;if(!Number.isFinite(Q)||Q<1||Q>100)return{ok:!1,error:"Invalid q (must be 1-100)",status:400};let x=k.searchParams.get("fit")||G.defaultFit||D;if(!F.has(x))return{ok:!1,error:`Invalid fit (must be one of: ${[...F].join(", ")})`,status:400};return{ok:!0,srcFsPath:j,srcUrl:J,width:C,height:N,format:O,quality:Q,fit:x}}async function P(){return await import("./chunk-3yn8ghtz.js")}
3
3
  export{g as yf,l as zf};
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{a as N,c as R,e as W,g as j,i as H,k as J}from"./chunk-0y46fbkx.js";import{l as k,m as w,n as B}from"./chunk-q9n5swx7.js";import"./chunk-vvds14af.js";import E from"fs";import K from"path";var V=[];function _(U){V=[];for(let A of U.storyFiles)if(V.push({text:A.fileName.toLowerCase(),original:A.fileName,type:"story",storyId:A.id}),V.push({text:A.relativePath.toLowerCase(),original:A.relativePath,type:"story",storyId:A.id}),A.story)for(let z of A.story.variants)V.push({text:z.title.toLowerCase(),original:z.title,type:"story",storyId:A.id,variantId:z.id})}function $(){return JSON.stringify(V)}async function O(U={}){let A=Date.now();N();let z=await k({mode:"build"});if(!z.config.enabled){W("Story feature is disabled in config.");return}let P=await H(z);w(z,P);let Y=J(z.config,P);B(z,Y),R(P.length);let X=U.outDir??z.config.outDir,Q=K.resolve(z.root,X);await E.promises.mkdir(Q,{recursive:!0}),console.log(` Building to ${X}...`);let M=F(z);await E.promises.writeFile(K.join(Q,"index.html"),M);let v={files:P.map((G)=>({id:G.id,fileName:G.fileName,relativePath:G.relativePath,treePath:G.treePath})),tree:Y};await E.promises.writeFile(K.join(Q,"stories.json"),JSON.stringify(v,null,2));let L={theme:z.config.theme,responsivePresets:z.config.responsivePresets,backgroundPresets:z.config.backgroundPresets};await E.promises.writeFile(K.join(Q,"config.json"),JSON.stringify(L,null,2));let Z=K.join(Q,"stories");await E.promises.mkdir(Z,{recursive:!0});for(let G of P){let g=await E.promises.readFile(G.path,"utf-8");await E.promises.writeFile(K.join(Z,`${G.id}.stx`),g)}_(z);let T=$();await E.promises.writeFile(K.join(Q,"search-index.json"),T);let C=Date.now()-A;j(P.length,X,C)}function F(U){let{theme:A}=U.config,z=A.title||"STX Story";return`<!DOCTYPE html>
2
+ import{a as N,c as R,e as W,g as j,i as H,k as J}from"./chunk-0y46fbkx.js";import{l as k,m as w,n as B}from"./chunk-q9n5swx7.js";import"./chunk-qrkxyb5p.js";import E from"fs";import K from"path";var V=[];function _(U){V=[];for(let A of U.storyFiles)if(V.push({text:A.fileName.toLowerCase(),original:A.fileName,type:"story",storyId:A.id}),V.push({text:A.relativePath.toLowerCase(),original:A.relativePath,type:"story",storyId:A.id}),A.story)for(let z of A.story.variants)V.push({text:z.title.toLowerCase(),original:z.title,type:"story",storyId:A.id,variantId:z.id})}function $(){return JSON.stringify(V)}async function O(U={}){let A=Date.now();N();let z=await k({mode:"build"});if(!z.config.enabled){W("Story feature is disabled in config.");return}let P=await H(z);w(z,P);let Y=J(z.config,P);B(z,Y),R(P.length);let X=U.outDir??z.config.outDir,Q=K.resolve(z.root,X);await E.promises.mkdir(Q,{recursive:!0}),console.log(` Building to ${X}...`);let M=F(z);await E.promises.writeFile(K.join(Q,"index.html"),M);let v={files:P.map((G)=>({id:G.id,fileName:G.fileName,relativePath:G.relativePath,treePath:G.treePath})),tree:Y};await E.promises.writeFile(K.join(Q,"stories.json"),JSON.stringify(v,null,2));let L={theme:z.config.theme,responsivePresets:z.config.responsivePresets,backgroundPresets:z.config.backgroundPresets};await E.promises.writeFile(K.join(Q,"config.json"),JSON.stringify(L,null,2));let Z=K.join(Q,"stories");await E.promises.mkdir(Z,{recursive:!0});for(let G of P){let g=await E.promises.readFile(G.path,"utf-8");await E.promises.writeFile(K.join(Z,`${G.id}.stx`),g)}_(z);let T=$();await E.promises.writeFile(K.join(Q,"search-index.json"),T);let C=Date.now()-A;j(P.length,X,C)}function F(U){let{theme:A}=U.config,z=A.title||"STX Story";return`<!DOCTYPE html>
3
3
  <html lang="en">
4
4
  <head>
5
5
  <meta charset="UTF-8">
@@ -1,3 +1,3 @@
1
1
  // @bun
2
- import{_s as Q}from"./chunk-ws1s0ar4.js";import"./chunk-b810yw1a.js";import{ru as R}from"./chunk-j2d5v745.js";import"./chunk-vvds14af.js";import O from"fs";import U from"path";async function W(j,b={}){let q=Date.now(),z={total:0,passed:0,failed:0,new:0,failures:[],duration:0},K=b.snapshotDir||R(j.root,"story","snapshots");await O.promises.mkdir(K,{recursive:!0});let B=j.storyFiles;if(b.filter&&b.filter.length>0)B=B.filter((J)=>b.filter.some((G)=>J.fileName.toLowerCase().includes(G.toLowerCase())));for(let J of B)await X(J,K,b,z);return z.duration=Date.now()-q,z}async function X(j,b,q,z){let K=j.story;if(!K)return;for(let B of K.variants){z.total++;let J=U.join(b,`${j.id}-${B.id}.snap`),G=Y(B.source||""),L=await _(J);if(q.update||!L){let M={storyId:j.id,variantId:B.id,component:j.fileName,timestamp:Date.now(),hash:G};if(await O.promises.writeFile(J,JSON.stringify(M,null,2)),!L)z.new++;z.passed++}else try{let M=await O.promises.readFile(J,"utf-8"),N=JSON.parse(M);if(N.hash===G)z.passed++;else z.failed++,z.failures.push({storyId:j.id,variantId:B.id,component:j.fileName,message:"Snapshot mismatch",difference:Z(N.hash,G)})}catch(M){z.failed++,z.failures.push({storyId:j.id,variantId:B.id,component:j.fileName,message:`Error reading snapshot: ${M}`})}}}async function A(j,b={}){return(await W(j,{...b,update:!0})).total}function Y(j){let b=0;for(let q=0;q<j.length;q++){let z=j.charCodeAt(q);b=(b<<5)-b+z,b=b&b}return b.toString(16)}function Z(j,b){if(j===b)return 0;let q=Math.abs(Number.parseInt(j,16)-Number.parseInt(b,16));return Math.min(q/1e6,1)}async function _(j){try{return await O.promises.access(j),!0}catch{return!1}}function I(j){let b=[];if(b.push(""),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(" STX Story Visual Regression Tests"),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(""),j.failures.length>0){b.push(" \u274C Failures:"),b.push("");for(let q of j.failures){if(b.push(` \u2022 ${q.component} / ${q.variantId}`),b.push(` ${q.message}`),q.difference!==void 0)b.push(` Difference: ${(q.difference*100).toFixed(2)}%`);b.push("")}}if(b.push(" Summary:"),b.push(` Total: ${j.total}`),b.push(` Passed: ${j.passed} \u2713`),b.push(` Failed: ${j.failed} \u2717`),j.new>0)b.push(` New: ${j.new} \u26A1`);return b.push(` Time: ${j.duration}ms`),b.push(""),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(""),b.join(`
2
+ import{$s as Q}from"./chunk-9e6pyjwz.js";import"./chunk-z698hwht.js";import{zu as R}from"./chunk-ecbqmesm.js";import"./chunk-qrkxyb5p.js";import O from"fs";import U from"path";async function W(j,b={}){let q=Date.now(),z={total:0,passed:0,failed:0,new:0,failures:[],duration:0},K=b.snapshotDir||R(j.root,"story","snapshots");await O.promises.mkdir(K,{recursive:!0});let B=j.storyFiles;if(b.filter&&b.filter.length>0)B=B.filter((J)=>b.filter.some((G)=>J.fileName.toLowerCase().includes(G.toLowerCase())));for(let J of B)await X(J,K,b,z);return z.duration=Date.now()-q,z}async function X(j,b,q,z){let K=j.story;if(!K)return;for(let B of K.variants){z.total++;let J=U.join(b,`${j.id}-${B.id}.snap`),G=Y(B.source||""),L=await _(J);if(q.update||!L){let M={storyId:j.id,variantId:B.id,component:j.fileName,timestamp:Date.now(),hash:G};if(await O.promises.writeFile(J,JSON.stringify(M,null,2)),!L)z.new++;z.passed++}else try{let M=await O.promises.readFile(J,"utf-8"),N=JSON.parse(M);if(N.hash===G)z.passed++;else z.failed++,z.failures.push({storyId:j.id,variantId:B.id,component:j.fileName,message:"Snapshot mismatch",difference:Z(N.hash,G)})}catch(M){z.failed++,z.failures.push({storyId:j.id,variantId:B.id,component:j.fileName,message:`Error reading snapshot: ${M}`})}}}async function A(j,b={}){return(await W(j,{...b,update:!0})).total}function Y(j){let b=0;for(let q=0;q<j.length;q++){let z=j.charCodeAt(q);b=(b<<5)-b+z,b=b&b}return b.toString(16)}function Z(j,b){if(j===b)return 0;let q=Math.abs(Number.parseInt(j,16)-Number.parseInt(b,16));return Math.min(q/1e6,1)}async function _(j){try{return await O.promises.access(j),!0}catch{return!1}}function I(j){let b=[];if(b.push(""),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(" STX Story Visual Regression Tests"),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(""),j.failures.length>0){b.push(" \u274C Failures:"),b.push("");for(let q of j.failures){if(b.push(` \u2022 ${q.component} / ${q.variantId}`),b.push(` ${q.message}`),q.difference!==void 0)b.push(` Difference: ${(q.difference*100).toFixed(2)}%`);b.push("")}}if(b.push(" Summary:"),b.push(` Total: ${j.total}`),b.push(` Passed: ${j.passed} \u2713`),b.push(` Failed: ${j.failed} \u2717`),j.new>0)b.push(` New: ${j.new} \u26A1`);return b.push(` Time: ${j.duration}ms`),b.push(""),b.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),b.push(""),b.join(`
3
3
  `)}async function S(j,b={}){let q=performance.now(),z=[],K=0,B=0;for(let G of j.storyFiles)try{let L=await Bun.file(G.path).text(),M=await Q(L,G.path);if(M.length>0){for(let N of M)z.push({storyId:G.id,variantId:"default",component:G.component,message:`A11y: ${N.message}`,expected:"No accessibility violations",actual:`${N.type}: ${N.message} (impact: ${N.impact})`});B++}else K++}catch(L){z.push({storyId:G.id,variantId:"default",component:G.component,message:`Failed to check a11y: ${L instanceof Error?L.message:String(L)}`,expected:"File readable for a11y check",actual:"File read error"}),B++}let J=performance.now()-q;return{total:j.storyFiles.length,passed:K,failed:B,new:0,failures:z,duration:J}}export{A as updateSnapshots,W as runVisualTests,S as runA11yTests,I as formatTestResults};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{Cu as a,Du as b,Eu as c,Fu as d,Gu as e,Hu as f,Iu as g,Ju as h,Ku as i,Lu as j,Mu as k,Nu as l,Ou as m,Pu as n,Qu as o,Ru as p}from"./chunk-pj9zybjf.js";import"./chunk-vvds14af.js";export{i as useTitle,f as useSeoMeta,i as usePageTitle,d as useHead,g as seoMetaToHeadConfig,c as resetHead,k as renderHtmlAttrs,j as renderHead,l as renderBodyAttrs,o as processTitleDirective,p as processMetaDirective,n as processHeadDirective,e as mergeHeadConfigs,b as getPageMeta,a as getHead,h as definePageMeta,m as applyHead};
2
+ import{Ku as a,Lu as b,Mu as c,Nu as d,Ou as e,Pu as f,Qu as g,Ru as h,Su as i,Tu as j,Uu as k,Vu as l,Wu as m,Xu as n,Yu as o,Zu as p}from"./chunk-2hyxkzpc.js";import"./chunk-qrkxyb5p.js";export{i as useTitle,f as useSeoMeta,i as usePageTitle,d as useHead,g as seoMetaToHeadConfig,c as resetHead,k as renderHtmlAttrs,j as renderHead,l as renderBodyAttrs,o as processTitleDirective,p as processMetaDirective,n as processHeadDirective,e as mergeHeadConfigs,b as getPageMeta,a as getHead,h as definePageMeta,m as applyHead};
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ import{Ue as a}from"./chunk-mz9twa1f.js";import"./chunk-dvvx1wny.js";import"./chunk-2astj1fq.js";import"./chunk-9e6pyjwz.js";import"./chunk-z698hwht.js";import"./chunk-70zb3560.js";import"./chunk-zsapb653.js";import"./chunk-kwrj745h.js";import"./chunk-df5wrw50.js";import"./chunk-e10pfrds.js";import"./chunk-ecbqmesm.js";import"./chunk-namj6apv.js";import"./chunk-dgrayagx.js";import"./chunk-2hyxkzpc.js";import"./chunk-r1g0bnpv.js";import"./chunk-2dk3ym1g.js";import"./chunk-8n0mjaf6.js";import"./chunk-5az2jb8y.js";import"./chunk-rcvnh2p7.js";import"./chunk-qrkxyb5p.js";export{a as buildApp};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{Qh as a,Rh as b,Sh as c,Th as d,Uh as e,Vh as f,Wh as g,Xh as h,Yh as i,Zh as j,_h as k}from"./chunk-9368t9jm.js";import"./chunk-vvds14af.js";export{i as processStaticDirectives,b as processPartialHydrationDirectives,h as onHydrated,g as isHydrated,d as hydrateIsland,e as hydrateByStrategy,f as hydrateAll,c as generatePartialHydrationCSS,j as generateIslandManifest,k as default,a as createIslandRegistry};
2
+ import{Qh as a,Rh as b,Sh as c,Th as d,Uh as e,Vh as f,Wh as g,Xh as h,Yh as i,Zh as j,_h as k}from"./chunk-9368t9jm.js";import"./chunk-qrkxyb5p.js";export{i as processStaticDirectives,b as processPartialHydrationDirectives,h as onHydrated,g as isHydrated,d as hydrateIsland,e as hydrateByStrategy,f as hydrateAll,c as generatePartialHydrationCSS,j as generateIslandManifest,k as default,a as createIslandRegistry};