@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.
- package/dist/builtins/image-alpha.d.ts +26 -0
- package/dist/builtins/image-alpha.js +57 -0
- package/dist/builtins/image-delivery.d.ts +33 -6
- package/dist/builtins/image-delivery.js +55 -51
- package/dist/builtins/image-placeholder.d.ts +9 -0
- package/dist/builtins/image-placeholder.js +26 -4
- package/dist/builtins/image-warmup-worker.d.ts +1 -0
- package/dist/builtins/image-warmup-worker.js +18 -0
- package/dist/builtins/image-worker.d.ts +40 -0
- package/dist/builtins/image-worker.js +82 -0
- package/dist/chunk-0050wktp.js +2 -0
- package/dist/{chunk-fkvg0m44.js → chunk-02hjbe0p.js} +1 -1
- package/dist/{chunk-g786azs5.js → chunk-0aqtbma7.js} +1 -1
- package/dist/{chunk-kz7rzjfd.js → chunk-0fkvezw3.js} +1 -1
- package/dist/{chunk-7vbwm0fg.js → chunk-0xw3atfx.js} +1 -1
- package/dist/chunk-1yxtx5y2.js +2 -0
- package/dist/{chunk-vc506z79.js → chunk-24r2ctta.js} +1 -1
- package/dist/{chunk-2cey22m2.js → chunk-2astj1fq.js} +346 -346
- package/dist/{chunk-naxteb5j.js → chunk-2dk3ym1g.js} +1 -1
- package/dist/{chunk-pj9zybjf.js → chunk-2hyxkzpc.js} +1 -1
- package/dist/chunk-2k9qqkx2.js +2 -0
- package/dist/chunk-2qk3qwmp.js +2 -0
- package/dist/{chunk-c9m4a26z.js → chunk-308r399e.js} +1 -1
- package/dist/{chunk-jx6qn384.js → chunk-3vydymb5.js} +1 -1
- package/dist/{chunk-aqzs1rqe.js → chunk-3yn8ghtz.js} +1 -1
- package/dist/{chunk-kpj1tk19.js → chunk-54mnf7n2.js} +1 -1
- package/dist/chunk-59fcj7ym.js +2 -0
- package/dist/{chunk-3meekwv6.js → chunk-5az2jb8y.js} +2 -2
- package/dist/chunk-5n2ps25k.js +2 -0
- package/dist/{chunk-456x7xb4.js → chunk-5zqec01h.js} +1 -1
- package/dist/chunk-62s55rv0.js +2 -0
- package/dist/{chunk-aspvxqzg.js → chunk-69knrrh9.js} +1 -1
- package/dist/{chunk-fvhg6b2b.js → chunk-70zb3560.js} +1 -1
- package/dist/{chunk-j03sjtfs.js → chunk-7bmqjfft.js} +1 -1
- package/dist/chunk-7dd808wv.js +2 -0
- package/dist/{chunk-hrxdza7v.js → chunk-7jqh58k9.js} +1 -1
- package/dist/{chunk-2rca15ge.js → chunk-88kr475r.js} +1 -1
- package/dist/chunk-8cwenbw3.js +2 -0
- package/dist/{chunk-9enf3e5m.js → chunk-8n0mjaf6.js} +1 -1
- package/dist/{chunk-k1jq596t.js → chunk-8r0yf1q7.js} +1 -1
- package/dist/{chunk-cy6rz73m.js → chunk-90ma145g.js} +1 -1
- package/dist/{chunk-ws1s0ar4.js → chunk-9e6pyjwz.js} +2 -2
- package/dist/{chunk-73wmd3vp.js → chunk-9q9z1027.js} +1 -1
- package/dist/{chunk-dt03j5ff.js → chunk-akrxtrh1.js} +1 -1
- package/dist/chunk-asf7qyyc.js +2 -0
- package/dist/{chunk-nevmgfc7.js → chunk-avpsqyaq.js} +1 -1
- package/dist/{chunk-qs9bq14j.js → chunk-b0wm1c6x.js} +1 -1
- package/dist/{chunk-gvjkh5mv.js → chunk-b1h7pyhs.js} +1 -1
- package/dist/{chunk-s17bnxws.js → chunk-bdr8dz57.js} +2 -2
- package/dist/chunk-bgwcrkg8.js +2 -0
- package/dist/{chunk-tvvqcwa5.js → chunk-brb3g571.js} +1 -1
- package/dist/chunk-bw0488z7.js +2 -0
- package/dist/chunk-c3pg3yx4.js +2 -0
- package/dist/chunk-cevmy9cn.js +2 -0
- package/dist/chunk-df5wrw50.js +3 -0
- package/dist/{chunk-dc6z4asx.js → chunk-dgrayagx.js} +1 -1
- package/dist/{chunk-kbf0xkp1.js → chunk-dsqcfv2k.js} +1 -1
- package/dist/chunk-dvszm8ay.js +2 -0
- package/dist/{chunk-3745eg0d.js → chunk-e0369ngw.js} +1 -1
- package/dist/{chunk-j6f8bq12.js → chunk-e10pfrds.js} +1 -1
- package/dist/{chunk-a9az6pen.js → chunk-e9dtt2cw.js} +2 -2
- package/dist/{chunk-j2d5v745.js → chunk-ecbqmesm.js} +1 -1
- package/dist/chunk-eqsqs6yj.js +2 -0
- package/dist/{chunk-8s9x1kcg.js → chunk-exrnsgtc.js} +1 -1
- package/dist/{chunk-k29v4m21.js → chunk-ez93xc1z.js} +1 -1
- package/dist/{chunk-2q30mata.js → chunk-f75yj981.js} +1 -1
- package/dist/chunk-fps56kyx.js +2 -0
- package/dist/{chunk-b8hj1ena.js → chunk-g2pjef44.js} +1 -1
- package/dist/{chunk-nsavhgg0.js → chunk-gctdc94b.js} +1 -1
- package/dist/chunk-gg74g03k.js +2 -0
- package/dist/chunk-gyprd56b.js +2 -0
- package/dist/{chunk-2f0kykg4.js → chunk-hrs2pepp.js} +1 -1
- package/dist/chunk-k0ht65ts.js +2 -0
- package/dist/{chunk-rsjbw0qt.js → chunk-k3w1ctbb.js} +1 -1
- package/dist/chunk-kb040j0d.js +2 -0
- package/dist/{chunk-273xtqav.js → chunk-kwrj745h.js} +3 -3
- package/dist/{chunk-f1hfbnxv.js → chunk-m4wwqznv.js} +1 -1
- package/dist/{chunk-ctdmn0az.js → chunk-mczfzq2y.js} +1 -1
- package/dist/{chunk-c84gtc0v.js → chunk-mp8nekfh.js} +1 -1
- package/dist/chunk-mr33j493.js +2 -0
- package/dist/{chunk-1zckawsv.js → chunk-mz9twa1f.js} +1 -1
- package/dist/{chunk-nyfqbqd2.js → chunk-namj6apv.js} +2 -2
- package/dist/{chunk-fe0pxn8d.js → chunk-nh2ze1a7.js} +1 -1
- package/dist/chunk-nkpww053.js +2 -0
- package/dist/{chunk-5mg4ddbx.js → chunk-nksce8wc.js} +1 -1
- package/dist/{chunk-fbb6eaw3.js → chunk-nq0ec0w1.js} +1 -1
- package/dist/{chunk-45e1wffb.js → chunk-nq19m0ja.js} +1 -1
- package/dist/{chunk-rhk3w1hf.js → chunk-nytw8ek5.js} +1 -1
- package/dist/chunk-pc70paa2.js +2 -0
- package/dist/chunk-pgafdv9x.js +2 -0
- package/dist/{chunk-brfcence.js → chunk-pty8ywaj.js} +1 -1
- package/dist/chunk-pwwhcdh3.js +2 -0
- package/dist/{chunk-49129rm8.js → chunk-qhzvf0nf.js} +1 -1
- package/dist/{chunk-vvds14af.js → chunk-qrkxyb5p.js} +1 -1
- package/dist/{chunk-z57k43ad.js → chunk-qsxtjtvv.js} +2 -2
- package/dist/{chunk-sh9m91w0.js → chunk-r05ekvht.js} +1 -1
- package/dist/{chunk-7z2wtxxj.js → chunk-r1g0bnpv.js} +1 -1
- package/dist/chunk-r8scz4g7.js +2 -0
- package/dist/{chunk-rwcnfdj5.js → chunk-rcvnh2p7.js} +1 -1
- package/dist/chunk-rw93qdw8.js +2 -0
- package/dist/{chunk-k9qd0v7e.js → chunk-sn1edmp8.js} +1 -1
- package/dist/{chunk-ebk6h629.js → chunk-tpvs4bzw.js} +1 -1
- package/dist/{chunk-qc63mkgk.js → chunk-v1jcfb07.js} +1 -1
- package/dist/{chunk-y3m051g6.js → chunk-v3taty7e.js} +1 -1
- package/dist/{chunk-9znajpe5.js → chunk-vtn57jw8.js} +10 -10
- package/dist/{chunk-xsd936j5.js → chunk-vy2nvpbw.js} +2 -2
- package/dist/{chunk-k4417q3k.js → chunk-wf3pcr37.js} +1 -1
- package/dist/{chunk-daxm49bn.js → chunk-ws4kh6et.js} +1 -1
- package/dist/{chunk-pytndzd2.js → chunk-x5xee26w.js} +1 -1
- package/dist/{chunk-szyc050e.js → chunk-xgrh0461.js} +1 -1
- package/dist/{chunk-kep0d46b.js → chunk-xsrb2heb.js} +1 -1
- package/dist/{chunk-hbvxhwvt.js → chunk-y1zwvdn5.js} +1 -1
- package/dist/chunk-y3egkbc2.js +2 -0
- package/dist/{chunk-rra7n04f.js → chunk-yc7fptr2.js} +2 -2
- package/dist/chunk-ycjjd5js.js +2 -0
- package/dist/{chunk-b70m51nc.js → chunk-yms0xgy0.js} +1 -1
- package/dist/{chunk-19yw8p2h.js → chunk-yyq7rpw2.js} +1 -1
- package/dist/{chunk-b810yw1a.js → chunk-z698hwht.js} +1 -1
- package/dist/chunk-zcea9z8j.js +2 -0
- package/dist/{chunk-n9bmagzv.js → chunk-zgbwr604.js} +1 -1
- package/dist/{chunk-sqwqyxzx.js → chunk-zsapb653.js} +1 -1
- package/dist/{chunk-jk6xwdfb.js → chunk-zwkzwymf.js} +1 -1
- package/dist/cli.js +14 -14
- package/dist/craft-entry.js +1 -1
- package/dist/craft.js +1 -1
- package/dist/database.js +1 -1
- package/dist/first-script-tag.d.ts +24 -0
- package/dist/first-script-tag.js +44 -0
- package/dist/includes.js +3 -2
- package/dist/loops.d.ts +15 -1
- package/dist/loops.js +18 -4
- package/dist/process.js +4 -29
- package/dist/pwa.js +2 -2
- package/dist/runtime-injection.js +2 -1
- package/dist/ssg.js +2 -2
- package/dist/visual-testing.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-08809py9.js +0 -2
- package/dist/chunk-273pkk23.js +0 -2
- package/dist/chunk-2zpf8z9f.js +0 -2
- package/dist/chunk-3m34hq6t.js +0 -2
- package/dist/chunk-3rajeek7.js +0 -2
- package/dist/chunk-5wep1h8r.js +0 -2
- package/dist/chunk-61bmp7rb.js +0 -2
- package/dist/chunk-6py5b6p4.js +0 -2
- package/dist/chunk-9ef162n1.js +0 -2
- package/dist/chunk-abefheag.js +0 -2
- package/dist/chunk-bbad74mr.js +0 -2
- package/dist/chunk-bjy23bjm.js +0 -2
- package/dist/chunk-bx9mxc6z.js +0 -2
- package/dist/chunk-dfewnpk4.js +0 -2
- package/dist/chunk-f1yazmb2.js +0 -2
- package/dist/chunk-fchtzkft.js +0 -2
- package/dist/chunk-ghvqy1n9.js +0 -2
- package/dist/chunk-gvyaeytx.js +0 -3
- package/dist/chunk-hxk2wkmr.js +0 -2
- package/dist/chunk-j9vkpac1.js +0 -2
- package/dist/chunk-k0rrma2q.js +0 -2
- package/dist/chunk-k4tbashh.js +0 -2
- package/dist/chunk-nwsnjyhg.js +0 -2
- package/dist/chunk-pgbhyh3z.js +0 -2
- package/dist/chunk-pnp60knr.js +0 -2
- package/dist/chunk-qvc2ddqn.js +0 -2
- package/dist/chunk-s4c6vvs3.js +0 -2
- package/dist/chunk-v4m87pv6.js +0 -2
- package/dist/chunk-v6kvq7ww.js +0 -2
- package/dist/chunk-vawmst0v.js +0 -2
- package/dist/chunk-x89acpn9.js +0 -2
- 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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
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 @@
|
|
|
1
|
+
|
|
@@ -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-
|
|
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-
|
|
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{
|
|
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{
|
|
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-
|
|
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};
|