@elurjs/kit 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +874 -0
- package/LICENSE +21 -0
- package/README.md +1330 -0
- package/bin/elur-kit.js +54 -0
- package/dist/lib/action/define.d.cts +72 -0
- package/dist/lib/action/define.d.ts +72 -0
- package/dist/lib/action/error-store.d.cts +29 -0
- package/dist/lib/action/error-store.d.ts +29 -0
- package/dist/lib/action/index.cjs +1 -0
- package/dist/lib/action/index.d.cts +64 -0
- package/dist/lib/action/index.d.ts +64 -0
- package/dist/lib/action/index.js +2 -0
- package/dist/lib/action/origin.d.cts +20 -0
- package/dist/lib/action/origin.d.ts +20 -0
- package/dist/lib/action/scan.d.cts +28 -0
- package/dist/lib/action/scan.d.ts +28 -0
- package/dist/lib/action/server.d.cts +30 -0
- package/dist/lib/action/server.d.ts +30 -0
- package/dist/lib/action-BB6N5yD-.js +111 -0
- package/dist/lib/action-BB6N5yD-.js.map +1 -0
- package/dist/lib/action-DZHihU3Z.cjs +2 -0
- package/dist/lib/action-DZHihU3Z.cjs.map +1 -0
- package/dist/lib/adapters/bun.cjs +30 -0
- package/dist/lib/adapters/bun.cjs.map +1 -0
- package/dist/lib/adapters/bun.d.cts +13 -0
- package/dist/lib/adapters/bun.d.ts +13 -0
- package/dist/lib/adapters/bun.js +65 -0
- package/dist/lib/adapters/bun.js.map +1 -0
- package/dist/lib/adapters/index.cjs +0 -0
- package/dist/lib/adapters/index.d.cts +30 -0
- package/dist/lib/adapters/index.d.ts +30 -0
- package/dist/lib/adapters/index.js +0 -0
- package/dist/lib/adapters/netlify.cjs +10 -0
- package/dist/lib/adapters/netlify.cjs.map +1 -0
- package/dist/lib/adapters/netlify.d.cts +13 -0
- package/dist/lib/adapters/netlify.d.ts +13 -0
- package/dist/lib/adapters/netlify.js +51 -0
- package/dist/lib/adapters/netlify.js.map +1 -0
- package/dist/lib/adapters/node.cjs +46 -0
- package/dist/lib/adapters/node.cjs.map +1 -0
- package/dist/lib/adapters/node.d.cts +13 -0
- package/dist/lib/adapters/node.d.ts +13 -0
- package/dist/lib/adapters/node.js +105 -0
- package/dist/lib/adapters/node.js.map +1 -0
- package/dist/lib/adapters/shared.d.cts +17 -0
- package/dist/lib/adapters/shared.d.ts +17 -0
- package/dist/lib/adapters/vercel.cjs +2 -0
- package/dist/lib/adapters/vercel.cjs.map +1 -0
- package/dist/lib/adapters/vercel.d.cts +9 -0
- package/dist/lib/adapters/vercel.d.ts +9 -0
- package/dist/lib/adapters/vercel.js +62 -0
- package/dist/lib/adapters/vercel.js.map +1 -0
- package/dist/lib/build/build.d.cts +83 -0
- package/dist/lib/build/build.d.ts +83 -0
- package/dist/lib/build/document-shell.d.cts +48 -0
- package/dist/lib/build/document-shell.d.ts +48 -0
- package/dist/lib/build/transform-source.d.cts +26 -0
- package/dist/lib/build/transform-source.d.ts +26 -0
- package/dist/lib/build/vite-build.d.cts +78 -0
- package/dist/lib/build/vite-build.d.ts +78 -0
- package/dist/lib/cache/policy.d.cts +25 -0
- package/dist/lib/cache/policy.d.ts +25 -0
- package/dist/lib/cache.d.cts +13 -0
- package/dist/lib/cache.d.ts +13 -0
- package/dist/lib/capabilities-C0TDQoMY.cjs +2 -0
- package/dist/lib/capabilities-C0TDQoMY.cjs.map +1 -0
- package/dist/lib/capabilities-C7txofmY.js +45 -0
- package/dist/lib/capabilities-C7txofmY.js.map +1 -0
- package/dist/lib/cli/commands.d.cts +15 -0
- package/dist/lib/cli/commands.d.ts +15 -0
- package/dist/lib/cli.cjs +5132 -0
- package/dist/lib/cli.cjs.map +1 -0
- package/dist/lib/cli.d.cts +30 -0
- package/dist/lib/cli.d.ts +30 -0
- package/dist/lib/cli.js +5131 -0
- package/dist/lib/cli.js.map +1 -0
- package/dist/lib/collections-Dbc8aRLf.js +233 -0
- package/dist/lib/collections-Dbc8aRLf.js.map +1 -0
- package/dist/lib/collections-VDy5g9p8.cjs +13 -0
- package/dist/lib/collections-VDy5g9p8.cjs.map +1 -0
- package/dist/lib/config/index.cjs +2 -0
- package/dist/lib/config/index.cjs.map +1 -0
- package/dist/lib/config/index.d.cts +100 -0
- package/dist/lib/config/index.d.ts +100 -0
- package/dist/lib/config/index.js +122 -0
- package/dist/lib/config/index.js.map +1 -0
- package/dist/lib/content/collections.d.cts +69 -0
- package/dist/lib/content/collections.d.ts +69 -0
- package/dist/lib/content/frontmatter.d.cts +20 -0
- package/dist/lib/content/frontmatter.d.ts +20 -0
- package/dist/lib/content/index.cjs +2 -0
- package/dist/lib/content/index.cjs.map +1 -0
- package/dist/lib/content/index.d.cts +5 -0
- package/dist/lib/content/index.d.ts +5 -0
- package/dist/lib/content/index.js +28 -0
- package/dist/lib/content/index.js.map +1 -0
- package/dist/lib/content/markdown.d.cts +6 -0
- package/dist/lib/content/markdown.d.ts +6 -0
- package/dist/lib/content/raw.d.cts +9 -0
- package/dist/lib/content/raw.d.ts +9 -0
- package/dist/lib/content/schema.d.cts +14 -0
- package/dist/lib/content/schema.d.ts +14 -0
- package/dist/lib/errors-0X7pCFNU.cjs +2 -0
- package/dist/lib/errors-0X7pCFNU.cjs.map +1 -0
- package/dist/lib/errors-ePYxDlhU.js +70 -0
- package/dist/lib/errors-ePYxDlhU.js.map +1 -0
- package/dist/lib/errors.d.cts +84 -0
- package/dist/lib/errors.d.ts +84 -0
- package/dist/lib/hydrate-8a3TbQbs.cjs +2 -0
- package/dist/lib/hydrate-8a3TbQbs.cjs.map +1 -0
- package/dist/lib/hydrate-CgZGrTxF.js +126 -0
- package/dist/lib/hydrate-CgZGrTxF.js.map +1 -0
- package/dist/lib/image/index.cjs +2 -0
- package/dist/lib/image/index.cjs.map +1 -0
- package/dist/lib/image/index.d.cts +37 -0
- package/dist/lib/image/index.d.ts +37 -0
- package/dist/lib/image/index.js +49 -0
- package/dist/lib/image/index.js.map +1 -0
- package/dist/lib/image/pipeline.d.cts +37 -0
- package/dist/lib/image/pipeline.d.ts +37 -0
- package/dist/lib/image/registry.cjs +2 -0
- package/dist/lib/image/registry.cjs.map +1 -0
- package/dist/lib/image/registry.d.cts +26 -0
- package/dist/lib/image/registry.d.ts +26 -0
- package/dist/lib/image/registry.js +23 -0
- package/dist/lib/image/registry.js.map +1 -0
- package/dist/lib/image/service.cjs +2 -0
- package/dist/lib/image/service.cjs.map +1 -0
- package/dist/lib/image/service.d.cts +164 -0
- package/dist/lib/image/service.d.ts +164 -0
- package/dist/lib/image/service.js +308 -0
- package/dist/lib/image/service.js.map +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.d.cts +38 -0
- package/dist/lib/index.d.ts +38 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/integrations/hooks.d.cts +58 -0
- package/dist/lib/integrations/hooks.d.ts +58 -0
- package/dist/lib/integrations/index.cjs +1 -0
- package/dist/lib/integrations/index.d.cts +18 -0
- package/dist/lib/integrations/index.d.ts +18 -0
- package/dist/lib/integrations/index.js +2 -0
- package/dist/lib/integrations-7dpN1RYB.js +35 -0
- package/dist/lib/integrations-7dpN1RYB.js.map +1 -0
- package/dist/lib/integrations-DDqfWjvH.cjs +2 -0
- package/dist/lib/integrations-DDqfWjvH.cjs.map +1 -0
- package/dist/lib/interpolation-plugin-Bt-KRKVY.js +365 -0
- package/dist/lib/interpolation-plugin-Bt-KRKVY.js.map +1 -0
- package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs +31 -0
- package/dist/lib/interpolation-plugin-CMPCDlVJ.cjs.map +1 -0
- package/dist/lib/island/generate-entry.d.cts +27 -0
- package/dist/lib/island/generate-entry.d.ts +27 -0
- package/dist/lib/island/hydrate.d.cts +33 -0
- package/dist/lib/island/hydrate.d.ts +33 -0
- package/dist/lib/island/index.cjs +1 -0
- package/dist/lib/island/index.d.cts +2 -0
- package/dist/lib/island/index.d.ts +2 -0
- package/dist/lib/island/index.js +1 -0
- package/dist/lib/island/island.d.cts +32 -0
- package/dist/lib/island/island.d.ts +32 -0
- package/dist/lib/island/scan.d.cts +14 -0
- package/dist/lib/island/scan.d.ts +14 -0
- package/dist/lib/manifest/index.cjs +3 -0
- package/dist/lib/manifest/index.cjs.map +1 -0
- package/dist/lib/manifest/index.d.cts +18 -0
- package/dist/lib/manifest/index.d.ts +18 -0
- package/dist/lib/manifest/index.js +110 -0
- package/dist/lib/manifest/index.js.map +1 -0
- package/dist/lib/middleware/index.d.cts +56 -0
- package/dist/lib/middleware/index.d.ts +56 -0
- package/dist/lib/middleware/stream-boundary.d.cts +53 -0
- package/dist/lib/middleware/stream-boundary.d.ts +53 -0
- package/dist/lib/node-http-DRAUhO0c.js +617 -0
- package/dist/lib/node-http-DRAUhO0c.js.map +1 -0
- package/dist/lib/node-http-DhxguYyz.cjs +13 -0
- package/dist/lib/node-http-DhxguYyz.cjs.map +1 -0
- package/dist/lib/render/render-to-string.d.cts +12 -0
- package/dist/lib/render/render-to-string.d.ts +12 -0
- package/dist/lib/render/ssr-flag.d.cts +4 -0
- package/dist/lib/render/ssr-flag.d.ts +4 -0
- package/dist/lib/route-scanner-JY6YrNH5.cjs +2 -0
- package/dist/lib/route-scanner-JY6YrNH5.cjs.map +1 -0
- package/dist/lib/route-scanner-XcLId7lK.js +100 -0
- package/dist/lib/route-scanner-XcLId7lK.js.map +1 -0
- package/dist/lib/router/client.cjs +2 -0
- package/dist/lib/router/client.cjs.map +1 -0
- package/dist/lib/router/client.d.cts +47 -0
- package/dist/lib/router/client.d.ts +47 -0
- package/dist/lib/router/client.js +298 -0
- package/dist/lib/router/client.js.map +1 -0
- package/dist/lib/router/route-scanner.d.cts +50 -0
- package/dist/lib/router/route-scanner.d.ts +50 -0
- package/dist/lib/runtime/capabilities.d.cts +47 -0
- package/dist/lib/runtime/capabilities.d.ts +47 -0
- package/dist/lib/runtime/context.d.cts +140 -0
- package/dist/lib/runtime/context.d.ts +140 -0
- package/dist/lib/runtime/handler.d.cts +39 -0
- package/dist/lib/runtime/handler.d.ts +39 -0
- package/dist/lib/runtime/index.cjs +1 -0
- package/dist/lib/runtime/index.d.cts +6 -0
- package/dist/lib/runtime/index.d.ts +6 -0
- package/dist/lib/runtime/index.js +4 -0
- package/dist/lib/runtime/node-http.d.cts +2 -0
- package/dist/lib/runtime/node-http.d.ts +2 -0
- package/dist/lib/runtime/security-headers.d.cts +13 -0
- package/dist/lib/runtime/security-headers.d.ts +13 -0
- package/dist/lib/runtime/static.d.cts +1 -0
- package/dist/lib/runtime/static.d.ts +1 -0
- package/dist/lib/runtime-CrH5X-Es.cjs +2 -0
- package/dist/lib/runtime-CrH5X-Es.cjs.map +1 -0
- package/dist/lib/runtime-Ds2-wH6P.js +520 -0
- package/dist/lib/runtime-Ds2-wH6P.js.map +1 -0
- package/dist/lib/scan-CY2O9ty6.cjs +2 -0
- package/dist/lib/scan-CY2O9ty6.cjs.map +1 -0
- package/dist/lib/scan-CjMUgnsd.js +22 -0
- package/dist/lib/scan-CjMUgnsd.js.map +1 -0
- package/dist/lib/scan-DXzYtmRQ.cjs +2 -0
- package/dist/lib/scan-DXzYtmRQ.cjs.map +1 -0
- package/dist/lib/scan-Dn93ycXG.js +33 -0
- package/dist/lib/scan-Dn93ycXG.js.map +1 -0
- package/dist/lib/seo/index.cjs +10 -0
- package/dist/lib/seo/index.cjs.map +1 -0
- package/dist/lib/seo/index.d.cts +99 -0
- package/dist/lib/seo/index.d.ts +99 -0
- package/dist/lib/seo/index.js +41 -0
- package/dist/lib/seo/index.js.map +1 -0
- package/dist/lib/shared-CfqOLsKj.cjs +183 -0
- package/dist/lib/shared-CfqOLsKj.cjs.map +1 -0
- package/dist/lib/shared-DU3iQgr7.js +236 -0
- package/dist/lib/shared-DU3iQgr7.js.map +1 -0
- package/dist/lib/ssr/match.d.cts +25 -0
- package/dist/lib/ssr/match.d.ts +25 -0
- package/dist/lib/ssr/render.d.cts +62 -0
- package/dist/lib/ssr/render.d.ts +62 -0
- package/dist/lib/ssr/server.d.cts +41 -0
- package/dist/lib/ssr/server.d.ts +41 -0
- package/dist/lib/ssr/stream.d.cts +48 -0
- package/dist/lib/ssr/stream.d.ts +48 -0
- package/dist/lib/stream-CpOG052V.js +104 -0
- package/dist/lib/stream-CpOG052V.js.map +1 -0
- package/dist/lib/stream-DmZjYqHm.cjs +17 -0
- package/dist/lib/stream-DmZjYqHm.cjs.map +1 -0
- package/dist/lib/types.d.cts +97 -0
- package/dist/lib/types.d.ts +97 -0
- package/dist/lib/vite/index.cjs +2 -0
- package/dist/lib/vite/index.cjs.map +1 -0
- package/dist/lib/vite/index.d.cts +41 -0
- package/dist/lib/vite/index.d.ts +41 -0
- package/dist/lib/vite/index.js +229 -0
- package/dist/lib/vite/index.js.map +1 -0
- package/dist/lib/vite/interpolation-plugin.d.cts +64 -0
- package/dist/lib/vite/interpolation-plugin.d.ts +64 -0
- package/package.json +292 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { mkdir as e, readFile as t, rename as n, rm as r, stat as i, writeFile as a } from "node:fs/promises";
|
|
2
|
+
import { basename as o, dirname as s, extname as c, join as l, resolve as u, sep as d } from "node:path";
|
|
3
|
+
import { createHash as f, randomBytes as p } from "node:crypto";
|
|
4
|
+
//#region src/image/service.ts
|
|
5
|
+
var m = "sharp-1", h = "v1", g = 12, _ = 80, v = 4, y;
|
|
6
|
+
async function b() {
|
|
7
|
+
if (y === null) return null;
|
|
8
|
+
if (y) return y();
|
|
9
|
+
try {
|
|
10
|
+
let e = (await import("sharp")).default;
|
|
11
|
+
return typeof e == "function" ? (y = async () => e, e) : (y = null, null);
|
|
12
|
+
} catch {
|
|
13
|
+
return y = null, null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function x() {
|
|
17
|
+
return await b() !== null;
|
|
18
|
+
}
|
|
19
|
+
function S(e, t, n, r) {
|
|
20
|
+
let i = f("sha256").update(e).digest("hex"), a = JSON.stringify({
|
|
21
|
+
width: t,
|
|
22
|
+
format: n,
|
|
23
|
+
quality: r,
|
|
24
|
+
withoutEnlargement: !0
|
|
25
|
+
});
|
|
26
|
+
return f("sha256").update(`${i}|${a}|${m}|${h}`).digest("hex").slice(0, g);
|
|
27
|
+
}
|
|
28
|
+
function C(e) {
|
|
29
|
+
return e.includes("\0") || e.includes("\\") || /%[0-9a-f]{2}/i.test(e) ? !1 : !e.replace(/^\/+/, "").split("/").some((e) => e === ".." || e === "." || e === "");
|
|
30
|
+
}
|
|
31
|
+
function w(e, t) {
|
|
32
|
+
return t === e || t.startsWith(`${e}${d}`);
|
|
33
|
+
}
|
|
34
|
+
function T(e, t, n) {
|
|
35
|
+
let r = u(e), i = u(t);
|
|
36
|
+
if (!w(r, i)) throw Error(`[elur-kit] Image ${n} escapes its allowed root (${i}).`);
|
|
37
|
+
}
|
|
38
|
+
function E(e) {
|
|
39
|
+
let t = 0, n = [], r = () => new Promise((r) => {
|
|
40
|
+
t < e ? (t++, r()) : n.push(() => {
|
|
41
|
+
t++, r();
|
|
42
|
+
});
|
|
43
|
+
}), i = () => {
|
|
44
|
+
t--;
|
|
45
|
+
let e = n.shift();
|
|
46
|
+
e ? e() : t < 0 && (t = 0);
|
|
47
|
+
};
|
|
48
|
+
return { async run(e) {
|
|
49
|
+
await r();
|
|
50
|
+
try {
|
|
51
|
+
return await e();
|
|
52
|
+
} finally {
|
|
53
|
+
i();
|
|
54
|
+
}
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
57
|
+
async function D(e, t) {
|
|
58
|
+
let i = `${e}.${process.pid}.${p(6).toString("hex")}.tmp`;
|
|
59
|
+
try {
|
|
60
|
+
await a(i, t), await n(i, e);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
throw await r(i, { force: !0 }).catch(() => {}), e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function O(e) {
|
|
66
|
+
try {
|
|
67
|
+
return await i(e), !0;
|
|
68
|
+
} catch {
|
|
69
|
+
return !1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function k(e) {
|
|
73
|
+
return {
|
|
74
|
+
capabilities: {
|
|
75
|
+
encoding: !1,
|
|
76
|
+
remote: !1,
|
|
77
|
+
runtimeEndpoint: !1,
|
|
78
|
+
filesystem: !0
|
|
79
|
+
},
|
|
80
|
+
async resolve(t, n) {
|
|
81
|
+
return A(t, {
|
|
82
|
+
...e,
|
|
83
|
+
publicDir: n.publicDir,
|
|
84
|
+
outDir: n.outDir
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function A(e, t) {
|
|
90
|
+
let { src: n, alt: r, widths: i, formats: a, quality: o, priority: s, loading: c, decoding: l, class: u, attributes: d = {} } = e, f = i?.length ? [...i] : [e.width ?? 0], p = a?.length ? [...a] : t.formats ?? ["webp", "avif"], m = (await j([{
|
|
91
|
+
src: n,
|
|
92
|
+
widths: f,
|
|
93
|
+
formats: p
|
|
94
|
+
}], {
|
|
95
|
+
...t,
|
|
96
|
+
quality: o ?? t.quality
|
|
97
|
+
})).manifest.entries[n], h = m ? m.variants.map((e) => ({
|
|
98
|
+
url: e.url,
|
|
99
|
+
width: e.width,
|
|
100
|
+
height: e.height,
|
|
101
|
+
format: e.format,
|
|
102
|
+
size: e.size
|
|
103
|
+
})) : [], g = [];
|
|
104
|
+
for (let e of p) {
|
|
105
|
+
let t = m ? F(m, e) : "";
|
|
106
|
+
t && g.push({
|
|
107
|
+
type: e === "jpeg" ? "image/jpeg" : `image/${e}`,
|
|
108
|
+
srcset: t
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
src: n,
|
|
113
|
+
width: m?.width,
|
|
114
|
+
height: m?.height,
|
|
115
|
+
sources: g,
|
|
116
|
+
attributes: {
|
|
117
|
+
alt: r,
|
|
118
|
+
width: m?.width ?? e.width,
|
|
119
|
+
height: m?.height ?? e.height,
|
|
120
|
+
loading: s ? "eager" : c ?? "lazy",
|
|
121
|
+
decoding: l ?? "async",
|
|
122
|
+
...s ? { fetchpriority: "high" } : {},
|
|
123
|
+
...u ? { class: u } : {},
|
|
124
|
+
...d
|
|
125
|
+
},
|
|
126
|
+
generated: h
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function j(n, r) {
|
|
130
|
+
let a = await b(), { publicDir: u, outDir: d, formats: p = ["webp", "avif"], quality: m = _, strict: h = !1, concurrency: g = v, base: y = "" } = r, x = {}, w = 0, k = E(g), A = /* @__PURE__ */ new Map(), j = /* @__PURE__ */ new Set(), M = (e, t) => {
|
|
131
|
+
j.has(e) || (j.add(e), console.warn(`[elur-kit] ${t}`));
|
|
132
|
+
};
|
|
133
|
+
if (!a) {
|
|
134
|
+
for (let { src: e } of n) {
|
|
135
|
+
if (x[e]) continue;
|
|
136
|
+
if (!C(e)) {
|
|
137
|
+
if (h) throw Error(`[elur-kit] Invalid image source path: ${e}`);
|
|
138
|
+
M(`path:${e}`, `Skipping invalid image source path: ${e}`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
let n = l(u, e.replace(/^\//, ""));
|
|
142
|
+
T(u, n, `source "${e}"`);
|
|
143
|
+
try {
|
|
144
|
+
let r = await t(n);
|
|
145
|
+
x[e] = {
|
|
146
|
+
src: e,
|
|
147
|
+
width: 0,
|
|
148
|
+
height: 0,
|
|
149
|
+
variants: [],
|
|
150
|
+
hash: f("sha256").update(r).digest("hex").slice(0, 8)
|
|
151
|
+
};
|
|
152
|
+
} catch {
|
|
153
|
+
if (h) throw Error(`[elur-kit] Image source not found: ${e}`);
|
|
154
|
+
M(`missing:${e}`, `Image source not found: ${e}. Skipping.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let e = {
|
|
158
|
+
version: 1,
|
|
159
|
+
entries: x
|
|
160
|
+
};
|
|
161
|
+
return r.manifestPath && await N(r.manifestPath, e), {
|
|
162
|
+
manifest: e,
|
|
163
|
+
count: 0,
|
|
164
|
+
optimized: !1
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
for (let { src: r, widths: g, formats: _ } of n) {
|
|
168
|
+
if (x[r]) continue;
|
|
169
|
+
if (!C(r)) {
|
|
170
|
+
if (h) throw Error(`[elur-kit] Invalid image source path: ${r}`);
|
|
171
|
+
M(`path:${r}`, `Skipping invalid image source path: ${r}`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
let n = l(u, r.replace(/^\//, ""));
|
|
175
|
+
T(u, n, `source "${r}"`);
|
|
176
|
+
let v;
|
|
177
|
+
try {
|
|
178
|
+
v = await t(n);
|
|
179
|
+
} catch {
|
|
180
|
+
if (h) throw Error(`[elur-kit] Image source not found: ${r}`);
|
|
181
|
+
M(`missing:${r}`, `Image not found: ${r}. Skipping.`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
let b = c(r), E = o(r, b).replace(/[^a-zA-Z0-9._-]+/g, "-"), j = s(r), N = _?.length ? _ : p, P = 0, F = 0;
|
|
185
|
+
try {
|
|
186
|
+
let e = await a(v).metadata();
|
|
187
|
+
P = e.width ?? 0, F = e.height ?? 0;
|
|
188
|
+
} catch {}
|
|
189
|
+
let I = [], L = async (t, n) => {
|
|
190
|
+
if (P > 0 && t > P) return;
|
|
191
|
+
let r = S(v, t, n, m), o = `${E}.${r}.${t}w.${n}`, c = l(j, o), u = l(d, c.replace(/^\//, ""));
|
|
192
|
+
T(d, u, `variant "${c}"`);
|
|
193
|
+
let f = `${y.replace(/\/$/, "")}/${c.replace(/\\/g, "/").replace(/^\//, "")}`;
|
|
194
|
+
if (await O(u)) try {
|
|
195
|
+
let e = await a(u).metadata();
|
|
196
|
+
I.push({
|
|
197
|
+
url: f,
|
|
198
|
+
width: e.width ?? t,
|
|
199
|
+
height: e.height ?? Math.round((e.height ?? 0) || (F && P ? t * F / P : 0)),
|
|
200
|
+
format: n,
|
|
201
|
+
size: (await i(u)).size
|
|
202
|
+
}), w++;
|
|
203
|
+
return;
|
|
204
|
+
} catch {}
|
|
205
|
+
let p = u;
|
|
206
|
+
if (A.has(p)) {
|
|
207
|
+
await A.get(p), I.push({
|
|
208
|
+
url: f,
|
|
209
|
+
width: t,
|
|
210
|
+
height: Math.round(F && P ? t * F / P : 0),
|
|
211
|
+
format: n,
|
|
212
|
+
size: (await i(u)).size
|
|
213
|
+
}), w++;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let g = (async () => {
|
|
217
|
+
try {
|
|
218
|
+
let r = await a(v).resize({
|
|
219
|
+
width: t,
|
|
220
|
+
withoutEnlargement: !0
|
|
221
|
+
}).toFormat(n, { quality: m }).toBuffer();
|
|
222
|
+
await e(s(u), { recursive: !0 }), await D(u, r);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
if (h) throw Error(`[elur-kit] Failed to generate ${o}: ${e instanceof Error ? e.message : String(e)}`);
|
|
225
|
+
M(`fail:${o}`, `Failed to generate ${o}.`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
I.push({
|
|
229
|
+
url: f,
|
|
230
|
+
width: t,
|
|
231
|
+
height: Math.round(F && P ? t * F / P : 0),
|
|
232
|
+
format: n,
|
|
233
|
+
size: (await i(u)).size
|
|
234
|
+
}), w++;
|
|
235
|
+
})().finally(() => A.delete(p));
|
|
236
|
+
A.set(p, g), await k.run(() => g);
|
|
237
|
+
}, R = [];
|
|
238
|
+
for (let e of g) for (let t of N) R.push(L(e, t));
|
|
239
|
+
await Promise.all(R), x[r] = {
|
|
240
|
+
src: r,
|
|
241
|
+
width: P,
|
|
242
|
+
height: F,
|
|
243
|
+
variants: I,
|
|
244
|
+
hash: f("sha256").update(v).digest("hex").slice(0, 8)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
let P = {
|
|
248
|
+
version: 1,
|
|
249
|
+
entries: x
|
|
250
|
+
};
|
|
251
|
+
return r.manifestPath && await N(r.manifestPath, P), {
|
|
252
|
+
manifest: P,
|
|
253
|
+
count: w,
|
|
254
|
+
optimized: !0
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async function M(e) {
|
|
258
|
+
try {
|
|
259
|
+
let n = await t(e, "utf8");
|
|
260
|
+
return JSON.parse(n);
|
|
261
|
+
} catch {
|
|
262
|
+
return {
|
|
263
|
+
version: 1,
|
|
264
|
+
entries: {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function N(t, n) {
|
|
269
|
+
await e(s(t), { recursive: !0 }), await D(t, JSON.stringify(n, null, 2));
|
|
270
|
+
}
|
|
271
|
+
function P(e, t) {
|
|
272
|
+
return e.entries[t];
|
|
273
|
+
}
|
|
274
|
+
function F(e, t) {
|
|
275
|
+
return e.variants.filter((e) => e.format === t).map((e) => `${e.url} ${e.width}w`).join(", ");
|
|
276
|
+
}
|
|
277
|
+
function I(e, t) {
|
|
278
|
+
let { alt: n, sizes: r, priority: i = !1, class: a, attributes: o = {}, fallbackSrc: s = e.src, fallbackWidth: c = e.width, fallbackHeight: l = e.height } = t, u = [...new Set(e.variants.map((e) => e.format))], d = i ? "" : " loading=\"lazy\"", f = i ? " fetchpriority=\"high\"" : "", p = r ? ` sizes="${R(r)}"` : "", m = a ? ` class="${R(a)}"` : "", h = Object.entries(o).map(([e, t]) => ` ${R(e)}="${R(String(t))}"`).join(""), g = u.map((t) => {
|
|
279
|
+
let n = F(e, t);
|
|
280
|
+
if (!n) return "";
|
|
281
|
+
let r = t === "jpeg" ? "image/jpeg" : `image/${t}`;
|
|
282
|
+
return `<source srcset="${n}"${p} type="${r}" />`;
|
|
283
|
+
}).filter(Boolean).join(""), _ = `<img src="${R(s)}" alt="${R(n)}" width="${c}" height="${l}"${d} decoding="async"${f}${m}${h} />`;
|
|
284
|
+
return g ? `<picture>${g}${_}</picture>` : _;
|
|
285
|
+
}
|
|
286
|
+
async function L(e, t) {
|
|
287
|
+
let n = [];
|
|
288
|
+
for (let r of Object.values(e.entries)) for (let e of r.variants) {
|
|
289
|
+
let r = e.url.replace(/^\/+/, ""), a = u(t, r);
|
|
290
|
+
if (!w(u(t), a)) {
|
|
291
|
+
n.push(e.url);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
await i(a);
|
|
296
|
+
} catch {
|
|
297
|
+
n.push(e.url);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return n;
|
|
301
|
+
}
|
|
302
|
+
function R(e) {
|
|
303
|
+
return e.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
export { I as buildPictureMarkup, F as buildSrcset, k as createImageService, A as getImage, P as getManifestEntry, x as isSharpAvailable, j as processImageBatch, M as readManifest, S as transformHash, L as validateManifestUrls, N as writeManifest };
|
|
307
|
+
|
|
308
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../src/image/service.ts"],"sourcesContent":["import { readFile, mkdir, writeFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join, dirname, extname, basename, resolve, sep } from \"node:path\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { ImageFormat } from \"./index.js\";\n\n// --- ImageService: metadata-driven image processing and manifest ---\n//\n// * Reads real image dimensions from the source file (sharp metadata).\n// * Generates hashed variant filenames from a SHA-256 transform key that\n// incorporates: content digest + normalized transform options +\n// encoder/service version + output naming version (§4.2).\n// * Applies path containment for sources and outputs (no traversal, no NUL,\n// no symlink escape, no Unicode/separator tricks).\n// * Writes outputs atomically (temp + rename) with single-flight per\n// transform key and a bounded concurrency pool.\n// * Supports `strict` mode: fails the build on missing sources or failed\n// transforms instead of emitting a partially-written variant.\n// * Falls back gracefully when sharp is not installed.\n\nconst ENCODER_VERSION = \"sharp-1\";\nconst NAMING_VERSION = \"v1\";\nconst HASH_LENGTH = 12;\nconst DEFAULT_QUALITY = 80;\nconst DEFAULT_CONCURRENCY = 4;\n\nexport interface ImageVariant {\n /** URL path relative to the site root, e.g. \"/images/hero.abc123def456.800w.webp\". */\n url: string;\n /** Width in pixels. */\n width: number;\n /** Height in pixels (preserves aspect ratio). */\n height: number;\n /** Format of the variant. */\n format: ImageFormat;\n /** File size in bytes. */\n size: number;\n}\n\nexport interface ImageEntry {\n /** Original source URL, e.g. \"/images/hero.jpg\". */\n src: string;\n /** Intrinsic width of the source. */\n width: number;\n /** Intrinsic height of the source. */\n height: number;\n /** All generated variants. */\n variants: ImageVariant[];\n /** Content hash of the source file. */\n hash: string;\n}\n\nexport interface ImageManifest {\n version: 1;\n entries: Record<string, ImageEntry>;\n}\n\nexport interface ProcessOptions {\n /** Absolute path to the public directory (source images). */\n publicDir: string;\n /** Absolute path to the output directory. */\n outDir: string;\n /** Formats to generate. Defaults to [\"webp\", \"avif\"]. */\n formats?: ImageFormat[];\n /** Quality (1-100). Defaults to 80. */\n quality?: number;\n /** Path to write the manifest JSON. */\n manifestPath?: string;\n /** When true, missing sources or failed transforms fail the build. */\n strict?: boolean;\n /** Max concurrent sharp transforms. Defaults to 4. */\n concurrency?: number;\n /** Optional URL base prefix applied to variant URLs. */\n base?: string;\n}\n\nexport interface ProcessResult {\n manifest: ImageManifest;\n /** Number of variants generated. */\n count: number;\n /** Whether sharp was available. */\n optimized: boolean;\n}\n\nlet sharpLoader: (() => Promise<any>) | null | undefined;\n\nasync function loadSharp(): Promise<any | null> {\n if (sharpLoader === null) return null;\n if (sharpLoader) return sharpLoader();\n try {\n // @ts-ignore — `sharp` is an optional peer dependency.\n const mod = await import(\"sharp\");\n const sharp = mod.default;\n if (typeof sharp !== \"function\") {\n sharpLoader = null;\n return null;\n }\n sharpLoader = async () => sharp;\n return sharp;\n } catch {\n sharpLoader = null;\n return null;\n }\n}\n\nexport async function isSharpAvailable(): Promise<boolean> {\n const sharp = await loadSharp();\n return sharp !== null;\n}\n\n// --- Transform identity (§4.2) ---\n\n/**\n * SHA-256 transform key. Stable for identical content+options and invalidated\n * whenever the source bytes, the effective transform options, the encoder\n * version or the naming scheme change.\n */\nexport function transformHash(sourceBuffer: Buffer, width: number, format: ImageFormat, quality: number): string {\n const contentDigest = createHash(\"sha256\").update(sourceBuffer).digest(\"hex\");\n const normalizedOptions = JSON.stringify({\n width,\n format,\n quality,\n withoutEnlargement: true,\n });\n return createHash(\"sha256\")\n .update(`${contentDigest}|${normalizedOptions}|${ENCODER_VERSION}|${NAMING_VERSION}`)\n .digest(\"hex\")\n .slice(0, HASH_LENGTH);\n}\n\n// --- Path containment (§9.5) ---\n\nfunction isSafeRelativePath(value: string): boolean {\n if (value.includes(\"\\0\") || value.includes(\"\\\\\")) return false;\n if (/%[0-9a-f]{2}/i.test(value)) return false;\n const segments = value.replace(/^\\/+/, \"\").split(\"/\");\n return !segments.some((segment) => segment === \"..\" || segment === \".\" || segment === \"\");\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction assertInside(root: string, candidate: string, label: string): void {\n const resolvedRoot = resolve(root);\n const resolvedCandidate = resolve(candidate);\n if (!isInside(resolvedRoot, resolvedCandidate)) {\n throw new Error(`[elur-kit] Image ${label} escapes its allowed root (${resolvedCandidate}).`);\n }\n}\n\n// --- Concurrency: bounded pool + single-flight ---\n\nfunction createPool(limit: number) {\n let active = 0;\n const waiters: Array<() => void> = [];\n const acquire = () =>\n new Promise<void>((resolve) => {\n if (active < limit) {\n active++;\n resolve();\n } else {\n waiters.push(() => {\n active++;\n resolve();\n });\n }\n });\n const release = () => {\n active--;\n const next = waiters.shift();\n if (next) next();\n else if (active < 0) active = 0;\n };\n return {\n async run<T>(fn: () => Promise<T>): Promise<T> {\n await acquire();\n try {\n return await fn();\n } finally {\n release();\n }\n },\n };\n}\n\n// --- Atomic writes (§9.6) ---\n\nasync function atomicWriteFile(path: string, data: Buffer | string): Promise<void> {\n const temp = `${path}.${process.pid}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n await writeFile(temp, data);\n await rename(temp, path);\n } catch (error) {\n await rm(temp, { force: true }).catch(() => { });\n throw error;\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n// --- Public programmatic API (§3.1 / §3.3) ---\n\nexport interface ImageRequest {\n src: string;\n alt: string;\n widths?: readonly number[];\n formats?: readonly ImageFormat[];\n sizes?: string;\n width?: number;\n height?: number;\n priority?: boolean;\n loading?: \"lazy\" | \"eager\";\n decoding?: \"async\" | \"sync\" | \"auto\";\n quality?: number;\n fit?: string;\n class?: string;\n attributes?: Record<string, unknown>;\n}\n\nexport interface GeneratedImage {\n url: string;\n width: number;\n height: number;\n format: ImageFormat;\n size: number;\n}\n\nexport interface ImageMetadata {\n src: string;\n width?: number;\n height?: number;\n sources: Array<{ type: string; srcset: string }>;\n attributes: Record<string, string | number | boolean | undefined>;\n generated: readonly GeneratedImage[];\n}\n\nexport interface ImageServiceContext {\n publicDir: string;\n outDir: string;\n manifest: ImageManifest;\n}\n\nexport interface ImageServiceCapabilities {\n /** Whether the encoder (sharp) is available. */\n encoding: boolean;\n /** Whether remote images can be fetched. */\n remote: boolean;\n /** Whether a runtime image endpoint exists. */\n runtimeEndpoint: boolean;\n /** Whether the host exposes a writable filesystem. */\n filesystem: boolean;\n}\n\nexport interface ImageService {\n resolve(request: ImageRequest, context: ImageServiceContext): Promise<ImageMetadata>;\n capabilities: ImageServiceCapabilities;\n}\n\n/**\n * Creates a build-time ImageService bound to a public/output directory pair.\n */\nexport function createImageService(options: ProcessOptions): ImageService {\n return {\n capabilities: {\n encoding: false,\n remote: false,\n runtimeEndpoint: false,\n filesystem: true,\n },\n async resolve(request, context) {\n return getImage(request, { ...options, publicDir: context.publicDir, outDir: context.outDir });\n },\n };\n}\n\n/**\n * Programmatic async image API (§3.1). Ensures the requested variants exist on\n * disk (build-time), then returns deterministic metadata (not opaque markup).\n */\nexport async function getImage(\n request: ImageRequest,\n options: ProcessOptions,\n): Promise<ImageMetadata> {\n const { src, alt, widths, formats, quality, priority, loading, decoding, class: className, attributes = {} } = request;\n const targetWidths = widths?.length ? [...widths] : [request.width ?? 0];\n const targetFormats = formats?.length ? [...formats] : options.formats ?? [\"webp\", \"avif\"];\n const result = await processImageBatch(\n [{ src, widths: targetWidths, formats: targetFormats }],\n { ...options, quality: quality ?? options.quality },\n );\n const entry = result.manifest.entries[src];\n const generated: GeneratedImage[] = entry\n ? entry.variants.map((v) => ({ url: v.url, width: v.width, height: v.height, format: v.format, size: v.size }))\n : [];\n\n const sources: ImageMetadata[\"sources\"] = [];\n for (const format of targetFormats) {\n const srcset = entry ? buildSrcset(entry, format) : \"\";\n if (srcset) sources.push({ type: format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`, srcset });\n }\n\n return {\n src,\n width: entry?.width,\n height: entry?.height,\n sources,\n attributes: {\n alt,\n width: entry?.width ?? request.width,\n height: entry?.height ?? request.height,\n loading: priority ? \"eager\" : (loading ?? \"lazy\"),\n decoding: decoding ?? \"async\",\n ...(priority ? { fetchpriority: \"high\" } : {}),\n ...(className ? { class: className } : {}),\n ...attributes,\n },\n generated,\n };\n}\n\n// --- Batch processing ---\n\nexport async function processImageBatch(\n images: { src: string; widths: number[]; formats?: ImageFormat[] }[],\n options: ProcessOptions,\n): Promise<ProcessResult> {\n const sharp = await loadSharp();\n const {\n publicDir,\n outDir,\n formats = [\"webp\", \"avif\"],\n quality = DEFAULT_QUALITY,\n strict = false,\n concurrency = DEFAULT_CONCURRENCY,\n base = \"\",\n } = options;\n const entries: Record<string, ImageEntry> = {};\n let count = 0;\n const pool = createPool(concurrency);\n const inFlight = new Map<string, Promise<void>>();\n\n const warned = new Set<string>();\n const warnOnce = (key: string, message: string): void => {\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[elur-kit] ${message}`);\n };\n\n if (!sharp) {\n // Without sharp, build a manifest with only the original source entries.\n for (const { src } of images) {\n if (entries[src]) continue;\n if (!isSafeRelativePath(src)) {\n if (strict) throw new Error(`[elur-kit] Invalid image source path: ${src}`);\n warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n continue;\n }\n const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n assertInside(publicDir, sourcePath, `source \"${src}\"`);\n try {\n const buffer = await readFile(sourcePath);\n entries[src] = {\n src,\n width: 0,\n height: 0,\n variants: [],\n hash: createHash(\"sha256\").update(buffer).digest(\"hex\").slice(0, 8),\n };\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Image source not found: ${src}`);\n warnOnce(`missing:${src}`, `Image source not found: ${src}. Skipping.`);\n }\n }\n const manifest: ImageManifest = { version: 1, entries };\n if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n return { manifest, count: 0, optimized: false };\n }\n\n for (const { src, widths, formats: imgFormats } of images) {\n if (entries[src]) continue;\n\n if (!isSafeRelativePath(src)) {\n if (strict) throw new Error(`[elur-kit] Invalid image source path: ${src}`);\n warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n continue;\n }\n\n const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n assertInside(publicDir, sourcePath, `source \"${src}\"`);\n\n let sourceBuffer: Buffer;\n try {\n sourceBuffer = await readFile(sourcePath);\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Image source not found: ${src}`);\n warnOnce(`missing:${src}`, `Image not found: ${src}. Skipping.`);\n continue;\n }\n\n const ext = extname(src);\n const safeBase = basename(src, ext).replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n const dir = dirname(src);\n const targetFormats = imgFormats?.length ? imgFormats : formats;\n\n // Read real metadata from the source.\n let sourceWidth = 0;\n let sourceHeight = 0;\n try {\n const meta = await sharp(sourceBuffer).metadata();\n sourceWidth = meta.width ?? 0;\n sourceHeight = meta.height ?? 0;\n } catch {\n // Fallback: no metadata.\n }\n\n const variants: ImageVariant[] = [];\n\n const processVariant = async (width: number, format: ImageFormat): Promise<void> => {\n // Never upscale: skip widths larger than the source.\n if (sourceWidth > 0 && width > sourceWidth) return;\n\n const hash = transformHash(sourceBuffer, width, format, quality);\n const variantName = `${safeBase}.${hash}.${width}w.${format}`;\n const variantRelPath = join(dir, variantName);\n const variantAbsPath = join(outDir, variantRelPath.replace(/^\\//, \"\"));\n assertInside(outDir, variantAbsPath, `variant \"${variantRelPath}\"`);\n const variantUrl = `${base.replace(/\\/$/, \"\")}/${variantRelPath.replace(/\\\\/g, \"/\").replace(/^\\//, \"\")}`;\n\n // Reuse an existing, valid output file (validated, not guessed).\n if (await fileExists(variantAbsPath)) {\n try {\n const info = await sharp(variantAbsPath).metadata();\n variants.push({\n url: variantUrl,\n width: info.width ?? width,\n height: info.height ?? Math.round((info.height ?? 0) || (sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0)),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n return;\n } catch {\n // Existing file invalid — regenerate below.\n }\n }\n\n const key = variantAbsPath;\n if (inFlight.has(key)) {\n await inFlight.get(key);\n variants.push({\n url: variantUrl,\n width,\n height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n return;\n }\n\n const task = (async () => {\n try {\n const buffer = await sharp(sourceBuffer)\n .resize({ width, withoutEnlargement: true })\n .toFormat(format, { quality })\n .toBuffer();\n await mkdir(dirname(variantAbsPath), { recursive: true });\n await atomicWriteFile(variantAbsPath, buffer);\n } catch (error) {\n if (strict) throw new Error(`[elur-kit] Failed to generate ${variantName}: ${error instanceof Error ? error.message : String(error)}`);\n warnOnce(`fail:${variantName}`, `Failed to generate ${variantName}.`);\n return;\n }\n variants.push({\n url: variantUrl,\n width,\n height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n format,\n size: (await stat(variantAbsPath)).size,\n });\n count++;\n })().finally(() => inFlight.delete(key));\n\n inFlight.set(key, task);\n await pool.run(() => task);\n };\n\n const tasks: Promise<void>[] = [];\n for (const width of widths) {\n for (const format of targetFormats) {\n tasks.push(processVariant(width, format));\n }\n }\n await Promise.all(tasks);\n\n entries[src] = {\n src,\n width: sourceWidth,\n height: sourceHeight,\n variants,\n hash: createHash(\"sha256\").update(sourceBuffer).digest(\"hex\").slice(0, 8),\n };\n }\n\n const manifest: ImageManifest = { version: 1, entries };\n if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n return { manifest, count, optimized: true };\n}\n\n/**\n * Read a manifest from disk, or return an empty one if it doesn't exist.\n */\nexport async function readManifest(path: string): Promise<ImageManifest> {\n try {\n const data = await readFile(path, \"utf8\");\n return JSON.parse(data) as ImageManifest;\n } catch {\n return { version: 1, entries: {} };\n }\n}\n\n/**\n * Write a manifest to disk atomically.\n */\nexport async function writeManifest(path: string, manifest: ImageManifest): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await atomicWriteFile(path, JSON.stringify(manifest, null, 2));\n}\n\n/**\n * Look up an image entry in the manifest by its source URL.\n */\nexport function getManifestEntry(manifest: ImageManifest, src: string): ImageEntry | undefined {\n return manifest.entries[src];\n}\n\n/**\n * Build a srcset string from manifest variants of a given format.\n * Returns e.g. \"/images/hero.abc.400w.webp 400w, /images/hero.abc.800w.webp 800w\".\n */\nexport function buildSrcset(entry: ImageEntry, format: ImageFormat): string {\n return entry.variants\n .filter((v) => v.format === format)\n .map((v) => `${v.url} ${v.width}w`)\n .join(\", \");\n}\n\n/**\n * Build the full <picture> markup for an image entry, with <source> per format\n * and a fallback <img>.\n */\nexport function buildPictureMarkup(entry: ImageEntry, opts: {\n alt: string;\n sizes?: string;\n priority?: boolean;\n class?: string;\n attributes?: Record<string, string>;\n fallbackSrc?: string;\n fallbackWidth?: number;\n fallbackHeight?: number;\n}): string {\n const {\n alt,\n sizes,\n priority = false,\n class: className,\n attributes = {},\n fallbackSrc = entry.src,\n fallbackWidth = entry.width,\n fallbackHeight = entry.height,\n } = opts;\n\n const formats = [...new Set(entry.variants.map((v) => v.format))];\n const loadingAttr = priority ? \"\" : ' loading=\"lazy\"';\n const fetchPriorityAttr = priority ? ' fetchpriority=\"high\"' : \"\";\n const sizesAttr = sizes ? ` sizes=\"${escapeAttr(sizes)}\"` : \"\";\n const classAttr = className ? ` class=\"${escapeAttr(className)}\"` : \"\";\n const extraAttrs = Object.entries(attributes)\n .map(([key, value]) => ` ${escapeAttr(key)}=\"${escapeAttr(String(value))}\"`)\n .join(\"\");\n\n const sources = formats\n .map((format) => {\n const srcset = buildSrcset(entry, format);\n if (!srcset) return \"\";\n const type = format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`;\n return `<source srcset=\"${srcset}\"${sizesAttr} type=\"${type}\" />`;\n })\n .filter(Boolean)\n .join(\"\");\n\n const img = `<img src=\"${escapeAttr(fallbackSrc)}\" alt=\"${escapeAttr(alt)}\" width=\"${fallbackWidth}\" height=\"${fallbackHeight}\"${loadingAttr} decoding=\"async\"${fetchPriorityAttr}${classAttr}${extraAttrs} />`;\n\n return sources ? `<picture>${sources}${img}</picture>` : img;\n}\n\n/**\n * Validate that every variant URL in the manifest corresponds to a real file\n * in the output directory. Returns a list of missing URLs.\n */\nexport async function validateManifestUrls(\n manifest: ImageManifest,\n outDir: string,\n): Promise<string[]> {\n const missing: string[] = [];\n for (const entry of Object.values(manifest.entries)) {\n for (const variant of entry.variants) {\n const relative = variant.url.replace(/^\\/+/, \"\");\n const resolved = resolve(outDir, relative);\n if (!isInside(resolve(outDir), resolved)) {\n missing.push(variant.url);\n continue;\n }\n try {\n await stat(resolved);\n } catch {\n missing.push(variant.url);\n }\n }\n }\n return missing;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./errors-0X7pCFNU.cjs"),t=require("./node-http-DhxguYyz.cjs"),n=require("./route-scanner-JY6YrNH5.cjs"),r=require("./scan-CY2O9ty6.cjs"),i=require("./interpolation-plugin-CMPCDlVJ.cjs"),a=require("./scan-DXzYtmRQ.cjs"),o=require("./image/service.cjs"),s=require("./image/registry.cjs"),c=require("./image/index.cjs"),l=require("./integrations-DDqfWjvH.cjs"),u=require("./hydrate-8a3TbQbs.cjs"),d=require("./stream-DmZjYqHm.cjs"),f=require("./runtime-CrH5X-Es.cjs"),p=require("./action-DZHihU3Z.cjs"),m=require("./adapters/vercel.cjs"),h=require("./adapters/netlify.cjs"),g=require("./adapters/bun.cjs"),_=require("./adapters/node.cjs"),v=require("./router/client.cjs"),y=require("./config/index.cjs"),b=require("./manifest/index.cjs");let x=require("node:fs/promises"),S=require("node:path"),C=require("node:fs"),w=require("node:crypto"),ee=require("@elurjs/core"),te=require("node:http"),T=require("vite"),E=require("node:async_hooks");function D(e,t){if("/"===t)return(0,S.join)(e,"index.html");let r=t.slice(1).split("/");return(0,S.join)(e,...r,"index.html")}function O(e){return e.includes(":")}function k(e,t){return e.replace(/:([a-zA-Z0-9_]+)(\*)?/g,(r,i,a)=>{let n=t[i];if(null==n)throw Error(`Missing value for dynamic segment "${i}" in path "${e}"`);return a&&Array.isArray(n)?n.join("/"):String(n)})}async function A(e){if(e.publicDir)try{(await(0,x.stat)(e.publicDir)).isDirectory()&&(await(0,x.mkdir)(e.outDir,{recursive:!0}),await(0,x.cp)(e.publicDir,e.outDir,{recursive:!0,force:!0}))}catch(e){if("ENOENT"!==e.code)throw e}let c=await n.t(e.appDir),u=await a.n(e.appDir),p=a.t(u),d={pages:0,skipped:[],files:[],islands:[],imagesProcessed:0,outDir:e.outDir};e.islandsDir&&(d.islands=await r.t(e.islandsDir)),e.generatedEntry&&(d.generatedEntry=await i.c({islands:d.islands,outFile:e.generatedEntry,hydrateImport:e.hydrateImport,routerImport:e.routerImport}));for(let t of c.pages){if(!O(t.path)){let r=await j(e,t,p);d.pages++,d.files.push(r);continue}let r=await M(e,t,p);0===r.length?d.skipped.push(t.path):(d.pages+=r.length,d.files.push(...r))}let f={lang:e.lang,clientEntry:e.clientEntry,renderEndpoint:!1};if(c.error404){let r=await t.c({routes:c,status:404,config:f,actions:p});if(r){let t=(0,S.join)(e.outDir,"404.html");await(0,x.mkdir)((0,S.dirname)(t),{recursive:!0}),await(0,x.writeFile)(t,r.html,"utf8"),d.files.push(t)}}if(c.error500){let r=await t.c({routes:c,status:500,config:f,actions:p});if(r){let t=(0,S.join)(e.outDir,"500.html");await(0,x.mkdir)((0,S.dirname)(t),{recursive:!0}),await(0,x.writeFile)(t,r.html,"utf8"),d.files.push(t)}}let m=s.consumeImageRegistry(),h=null;if(m.length>0&&e.publicDir){let r=(0,S.join)(e.outDir,".elur","image-manifest.json"),i=await o.processImageBatch(m,{publicDir:e.publicDir,outDir:e.outDir,formats:e.imageFormats,manifestPath:r});if(d.imagesProcessed=i.count,i.optimized&&i.count>0){h=i.manifest,s.setImageManifest(h),d.pages=0,d.files=[];for(let t of c.pages){if(!O(t.path)){let r=await j(e,t,p);d.pages++,d.files.push(r);continue}let r=await M(e,t,p);0===r.length?d.skipped.push(t.path):(d.pages+=r.length,d.files.push(...r))}if(c.error404){let r=await t.c({routes:c,status:404,config:f,actions:p});if(r){let t=(0,S.join)(e.outDir,"404.html");await(0,x.mkdir)((0,S.dirname)(t),{recursive:!0}),await(0,x.writeFile)(t,r.html,"utf8"),d.files.push(t)}}if(c.error500){let r=await t.c({routes:c,status:500,config:f,actions:p});if(r){let t=(0,S.join)(e.outDir,"500.html");await(0,x.mkdir)((0,S.dirname)(t),{recursive:!0}),await(0,x.writeFile)(t,r.html,"utf8"),d.files.push(t)}}}}return s.setImageManifest(null),e.integrations&&e.integrations.length>0&&await l.t(e.integrations,"build",[d,{root:e.root??e.outDir,command:"build"}]),d}async function j(e,t,r){return N(e,t,{},r)}async function M(e,t,r){let{generateStaticParams:i}=await(import(t.pagePath));if(!i)return[];let a=await i();if(!Array.isArray(a)||0===a.length)return[];let n=[];for(let i of a)n.push(await N(e,t,i,r));return n}async function N(e,r,i,a){let{html:n}=await t.l({route:r,params:i,searchParams:new URLSearchParams,config:{lang:e.lang,clientEntry:e.clientEntry,renderEndpoint:!1},actions:a}),o=O(r.path)?k(r.path,i):r.path,s=D(e.outDir,o);return await(0,x.mkdir)((0,S.dirname)(s),{recursive:!0}),await(0,x.writeFile)(s,n,"utf8"),s}function ne(e,t,r,i="load",a){let n="only"!==i&&(a?.ssr??!0),o=a?.fallback,s=t=>`<div data-elur-island="${L(e)}" data-directive="${i}" data-props='${R(r)}'>${t}</div>`;return{__isElurTemplate:!0,[ee.ELUR_RENDER_PROTOCOL]:{async renderServer(i){let a="";if(n)try{let e=t(r);a=null!==e&&!1!==e&&void 0!==e?await i.render(e,{markers:!0}):await F(o,i)}catch(t){throw P(e,t)}else a=await F(o,i);return s(a)}},_render(e,i){let a=document.createElement("div"),l="";if(n){let e=t(r);if(null!==e&&!1!==e&&void 0!==e){let t=e._render(a,null);l=a.innerHTML,t()}else l=I(o,a)}else l=I(o,a);let c=document.createElement("template");c.innerHTML=s(l);let u=c.content,p=u.firstChild;return e.insertBefore(u,i),()=>{p?.parentNode&&p.parentNode.removeChild(p)}}}}function P(e,t){let r=t instanceof Error?t:Error(String(t)),i=t instanceof Error?t.message:String(t);return Error(`[elur-kit] Island "${e}" threw during SSR: ${i}\n If the component accesses browser-only globals (document, window, etc.),\n use directive: "only" or options: { ssr: false } to skip server rendering.\n For environment reads (matchMedia, localStorage, navigator) you may guard\n the access with isSSR() from "@elurjs/kit".`,{cause:r})}async function F(e,t){return null==e||""===e?"":"string"==typeof e?e:t.render(e,{markers:!1})}function I(e,t){if(null==e||""===e)return"";if("string"==typeof e)return e;let r=e._render(t,null),i=t.innerHTML;return r(),i}function L(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function R(e){return JSON.stringify(e??null).replace(/</g,"\\u003c").replace(/'/g,"\\u0027")}async function z(r){let o=await n.t(r.appDir),s=await a.n(r.appDir),l=a.t(s),c=r.root?await i.i(r.root):null,u=async(e,t)=>{let r=G(t,o),i=r?s[r]:Object.values(s).find(t=>t[e])??void 0,a=i?i[e]:void 0;if(!a)return;let n=(await(import(a)))[e];return"function"==typeof n?n:void 0},p=(0,te.createServer)(async(a,n)=>{let s,p=a.url??"/";if(p.includes("?")&&(p=p.split("?")[0]),"/__elur-js/actions"===p&&"POST"===a.method){try{let e=await U(a),i=t.t(a,e),o=await t.n(i,u,r.actionSecurity);n.writeHead(o.status,Object.fromEntries(o.headers.entries())),n.end(await o.text())}catch(t){console.error("[action] error handling",t),n.writeHead(500,{"Content-Type":"text/plain; charset=utf-8"}),n.end(e.l(t).message)}return}if("/__elur-js/render"===p){let e=new URL(a.url??"/","http://localhost"),i=e.searchParams.get("page")??"/",s=e.searchParams.get("search")??"",c=(a.headers.accept??"").includes("application/json");try{let e,u,p,m,h=t.t(a),g=await K(r,i,o),w=`/__elur-js/render${i}?${s}`;if(r.cacheDir&&"number"==typeof g&&V(h)){let t=await f.g(r.cacheDir,w);if(t)e=q(t.html),u=J(t.html);else{let t=await d.n({routes:o,pathname:i,searchParams:new URLSearchParams(s),config:{lang:r.lang??"es",clientEntry:r.clientEntry},actions:l,request:h});e=t.body,u=t.title,p=t.clearActionErrorCookie,m=t.head,await f._(r.cacheDir,w,t.fullHtml??"",g)}}else{let t=await d.n({routes:o,pathname:i,searchParams:new URLSearchParams(s),config:{lang:r.lang??"es",clientEntry:r.clientEntry},actions:l,request:h});e=t.body,u=t.title,p=t.clearActionErrorCookie,m=t.head}if(c){let t={"Content-Type":"application/json; charset=utf-8"},r=p;r&&(t["X-Elur-Action-Clear-Cookie"]=r),n.writeHead(200,t),n.end(JSON.stringify({title:u,body:e,head:m,clearActionErrorCookie:r}))}else{let t={"Content-Type":"text/html; charset=utf-8"};p&&(t["Set-Cookie"]=p),n.writeHead(200,t),n.end(e)}}catch(e){if(e instanceof d.t)return console.log(`[ssr] render endpoint: no route for ${i}`),n.writeHead(404,{"Content-Type":"text/plain"}),void n.end("Not Found");console.error("[ssr] streaming render error",e),n.writeHead(500,{"Content-Type":"text/plain"}),n.end("Internal Server Error")}return}if(c&&i.a(p,c.config)){let e=await i.o(c,t.t(a));if("response"===e.kind)return n.writeHead(e.response.status,Object.fromEntries(e.response.headers.entries())),void n.end(Buffer.from(await e.response.arrayBuffer()));s=e.headers}let m=t.a(p,o.api);if(m){try{let e=(await(import(m.route.routePath)))[a.method??"GET"];if("function"!=typeof e)return n.writeHead(405,{"Content-Type":"text/plain"}),void n.end(`Method not allowed: ${a.method}`);let r=a.method&&"GET"!==a.method&&"HEAD"!==a.method?await U(a):void 0,i=t.t(a,r);H(i.headers,s);let o=await e(i,{params:m.params});n.writeHead(o.status,Object.fromEntries(o.headers.entries())),n.end(Buffer.from(await o.arrayBuffer()))}catch(t){console.error("[api] error handling",p,t),n.writeHead(500,{"Content-Type":"text/plain; charset=utf-8"}),n.end(e.l(t).message)}return}if(r.publicDir)try{if(await B(n,r.publicDir,p))return}catch(e){console.error("[static] error serving",p,e)}let h=t.o(p,o.pages),g={lang:r.lang??"es",clientEntry:r.clientEntry};if(h)try{let e=t.t(a);H(e.headers,s);let i,o,c=(h.route.dataPath?(await(import(h.route.dataPath))).revalidate:void 0)??r.defaultRevalidate;if(!1!==r.streaming&&h.route.loadingPath)i=await d.r({route:h.route,params:h.params,searchParams:new URLSearchParams(a.url?.split("?")[1]??""),config:g,actions:l,request:e});else if(r.cacheDir&&"number"==typeof c&&V(e)){let n=new URL(e.url).pathname+new URL(e.url).search,s=await f.g(r.cacheDir,n);if(s)i=s.html;else{let s=await t.l({route:h.route,params:h.params,searchParams:new URLSearchParams(a.url?.split("?")[1]??""),config:g,actions:l,request:e});i=s.html,o=s.clearActionErrorCookie,await f._(r.cacheDir,n,i,c)}}else{let r=await t.l({route:h.route,params:h.params,searchParams:new URLSearchParams(a.url?.split("?")[1]??""),config:g,actions:l,request:e});i=r.html,o=r.clearActionErrorCookie}let u={"Content-Type":"text/html; charset=utf-8"};return o&&(u["Set-Cookie"]=o),n.writeHead(200,u),void n.end(i)}catch(a){console.error("[ssr] error rendering",p,a);let r=await t.c({routes:o,status:500,error:a,config:g,actions:l});return void(r?(n.writeHead(r.status,{"Content-Type":"text/html; charset=utf-8"}),n.end(r.html)):(n.writeHead(500,{"Content-Type":"text/plain; charset=utf-8"}),n.end(e.l(a).message)))}let w=await t.c({routes:o,status:404,config:g,actions:l});if(w)return n.writeHead(w.status,{"Content-Type":"text/html; charset=utf-8"}),void n.end(w.html);n.writeHead(404,{"Content-Type":"text/plain; charset=utf-8"}),n.end(`Not found: ${a.url}`)});return{server:p,listen:()=>new Promise(e=>{p.listen(r.port??3e3,r.host??"127.0.0.1",()=>{console.log(`\n → SSR server http://${r.host??"127.0.0.1"}:${r.port??3e3}`),e()})}),close:()=>new Promise((e,t)=>{p.close(r=>r?t(r):e())})}}async function B(e,t,r){let i=await f.m(t,r);if(!i)return!1;let a=W(i),n=await(0,x.readFile)(i);return a.includes("text/html")&&(n=n.toString("utf8").replace('<meta name="elur:render-endpoint" content="off" />','<meta name="elur:render-endpoint" content="on" />')),e.writeHead(200,{"Content-Type":a,"Content-Length":Buffer.byteLength(n)}),e.end(n),!0}function V(e){return!e.headers.has("cookie")&&!e.headers.has("authorization")}function H(e,t){if(t)for(let[r,i]of Object.entries(t))e.set(r,i)}function U(e){return new Promise((t,r)=>{let i="";e.setEncoding("utf8"),e.on("data",e=>{i+=e}),e.on("end",()=>t(i)),e.on("error",r)})}function W(e){switch((0,S.extname)(e)){case".html":return"text/html; charset=utf-8";case".js":case".mjs":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".avif":return"image/avif";case".ico":return"image/x-icon";case".woff":return"font/woff";case".woff2":return"font/woff2";case".wasm":return"application/wasm";default:return"application/octet-stream"}}function G(e,r){if(!e)return;if(r.pages.some(t=>t.path===e))return e;let i=t.o(e,r.pages);return i?i.route.path:e}async function K(e,r,i){let a=t.o(r,i.pages);if(a)return(a.route.dataPath?(await(import(a.route.dataPath))).revalidate:void 0)??e.defaultRevalidate}function q(e){let t=e.match(/<div id="app">([\s\S]*)<\/div>\s*(<script|$)/);return t?t[1].trim():e}function J(e){let t=e.match(/<title>([^<]*)<\/title>/);return t?t[1]:""}var Y;async function X(){if(null===Y)return null;if(Y)return Y();try{let e=(await(import("sharp"))).default;return"function"==typeof e?(Y=async()=>e,e):(Y=null,null)}catch{return Y=null,null}}async function re(e,t){let r=await X();if(!r)return console.warn("[elur-kit] Image optimization requires `sharp`. Install it with:\n npm install sharp\n # or\n bun add sharp\nSkipping image processing — using original files."),[];let{publicDir:i,outDir:a,formats:n=["webp","avif"],quality:o=80}=t,s=[],l=!1;for(let{src:t,widths:c,formats:u}of e){let p=(0,S.join)(i,t.replace(/^\//,""));try{await(0,x.stat)(p)}catch{l||=(console.warn(`[elur-kit] Image not found: ${p}. Skipping.`),!0);continue}let d=await(0,x.readFile)(p),f=(0,w.createHash)("md5").update(d).digest("hex").slice(0,8),m=(0,S.extname)(t),h=(0,S.basename)(t,m),g=(0,S.dirname)(t),y=[],v=u.length>0?u:n;for(let t of c)for(let i of v){let n=`${h}-${t}w-${f}.${i}`,s=(0,S.join)(g,n),l=(0,S.join)(a,s.replace(/^\//,""));try{await(0,x.mkdir)((0,S.dirname)(l),{recursive:!0}),await r(d).resize({width:t,withoutEnlargement:!0}).toFormat(i,{quality:o}).toFile(l),y.push({path:s,width:t,format:i})}catch(e){console.warn(`[elur-kit] Failed to generate ${n}:`,e)}}y.length>0&&s.push({src:t,variants:y})}return s}async function ie(){return null!==await X()}var ae=new E.AsyncLocalStorage;function oe(e){let t=`elur-stream-${(0,w.randomUUID)().slice(0,8)}`,r=ae.getStore();return r&&r.boundaries.set(t,{promise:e.promise,children:e.children}),{__isElurTemplate:!0,mount(r){let i="string"==typeof r?document.querySelector(r):r;if(!i)throw Error("[elur-kit] streamBoundary(): container not found");let a=e.fallback.mount(i);return e.promise.then(t=>{let r=e.children(t);i.innerHTML="";let n=r.mount(i);a.__elurChildHandle=n}).catch(e=>{console.error(`[elur-kit] streamBoundary ${t} failed:`,e)}),{unmount(){let e=a.__elurChildHandle;e?.unmount&&e.unmount(),a.unmount()}}},_render(r,i){let a=e.fallback._render(r,i);return e.promise.then(e=>{}).catch(e=>{console.error(`[elur-kit] streamBoundary ${t} failed:`,e)}),a}}}async function se(e){let t=e.logPrefix??"[client]";console.log(`${t} Building hydration bundle...`);let r=await ce(e.userConfigPath,e.root),a=i.r(e.interpolation??"auto")?i.n({appDir:e.appDir,islandsDir:e.islandsDir}):[],n={...r,root:e.root,base:e.base??r.base??"/",build:{...r.build??{},outDir:e.outDir,emptyOutDir:!0},plugins:[...r.plugins??[],a],configFile:!1},o=await(0,T.build)(n),s=(Array.isArray(o)?o:[o]).reduce((e,t)=>e+("output"in t?t.output?.length??0:0),0);return console.log(`${t} ✓ ${s} asset(s) emitted → ${(0,S.relative)(e.root,e.outDir)}`),{outDir:e.outDir,outputCount:s}}async function ce(e,t){let r=await(import(e)),i=r.default??r,a="function"==typeof i?await i({command:"build",mode:"production"}):i;return(a&&"function"==typeof a.then?await a:a)??{}}async function Z(e){let t=(0,S.resolve)(e.outDir),r=(0,S.resolve)(e.tempDir??(0,S.join)((0,S.dirname)(t),`.${le(t)}.tmp-${process.pid}`));return await(0,x.rm)(r,{recursive:!0,force:!0}),await(0,x.mkdir)(r,{recursive:!0}),{tempDir:r,commit:async()=>{let i=e.keepExisting&&(0,C.existsSync)(t)?`${t}.bak-${process.pid}`:void 0;i&&(await(0,x.rm)(i,{recursive:!0,force:!0}),await Q(t,i));try{await Q(r,t)}catch(e){if(!$(e))throw i&&await Q(i,t),e;await(0,x.cp)(r,t,{recursive:!0,force:!0}),await(0,x.rm)(r,{recursive:!0,force:!0})}i&&await(0,x.rm)(i,{recursive:!0,force:!0})},rollback:async()=>{await(0,x.rm)(r,{recursive:!0,force:!0})}}}function le(e){let t=e.split(/[\\/]+/).filter(Boolean);return t[t.length-1]??"output"}async function Q(e,t){await(0,x.rm)(t,{recursive:!0,force:!0});try{await(0,x.rename)(e,t)}catch(r){if(!$(r))throw r;await(0,x.cp)(e,t,{recursive:!0,force:!0}),await(0,x.rm)(e,{recursive:!0,force:!0})}}function $(e){return"EXDEV"===e?.code}async function ue(e){try{if(await(0,x.access)(e.publicDir),!(await(0,x.stat)(e.publicDir)).isDirectory())return 0}catch{return 0}return await(0,x.mkdir)(e.outDir,{recursive:!0}),await(0,x.cp)(e.publicDir,e.outDir,{recursive:!0,force:!0}),de(e.outDir)}async function de(e){let{readdir:t}=await(import("node:fs/promises")),r=0;return await async function e(i){let a=await t(i,{withFileTypes:!0});for(let t of a){let a=(0,S.join)(i,t.name);t.isDirectory()?await e(a):r++}}(e),r}exports.ACTION_ERROR_COOKIE=t.d,exports.ActionFailure=e.t,exports.RedirectResponse=e.n,exports.RequestContext=f.a,exports.assertClientImportAllowed=b.assertClientImportAllowed,exports.beginAtomicStage=Z,exports.build=A,exports.buildClientBundle=se,exports.buildEntrySource=i.s,exports.buildHeadTags=t.g,exports.buildPictureMarkup=o.buildPictureMarkup,exports.buildSrcset=o.buildSrcset,exports.bunAdapter=g.bunAdapter,exports.callAction=p.t,exports.clearActionErrorCookieHeader=t.f,exports.clearCache=f.h,exports.collectShellExtras=t.s,exports.consumeImageRegistry=s.consumeImageRegistry,exports.copyPublicAssets=ue,exports.createAppManifest=b.createAppManifest,exports.createSsrServer=z,exports.createWebHandler=f.t,exports.decodeActionErrorCookie=t.p,exports.defineAction=p.r,exports.defineConfig=y.defineConfig,exports.documentShell=t._,exports.encodeActionErrorCookie=t.m,exports.fail=e.i,exports.generateClientEntry=i.c,exports.getCachedHtml=f.g,exports.getManifestEntry=o.getManifestEntry,exports.guessContentType=f.o,exports.handleActionRequest=t.n,exports.htmlResponse=f.s,exports.hydrateIslands=u.n,exports.image=c.image,exports.incomingMessageToRequest=t.t,exports.isSSR=t.y,exports.isSharpAvailable=ie,exports.island=ne,exports.jsonResponse=f.c,exports.loadElurConfig=y.loadElurConfig,exports.loadMiddleware=i.i,exports.matchApiRoute=t.a,exports.matchRoute=t.o,exports.matchesMiddleware=i.a,exports.methodNotAllowed=f.l,exports.netlifyAdapter=h.netlifyAdapter,exports.nodeAdapter=_.nodeAdapter,exports.notFound=f.u,exports.originForbidden=t.r,exports.processImageBatch=o.processImageBatch,exports.processImages=re,exports.readManifest=o.readManifest,exports.redirect=e.c,exports.renderErrorPage=t.c,exports.renderPage=t.l,exports.renderPageBody=d.n,exports.renderStreamingPage=d.r,exports.renderToString=t.v,exports.resolveStaticFile=f.m,exports.runIntegrationHook=l.t,exports.runMiddleware=i.o,exports.scanActions=a.n,exports.scanIslands=r.t,exports.scanRoutes=n.t,exports.serveStaticFile=f.d,exports.serverError=f.f,exports.setActionErrorCookieHeader=t.h,exports.setCachedHtml=f._,exports.setImageManifest=s.setImageManifest,exports.startClientRouter=v.startClientRouter,exports.streamBoundary=oe,exports.textResponse=f.p,exports.validateManifestRoutes=b.validateManifestRoutes,exports.validateManifestUrls=o.validateManifestUrls,exports.vercelAdapter=m.vercelAdapter,exports.verifyOrigin=t.i,exports.writeAppManifest=b.writeAppManifest,exports.writeManifest=o.writeManifest,exports.writeRouteTypes=b.writeRouteTypes;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/build/build.ts","../../src/island/island.ts","../../src/ssr/server.ts","../../src/image/pipeline.ts","../../src/middleware/stream-boundary.ts","../../src/build/vite-build.ts"],"sourcesContent":["import { cp, mkdir, stat, writeFile } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { scanRoutes, type PageRoute, type ScannedRoutes } from \"../router/route-scanner.js\";\nimport { scanIslands, type IslandModule } from \"../island/scan.js\";\nimport { generateClientEntry } from \"../island/generate-entry.js\";\nimport { renderPage, renderErrorPage } from \"../ssr/render.js\";\nimport { scanActions, actionNames } from \"../action/scan.js\";\nimport { consumeImageRegistry, setImageManifest, type ImageFormat } from \"../image/index.js\";\nimport { processImageBatch, type ImageManifest } from \"../image/service.js\";\nimport { runIntegrationHook, type ElurKitIntegration } from \"../integrations/index.js\";\nimport type { RouteParams, GenerateStaticParams } from \"../types.js\";\n\nexport interface BuildConfig {\n /** Absolute path to the app directory (e.g. /project/src/app). */\n appDir: string;\n /** Absolute path to the output directory (e.g. /project/dist). */\n outDir: string;\n /** Absolute path to the project root (e.g. /project). When provided, action\n * paths in the serialized HTML shell are made relative to this root. */\n root?: string;\n /** Base path for the client entry module, e.g. \"/_elur/entry-client.js\". */\n clientEntry?: string;\n /** Default language for the HTML shell. */\n lang?: string;\n /**\n * Absolute path to the islands directory (e.g. /project/src/islands).\n * When set, `build` scans it and generates a client entry module listing\n * every island so you don't have to maintain `entry-client.ts` by hand.\n */\n islandsDir?: string;\n /**\n * Absolute path where the generated client entry module is written\n * (e.g. /project/.elur/entry-client.ts). Required when `islandsDir` is set.\n */\n generatedEntry?: string;\n /**\n * Import specifier the generated entry uses for `hydrateIslands`.\n * Defaults to the published subpath `@elurjs/kit/island`.\n */\n hydrateImport?: string;\n /**\n * Import specifier the generated entry uses for `startClientRouter`.\n * Defaults to the published subpath `@elurjs/kit/router`.\n */\n routerImport?: string;\n /** Absolute path to the public directory for static assets (optional). */\n publicDir?: string;\n /** Image formats to generate when sharp is available. Defaults to [\"webp\", \"avif\"]. */\n imageFormats?: ImageFormat[];\n /**\n * Whether the SSR render endpoint (`/__elur-js/render`) exists at runtime.\n * Defaults to `true` (dev, preview and SSR deployments). Set to `false` for\n * fully static outputs so the emitted HTML tells the client router to skip\n * the endpoint (no 404 storms on static hosts like Vercel).\n */\n renderEndpoint?: boolean;\n /**\n * Integrations to invoke during the build lifecycle. When provided, the\n * `build` hook fires after all pages and image variants are generated,\n * giving integrations a chance to write post-build artifacts (sitemaps,\n * robots.txt, search indexes, etc.) into the output directory.\n */\n integrations?: ElurKitIntegration[];\n}\n\nexport interface BuildResult {\n /** Number of static HTML pages generated. */\n pages: number;\n /** Paths that were skipped because they are dynamic without a static param list. */\n skipped: string[];\n /** Absolute paths to the generated HTML files. */\n files: string[];\n /** Islands discovered when `islandsDir` is set. */\n islands: IslandModule[];\n /** Absolute path to the generated client entry, if one was written. */\n generatedEntry?: string;\n /** Number of image variants generated (0 if sharp is not installed). */\n imagesProcessed: number;\n /** Absolute path to the output directory where build artifacts were written.\n * When called via the CLI, this is the atomic staging directory (not the\n * final `dist/`). Integration `build` hooks should write post-build\n * artifacts here so they survive the atomic swap. */\n outDir: string;\n}\n\nfunction urlToFilePath(outDir: string, urlPath: string): string {\n if (urlPath === \"/\") {\n return join(outDir, \"index.html\");\n }\n\n const segments = urlPath.slice(1).split(\"/\");\n return join(outDir, ...segments, \"index.html\");\n}\n\nfunction isDynamic(path: string): boolean {\n return path.includes(\":\");\n}\n\nfunction buildConcreteUrl(path: string, params: RouteParams): string {\n return path.replace(/:([a-zA-Z0-9_]+)(\\*)?/g, (_, name, catchAll) => {\n const value = params[name];\n if (value === undefined || value === null) {\n throw new Error(\n `Missing value for dynamic segment \"${name}\" in path \"${path}\"`,\n );\n }\n if (catchAll) {\n return Array.isArray(value) ? value.join(\"/\") : String(value);\n }\n return String(value);\n });\n}\n\n/**\n * Builds a static site from a scanned route tree.\n *\n * @param config Build configuration.\n * @returns Summary of generated files.\n */\nexport async function build(config: BuildConfig): Promise<BuildResult> {\n if (config.publicDir) {\n try {\n if ((await stat(config.publicDir)).isDirectory()) {\n await mkdir(config.outDir, { recursive: true });\n await cp(config.publicDir, config.outDir, { recursive: true, force: true });\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n }\n\n const routes = await scanRoutes(config.appDir);\n const actions = await scanActions(config.appDir);\n // Only action names are serialized into the HTML shell; full paths stay on the server.\n const publicActions = actionNames(actions);\n const result: BuildResult = { pages: 0, skipped: [], files: [], islands: [], imagesProcessed: 0, outDir: config.outDir };\n\n // Scan islands and generate the client entry before rendering pages, so the\n // hydration bundle stays in sync with what the app actually uses.\n if (config.islandsDir) {\n result.islands = await scanIslands(config.islandsDir);\n }\n\n if (config.generatedEntry) {\n result.generatedEntry = await generateClientEntry({\n islands: result.islands,\n outFile: config.generatedEntry,\n hydrateImport: config.hydrateImport,\n routerImport: config.routerImport,\n });\n }\n\n for (const route of routes.pages) {\n if (!isDynamic(route.path)) {\n const filePath = await buildPage(config, route, publicActions);\n result.pages++;\n result.files.push(filePath);\n continue;\n }\n\n const dynamicFiles = await buildDynamicPages(config, route, publicActions);\n if (dynamicFiles.length === 0) {\n result.skipped.push(route.path);\n } else {\n result.pages += dynamicFiles.length;\n result.files.push(...dynamicFiles);\n }\n }\n\n // Generate static 404 and 500 error pages when they exist.\n const errorConfig = { lang: config.lang, clientEntry: config.clientEntry, renderEndpoint: false };\n if (routes.error404) {\n const result404 = await renderErrorPage({\n routes,\n status: 404,\n config: errorConfig,\n actions: publicActions,\n });\n if (result404) {\n const filePath = join(config.outDir, \"404.html\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, result404.html, \"utf8\");\n result.files.push(filePath);\n }\n }\n\n if (routes.error500) {\n const result500 = await renderErrorPage({\n routes,\n status: 500,\n config: errorConfig,\n actions: publicActions,\n });\n if (result500) {\n const filePath = join(config.outDir, \"500.html\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, result500.html, \"utf8\");\n result.files.push(filePath);\n }\n }\n\n // Process registered images with the ImageService (if sharp is installed).\n // This is a two-pass process:\n // 1. First render pass registers all images (already done above).\n // 2. Process registered images → produce manifest.\n // 3. If variants were generated, set the manifest and re-render pages\n // so the markup uses real <picture>/<source> with hashed URLs.\n const registeredImages = consumeImageRegistry();\n let manifest: ImageManifest | null = null;\n if (registeredImages.length > 0 && config.publicDir) {\n const manifestPath = join(config.outDir, \".elur\", \"image-manifest.json\");\n const processResult = await processImageBatch(registeredImages, {\n publicDir: config.publicDir,\n outDir: config.outDir,\n formats: config.imageFormats,\n manifestPath,\n });\n result.imagesProcessed = processResult.count;\n\n if (processResult.optimized && processResult.count > 0) {\n manifest = processResult.manifest;\n setImageManifest(manifest);\n\n // Re-render all pages with the manifest so image() emits <picture>.\n result.pages = 0;\n result.files = [];\n for (const route of routes.pages) {\n if (!isDynamic(route.path)) {\n const filePath = await buildPage(config, route, publicActions);\n result.pages++;\n result.files.push(filePath);\n continue;\n }\n const dynamicFiles = await buildDynamicPages(config, route, publicActions);\n if (dynamicFiles.length === 0) {\n result.skipped.push(route.path);\n } else {\n result.pages += dynamicFiles.length;\n result.files.push(...dynamicFiles);\n }\n }\n\n // Re-render error pages too.\n if (routes.error404) {\n const result404 = await renderErrorPage({\n routes,\n status: 404,\n config: errorConfig,\n actions: publicActions,\n });\n if (result404) {\n const filePath = join(config.outDir, \"404.html\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, result404.html, \"utf8\");\n result.files.push(filePath);\n }\n }\n if (routes.error500) {\n const result500 = await renderErrorPage({\n routes,\n status: 500,\n config: errorConfig,\n actions: publicActions,\n });\n if (result500) {\n const filePath = join(config.outDir, \"500.html\");\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, result500.html, \"utf8\");\n result.files.push(filePath);\n }\n }\n }\n }\n\n // Clear the manifest so subsequent builds start fresh.\n setImageManifest(null);\n\n // Fire the `build` integration hook so integrations can write\n // post-build artifacts (sitemaps, robots.txt, search indexes, etc.)\n // into the output directory. This runs after all pages, image variants,\n // and the manifest are written, but before the atomic staging commit\n // (when called via the CLI), so integration artifacts survive the swap.\n if (config.integrations && config.integrations.length > 0) {\n await runIntegrationHook(config.integrations, \"build\", [\n result,\n { root: config.root ?? config.outDir, command: \"build\" },\n ]);\n }\n\n return result;\n}\n\nasync function buildPage(\n config: BuildConfig,\n route: PageRoute,\n actions: Record<string, string[]>,\n): Promise<string> {\n return buildConcretePage(config, route, {}, actions);\n}\n\nasync function buildDynamicPages(\n config: BuildConfig,\n route: PageRoute,\n actions: Record<string, string[]>,\n): Promise<string[]> {\n const { generateStaticParams } = (await import(\n route.pagePath\n )) as { generateStaticParams?: GenerateStaticParams };\n\n if (!generateStaticParams) {\n return [];\n }\n\n const paramList = await generateStaticParams();\n if (!Array.isArray(paramList) || paramList.length === 0) {\n return [];\n }\n\n const files: string[] = [];\n for (const params of paramList) {\n files.push(await buildConcretePage(config, route, params, actions));\n }\n return files;\n}\n\nasync function buildConcretePage(\n config: BuildConfig,\n route: PageRoute,\n params: RouteParams,\n actions: Record<string, string[]>,\n): Promise<string> {\n const { html: htmlOut } = await renderPage({\n route,\n params,\n searchParams: new URLSearchParams(),\n config: { lang: config.lang, clientEntry: config.clientEntry, renderEndpoint: false },\n actions,\n });\n\n const urlPath = isDynamic(route.path) ? buildConcreteUrl(route.path, params) : route.path;\n const filePath = urlToFilePath(config.outDir, urlPath);\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, htmlOut, \"utf8\");\n\n return filePath;\n}\n\nexport { scanRoutes, type PageRoute, type ScannedRoutes };\n","import { ELUR_RENDER_PROTOCOL, type ElurTemplate, type ServerRenderProtocolContext } from \"@elurjs/core\";\n\n// --- Islands helper ---\n//\n// Marks a component as an island. During server-side rendering it emits a\n// static placeholder with `data-elur-island` attributes. The client entry finds\n// these markers and hydrates them with the real component + reactive signals.\n//\n// SSR strategy\n// ------------\n// By default the component is executed on the server to produce a fallback HTML\n// fragment (better first paint, SEO, less layout shift). Components that access\n// browser-only globals (`document`, `window`, `navigator`, ...) in their body\n// cannot run on the server. Two opt-out mechanisms are provided, mirroring the\n// industry standard (Astro `client:only`, Next.js `dynamic(..., { ssr: false })`):\n//\n// 1. directive: \"only\" — shortcut for client-only with `load` scheduling.\n// 2. options: { ssr: false } — client-only with any directive (load/idle/visible).\n//\n// When SSR is skipped, only `options.fallback` (a ElurTemplate or string) is\n// rendered into the marker. The client hydrates from scratch.\n//\n// When SSR runs and the component throws, the error is NOT swallowed: it is\n// re-thrown wrapped with an actionable message naming the island and suggesting\n// `directive: \"only\"` / `{ ssr: false }` / `isSSR()`. This matches Astro and\n// Next.js, which never try/catch to \"auto-detect\" client-only components.\n\nexport type IslandDirective = \"load\" | \"idle\" | \"visible\" | \"only\";\n\nexport interface IslandComponent<TProps = unknown> {\n (props: TProps): ElurTemplate | null | false | undefined;\n}\n\n/**\n * Options for {@link island}.\n *\n * - `ssr`: Whether to execute the component on the server. Defaults to `true`\n * unless `directive === \"only\"` (then `false`). When `false`, the component\n * is never called during SSR; only `fallback` is rendered.\n * - `fallback`: HTML to render inside the island marker when SSR is skipped or\n * the component returns null/false. Accepts a `ElurTemplate` (reactive, with\n * signals) or a plain string. Defaults to an empty string.\n */\nexport interface IslandOptions {\n ssr?: boolean;\n fallback?: ElurTemplate | string;\n}\n\n/**\n * Renders a component to a static HTML string with island markers.\n *\n * @param name Unique island name used by the client entry to look up the module.\n * @param component Island component. Executed on the server unless `directive`\n * is `\"only\"` or `options.ssr` is `false`.\n * @param props Props passed to the component and serialized for hydration.\n * @param directive When to hydrate on the client. Use `\"only\"` to skip SSR\n * entirely (client-only island).\n * @param options SSR strategy and fallback content.\n * @returns A ElurTemplate that renders the island placeholder.\n */\nexport function island<TProps>(\n name: string,\n component: IslandComponent<TProps>,\n props: TProps,\n directive: IslandDirective = \"load\",\n options?: IslandOptions,\n): ElurTemplate {\n // `directive: \"only\"` forces ssr off; explicit `options.ssr` wins otherwise.\n const ssr = directive === \"only\" ? false : (options?.ssr ?? true);\n const fallback = options?.fallback;\n\n const markerHtml = (innerHtml: string) =>\n `<div data-elur-island=\"${escapeHtml(name)}\" data-directive=\"${directive}\" data-props='${serializeProps(props)}'>${innerHtml}</div>`;\n\n return {\n __isElurTemplate: true as const,\n [ELUR_RENDER_PROTOCOL]: {\n async renderServer(context: ServerRenderProtocolContext) {\n let innerHtml = \"\";\n if (ssr) {\n try {\n const template = component(props);\n if (template !== null && template !== false && template !== undefined) {\n innerHtml = await context.render(template, { markers: true });\n } else {\n // Component returned null/false/undefined — render fallback if any.\n innerHtml = await renderFallback(fallback, context);\n }\n } catch (error) {\n throw wrapIslandSSRError(name, error);\n }\n } else {\n innerHtml = await renderFallback(fallback, context);\n }\n return markerHtml(innerHtml);\n },\n },\n _render(parent: Node, before: Node | null): () => void {\n const container = document.createElement(\"div\");\n let innerHtml = \"\";\n if (ssr) {\n const template = component(props);\n if (template !== null && template !== false && template !== undefined) {\n const dispose = template._render(container, null);\n innerHtml = container.innerHTML;\n dispose();\n } else {\n // null/false/undefined — render fallback if any.\n innerHtml = renderFallbackSync(fallback, container);\n }\n } else {\n innerHtml = renderFallbackSync(fallback, container);\n }\n const wrapper = document.createElement(\"template\");\n wrapper.innerHTML = markerHtml(innerHtml);\n const fragment = wrapper.content;\n const inserted = fragment.firstChild;\n parent.insertBefore(fragment, before);\n return () => {\n if (inserted?.parentNode) inserted.parentNode.removeChild(inserted);\n };\n },\n } as unknown as ElurTemplate;\n}\n\n/**\n * Wraps an SSR error from an island component with an actionable message.\n *\n * Following the Astro/Next.js convention, SSR errors are never silently\n * swallowed — they propagate so real bugs surface. The wrapper adds the island\n * name and three concrete remediation paths.\n */\nfunction wrapIslandSSRError(name: string, error: unknown): Error {\n const cause = error instanceof Error ? error : new Error(String(error));\n const msg = error instanceof Error ? error.message : String(error);\n return new Error(\n `[elur-kit] Island \"${name}\" threw during SSR: ${msg}\\n` +\n ` If the component accesses browser-only globals (document, window, etc.),\\n` +\n ` use directive: \"only\" or options: { ssr: false } to skip server rendering.\\n` +\n ` For environment reads (matchMedia, localStorage, navigator) you may guard\\n` +\n ` the access with isSSR() from \"@elurjs/kit\".`,\n { cause },\n );\n}\n\n/** Renders the fallback (ElurTemplate or string) to an HTML string on the server. */\nasync function renderFallback(\n fallback: ElurTemplate | string | undefined,\n context: ServerRenderProtocolContext,\n): Promise<string> {\n if (fallback == null || fallback === \"\") return \"\";\n if (typeof fallback === \"string\") return fallback;\n return context.render(fallback, { markers: false });\n}\n\n/** Renders the fallback into a container and returns its innerHTML (client path). */\nfunction renderFallbackSync(fallback: ElurTemplate | string | undefined, container: HTMLElement): string {\n if (fallback == null || fallback === \"\") return \"\";\n if (typeof fallback === \"string\") return fallback;\n const dispose = fallback._render(container, null);\n const html = container.innerHTML;\n dispose();\n return html;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction serializeProps(props: unknown): string {\n return JSON.stringify(props ?? null)\n .replace(/</g, \"\\\\u003c\")\n .replace(/'/g, \"\\\\u0027\");\n}\n","import { createServer, type IncomingMessage, type Server } from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { extname } from \"node:path\";\nimport { scanRoutes } from \"../router/route-scanner.js\";\nimport { scanActions, actionNames } from \"../action/scan.js\";\nimport { handleActionRequest, type ActionSecurityOptions } from \"../action/server.js\";\nimport { getCachedHtml, setCachedHtml } from \"../cache.js\";\nimport { matchApiRoute, matchRoute } from \"./match.js\";\nimport { renderPage, renderErrorPage } from \"./render.js\";\nimport { renderPageBody, renderStreamingPage, RouteNotFoundError } from \"./stream.js\";\nimport { loadMiddleware, matchesMiddleware, runMiddleware } from \"../middleware/index.js\";\nimport { incomingMessageToRequest } from \"../runtime/node-http.js\";\nimport { resolveStaticFile } from \"../runtime/static.js\";\nimport { toPublicErrorInfo } from \"../errors.js\";\n\nexport interface SsrServerOptions {\n /** Absolute path to the app directory (e.g. /project/src/app). */\n appDir: string;\n /** Absolute path to the project root. When provided, action paths in the\n * serialized HTML shell are made relative to this root. */\n root?: string;\n /** Absolute path to the public directory for static files (optional). */\n publicDir?: string;\n /** Base path for the client entry module, e.g. \"/_elur/entry-client.js\". */\n clientEntry?: string;\n /** Default language for the HTML shell. */\n lang?: string;\n port?: number;\n host?: string;\n /** Absolute path to the ISR cache directory (optional). */\n cacheDir?: string;\n /** Default revalidate interval in seconds when a page does not export one. */\n defaultRevalidate?: number;\n /** If true, render pages with loading.ts boundaries using streaming. */\n streaming?: boolean;\n /** CSRF / origin policy applied to the server actions endpoint. */\n actionSecurity?: ActionSecurityOptions;\n}\n\nexport interface SsrServer {\n server: Server;\n listen(): Promise<void>;\n close(): Promise<void>;\n}\n\n/**\n * Create an SSR server that renders pages on demand and serves static files.\n */\nexport async function createSsrServer(options: SsrServerOptions): Promise<SsrServer> {\n const routes = await scanRoutes(options.appDir);\n const actions = await scanActions(options.appDir);\n const publicActions = actionNames(actions);\n\n // Load user middleware (src/middleware.ts) if it exists.\n const middleware = options.root ? await loadMiddleware(options.root) : null;\n\n const resolveAction = async (name: string, page?: string) => {\n const pageKey = resolveActionPageKey(page, routes);\n const pageActions = pageKey ? actions[pageKey] : Object.values(actions).find((p) => p[name]) ?? undefined;\n const actionPath = pageActions ? pageActions[name] : undefined;\n if (!actionPath) return undefined;\n const mod = (await import(actionPath)) as Record<string, unknown>;\n const action = mod[name];\n if (typeof action === \"function\") {\n return action as (...args: unknown[]) => unknown;\n }\n return undefined;\n };\n\n const server = createServer(async (req, res) => {\n let urlPath = req.url ?? \"/\";\n if (urlPath.includes(\"?\")) urlPath = urlPath.split(\"?\")[0];\n\n // Server actions endpoint.\n if (urlPath === \"/__elur-js/actions\" && req.method === \"POST\") {\n try {\n const body = await readRequestBody(req);\n const request = incomingMessageToRequest(req, body);\n const response = await handleActionRequest(request, resolveAction, options.actionSecurity);\n res.writeHead(response.status, Object.fromEntries(response.headers.entries()));\n res.end(await response.text());\n } catch (err) {\n console.error(\"[action] error handling\", err);\n res.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(toPublicErrorInfo(err).message);\n }\n return;\n }\n\n if (urlPath === \"/__elur-js/render\") {\n const renderUrl = new URL(req.url ?? \"/\", \"http://localhost\");\n const page = renderUrl.searchParams.get(\"page\") ?? \"/\";\n const search = renderUrl.searchParams.get(\"search\") ?? \"\";\n const wantsJson = (req.headers[\"accept\"] ?? \"\").includes(\"application/json\");\n try {\n const request = incomingMessageToRequest(req);\n\n // ISR: cache the real content served by this endpoint when a cache\n // directory is configured, so streamed pages regenerate on a TTL.\n let body: string;\n let title: string;\n let lastRenderedCookie: string | undefined;\n let lastRenderedHead: string | undefined;\n const ttl = await resolveTtl(options, page, routes);\n const cacheKey = `/__elur-js/render${page}?${search}`;\n if (options.cacheDir && typeof ttl === \"number\" && canUsePublicCache(request)) {\n const cached = await getCachedHtml(options.cacheDir, cacheKey);\n if (cached) {\n body = extractBody(cached.html);\n title = extractTitle(cached.html);\n } else {\n const rendered = await renderPageBody({\n routes,\n pathname: page,\n searchParams: new URLSearchParams(search),\n config: { lang: options.lang ?? \"es\", clientEntry: options.clientEntry },\n actions: publicActions,\n request,\n });\n body = rendered.body;\n title = rendered.title;\n lastRenderedCookie = rendered.clearActionErrorCookie;\n lastRenderedHead = rendered.head;\n await setCachedHtml(options.cacheDir, cacheKey, rendered.fullHtml ?? \"\", ttl);\n }\n } else {\n const rendered = await renderPageBody({\n routes,\n pathname: page,\n searchParams: new URLSearchParams(search),\n config: { lang: options.lang ?? \"es\", clientEntry: options.clientEntry },\n actions: publicActions,\n request,\n });\n body = rendered.body;\n title = rendered.title;\n lastRenderedCookie = rendered.clearActionErrorCookie;\n lastRenderedHead = rendered.head;\n }\n\n if (wantsJson) {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json; charset=utf-8\" };\n // The SPA router applies the cookie via document.cookie so the next\n // full reload does not re-feed stale errors to the page.\n const setCookie = lastRenderedCookie;\n if (setCookie) headers[\"X-Elur-Action-Clear-Cookie\"] = setCookie;\n res.writeHead(200, headers);\n res.end(JSON.stringify({ title, body, head: lastRenderedHead, clearActionErrorCookie: setCookie }));\n } else {\n const headers: Record<string, string> = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (lastRenderedCookie) headers[\"Set-Cookie\"] = lastRenderedCookie;\n res.writeHead(200, headers);\n res.end(body);\n }\n } catch (err) {\n if (err instanceof RouteNotFoundError) {\n console.log(`[ssr] render endpoint: no route for ${page}`);\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"Not Found\");\n return;\n }\n console.error(\"[ssr] streaming render error\", err);\n res.writeHead(500, { \"Content-Type\": \"text/plain\" });\n res.end(\"Internal Server Error\");\n }\n return;\n }\n\n // Run middleware before routing (skip for internal endpoints handled above).\n let middlewareHeaders: Record<string, string> | undefined;\n if (middleware && matchesMiddleware(urlPath, middleware.config)) {\n const mwResult = await runMiddleware(middleware, incomingMessageToRequest(req));\n if (mwResult.kind === \"response\") {\n res.writeHead(mwResult.response.status, Object.fromEntries(mwResult.response.headers.entries()));\n res.end(Buffer.from(await mwResult.response.arrayBuffer()));\n return;\n }\n middlewareHeaders = mwResult.headers;\n }\n\n // Try API routes first.\n const apiMatch = matchApiRoute(urlPath, routes.api);\n if (apiMatch) {\n try {\n const mod = (await import(apiMatch.route.routePath)) as Record<\n string,\n (request: Request, context?: { params: Record<string, string | string[]> }) => unknown\n >;\n const handler = mod[req.method ?? \"GET\"];\n if (typeof handler !== \"function\") {\n res.writeHead(405, { \"Content-Type\": \"text/plain\" });\n res.end(`Method not allowed: ${req.method}`);\n return;\n }\n const body = req.method && req.method !== \"GET\" && req.method !== \"HEAD\" ? await readRequestBody(req) : undefined;\n const request = incomingMessageToRequest(req, body);\n applyHeaders(request.headers, middlewareHeaders);\n const response = (await handler(request, { params: apiMatch.params })) as Response;\n res.writeHead(response.status, Object.fromEntries(response.headers.entries()));\n res.end(Buffer.from(await response.arrayBuffer()));\n } catch (err) {\n console.error(\"[api] error handling\", urlPath, err);\n res.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(toPublicErrorInfo(err).message);\n }\n return;\n }\n\n // Try static files first.\n if (options.publicDir) {\n try {\n const served = await tryServeStatic(res, options.publicDir, urlPath);\n if (served) return;\n } catch (err) {\n console.error(\"[static] error serving\", urlPath, err);\n }\n }\n\n // Try SSR page rendering.\n const match = matchRoute(urlPath, routes.pages);\n const config = { lang: options.lang ?? \"es\", clientEntry: options.clientEntry };\n if (match) {\n try {\n const request = incomingMessageToRequest(req);\n applyHeaders(request.headers, middlewareHeaders);\n\n let html: string;\n let clearActionErrorCookie: string | undefined;\n const revalidate = match.route.dataPath\n ? ((await import(match.route.dataPath)) as { revalidate?: number }).revalidate\n : undefined;\n const ttl = revalidate ?? options.defaultRevalidate;\n const useStreaming = options.streaming !== false && match.route.loadingPath;\n if (useStreaming) {\n html = await renderStreamingPage({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(req.url?.split(\"?\")[1] ?? \"\"),\n config,\n actions: publicActions,\n request,\n });\n } else if (options.cacheDir && typeof ttl === \"number\" && canUsePublicCache(request)) {\n const cacheKey = new URL(request.url).pathname + new URL(request.url).search;\n const cached = await getCachedHtml(options.cacheDir, cacheKey);\n if (cached) {\n html = cached.html;\n } else {\n const result = await renderPage({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(req.url?.split(\"?\")[1] ?? \"\"),\n config,\n actions: publicActions,\n request,\n });\n html = result.html;\n clearActionErrorCookie = result.clearActionErrorCookie;\n await setCachedHtml(options.cacheDir, cacheKey, html, ttl);\n }\n } else {\n const result = await renderPage({\n route: match.route,\n params: match.params,\n searchParams: new URLSearchParams(req.url?.split(\"?\")[1] ?? \"\"),\n config,\n actions: publicActions,\n request,\n });\n html = result.html;\n clearActionErrorCookie = result.clearActionErrorCookie;\n }\n const responseHeaders: Record<string, string> = { \"Content-Type\": \"text/html; charset=utf-8\" };\n if (clearActionErrorCookie) responseHeaders[\"Set-Cookie\"] = clearActionErrorCookie;\n res.writeHead(200, responseHeaders);\n res.end(html);\n return;\n } catch (err) {\n console.error(\"[ssr] error rendering\", urlPath, err);\n const errorResult = await renderErrorPage({\n routes,\n status: 500,\n error: err,\n config,\n actions: publicActions,\n });\n if (errorResult) {\n res.writeHead(errorResult.status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(errorResult.html);\n } else {\n res.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(toPublicErrorInfo(err).message);\n }\n return;\n }\n }\n\n const errorResult = await renderErrorPage({\n routes,\n status: 404,\n config,\n actions: publicActions,\n });\n if (errorResult) {\n res.writeHead(errorResult.status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(errorResult.html);\n return;\n }\n\n res.writeHead(404, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(`Not found: ${req.url}`);\n });\n\n return {\n server,\n listen() {\n return new Promise((resolve) => {\n server.listen(options.port ?? 3000, options.host ?? \"127.0.0.1\", () => {\n console.log(\n `\\n → SSR server http://${options.host ?? \"127.0.0.1\"}:${options.port ?? 3000}`,\n );\n resolve();\n });\n });\n },\n close() {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n },\n };\n}\n\nasync function tryServeStatic(\n res: import(\"node:http\").ServerResponse,\n publicDir: string,\n urlPath: string,\n): Promise<boolean> {\n const filePath = await resolveStaticFile(publicDir, urlPath);\n if (!filePath) return false;\n const contentType = guessContentType(filePath);\n let data: Buffer | string = await readFile(filePath);\n if (contentType.includes(\"text/html\")) {\n // The SSG build bakes `render-endpoint content=\"off\"` into the static\n // HTML so purely static deployments never probe the endpoint. This server\n // (SSR `start`) DOES expose /__elur-js/render, so advertise it: SPA\n // navigations then fetch live server-rendered content instead of the\n // stale static file (e.g. after a mutating server action).\n data = data\n .toString(\"utf8\")\n .replace(\n '<meta name=\"elur:render-endpoint\" content=\"off\" />',\n '<meta name=\"elur:render-endpoint\" content=\"on\" />',\n );\n }\n res.writeHead(200, { \"Content-Type\": contentType, \"Content-Length\": Buffer.byteLength(data) });\n res.end(data);\n return true;\n}\n\nfunction canUsePublicCache(request: Request): boolean {\n return !request.headers.has(\"cookie\") && !request.headers.has(\"authorization\");\n}\n\nfunction applyHeaders(headers: Headers, values: Record<string, string> | undefined): void {\n if (!values) return;\n for (const [name, value] of Object.entries(values)) headers.set(name, value);\n}\n\nfunction readRequestBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n body += chunk;\n });\n req.on(\"end\", () => resolve(body));\n req.on(\"error\", reject);\n });\n}\n\nfunction guessContentType(filePath: string): string {\n switch (extname(filePath)) {\n case \".html\":\n return \"text/html; charset=utf-8\";\n case \".js\":\n case \".mjs\":\n return \"application/javascript; charset=utf-8\";\n case \".css\":\n return \"text/css; charset=utf-8\";\n case \".json\":\n return \"application/json; charset=utf-8\";\n case \".svg\":\n return \"image/svg+xml\";\n case \".png\":\n return \"image/png\";\n case \".jpg\":\n case \".jpeg\":\n return \"image/jpeg\";\n case \".webp\":\n return \"image/webp\";\n case \".avif\":\n return \"image/avif\";\n case \".ico\":\n return \"image/x-icon\";\n case \".woff\":\n return \"font/woff\";\n case \".woff2\":\n return \"font/woff2\";\n case \".wasm\":\n return \"application/wasm\";\n default:\n return \"application/octet-stream\";\n }\n}\n\n/**\n * Maps a concrete page path (e.g. `/movies/inception`) to the route pattern\n * key used by the action registry (e.g. `/movies/:slug`). Falls back to the\n * path itself when it matches an exact registry key.\n */\nexport function resolveActionPageKey(\n page: string | undefined,\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n): string | undefined {\n if (!page) return undefined;\n if (routes.pages.some((route) => route.path === page)) return page;\n const match = matchRoute(page, routes.pages);\n return match ? match.route.path : page;\n}\n\n/** Resolves the ISR TTL for a page: route `revalidate` or the default. */\nasync function resolveTtl(\n options: SsrServerOptions,\n pathname: string,\n routes: Awaited<ReturnType<typeof scanRoutes>>,\n): Promise<number | undefined> {\n const match = matchRoute(pathname, routes.pages);\n if (!match) return undefined;\n const revalidate = match.route.dataPath\n ? ((await import(match.route.dataPath)) as { revalidate?: number }).revalidate\n : undefined;\n return revalidate ?? options.defaultRevalidate;\n}\n\nfunction extractBody(fullHtml: string): string {\n const match = fullHtml.match(/<div id=\"app\">([\\s\\S]*)<\\/div>\\s*(<script|$)/);\n return match ? match[1].trim() : fullHtml;\n}\n\nfunction extractTitle(fullHtml: string): string {\n const match = fullHtml.match(/<title>([^<]*)<\\/title>/);\n return match ? match[1] : \"\";\n}\n","// --- Image pipeline: build-time variant generation with sharp (optional) ---\n//\n// When `sharp` is installed, the pipeline generates WebP/AVIF variants at\n// multiple widths for every image registered during the render pass. Variants\n// are written to the output directory with a content-based hash so they can\n// be cached indefinitely.\n//\n// When `sharp` is not installed, the pipeline is a no-op and `image()`\n// emits a plain `<img>` with srcset pointing to manually-provided files.\n\nimport { readFile, mkdir, stat } from \"node:fs/promises\";\nimport { join, dirname, extname, basename } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport type { ImageFormat } from \"./index.js\";\n\nlet sharpLoader: (() => Promise<any>) | null | undefined;\n\nasync function loadSharp(): Promise<any | null> {\n if (sharpLoader === null) return null;\n if (sharpLoader) return sharpLoader();\n try {\n // @ts-ignore — `sharp` is an optional peer dependency.\n const mod = await import(\"sharp\");\n const sharp = mod.default;\n if (typeof sharp !== \"function\") {\n sharpLoader = null;\n return null;\n }\n sharpLoader = async () => sharp;\n return sharp;\n } catch {\n sharpLoader = null;\n return null;\n }\n}\n\nexport interface PipelineOptions {\n /** Absolute path to the public directory (source images live here). */\n publicDir: string;\n /** Absolute path to the output directory (variants are written here). */\n outDir: string;\n /** Formats to generate. Defaults to [\"webp\", \"avif\"]. */\n formats?: ImageFormat[];\n /** Quality (1-100). Defaults to 80. */\n quality?: number;\n}\n\nexport interface ProcessedImage {\n /** Original source path (e.g. \"/images/hero.jpg\"). */\n src: string;\n /** Generated variant paths relative to outDir. */\n variants: { path: string; width: number; format: ImageFormat }[];\n}\n\n/**\n * Processes a batch of images: for each image, generates variants at the\n * specified widths and formats using sharp. Returns the list of generated\n * files.\n *\n * If sharp is not installed, returns an empty array and logs a warning once.\n */\nexport async function processImages(\n images: { src: string; widths: number[]; formats: ImageFormat[] }[],\n options: PipelineOptions,\n): Promise<ProcessedImage[]> {\n const sharp = await loadSharp();\n if (!sharp) {\n console.warn(\n \"[elur-kit] Image optimization requires `sharp`. Install it with:\\n\" +\n \" npm install sharp\\n\" +\n \" # or\\n\" +\n \" bun add sharp\\n\" +\n \"Skipping image processing — using original files.\",\n );\n return [];\n }\n\n const { publicDir, outDir, formats = [\"webp\", \"avif\"], quality = 80 } = options;\n const results: ProcessedImage[] = [];\n let warned = false;\n\n for (const { src, widths, formats: imgFormats } of images) {\n const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n try {\n await stat(sourcePath);\n } catch {\n if (!warned) {\n console.warn(`[elur-kit] Image not found: ${sourcePath}. Skipping.`);\n warned = true;\n }\n continue;\n }\n\n const buffer = await readFile(sourcePath);\n const hash = createHash(\"md5\").update(buffer).digest(\"hex\").slice(0, 8);\n const ext = extname(src);\n const base = basename(src, ext);\n const dir = dirname(src);\n\n const variants: { path: string; width: number; format: ImageFormat }[] = [];\n const targetFormats = imgFormats.length > 0 ? imgFormats : formats;\n\n for (const width of widths) {\n for (const format of targetFormats) {\n const variantName = `${base}-${width}w-${hash}.${format}`;\n const variantRelPath = join(dir, variantName);\n const variantAbsPath = join(outDir, variantRelPath.replace(/^\\//, \"\"));\n\n try {\n await mkdir(dirname(variantAbsPath), { recursive: true });\n await sharp(buffer)\n .resize({ width, withoutEnlargement: true })\n .toFormat(format, { quality })\n .toFile(variantAbsPath);\n variants.push({ path: variantRelPath, width, format });\n } catch (err) {\n console.warn(`[elur-kit] Failed to generate ${variantName}:`, err);\n }\n }\n }\n\n if (variants.length > 0) {\n results.push({ src, variants });\n }\n }\n\n return results;\n}\n\n/**\n * Checks whether sharp is available without actually loading it.\n */\nexport async function isSharpAvailable(): Promise<boolean> {\n const sharp = await loadSharp();\n return sharp !== null;\n}\n","// --- Stream boundary (per-request, real Suspense streaming) ---\n//\n// `streamBoundary()` wraps a promise in a loading fallback. During SSR runtime,\n// the server emits the fallback HTML immediately, then streams a `<template>`\n// chunk with a replacement script that the browser executes to swap the\n// fallback for the resolved content in-place (real Suspense streaming).\n//\n// In SSG (build time), boundaries are resolved synchronously — the build waits\n// for all promises before writing the HTML, so no streaming occurs.\n//\n// Boundaries are tracked per-request via AsyncLocalStorage to avoid global\n// state leakage between concurrent requests.\n\nimport type { ElurTemplate } from \"@elurjs/core\";\nimport { randomUUID } from \"node:crypto\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nexport interface StreamBoundaryOptions<T> {\n /** Fallback content shown while the promise resolves. */\n fallback: ElurTemplate;\n /** Promise that resolves to a ElurTemplate. */\n promise: Promise<T>;\n /** Renders the resolved value to a ElurTemplate. */\n children: (value: T) => ElurTemplate;\n}\n\n/** Per-request boundary registry. */\ninterface BoundaryContext {\n boundaries: Map<string, {\n promise: Promise<unknown>;\n children: (value: unknown) => ElurTemplate;\n }>;\n}\n\nconst boundaryALS = new AsyncLocalStorage<BoundaryContext>();\n\n/**\n * Gets the current per-request boundary context, if any.\n * Used by the streaming response to collect boundaries for later resolution.\n */\nexport function getCurrentBoundaryContext(): BoundaryContext | undefined {\n return boundaryALS.getStore();\n}\n\n/**\n * Runs a function within a per-request boundary context.\n * Used by the SSR streaming pipeline to collect boundaries.\n */\nexport function withBoundaryContext<T>(fn: () => T): T {\n const ctx: BoundaryContext = { boundaries: new Map() };\n return boundaryALS.run(ctx, fn);\n}\n\n/**\n * Builds the fallback HTML wrapper for a boundary ID.\n * The fallback content is wrapped in a `<div>` with the boundary ID so the\n * browser can find it and replace it when the resolved content arrives.\n *\n * (v2.1 — Fix #4: real Suspense streaming with `<template>` replacement)\n */\nexport function buildFallbackHtml(boundaryId: string, fallbackHtml: string): string {\n return `<div id=\"${boundaryId}\" style=\"display:contents\" data-elur-boundary=\"${boundaryId}\">${fallbackHtml}</div>`;\n}\n\n/**\n * Builds the resolved content chunk for a boundary ID.\n * Emits a `<template>` element with the resolved content, followed by a\n * `<script>` that replaces the fallback div with the template content\n * in-place. This is real Suspense streaming — the browser swaps the DOM\n * node without a full re-render.\n *\n * (v2.1 — Fix #4: real Suspense streaming with `<template>` replacement)\n */\nexport function buildResolvedChunk(boundaryId: string, resolvedHtml: string): string {\n // Escape the resolved HTML for safe embedding inside a <template> tag.\n // <template> content is inert (not parsed as DOM), so we store the raw\n // HTML and clone it via `content.cloneNode(true)`.\n return `<template id=\"${boundaryId}-tpl\">${resolvedHtml}</template>` +\n `<script>(function(){` +\n `var t=document.getElementById(${JSON.stringify(boundaryId + \"-tpl\")});` +\n `var f=document.getElementById(${JSON.stringify(boundaryId)});` +\n `if(t&&f){f.replaceWith(t.content.cloneNode(true));}` +\n `document.dispatchEvent(new CustomEvent(\"elur:rendered\"));` +\n `})();</script>`;\n}\n\n/**\n * Creates a stream boundary. During SSR, emits the fallback and registers the\n * promise for later resolution by the streaming pipeline. During SSG, the\n * build awaits all boundaries before writing HTML.\n *\n * The boundary ID is deterministic per-request via crypto.randomUUID().\n */\nexport function streamBoundary<T>(options: StreamBoundaryOptions<T>): ElurTemplate {\n const id = `elur-stream-${randomUUID().slice(0, 8)}`;\n const ctx = boundaryALS.getStore();\n\n // In SSR mode with a boundary context, register the promise for later.\n if (ctx) {\n ctx.boundaries.set(id, {\n promise: options.promise,\n children: options.children as (value: unknown) => ElurTemplate,\n });\n }\n\n return {\n __isElurTemplate: true as const,\n mount(container: Element | string) {\n const el = typeof container === \"string\" ? document.querySelector(container) : container;\n if (!el) throw new Error(\"[elur-kit] streamBoundary(): container not found\");\n // Render fallback initially.\n const handle = options.fallback.mount(el);\n // Attempt to resolve and swap (works in both SSR and client).\n options.promise\n .then((value) => {\n const content = options.children(value);\n el.innerHTML = \"\";\n const childHandle = content.mount(el);\n // Store the new handle for cleanup.\n (handle as any).__elurChildHandle = childHandle;\n })\n .catch((err) => {\n console.error(`[elur-kit] streamBoundary ${id} failed:`, err);\n });\n return {\n unmount() {\n const childHandle = (handle as any).__elurChildHandle;\n if (childHandle?.unmount) childHandle.unmount();\n handle.unmount();\n },\n };\n },\n _render(parent: Node, before: Node | null): () => void {\n // For SSR/build: render fallback inline. The promise resolution is\n // handled by the streaming pipeline when available.\n const dispose = options.fallback._render(parent, before);\n\n // Kick off the promise resolution in the background.\n options.promise\n .then((value) => {\n void value;\n })\n .catch((err) => {\n console.error(`[elur-kit] streamBoundary ${id} failed:`, err);\n });\n\n return dispose;\n },\n } as unknown as ElurTemplate;\n}\n","import { mkdir, rm, rename, stat, cp, access } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve, dirname, relative } from \"node:path\";\nimport { build as viteBuild, type InlineConfig, type PluginOption } from \"vite\";\nimport { elurJsInterpolationPlugin, shouldUseLegacyInterpolation, type InterpolationMode } from \"../vite/interpolation-plugin.js\";\n\n// --- Programmatic Vite build orchestration ---\n//\n// Replaces the previous `spawnSync(\"npx\", [\"vite\", \"build\", ...])` approach\n// with direct use of the Vite JavaScript API. Benefits:\n//\n// * No child-process overhead or `npx` resolution latency.\n// * Shared module cache across build phases (faster large builds).\n// * Structured errors instead of exit-code parsing.\n// * Atomic output staging: build into a temp directory, then rename to the\n// final destination so a crashed build never leaves a half-written dist.\n\nexport interface ClientBuildOptions {\n /** Project root (absolute). */\n root: string;\n /** Absolute path to the user's Vite client config (e.g. vite.client.config.ts). */\n userConfigPath: string;\n /** Absolute path to the app directory (used by the interpolation plugin). */\n appDir: string;\n /** Absolute path to the islands directory (used by the interpolation plugin). */\n islandsDir: string;\n /** Output directory for the client bundle (absolute). */\n outDir: string;\n /** Optional base path. */\n base?: string;\n /** Optional log prefix. */\n logPrefix?: string;\n /**\n * How the legacy interpolation transform is handled (default: \"auto\").\n * With a Elur core that supports partial attribute interpolation natively\n * the transform is not applied; use \"legacy\" for migrations against older\n * cores and \"off\" to never transform.\n */\n interpolation?: InterpolationMode;\n}\n\nexport interface ClientBuildResult {\n /** Output directory (same as `outDir` input). */\n outDir: string;\n /** Number of chunks/assets emitted, if reported by Vite. */\n outputCount: number;\n}\n\n/**\n * Build the client hydration bundle using the Vite JavaScript API.\n *\n * The user's config is loaded programmatically and the elur interpolation\n * plugin is injected so partial attribute interpolations inside islands are\n * transformed before reaching the browser.\n */\nexport async function buildClientBundle(options: ClientBuildOptions): Promise<ClientBuildResult> {\n const log = options.logPrefix ?? \"[client]\";\n console.log(`${log} Building hydration bundle...`);\n\n const userConfig = await loadUserConfig(options.userConfigPath, options.root);\n const pluginOptions: PluginOption = shouldUseLegacyInterpolation(options.interpolation ?? \"auto\")\n ? elurJsInterpolationPlugin({\n appDir: options.appDir,\n islandsDir: options.islandsDir,\n })\n : [];\n\n const config: InlineConfig = {\n ...userConfig,\n root: options.root,\n base: options.base ?? userConfig.base ?? \"/\",\n build: {\n ...(userConfig.build ?? {}),\n outDir: options.outDir,\n emptyOutDir: true,\n },\n plugins: [...(userConfig.plugins ?? []), pluginOptions],\n configFile: false,\n };\n\n const result = await viteBuild(config);\n const outputs = Array.isArray(result) ? result : [result];\n const outputCount = outputs.reduce(\n (n, r) => n + (\"output\" in r ? (r.output?.length ?? 0) : 0),\n 0,\n );\n console.log(`${log} ✓ ${outputCount} asset(s) emitted → ${relative(options.root, options.outDir)}`);\n return { outDir: options.outDir, outputCount };\n}\n\nasync function loadUserConfig(path: string, _root: string): Promise<InlineConfig> {\n const mod = await import(path);\n const raw = mod.default ?? mod;\n const resolved = typeof raw === \"function\" ? await raw({ command: \"build\", mode: \"production\" }) : raw;\n return (resolved && typeof resolved.then === \"function\" ? await resolved : resolved) ?? {};\n}\n\n// --- Atomic output staging ---\n\nexport interface AtomicStageOptions {\n /** Final destination directory (absolute). */\n outDir: string;\n /** Build into this temp directory first, then rename to `outDir`. */\n tempDir?: string;\n /** Whether to preserve existing content in `outDir` during the swap. */\n keepExisting?: boolean;\n}\n\nexport interface AtomicStage {\n tempDir: string;\n /** Call after the build succeeds to atomically swap temp → outDir. */\n commit: () => Promise<void>;\n /** Call on failure to clean up the temp directory. */\n rollback: () => Promise<void>;\n}\n\n/**\n * Prepare an atomic staging directory for build output.\n *\n * Usage:\n * const stage = await beginAtomicStage({ outDir });\n * try {\n * await buildInto(stage.tempDir);\n * await stage.commit();\n * } catch (err) {\n * await stage.rollback();\n * throw err;\n * }\n */\nexport async function beginAtomicStage(options: AtomicStageOptions): Promise<AtomicStage> {\n const outDir = resolve(options.outDir);\n const tempDir = resolve(options.tempDir ?? join(dirname(outDir), `.${basename(outDir)}.tmp-${process.pid}`));\n\n // Start from a clean temp directory.\n await rm(tempDir, { recursive: true, force: true });\n await mkdir(tempDir, { recursive: true });\n\n const commit = async () => {\n // Backup the existing output if requested, then swap.\n const backup = options.keepExisting && existsSync(outDir) ? `${outDir}.bak-${process.pid}` : undefined;\n if (backup) {\n await rm(backup, { recursive: true, force: true });\n await safeRename(outDir, backup);\n }\n try {\n await safeRename(tempDir, outDir);\n } catch (err) {\n // On some platforms, renaming across mount points fails. Fall back to a\n // recursive copy + clean, which is not atomic but still correct.\n if (isCrossDevice(err)) {\n await cp(tempDir, outDir, { recursive: true, force: true });\n await rm(tempDir, { recursive: true, force: true });\n } else {\n if (backup) await safeRename(backup, outDir);\n throw err;\n }\n }\n if (backup) await rm(backup, { recursive: true, force: true });\n };\n\n const rollback = async () => {\n await rm(tempDir, { recursive: true, force: true });\n };\n\n return { tempDir, commit, rollback };\n}\n\nfunction basename(path: string): string {\n const parts = path.split(/[\\\\/]+/).filter(Boolean);\n return parts[parts.length - 1] ?? \"output\";\n}\n\nasync function safeRename(src: string, dest: string): Promise<void> {\n await rm(dest, { recursive: true, force: true });\n try {\n await rename(src, dest);\n } catch (err) {\n if (isCrossDevice(err)) {\n await cp(src, dest, { recursive: true, force: true });\n await rm(src, { recursive: true, force: true });\n } else {\n throw err;\n }\n }\n}\n\nfunction isCrossDevice(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException)?.code;\n return code === \"EXDEV\";\n}\n\n// --- Public asset copy ---\n\nexport interface CopyPublicAssetsOptions {\n /** Absolute path to the public directory. */\n publicDir: string;\n /** Absolute path to the output directory. */\n outDir: string;\n}\n\n/**\n * Copy the public directory into the output directory.\n * Returns the number of files copied.\n */\nexport async function copyPublicAssets(options: CopyPublicAssetsOptions): Promise<number> {\n try {\n await access(options.publicDir);\n const s = await stat(options.publicDir);\n if (!s.isDirectory()) return 0;\n } catch {\n return 0;\n }\n await mkdir(options.outDir, { recursive: true });\n await cp(options.publicDir, options.outDir, { recursive: true, force: true });\n return countFiles(options.outDir);\n}\n\nasync function countFiles(dir: string): Promise<number> {\n const { readdir } = await import(\"node:fs/promises\");\n let count = 0;\n async function walk(d: string): Promise<void> {\n const entries = await readdir(d, { withFileTypes: true });\n for (const entry of entries) {\n const path = join(d, entry.name);\n if (entry.isDirectory()) await walk(path);\n else count++;\n }\n }\n await walk(dir);\n return count;\n}\n"],"mappings":"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"}
|