@pyreon/zero 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{api-routes-DANluJic.js → api-routes-Ci0kVmM4.js} +2 -2
- package/lib/client.js +4 -1
- package/lib/env.js +6 -6
- package/lib/font.js +3 -3
- package/lib/{fs-router-ZebyutPa.js → fs-router-MewHc5SB.js} +25 -30
- package/lib/i18n-routing.js +112 -1
- package/lib/image.js +140 -58
- package/lib/index.js +252 -82
- package/lib/og-image.js +5 -5
- package/lib/rolldown-runtime-CjeV3_4I.js +18 -0
- package/lib/script.js +114 -25
- package/lib/seo.js +186 -15
- package/lib/server.js +274 -564
- package/lib/types/config.d.ts +275 -3
- package/lib/types/env.d.ts +2 -2
- package/lib/types/i18n-routing.d.ts +193 -2
- package/lib/types/image.d.ts +105 -5
- package/lib/types/index.d.ts +634 -182
- package/lib/types/script.d.ts +78 -6
- package/lib/types/seo.d.ts +128 -4
- package/lib/types/server.d.ts +575 -72
- package/lib/vite-plugin-xjWZwudX.js +2454 -0
- package/package.json +11 -10
- package/src/adapters/bun.ts +20 -1
- package/src/adapters/cloudflare.ts +78 -1
- package/src/adapters/index.ts +25 -3
- package/src/adapters/netlify.ts +63 -1
- package/src/adapters/node.ts +25 -1
- package/src/adapters/static.ts +26 -1
- package/src/adapters/validate.ts +8 -1
- package/src/adapters/vercel.ts +76 -1
- package/src/adapters/warn-missing-env.ts +49 -0
- package/src/app.ts +14 -0
- package/src/client.ts +18 -0
- package/src/entry-server.ts +55 -5
- package/src/env.ts +7 -7
- package/src/font.ts +3 -3
- package/src/fs-router.ts +72 -3
- package/src/i18n-routing.ts +246 -12
- package/src/image.tsx +242 -91
- package/src/index.ts +4 -4
- package/src/isr.ts +24 -6
- package/src/manifest.ts +675 -0
- package/src/og-image.ts +5 -5
- package/src/script.tsx +159 -36
- package/src/seo.ts +346 -15
- package/src/server.ts +10 -2
- package/src/ssg-plugin.ts +1211 -54
- package/src/types.ts +301 -10
- package/src/vercel-revalidate-handler.ts +204 -0
- package/src/vite-plugin.ts +108 -30
- package/lib/vite-plugin-E4BHYvYW.js +0 -855
package/lib/types/server.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as _$_pyreon_router0 from "@pyreon/router";
|
|
|
4
4
|
import { RouteRecord } from "@pyreon/router";
|
|
5
5
|
import { Middleware, MiddlewareContext } from "@pyreon/server";
|
|
6
6
|
import { Plugin } from "vite";
|
|
7
|
+
|
|
7
8
|
//#region src/app.d.ts
|
|
8
9
|
interface CreateAppOptions {
|
|
9
10
|
/** Route definitions (from file-based routing or manual). */
|
|
@@ -16,6 +17,18 @@ interface CreateAppOptions {
|
|
|
16
17
|
layout?: ComponentFn;
|
|
17
18
|
/** Global error component. */
|
|
18
19
|
errorComponent?: ComponentFn;
|
|
20
|
+
/**
|
|
21
|
+
* Base URL prefix for the deployed app (e.g. `/blog/`). Forwarded to
|
|
22
|
+
* `createRouter({ base })` so RouterLinks render correctly prefixed
|
|
23
|
+
* hrefs (`<a href="/blog/about">` instead of `<a href="/about">`) and
|
|
24
|
+
* the router strips the prefix from incoming URLs before matching.
|
|
25
|
+
*
|
|
26
|
+
* Default: `'/'`. Pre-fix this was disconnected from `zero({ base })`
|
|
27
|
+
* — RouterLinks rendered un-prefixed hrefs even when Vite's asset URL
|
|
28
|
+
* rewriting was correctly using the prefix, causing client-side
|
|
29
|
+
* navigation to break against subpath deploys.
|
|
30
|
+
*/
|
|
31
|
+
base?: string;
|
|
19
32
|
}
|
|
20
33
|
/**
|
|
21
34
|
* Create a full Zero app — assembles router, head provider, and root layout.
|
|
@@ -61,6 +74,66 @@ interface ApiRouteEntry {
|
|
|
61
74
|
module: ApiRouteModule;
|
|
62
75
|
}
|
|
63
76
|
//#endregion
|
|
77
|
+
//#region src/i18n-routing.d.ts
|
|
78
|
+
interface I18nRoutingConfig {
|
|
79
|
+
/** Supported locales. e.g. ["en", "de", "cs"] */
|
|
80
|
+
locales: string[];
|
|
81
|
+
/** Default locale — served without prefix (/ instead of /en/). */
|
|
82
|
+
defaultLocale: string;
|
|
83
|
+
/** Redirect root to detected locale. Default: true */
|
|
84
|
+
detectLocale?: boolean;
|
|
85
|
+
/** Cookie name to persist locale preference. Default: "locale" */
|
|
86
|
+
cookieName?: string;
|
|
87
|
+
/** URL strategy. Default: "prefix-except-default" */
|
|
88
|
+
strategy?: 'prefix' | 'prefix-except-default';
|
|
89
|
+
}
|
|
90
|
+
interface LocaleContext {
|
|
91
|
+
/** Current locale code. e.g. "en", "de" */
|
|
92
|
+
locale: string;
|
|
93
|
+
/** All supported locales. */
|
|
94
|
+
locales: string[];
|
|
95
|
+
/** Default locale. */
|
|
96
|
+
defaultLocale: string;
|
|
97
|
+
/** Build a localized path. e.g. localePath("/about", "de") → "/de/about" */
|
|
98
|
+
localePath: (path: string, locale?: string) => string;
|
|
99
|
+
/** Get hreflang alternates for the current path. */
|
|
100
|
+
alternates: () => Array<{
|
|
101
|
+
locale: string;
|
|
102
|
+
url: string;
|
|
103
|
+
}>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Detect preferred locale from Accept-Language header.
|
|
107
|
+
*/
|
|
108
|
+
declare function detectLocaleFromHeader(acceptLanguage: string | null | undefined, locales: string[], defaultLocale: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Create a LocaleContext for use in components and loaders.
|
|
111
|
+
*/
|
|
112
|
+
declare function createLocaleContext(locale: string, path: string, config: I18nRoutingConfig): LocaleContext;
|
|
113
|
+
/**
|
|
114
|
+
* I18n routing middleware for Zero's server.
|
|
115
|
+
*
|
|
116
|
+
* - Detects locale from URL prefix or Accept-Language header
|
|
117
|
+
* - Redirects root to preferred locale (when detectLocale is true)
|
|
118
|
+
* - Sets locale context for loaders and components
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* // zero.config.ts
|
|
123
|
+
* import { i18nRouting } from "@pyreon/zero"
|
|
124
|
+
*
|
|
125
|
+
* export default defineConfig({
|
|
126
|
+
* plugins: [
|
|
127
|
+
* i18nRouting({
|
|
128
|
+
* locales: ["en", "de", "cs"],
|
|
129
|
+
* defaultLocale: "en",
|
|
130
|
+
* }),
|
|
131
|
+
* ],
|
|
132
|
+
* })
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function i18nRouting(config: I18nRoutingConfig): Plugin;
|
|
136
|
+
//#endregion
|
|
64
137
|
//#region src/types.d.ts
|
|
65
138
|
type RenderMode = 'ssr' | 'ssg' | 'spa' | 'isr';
|
|
66
139
|
interface ISRConfig {
|
|
@@ -73,6 +146,42 @@ interface ISRConfig {
|
|
|
73
146
|
* space (e.g. `/user/:id` where `:id` is free-form).
|
|
74
147
|
*/
|
|
75
148
|
maxEntries?: number;
|
|
149
|
+
/**
|
|
150
|
+
* Cache-key derivation function. The default keys cache entries by
|
|
151
|
+
* `url.pathname` ONLY — query strings, cookies, and headers are
|
|
152
|
+
* stripped.
|
|
153
|
+
*
|
|
154
|
+
* **⚠️ Auth-gated incompatibility.** The default behavior is
|
|
155
|
+
* unsafe for request-dependent loaders. A loader that reads
|
|
156
|
+
* `request.headers.get('cookie')` to gate auth will render ONCE
|
|
157
|
+
* with the first user's cookie, then serve that HTML to every
|
|
158
|
+
* subsequent user. To use ISR with personalized / auth-gated
|
|
159
|
+
* pages, supply a `cacheKey` that varies on the auth identifier
|
|
160
|
+
* (session cookie, user-id header, etc.), OR don't use ISR for
|
|
161
|
+
* such routes — use SSR instead.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // Vary cache by session cookie:
|
|
165
|
+
* isr: {
|
|
166
|
+
* revalidate: 60,
|
|
167
|
+
* cacheKey: (req) => {
|
|
168
|
+
* const url = new URL(req.url)
|
|
169
|
+
* const session = req.headers.get('cookie')?.match(/session=([^;]+)/)?.[1] ?? 'anon'
|
|
170
|
+
* return `${url.pathname}::${session}`
|
|
171
|
+
* },
|
|
172
|
+
* }
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* // Vary by a query parameter:
|
|
176
|
+
* isr: {
|
|
177
|
+
* revalidate: 60,
|
|
178
|
+
* cacheKey: (req) => {
|
|
179
|
+
* const url = new URL(req.url)
|
|
180
|
+
* return `${url.pathname}?sort=${url.searchParams.get('sort') ?? ''}`
|
|
181
|
+
* },
|
|
182
|
+
* }
|
|
183
|
+
*/
|
|
184
|
+
cacheKey?: (req: Request) => string;
|
|
76
185
|
}
|
|
77
186
|
interface ZeroConfig {
|
|
78
187
|
/** Default rendering mode. Default: "ssr" */
|
|
@@ -86,13 +195,167 @@ interface ZeroConfig {
|
|
|
86
195
|
/** SSG options — only used when mode is "ssg". */
|
|
87
196
|
ssg?: {
|
|
88
197
|
/** Paths to prerender (or function returning paths). */paths?: string[] | (() => string[] | Promise<string[]>);
|
|
198
|
+
/**
|
|
199
|
+
* Auto-emit `dist/404.html` from the route tree's `_404.tsx` /
|
|
200
|
+
* `_not-found.tsx` convention. fs-router already wires `_404.tsx` as
|
|
201
|
+
* `notFoundComponent` on its parent layout route; the SSG plugin walks
|
|
202
|
+
* the tree, picks up the first one, renders it through the same SSR
|
|
203
|
+
* pipeline as regular paths (so styler CSS / @pyreon/head metadata land
|
|
204
|
+
* correctly), and writes the result to `dist/404.html`. Static hosts
|
|
205
|
+
* (Netlify, Cloudflare Pages, GitHub Pages, S3+CloudFront) serve this
|
|
206
|
+
* file automatically for unmatched URLs. Default: `true`. Set to
|
|
207
|
+
* `false` to opt out — the route tree is left alone.
|
|
208
|
+
*/
|
|
209
|
+
emit404?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* When a route loader throws `redirect('/target')` during SSG, write
|
|
212
|
+
* a `dist/_redirects` file (Netlify / Cloudflare Pages convention)
|
|
213
|
+
* AND a `dist/_redirects.json` (Vercel convention) listing every
|
|
214
|
+
* redirected source path → target. Static hosts pick whichever
|
|
215
|
+
* format their platform supports automatically. The redirected
|
|
216
|
+
* path's HTML file is NOT emitted — the redirect is the response.
|
|
217
|
+
*
|
|
218
|
+
* Without this option, redirect-throwing loaders land in
|
|
219
|
+
* `errors[]` and the path silently disappears from the build —
|
|
220
|
+
* the user sees no output for `/old` AND no warning that the
|
|
221
|
+
* loader ran a redirect. Default: `true`. Set to `false` to
|
|
222
|
+
* restore the pre-PR-B behaviour (redirects treated as errors).
|
|
223
|
+
*/
|
|
224
|
+
emitRedirects?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Additionally emit a static HTML file at the source path with a
|
|
227
|
+
* `<meta http-equiv="refresh">` redirect — for adapters / hosts
|
|
228
|
+
* that don't read `_redirects` (plain S3, GitHub Pages, simple
|
|
229
|
+
* file servers). The meta-refresh fallback works on any HTTP
|
|
230
|
+
* server that serves static files.
|
|
231
|
+
*
|
|
232
|
+
* - `'none'` (default): only `_redirects` / `_redirects.json` are
|
|
233
|
+
* emitted; no per-redirect HTML file.
|
|
234
|
+
* - `'meta-refresh'`: emit `dist/<source>/index.html` containing
|
|
235
|
+
* `<meta http-equiv="refresh" content="0; url=<target>">` plus
|
|
236
|
+
* a canonical link tag for SEO. Status code information is
|
|
237
|
+
* lost (meta-refresh has no status equivalent), so 301/302/307/
|
|
238
|
+
* 308 all collapse to "client-side refresh".
|
|
239
|
+
*/
|
|
240
|
+
redirectsAsHtml?: 'none' | 'meta-refresh';
|
|
241
|
+
/**
|
|
242
|
+
* Callback invoked when a path's render throws (loader-throw that
|
|
243
|
+
* isn't a `redirect()`, render exception, anything that lands in the
|
|
244
|
+
* `errors[]` collection). Returns either:
|
|
245
|
+
* - `string` → written as the path's HTML in place of the failed
|
|
246
|
+
* render. Use this to emit a per-path fallback page (e.g. a generic
|
|
247
|
+
* "this content is temporarily unavailable" template) so static
|
|
248
|
+
* hosts have something to serve at that URL instead of 404'ing.
|
|
249
|
+
* - `null` → skip; the path produces no HTML output. The error
|
|
250
|
+
* stays in `errors[]` for the post-build summary.
|
|
251
|
+
*
|
|
252
|
+
* The callback runs ONCE per failed path. Async callbacks are
|
|
253
|
+
* awaited. If the callback itself throws, the throw is captured as
|
|
254
|
+
* a separate error entry and the path is skipped (no fallback HTML).
|
|
255
|
+
* Default: `undefined` — failed paths just land in `errors[]`.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ssg: {
|
|
259
|
+
* onPathError: async (path, error) => {
|
|
260
|
+
* console.error(\`SSG render failed for \${path}:\`, error)
|
|
261
|
+
* return \`<!DOCTYPE html><html><body><h1>Page unavailable</h1></body></html>\`
|
|
262
|
+
* },
|
|
263
|
+
* }
|
|
264
|
+
*/
|
|
265
|
+
onPathError?: (path: string, error: unknown) => string | null | Promise<string | null>;
|
|
266
|
+
/**
|
|
267
|
+
* When `'json'` (default), write `dist/_pyreon-ssg-errors.json` after
|
|
268
|
+
* the render loop summarising every error encountered (path traversal,
|
|
269
|
+
* timeout, render exception, getStaticPaths throw, fallback callback
|
|
270
|
+
* throw). Each entry has `{ path, message, name, stack }`. The file
|
|
271
|
+
* is ONLY written when `errors.length > 0` — successful builds don't
|
|
272
|
+
* leak an empty manifest. Reading it lets CI gate on render failures
|
|
273
|
+
* without parsing console output (e.g.
|
|
274
|
+
* `cat dist/_pyreon-ssg-errors.json | jq '.errors | length' | grep -q 0`).
|
|
275
|
+
*
|
|
276
|
+
* Set to `'none'` to opt out entirely — errors stay in console-only,
|
|
277
|
+
* matching pre-PR-G behaviour.
|
|
278
|
+
*/
|
|
279
|
+
errorArtifact?: 'json' | 'none';
|
|
280
|
+
/**
|
|
281
|
+
* Maximum number of paths rendered in parallel during the SSG closeBundle
|
|
282
|
+
* loop. Default: `4` — a sensible balance between speedup and the risk
|
|
283
|
+
* of exhausting downstream resources (DB connection pools, fetch
|
|
284
|
+
* rate-limits) inside loaders. Set to `1` to render fully sequentially
|
|
285
|
+
* (the pre-PR-D behaviour). Set to a higher value for faster builds
|
|
286
|
+
* on CI / multi-core hosts; the practical ceiling is the number of
|
|
287
|
+
* loader-side concurrent connections your app's data layer tolerates.
|
|
288
|
+
*
|
|
289
|
+
* The render-error pipeline (`onPathError` callback, `errors[]`
|
|
290
|
+
* collection, `_pyreon-ssg-errors.json` artifact) is unchanged —
|
|
291
|
+
* concurrency only affects how many paths are in flight at once,
|
|
292
|
+
* not how their successes / failures are recorded.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ssg: {
|
|
296
|
+
* concurrency: 8, // Faster builds for static-content sites
|
|
297
|
+
* }
|
|
298
|
+
*/
|
|
299
|
+
concurrency?: number;
|
|
300
|
+
/**
|
|
301
|
+
* Per-path progress callback. Invoked once per path AFTER its render
|
|
302
|
+
* settles (success, redirect, OR failure) — never during in-flight
|
|
303
|
+
* renders. Receives `{ completed, total, currentPath, elapsed }`
|
|
304
|
+
* where:
|
|
305
|
+
* - `completed` is the count of paths whose render has settled (1-indexed)
|
|
306
|
+
* - `total` is the full path count from `resolvePaths()`
|
|
307
|
+
* - `currentPath` is the path that just settled
|
|
308
|
+
* - `elapsed` is wall-clock ms since the loop started
|
|
309
|
+
*
|
|
310
|
+
* Use cases: build-tool progress bars (Vite picks up stdout), CI
|
|
311
|
+
* heartbeat lines on long builds (10k-path sites take minutes —
|
|
312
|
+
* silent stretches look hung), build-time perf instrumentation.
|
|
313
|
+
*
|
|
314
|
+
* Async callbacks are awaited before the next path's progress fires,
|
|
315
|
+
* so a slow callback can serialize progress reporting (it does NOT
|
|
316
|
+
* gate the worker pool — paths keep rendering in parallel; only
|
|
317
|
+
* the progress callbacks themselves are serialized). Throws are
|
|
318
|
+
* captured into `errors[]` with the path suffix `(onProgress)` so
|
|
319
|
+
* a buggy callback can't take down the build.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ssg: {
|
|
323
|
+
* onProgress: ({ completed, total, currentPath, elapsed }) => {
|
|
324
|
+
* console.log(`[${completed}/${total}] ${currentPath} (${elapsed}ms)`)
|
|
325
|
+
* },
|
|
326
|
+
* }
|
|
327
|
+
*/
|
|
328
|
+
onProgress?: (info: {
|
|
329
|
+
completed: number;
|
|
330
|
+
total: number;
|
|
331
|
+
currentPath: string;
|
|
332
|
+
elapsed: number;
|
|
333
|
+
}) => void | Promise<void>;
|
|
89
334
|
};
|
|
90
335
|
/** ISR config — only used when mode is "isr". */
|
|
91
336
|
isr?: ISRConfig;
|
|
92
|
-
/**
|
|
93
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Deploy adapter. Default: `"node"`.
|
|
339
|
+
*
|
|
340
|
+
* Accepts either a built-in adapter name (string) OR a constructed
|
|
341
|
+
* `Adapter` instance (e.g. `vercelAdapter()`). The scaffolded templates
|
|
342
|
+
* emit the instance form (`adapter: vercelAdapter()`) by convention.
|
|
343
|
+
* `resolveAdapter` (see `adapters/index.ts`) accepts both shapes —
|
|
344
|
+
* strings go through a switch lookup, instances pass through
|
|
345
|
+
* unchanged.
|
|
346
|
+
*/
|
|
347
|
+
adapter?: 'node' | 'bun' | 'static' | 'vercel' | 'cloudflare' | 'netlify' | Adapter;
|
|
94
348
|
/** Base URL path. Default: "/" */
|
|
95
349
|
base?: string;
|
|
350
|
+
/**
|
|
351
|
+
* i18n routing — locale-prefixed route variants generated at build time
|
|
352
|
+
* (PR H of the SSG roadmap). When set, every `FileRoute` is fanned into
|
|
353
|
+
* per-locale duplicates by `expandRoutesForLocales` from
|
|
354
|
+
* `@pyreon/zero`. Independent from the `i18nRouting()` Vite plugin
|
|
355
|
+
* (which only handles request-time locale detection); both can be used
|
|
356
|
+
* together. See `expandRoutesForLocales` JSDoc for strategy semantics.
|
|
357
|
+
*/
|
|
358
|
+
i18n?: I18nRoutingConfig;
|
|
96
359
|
/** App-level middleware applied to all routes. */
|
|
97
360
|
middleware?: Middleware[];
|
|
98
361
|
/** Server port for dev/preview. Default: 3000 */
|
|
@@ -135,6 +398,25 @@ interface RouteFileExports {
|
|
|
135
398
|
* must validate session on every navigation rather than serve stale data.
|
|
136
399
|
*/
|
|
137
400
|
hasGcTime: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Has `export function getStaticPaths` or `export const getStaticPaths`.
|
|
403
|
+
* Used at SSG build time to enumerate concrete values for dynamic routes
|
|
404
|
+
* (`/posts/[id].tsx` → `[/posts/1, /posts/2, …]`). The function returns
|
|
405
|
+
* `Array<{ params: Record<string, string> }>`. Mirrors Astro's per-route
|
|
406
|
+
* convention. Without it, dynamic routes are silently skipped during SSG
|
|
407
|
+
* auto-detect — the user must hand-list every value in `ssg.paths`.
|
|
408
|
+
*/
|
|
409
|
+
hasGetStaticPaths: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Has `export const revalidate` (number, in seconds, or `false` for
|
|
412
|
+
* never-revalidate). PR I — build-time ISR. The SSG plugin emits a
|
|
413
|
+
* `dist/_pyreon-revalidate.json` manifest mapping `{ path: revalidate }`
|
|
414
|
+
* which the deploy adapter (Vercel / Cloudflare / Netlify) consumes
|
|
415
|
+
* to wire platform-specific ISR rebuild-on-stale. The route generator
|
|
416
|
+
* does NOT inline `revalidate` onto the route record — it's a
|
|
417
|
+
* build-time-only concern that never reaches the runtime router.
|
|
418
|
+
*/
|
|
419
|
+
hasRevalidate: boolean;
|
|
138
420
|
/**
|
|
139
421
|
* Raw text of the `export const meta = …` initializer, captured as a
|
|
140
422
|
* literal expression. When present, the route generator inlines this
|
|
@@ -154,6 +436,22 @@ interface RouteFileExports {
|
|
|
154
436
|
* as a literal expression. Same inlining strategy as `metaLiteral`.
|
|
155
437
|
*/
|
|
156
438
|
renderModeLiteral?: string;
|
|
439
|
+
/**
|
|
440
|
+
* Raw text of the `export const revalidate = …` initializer (e.g.
|
|
441
|
+
* `'60'`, `'false'`, `'3600'`). Captured at scan time so the SSG
|
|
442
|
+
* plugin can read the value to emit the build-time ISR manifest
|
|
443
|
+
* WITHOUT loading the route module — which is critical because the
|
|
444
|
+
* manifest is emitted from the synthetic SSR build's outer plugin
|
|
445
|
+
* context, where evaluating route modules would re-trigger the
|
|
446
|
+
* recursive sub-build env-flag guard.
|
|
447
|
+
*
|
|
448
|
+
* Only set when the revalidate export is a top-level
|
|
449
|
+
* `export const revalidate = <numeric|boolean literal>` that passes
|
|
450
|
+
* `isPureLiteral`. Anything else (function calls, references to
|
|
451
|
+
* other declarations) leaves this undefined and the manifest falls
|
|
452
|
+
* back to omitting the entry.
|
|
453
|
+
*/
|
|
454
|
+
revalidateLiteral?: string;
|
|
157
455
|
}
|
|
158
456
|
/** Internal representation of a file-system route before conversion to RouteRecord. */
|
|
159
457
|
interface FileRoute {
|
|
@@ -196,16 +494,71 @@ interface Adapter {
|
|
|
196
494
|
name: string;
|
|
197
495
|
/** Build the production server/output for this adapter. */
|
|
198
496
|
build(options: AdapterBuildOptions): Promise<void>;
|
|
497
|
+
/**
|
|
498
|
+
* Revalidate a prerendered path on the deploy platform's ISR layer
|
|
499
|
+
* (PR I — build-time ISR). Called by user code (webhook handlers,
|
|
500
|
+
* cron jobs, CMS triggers, etc.) to trigger a rebuild-on-stale for
|
|
501
|
+
* the named path. Optional — adapters without platform ISR support
|
|
502
|
+
* (static, node, bun) implement a no-op. Returns `{ regenerated:
|
|
503
|
+
* boolean }` so user code can branch on whether the platform actually
|
|
504
|
+
* accepted the revalidation request.
|
|
505
|
+
*
|
|
506
|
+
* Distinct from runtime ISR (`mode: 'isr'`, on-demand LRU caching in
|
|
507
|
+
* `@pyreon/zero/server`'s `createISRHandler`). Build-time ISR is
|
|
508
|
+
* static prerender + platform-driven rebuild-on-stale; runtime ISR is
|
|
509
|
+
* SSR-cached-with-TTL. They can coexist.
|
|
510
|
+
*
|
|
511
|
+
* Per-route `revalidate` metadata flows from `export const revalidate
|
|
512
|
+
* = 60` in route files into a `dist/_pyreon-revalidate.json` manifest
|
|
513
|
+
* the adapter reads at deploy time. Adapters use that manifest to
|
|
514
|
+
* configure platform ISR (Vercel `output/config.json`, Cloudflare
|
|
515
|
+
* Cache API rules, Netlify revalidation headers).
|
|
516
|
+
*/
|
|
517
|
+
revalidate?(path: string): Promise<AdapterRevalidateResult>;
|
|
199
518
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Result of `Adapter.revalidate(path)`. `regenerated: false` means the
|
|
521
|
+
* adapter does not support platform ISR (no-op fallback) OR the
|
|
522
|
+
* platform rejected the request. Adapters that throw on platform-API
|
|
523
|
+
* failure should let it propagate so user code can handle the rejection.
|
|
524
|
+
*/
|
|
525
|
+
interface AdapterRevalidateResult {
|
|
526
|
+
regenerated: boolean;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Inputs the build pipeline passes to an adapter's `build()` method.
|
|
530
|
+
*
|
|
531
|
+
* The `kind` field discriminates the two shapes. **SSR mode** (`'ssr'`)
|
|
532
|
+
* carries `serverEntry` + `clientOutDir` so adapters can wrap the user's
|
|
533
|
+
* server bundle as a serverless function. **SSG mode** (`'ssg'`) carries
|
|
534
|
+
* only `outDir` (which IS the rendered dist/) — no serverEntry exists
|
|
535
|
+
* because every page is already prerendered. SSG-mode adapters write
|
|
536
|
+
* platform-specific routing config so the host knows the deploy is
|
|
537
|
+
* fully-static (no function invocation per request).
|
|
538
|
+
*
|
|
539
|
+
* Pre-PR-J this was a single SSR-shaped struct; the SSG path had no way
|
|
540
|
+
* to invoke `adapter.build()` because it couldn't supply `serverEntry`.
|
|
541
|
+
* Adding `kind` (with TS-narrowing per branch) lets `ssgPlugin`
|
|
542
|
+
* `closeBundle` call `adapter.build({ kind: 'ssg', outDir, config })`
|
|
543
|
+
* cleanly, AND keeps the SSR-mode adapter implementations unchanged.
|
|
544
|
+
*/
|
|
545
|
+
type AdapterBuildOptions = {
|
|
546
|
+
kind: 'ssr'; /** Path to the built server entry. */
|
|
547
|
+
serverEntry: string; /** Path to the client build output. */
|
|
548
|
+
clientOutDir: string; /** Final output directory. */
|
|
206
549
|
outDir: string;
|
|
207
550
|
config: ZeroConfig;
|
|
208
|
-
}
|
|
551
|
+
} | {
|
|
552
|
+
kind: 'ssg';
|
|
553
|
+
/**
|
|
554
|
+
* The rendered dist directory. For SSG, this directory IS the
|
|
555
|
+
* publishable output — adapters write platform-specific routing
|
|
556
|
+
* config alongside (e.g. `.vercel/output/config.json`,
|
|
557
|
+
* `_routes.json`, `netlify.toml`) but generally don't move files.
|
|
558
|
+
*/
|
|
559
|
+
outDir: string;
|
|
560
|
+
config: ZeroConfig;
|
|
561
|
+
};
|
|
209
562
|
//#endregion
|
|
210
563
|
//#region src/entry-server.d.ts
|
|
211
564
|
interface CreateServerOptions {
|
|
@@ -257,6 +610,33 @@ declare function defineConfig(config: ZeroConfig): ZeroConfig;
|
|
|
257
610
|
declare function resolveConfig(userConfig?: ZeroConfig): Required<Pick<ZeroConfig, 'mode' | 'base' | 'port' | 'adapter'>> & ZeroConfig;
|
|
258
611
|
//#endregion
|
|
259
612
|
//#region src/fs-router.d.ts
|
|
613
|
+
/**
|
|
614
|
+
* Return type of a route file's `getStaticPaths()` export. Each entry
|
|
615
|
+
* supplies one set of concrete values for the route's dynamic segments;
|
|
616
|
+
* the SSG plugin expands the route's URL pattern with these params and
|
|
617
|
+
* renders one HTML file per entry.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* ```tsx
|
|
621
|
+
* // src/routes/posts/[id].tsx
|
|
622
|
+
* import type { GetStaticPaths } from '@pyreon/zero/server'
|
|
623
|
+
*
|
|
624
|
+
* export const getStaticPaths: GetStaticPaths<{ id: string }> = async () => {
|
|
625
|
+
* const posts = await fetch('https://api.example.com/posts').then(r => r.json())
|
|
626
|
+
* return posts.map((p) => ({ params: { id: p.slug } }))
|
|
627
|
+
* }
|
|
628
|
+
*
|
|
629
|
+
* export default function Post() { ... }
|
|
630
|
+
* ```
|
|
631
|
+
*
|
|
632
|
+
* For catch-all routes (`/blog/[...slug].tsx`), pass the full path through
|
|
633
|
+
* the catch-all param: `{ params: { slug: 'a/b' } }` → `/blog/a/b`.
|
|
634
|
+
*/
|
|
635
|
+
type GetStaticPaths<TParams extends Record<string, string> = Record<string, string>> = () => Array<{
|
|
636
|
+
params: TParams;
|
|
637
|
+
}> | Promise<Array<{
|
|
638
|
+
params: TParams;
|
|
639
|
+
}>>;
|
|
260
640
|
/**
|
|
261
641
|
* Parse a set of file paths (relative to routes dir) into FileRoute objects.
|
|
262
642
|
*
|
|
@@ -328,9 +708,97 @@ declare function scanRouteFiles(routesDir: string): Promise<string[]>;
|
|
|
328
708
|
*/
|
|
329
709
|
declare function createISRHandler(handler: (req: Request) => Promise<Response>, config: ISRConfig): (req: Request) => Promise<Response>;
|
|
330
710
|
//#endregion
|
|
711
|
+
//#region src/vercel-revalidate-handler.d.ts
|
|
712
|
+
/**
|
|
713
|
+
* M3.1 — Drop-in Vercel revalidate webhook handler.
|
|
714
|
+
*
|
|
715
|
+
* Pre-M3.1 the `vercelAdapter.revalidate(path)` (PR I) POSTed to
|
|
716
|
+
* `/api/_pyreon-revalidate?path=...&secret=...` — a CONVENTION that users
|
|
717
|
+
* had to implement themselves. This helper scaffolds the convention:
|
|
718
|
+
*
|
|
719
|
+
* // src/routes/api/_pyreon-revalidate.ts (or `pages/api/...` in
|
|
720
|
+
* // Next-style apps deployed to Vercel)
|
|
721
|
+
* export { vercelRevalidateHandler as default } from '@pyreon/zero/server'
|
|
722
|
+
*
|
|
723
|
+
* The handler validates the secret query param against
|
|
724
|
+
* `VERCEL_REVALIDATE_TOKEN`, validates the path is in the build-time
|
|
725
|
+
* revalidate manifest, and calls Vercel's `res.revalidate(path)` API.
|
|
726
|
+
*
|
|
727
|
+
* Returns a standard `(req: Request) => Response` Web API handler — works
|
|
728
|
+
* with Vercel Edge functions, Node serverless functions (via Vercel's
|
|
729
|
+
* `@vercel/node` adapter that bridges Node `req`/`res` to Web standard
|
|
730
|
+
* fetch shapes), and the in-process `mode: 'ssr'` runtime.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* // src/routes/api/_pyreon-revalidate.ts
|
|
734
|
+
* import { vercelRevalidateHandler } from '@pyreon/zero/server'
|
|
735
|
+
*
|
|
736
|
+
* export const POST = vercelRevalidateHandler({
|
|
737
|
+
* // Optional — defaults to reading `_pyreon-revalidate.json` from cwd.
|
|
738
|
+
* manifestPath: './dist/_pyreon-revalidate.json',
|
|
739
|
+
* })
|
|
740
|
+
*
|
|
741
|
+
* @example
|
|
742
|
+
* // Custom revalidate impl (e.g. for a self-hosted Pyreon SSR runtime
|
|
743
|
+
* // that wants build-time revalidate behavior without Vercel's
|
|
744
|
+
* // `res.revalidate()` API):
|
|
745
|
+
* export const POST = vercelRevalidateHandler({
|
|
746
|
+
* onRevalidate: async (path) => {
|
|
747
|
+
* // Clear your in-process ISR cache, emit a metrics event, etc.
|
|
748
|
+
* await myCache.invalidate(path)
|
|
749
|
+
* },
|
|
750
|
+
* })
|
|
751
|
+
*/
|
|
752
|
+
interface VercelRevalidateHandlerOptions {
|
|
753
|
+
/**
|
|
754
|
+
* Absolute or cwd-relative path to the `_pyreon-revalidate.json` manifest.
|
|
755
|
+
* Defaults to `./dist/_pyreon-revalidate.json` (the standard SSG output).
|
|
756
|
+
*
|
|
757
|
+
* The handler refuses to revalidate paths NOT in this manifest — protects
|
|
758
|
+
* against arbitrary-path revalidation attacks even when the secret leaks.
|
|
759
|
+
*/
|
|
760
|
+
manifestPath?: string;
|
|
761
|
+
/**
|
|
762
|
+
* Custom revalidation impl. Defaults to calling Vercel's `res.revalidate()`
|
|
763
|
+
* API via the dynamic `@vercel/node`-bridged response object on globalThis
|
|
764
|
+
* (Vercel injects it for serverless functions).
|
|
765
|
+
*
|
|
766
|
+
* Supply this when running OUTSIDE Vercel (self-hosted SSR with a custom
|
|
767
|
+
* in-process ISR cache, edge runtimes that have their own purge API, etc.).
|
|
768
|
+
* Receives the validated path; throw to signal failure (handler returns 500).
|
|
769
|
+
*/
|
|
770
|
+
onRevalidate?: (path: string) => void | Promise<void>;
|
|
771
|
+
/**
|
|
772
|
+
* Override the env-var name the handler reads the secret from. Default
|
|
773
|
+
* `VERCEL_REVALIDATE_TOKEN` matches the adapter's `revalidate()` write.
|
|
774
|
+
* Useful when adopting the helper outside Vercel and the production
|
|
775
|
+
* webhook uses a different secret name.
|
|
776
|
+
*/
|
|
777
|
+
secretEnvVar?: string;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Create the Web-standard request handler. Reads the manifest once on first
|
|
781
|
+
* invocation (cached in-process) so repeated revalidations don't re-read the
|
|
782
|
+
* file. Manifest read failures cache the failure too — until next process
|
|
783
|
+
* restart, all requests get the same 500 response (signals deploy-time misconfig).
|
|
784
|
+
*/
|
|
785
|
+
declare function vercelRevalidateHandler(options?: VercelRevalidateHandlerOptions): (req: Request) => Promise<Response>;
|
|
786
|
+
/**
|
|
787
|
+
* Reset the in-process manifest cache. Test-only — production code never
|
|
788
|
+
* reaches this. Used by unit tests to exercise the "manifest changed
|
|
789
|
+
* between requests" path without spinning up a new handler.
|
|
790
|
+
* @internal
|
|
791
|
+
*/
|
|
792
|
+
declare function _resetVercelRevalidateHandlerCache(handler: (req: Request) => Promise<Response>): void;
|
|
793
|
+
//#endregion
|
|
331
794
|
//#region src/adapters/bun.d.ts
|
|
332
795
|
/**
|
|
333
796
|
* Bun adapter — generates a standalone Bun.serve() entry.
|
|
797
|
+
*
|
|
798
|
+
* **SSG mode (PR J)**: no-op. Bun adapter exists for serving the SSR
|
|
799
|
+
* runtime; SSG output is already complete static HTML — serve it with
|
|
800
|
+
* any static-file server (`bun preview` / `bunx serve` / nginx / Caddy).
|
|
801
|
+
* Use `staticAdapter()` if you want explicit SSG semantics.
|
|
334
802
|
*/
|
|
335
803
|
declare function bunAdapter(): Adapter;
|
|
336
804
|
//#endregion
|
|
@@ -384,6 +852,11 @@ declare function netlifyAdapter(): Adapter;
|
|
|
384
852
|
//#region src/adapters/node.d.ts
|
|
385
853
|
/**
|
|
386
854
|
* Node.js adapter — generates a standalone server entry using node:http.
|
|
855
|
+
*
|
|
856
|
+
* **SSG mode (PR J)**: no-op. Node adapter exists for serving the SSR
|
|
857
|
+
* runtime; SSG output is already complete static HTML — serve it with
|
|
858
|
+
* any static-file server (`bun preview` / nginx / Caddy / `npx serve`).
|
|
859
|
+
* Use `staticAdapter()` if you want explicit SSG semantics.
|
|
387
860
|
*/
|
|
388
861
|
declare function nodeAdapter(): Adapter;
|
|
389
862
|
//#endregion
|
|
@@ -391,6 +864,15 @@ declare function nodeAdapter(): Adapter;
|
|
|
391
864
|
/**
|
|
392
865
|
* Static adapter — just copies the client build output.
|
|
393
866
|
* Used with SSG mode where all pages are pre-rendered at build time.
|
|
867
|
+
*
|
|
868
|
+
* **SSG mode (PR J)**: no-op — `outDir` already IS the dist directory
|
|
869
|
+
* the SSG plugin produced. Copying it onto itself would only fail. The
|
|
870
|
+
* static adapter is the canonical zero-overhead deploy target for
|
|
871
|
+
* pure-static sites.
|
|
872
|
+
*
|
|
873
|
+
* **SSR mode**: copies clientOutDir → outDir. Calling `static` with SSR
|
|
874
|
+
* mode is unusual — the static adapter doesn't support server-side
|
|
875
|
+
* execution — but preserved as a "client-only output packager".
|
|
394
876
|
*/
|
|
395
877
|
declare function staticAdapter(): Adapter;
|
|
396
878
|
//#endregion
|
|
@@ -419,6 +901,16 @@ declare function vercelAdapter(): Adapter;
|
|
|
419
901
|
/**
|
|
420
902
|
* Resolve the adapter from config.
|
|
421
903
|
* Returns a built-in adapter or throws if unknown.
|
|
904
|
+
*
|
|
905
|
+
* Accepts BOTH forms — the `ZeroConfig.adapter` type advertises string
|
|
906
|
+
* names (`'vercel'` / `'cloudflare'` / …) but the scaffolded templates
|
|
907
|
+
* historically emit `adapter: vercelAdapter()` (an Adapter instance via
|
|
908
|
+
* the named factory). Both work: a string goes through the switch lookup;
|
|
909
|
+
* an Adapter object (duck-typed via `name` + `build` fields) passes
|
|
910
|
+
* through. Pre-PR-J `resolveAdapter` was never called from production
|
|
911
|
+
* code so the string-vs-object mismatch was invisible; PR J wires the
|
|
912
|
+
* call into `ssgPlugin.closeBundle`, surfacing the contract divergence.
|
|
913
|
+
* The passthrough preserves both shapes without a breaking type change.
|
|
422
914
|
*/
|
|
423
915
|
declare function resolveAdapter(config: ZeroConfig): Adapter;
|
|
424
916
|
//#endregion
|
|
@@ -465,6 +957,12 @@ declare function compose(...middlewares: Middleware[]): Middleware;
|
|
|
465
957
|
declare function getContext(ctx: MiddlewareContext): Record<string, unknown>;
|
|
466
958
|
//#endregion
|
|
467
959
|
//#region src/vite-plugin.d.ts
|
|
960
|
+
/**
|
|
961
|
+
* Retrieve the `ZeroConfig` that was passed to `zeroPlugin(userConfig)`
|
|
962
|
+
* when the plugin was created. Returns `undefined` if the argument
|
|
963
|
+
* isn't a recognized pyreon-zero main plugin instance.
|
|
964
|
+
*/
|
|
965
|
+
declare function getZeroPluginConfig(plugin: Plugin): ZeroConfig | undefined;
|
|
468
966
|
/**
|
|
469
967
|
* Zero Vite plugin — adds file-based routing and zero-config conventions
|
|
470
968
|
* on top of @pyreon/vite-plugin.
|
|
@@ -588,6 +1086,62 @@ interface SitemapConfig {
|
|
|
588
1086
|
priority?: number;
|
|
589
1087
|
/** Additional URLs to include (for dynamic routes). */
|
|
590
1088
|
additionalPaths?: SitemapEntry[];
|
|
1089
|
+
/**
|
|
1090
|
+
* When `true` AND the build is running in SSG mode, the sitemap reads
|
|
1091
|
+
* the resolved-paths manifest emitted by the SSG plugin
|
|
1092
|
+
* (`dist/_pyreon-ssg-paths.json`) and includes EVERY prerendered URL —
|
|
1093
|
+
* including dynamic routes enumerated via `getStaticPaths` (PR A) and
|
|
1094
|
+
* per-locale variants (PR H, when shipped). Without this flag the
|
|
1095
|
+
* sitemap walks the file-system route tree directly and silently
|
|
1096
|
+
* skips dynamic routes (`[id]` / `[...slug]`) because their concrete
|
|
1097
|
+
* values aren't knowable without running each route's enumerator.
|
|
1098
|
+
*
|
|
1099
|
+
* Sequencing: when `true`, sitemap.xml emission moves from Vite's
|
|
1100
|
+
* `generateBundle` hook (where the SSG plugin's path enumeration
|
|
1101
|
+
* hasn't run yet) to `closeBundle` with `enforce: 'post'` so it
|
|
1102
|
+
* runs AFTER the SSG plugin. The user must ensure `seoPlugin()` is
|
|
1103
|
+
* placed AFTER `zero()` in the Vite plugin array (the canonical
|
|
1104
|
+
* ordering — `closeBundle` hooks fire in plugin-registration order).
|
|
1105
|
+
*
|
|
1106
|
+
* Falls back gracefully: when the manifest doesn't exist (mode is
|
|
1107
|
+
* not `ssg`, or the SSG step was skipped), the sitemap still walks
|
|
1108
|
+
* the file-system routes — same shape as without this flag.
|
|
1109
|
+
*
|
|
1110
|
+
* Default: `false` (preserves prior behaviour). Set `true` for SSG
|
|
1111
|
+
* sites that ship dynamic-route enumerations.
|
|
1112
|
+
*/
|
|
1113
|
+
useSsgPaths?: boolean;
|
|
1114
|
+
/**
|
|
1115
|
+
* Emit `<xhtml:link rel="alternate" hreflang="...">` cross-references
|
|
1116
|
+
* inside each `<url>` entry, declaring the locale variants of every
|
|
1117
|
+
* page (PR K — i18n follow-up).
|
|
1118
|
+
*
|
|
1119
|
+
* Accepts:
|
|
1120
|
+
* - `true` — read the i18n config from the SSG paths manifest
|
|
1121
|
+
* (which `zero({ i18n: ... })` automatically embeds when SSG runs).
|
|
1122
|
+
* Zero-config win — declare i18n once, sitemap picks it up.
|
|
1123
|
+
* - `I18nRoutingConfig` — pass the i18n config explicitly. Use when
|
|
1124
|
+
* the project doesn't run SSG (file-scan sitemap path) but still
|
|
1125
|
+
* wants hreflang in the emitted sitemap.
|
|
1126
|
+
* - `false` / omitted — no hreflang, plain `<url>` entries.
|
|
1127
|
+
*
|
|
1128
|
+
* The emitted shape per page-cluster is the Google-recommended form:
|
|
1129
|
+
*
|
|
1130
|
+
* <url>
|
|
1131
|
+
* <loc>https://example.com/about</loc>
|
|
1132
|
+
* <xhtml:link rel="alternate" hreflang="en" href="https://example.com/about"/>
|
|
1133
|
+
* <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/about"/>
|
|
1134
|
+
* <xhtml:link rel="alternate" hreflang="cs" href="https://example.com/cs/about"/>
|
|
1135
|
+
* <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about"/>
|
|
1136
|
+
* </url>
|
|
1137
|
+
*
|
|
1138
|
+
* The `x-default` entry points at the default-locale URL so search
|
|
1139
|
+
* engines have a fallback when the user's language doesn't match any
|
|
1140
|
+
* of the configured locales. URLs are clustered by their un-prefixed
|
|
1141
|
+
* (default-locale) form — `/about`, `/de/about`, `/cs/about` collapse
|
|
1142
|
+
* into ONE `<url>` entry with three `xhtml:link` siblings.
|
|
1143
|
+
*/
|
|
1144
|
+
hreflang?: boolean | I18nRoutingConfig;
|
|
591
1145
|
}
|
|
592
1146
|
interface SitemapEntry {
|
|
593
1147
|
path: string;
|
|
@@ -598,8 +1152,14 @@ interface SitemapEntry {
|
|
|
598
1152
|
type ChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
599
1153
|
/**
|
|
600
1154
|
* Generate a sitemap.xml string from route file paths.
|
|
1155
|
+
*
|
|
1156
|
+
* When `i18n` is set (PR K — passed by `seoPlugin` after reading the
|
|
1157
|
+
* i18n config from `zero({ i18n: ... })`), URLs are clustered by their
|
|
1158
|
+
* un-prefixed (default-locale) form and each `<url>` carries
|
|
1159
|
+
* `<xhtml:link rel="alternate" hreflang="...">` siblings for every
|
|
1160
|
+
* locale variant + an `x-default` entry pointing at the default locale.
|
|
601
1161
|
*/
|
|
602
|
-
declare function generateSitemap(routeFiles: string[], config: SitemapConfig): string;
|
|
1162
|
+
declare function generateSitemap(routeFiles: string[], config: SitemapConfig, i18n?: I18nRoutingConfig): string;
|
|
603
1163
|
interface RobotsConfig {
|
|
604
1164
|
/** Rules per user-agent. */
|
|
605
1165
|
rules?: RobotsRule[];
|
|
@@ -649,7 +1209,10 @@ interface SeoPluginConfig {
|
|
|
649
1209
|
* pyreon(),
|
|
650
1210
|
* zero(),
|
|
651
1211
|
* seoPlugin({
|
|
652
|
-
* sitemap: {
|
|
1212
|
+
* sitemap: {
|
|
1213
|
+
* origin: "https://example.com",
|
|
1214
|
+
* useSsgPaths: true, // include dynamic-route enumerations
|
|
1215
|
+
* },
|
|
653
1216
|
* robots: { sitemap: "https://example.com/sitemap.xml" },
|
|
654
1217
|
* }),
|
|
655
1218
|
* ],
|
|
@@ -904,65 +1467,5 @@ declare function inferJsonLd(options: InferJsonLdOptions): Record<string, unknow
|
|
|
904
1467
|
*/
|
|
905
1468
|
declare function aiPlugin(config: AiPluginConfig): Plugin;
|
|
906
1469
|
//#endregion
|
|
907
|
-
|
|
908
|
-
interface I18nRoutingConfig {
|
|
909
|
-
/** Supported locales. e.g. ["en", "de", "cs"] */
|
|
910
|
-
locales: string[];
|
|
911
|
-
/** Default locale — served without prefix (/ instead of /en/). */
|
|
912
|
-
defaultLocale: string;
|
|
913
|
-
/** Redirect root to detected locale. Default: true */
|
|
914
|
-
detectLocale?: boolean;
|
|
915
|
-
/** Cookie name to persist locale preference. Default: "locale" */
|
|
916
|
-
cookieName?: string;
|
|
917
|
-
/** URL strategy. Default: "prefix-except-default" */
|
|
918
|
-
strategy?: 'prefix' | 'prefix-except-default';
|
|
919
|
-
}
|
|
920
|
-
interface LocaleContext {
|
|
921
|
-
/** Current locale code. e.g. "en", "de" */
|
|
922
|
-
locale: string;
|
|
923
|
-
/** All supported locales. */
|
|
924
|
-
locales: string[];
|
|
925
|
-
/** Default locale. */
|
|
926
|
-
defaultLocale: string;
|
|
927
|
-
/** Build a localized path. e.g. localePath("/about", "de") → "/de/about" */
|
|
928
|
-
localePath: (path: string, locale?: string) => string;
|
|
929
|
-
/** Get hreflang alternates for the current path. */
|
|
930
|
-
alternates: () => Array<{
|
|
931
|
-
locale: string;
|
|
932
|
-
url: string;
|
|
933
|
-
}>;
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Detect preferred locale from Accept-Language header.
|
|
937
|
-
*/
|
|
938
|
-
declare function detectLocaleFromHeader(acceptLanguage: string | null | undefined, locales: string[], defaultLocale: string): string;
|
|
939
|
-
/**
|
|
940
|
-
* Create a LocaleContext for use in components and loaders.
|
|
941
|
-
*/
|
|
942
|
-
declare function createLocaleContext(locale: string, path: string, config: I18nRoutingConfig): LocaleContext;
|
|
943
|
-
/**
|
|
944
|
-
* I18n routing middleware for Zero's server.
|
|
945
|
-
*
|
|
946
|
-
* - Detects locale from URL prefix or Accept-Language header
|
|
947
|
-
* - Redirects root to preferred locale (when detectLocale is true)
|
|
948
|
-
* - Sets locale context for loaders and components
|
|
949
|
-
*
|
|
950
|
-
* @example
|
|
951
|
-
* ```ts
|
|
952
|
-
* // zero.config.ts
|
|
953
|
-
* import { i18nRouting } from "@pyreon/zero"
|
|
954
|
-
*
|
|
955
|
-
* export default defineConfig({
|
|
956
|
-
* plugins: [
|
|
957
|
-
* i18nRouting({
|
|
958
|
-
* locales: ["en", "de", "cs"],
|
|
959
|
-
* defaultLocale: "en",
|
|
960
|
-
* }),
|
|
961
|
-
* ],
|
|
962
|
-
* })
|
|
963
|
-
* ```
|
|
964
|
-
*/
|
|
965
|
-
declare function i18nRouting(config: I18nRoutingConfig): Plugin;
|
|
966
|
-
//#endregion
|
|
967
|
-
export { type AiPluginConfig, type CreateAppOptions, type CreateServerOptions, type FaviconLocaleConfig, type FaviconPluginConfig, type GenerateRouteModuleOptions, type InferJsonLdOptions, type OgImageLayer, type OgImagePluginConfig, type OgImageTemplate, type RobotsConfig, type SeoPluginConfig, type SitemapConfig, aiPlugin, bunAdapter, cloudflareAdapter, compose, createApp, createISRHandler, createLocaleContext, createServer, zeroPlugin as default, defineConfig, detectLocaleFromHeader, faviconLinks, faviconPlugin, filePathToUrlPath, generateLlmsFullTxt, generateLlmsTxt, generateMiddlewareModule, generateRobots, generateRouteModule, generateSitemap, getContext, i18nRouting, inferJsonLd, jsonLd, netlifyAdapter, nodeAdapter, ogImagePath, ogImagePlugin, parseFileRoutes, render404Page, resolveAdapter, resolveConfig, scanRouteFiles, seoMiddleware, seoPlugin, staticAdapter, vercelAdapter };
|
|
1470
|
+
export { type AiPluginConfig, type CreateAppOptions, type CreateServerOptions, type FaviconLocaleConfig, type FaviconPluginConfig, type GenerateRouteModuleOptions, type GetStaticPaths, type InferJsonLdOptions, type OgImageLayer, type OgImagePluginConfig, type OgImageTemplate, type RobotsConfig, type SeoPluginConfig, type SitemapConfig, type VercelRevalidateHandlerOptions, _resetVercelRevalidateHandlerCache, aiPlugin, bunAdapter, cloudflareAdapter, compose, createApp, createISRHandler, createLocaleContext, createServer, zeroPlugin as default, defineConfig, detectLocaleFromHeader, faviconLinks, faviconPlugin, filePathToUrlPath, generateLlmsFullTxt, generateLlmsTxt, generateMiddlewareModule, generateRobots, generateRouteModule, generateSitemap, getContext, getZeroPluginConfig, i18nRouting, inferJsonLd, jsonLd, netlifyAdapter, nodeAdapter, ogImagePath, ogImagePlugin, parseFileRoutes, render404Page, resolveAdapter, resolveConfig, scanRouteFiles, seoMiddleware, seoPlugin, staticAdapter, vercelAdapter, vercelRevalidateHandler };
|
|
968
1471
|
//# sourceMappingURL=server2.d.ts.map
|