@pyreon/zero 0.14.0 → 0.15.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 +146 -0
- package/lib/client.js +3 -1
- package/lib/csp.js +19 -9
- package/lib/{fs-router-CQ7Zxeca.js → fs-router-ZebyutPa.js} +43 -6
- package/lib/image-plugin.js +4 -0
- package/lib/image.js +1 -50
- package/lib/index.js +1 -50
- package/lib/link.js +1 -49
- package/lib/script.js +1 -49
- package/lib/server.js +6 -688
- package/lib/theme.js +1 -50
- package/lib/types/i18n-routing.d.ts +4 -4
- package/lib/types/index.d.ts +23 -13
- package/lib/types/link.d.ts +3 -3
- package/lib/types/server.d.ts +28 -5
- package/lib/types/theme.d.ts +2 -2
- package/lib/vite-plugin-E4BHYvYW.js +855 -0
- package/package.json +15 -13
- package/src/app.ts +21 -1
- package/src/csp.ts +28 -12
- package/src/fs-router.ts +53 -3
- package/src/ssg-plugin.ts +366 -0
- package/src/types.ts +28 -9
- package/src/vite-plugin.ts +220 -40
- package/lib/actions.js.map +0 -1
- package/lib/ai.js.map +0 -1
- package/lib/api-routes.js.map +0 -1
- package/lib/cache.js.map +0 -1
- package/lib/client.js.map +0 -1
- package/lib/compression.js.map +0 -1
- package/lib/config.js.map +0 -1
- package/lib/cors.js.map +0 -1
- package/lib/csp.js.map +0 -1
- package/lib/env.js.map +0 -1
- package/lib/favicon.js.map +0 -1
- package/lib/font.js.map +0 -1
- package/lib/fs-router-3xzp-4Wj.js.map +0 -1
- package/lib/fs-router-CQ7Zxeca.js.map +0 -1
- package/lib/i18n-routing.js.map +0 -1
- package/lib/image-plugin.js.map +0 -1
- package/lib/image.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/link.js.map +0 -1
- package/lib/logger.js.map +0 -1
- package/lib/meta.js.map +0 -1
- package/lib/middleware.js.map +0 -1
- package/lib/og-image.js.map +0 -1
- package/lib/rate-limit.js.map +0 -1
- package/lib/script.js.map +0 -1
- package/lib/seo.js.map +0 -1
- package/lib/server.js.map +0 -1
- package/lib/testing.js.map +0 -1
- package/lib/theme.js.map +0 -1
- package/lib/types/actions.d.ts.map +0 -1
- package/lib/types/ai.d.ts.map +0 -1
- package/lib/types/api-routes.d.ts.map +0 -1
- package/lib/types/cache.d.ts.map +0 -1
- package/lib/types/client.d.ts.map +0 -1
- package/lib/types/compression.d.ts.map +0 -1
- package/lib/types/config.d.ts.map +0 -1
- package/lib/types/cors.d.ts.map +0 -1
- package/lib/types/csp.d.ts.map +0 -1
- package/lib/types/env.d.ts.map +0 -1
- package/lib/types/favicon.d.ts.map +0 -1
- package/lib/types/font.d.ts.map +0 -1
- package/lib/types/i18n-routing.d.ts.map +0 -1
- package/lib/types/image-plugin.d.ts.map +0 -1
- package/lib/types/image.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/link.d.ts.map +0 -1
- package/lib/types/logger.d.ts.map +0 -1
- package/lib/types/meta.d.ts.map +0 -1
- package/lib/types/middleware.d.ts.map +0 -1
- package/lib/types/og-image.d.ts.map +0 -1
- package/lib/types/rate-limit.d.ts.map +0 -1
- package/lib/types/script.d.ts.map +0 -1
- package/lib/types/seo.d.ts.map +0 -1
- package/lib/types/server.d.ts.map +0 -1
- package/lib/types/testing.d.ts.map +0 -1
- package/lib/types/theme.d.ts.map +0 -1
package/lib/server.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["flattenRoutePatterns","sharpWarned","warnSharpMissing","escapeXml"],"sources":["../src/app.ts","../src/api-routes.ts","../src/not-found.ts","../src/entry-server.ts","../src/config.ts","../src/isr.ts","../src/adapters/validate.ts","../src/adapters/bun.ts","../src/adapters/cloudflare.ts","../src/adapters/netlify.ts","../src/adapters/node.ts","../src/adapters/static.ts","../src/adapters/vercel.ts","../src/adapters/index.ts","../src/middleware.ts","../src/error-overlay.ts","../src/vite-plugin.ts","../src/favicon.ts","../src/seo.ts","../src/og-image.ts","../src/ai.ts","../src/i18n-routing.ts"],"sourcesContent":["import type { ComponentFn, Props } from '@pyreon/core'\nimport { Fragment, h } from '@pyreon/core'\nimport { HeadProvider } from '@pyreon/head'\nimport type { RouteRecord } from '@pyreon/router'\nimport { createRouter, RouterProvider, RouterView } from '@pyreon/router'\n\n// ─── App assembly ────────────────────────────────────────────────────────────\n\nexport interface CreateAppOptions {\n /** Route definitions (from file-based routing or manual). */\n routes: RouteRecord[]\n\n /** Router mode. Default: \"history\" for SSR, \"hash\" for SPA. */\n routerMode?: 'hash' | 'history'\n\n /** Initial URL for SSR. */\n url?: string\n\n /** Root layout component wrapping all routes. */\n layout?: ComponentFn\n\n /** Global error component. */\n errorComponent?: ComponentFn\n}\n\n/**\n * Create a full Zero app — assembles router, head provider, and root layout.\n *\n * Used internally by entry-server and entry-client.\n */\nexport function createApp(options: CreateAppOptions) {\n const router = createRouter({\n routes: options.routes,\n mode: options.routerMode ?? 'history',\n ...(options.url ? { url: options.url } : {}),\n scrollBehavior: 'top',\n })\n\n const Layout = options.layout ?? DefaultLayout\n\n function App() {\n return h(\n HeadProvider,\n null,\n h(\n RouterProvider as ComponentFn<Props>,\n { router },\n h(Layout, null, h(RouterView as ComponentFn<Props>, null)),\n ),\n )\n }\n\n return { App, router }\n}\n\nfunction DefaultLayout(props: Props) {\n return h(Fragment, null, ...(Array.isArray(props.children) ? props.children : [props.children]))\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** HTTP methods supported by API routes. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** Context passed to API route handlers. */\nexport interface ApiContext {\n /** The incoming request. */\n request: Request\n /** Parsed URL. */\n url: URL\n /** URL path. */\n path: string\n /** Dynamic route parameters (e.g., { id: \"123\" }). */\n params: Record<string, string>\n /** Request headers. */\n headers: Headers\n}\n\n/** An API route handler function. */\nexport type ApiHandler = (ctx: ApiContext) => Response | Promise<Response>\n\n/** An API route module — exports named HTTP method handlers. */\nexport interface ApiRouteModule {\n GET?: ApiHandler\n POST?: ApiHandler\n PUT?: ApiHandler\n PATCH?: ApiHandler\n DELETE?: ApiHandler\n HEAD?: ApiHandler\n OPTIONS?: ApiHandler\n}\n\n/** A registered API route entry. */\nexport interface ApiRouteEntry {\n /** URL pattern (e.g., \"/api/posts/:id\"). */\n pattern: string\n /** The route module with method handlers. */\n module: ApiRouteModule\n}\n\n// ─── Pattern matching ────────────────────────────────────────────────────────\n\n/**\n * Match a URL path against an API route pattern.\n * Returns extracted params or null if no match.\n */\nexport function matchApiRoute(pattern: string, path: string): Record<string, string> | null {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n const params: Record<string, string> = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i]\n if (!pp) continue\n\n // Catch-all: :param*\n if (pp.endsWith('*')) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).join('/')\n return params\n }\n\n // No more path segments\n if (i >= pathParts.length) return null\n\n // Dynamic segment: :param\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = pathParts[i]!\n continue\n }\n\n // Static segment\n if (pp !== pathParts[i]) return null\n }\n\n return patternParts.length === pathParts.length ? params : null\n}\n\n// ─── Middleware ───────────────────────────────────────────────────────────────\n\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * Create a middleware that dispatches API route requests.\n * API routes are matched by URL pattern and HTTP method.\n */\nexport function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const route of routes) {\n const params = matchApiRoute(route.pattern, ctx.path)\n if (!params) continue\n\n const method = ctx.req.method.toUpperCase() as HttpMethod\n const handler = route.module[method]\n\n if (!handler) {\n // Route matched but method not supported\n const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')\n return new Response(null, {\n status: 405,\n headers: {\n Allow: allowed,\n 'Content-Type': 'application/json',\n },\n })\n }\n\n return handler({\n request: ctx.req,\n url: ctx.url,\n path: ctx.path,\n params,\n headers: ctx.req.headers,\n })\n }\n }\n}\n\n// ─── Virtual module generation ───────────────────────────────────────────────\n\n/**\n * Detect whether a route file is an API route.\n * API routes are `.ts` or `.js` files inside an `api/` directory.\n */\nexport function isApiRoute(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/')\n return (\n normalized.startsWith('api/') &&\n (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&\n !normalized.endsWith('.tsx') &&\n !normalized.endsWith('.jsx')\n )\n}\n\n/**\n * Convert an API route file path to a URL pattern.\n *\n * Examples:\n * \"api/posts.ts\" → \"/api/posts\"\n * \"api/posts/index.ts\" → \"/api/posts\"\n * \"api/posts/[id].ts\" → \"/api/posts/:id\"\n * \"api/[...path].ts\" → \"/api/:path*\"\n */\nexport function apiFilePathToPattern(filePath: string): string {\n let route = filePath\n // Remove extension\n for (const ext of ['.ts', '.js']) {\n if (route.endsWith(ext)) {\n route = route.slice(0, -ext.length)\n break\n }\n }\n\n const segments = route.split('/')\n const urlSegments: string[] = []\n\n for (const seg of segments) {\n if (seg === 'index') continue\n\n // Catch-all: [...param]\n const catchAll = seg.match(/^\\[\\.\\.\\.(\\w+)\\]$/)\n if (catchAll) {\n urlSegments.push(`:${catchAll[1]}*`)\n continue\n }\n\n // Dynamic: [param]\n const dynamic = seg.match(/^\\[(\\w+)\\]$/)\n if (dynamic) {\n urlSegments.push(`:${dynamic[1]}`)\n continue\n }\n\n urlSegments.push(seg)\n }\n\n return `/${urlSegments.join('/')}`\n}\n\n/**\n * Generate a virtual module that exports API route entries.\n * Each entry maps a URL pattern to a module with HTTP method handlers.\n */\nexport function generateApiRouteModule(files: string[], routesDir: string): string {\n const apiFiles = files.filter(isApiRoute)\n\n if (apiFiles.length === 0) {\n return 'export const apiRoutes = []\\n'\n }\n\n const imports: string[] = []\n const entries: string[] = []\n\n for (let i = 0; i < apiFiles.length; i++) {\n const name = `_api${i}`\n const file = apiFiles[i]\n if (!file) continue\n const fullPath = `${routesDir}/${file}`\n const pattern = apiFilePathToPattern(file)\n\n imports.push(`import * as ${name} from \"${fullPath}\"`)\n entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)\n }\n\n return [...imports, '', 'export const apiRoutes = [', entries.join(',\\n'), ']'].join('\\n')\n}\n","import type { ComponentFn } from \"@pyreon/core\";\nimport { h } from \"@pyreon/core\";\nimport { renderToString } from \"@pyreon/runtime-server\";\n\n// ─── 404 Not Found rendering ────────────────────────────────────────────────\n//\n// Shared utility for rendering 404 pages in both dev (vite-plugin) and\n// production (entry-server). Renders the notFoundComponent into HTML\n// and wraps it in a minimal document if no template is provided.\n\nconst DEFAULT_404_BODY =\n\t\"<h1>404 — Not Found</h1><p>The page you requested does not exist.</p>\";\n\n/**\n * Render a 404 component to a full HTML string.\n * If no component is provided, returns a default 404 page.\n */\nexport async function render404Page(\n\tcomponent: ComponentFn | undefined,\n\ttemplate?: string,\n): Promise<string> {\n\tlet body: string;\n\tif (component) {\n\t\tbody = await renderToString(h(component, null));\n\t} else {\n\t\tbody = DEFAULT_404_BODY;\n\t}\n\n\tif (template?.includes(\"<!--pyreon-app-->\")) {\n\t\treturn template.replace(\"<!--pyreon-app-->\", body);\n\t}\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>404 — Not Found</title>\n</head>\n<body>\n ${body}\n</body>\n</html>`;\n}\n","import type { ComponentFn } from \"@pyreon/core\";\nimport type { RouteRecord } from \"@pyreon/router\";\nimport type { Middleware, MiddlewareContext } from \"@pyreon/server\";\nimport { createHandler } from \"@pyreon/server\";\nimport type { ApiRouteEntry } from \"./api-routes\";\nimport { createApiMiddleware } from \"./api-routes\";\nimport { createApp } from \"./app\";\nimport { render404Page } from \"./not-found\";\nimport type { RouteMiddlewareEntry, ZeroConfig } from \"./types\";\n\n// ─── Server entry factory ───────────────────────────────────────────────────\n\nexport interface CreateServerOptions {\n\t/** Route definitions. */\n\troutes: RouteRecord[];\n\t/** Zero config. */\n\tconfig?: ZeroConfig;\n\t/** Additional middleware. */\n\tmiddleware?: Middleware[];\n\t/** Per-route middleware from virtual:zero/route-middleware. */\n\trouteMiddleware?: RouteMiddlewareEntry[];\n\t/** API route entries from virtual:zero/api-routes. */\n\tapiRoutes?: ApiRouteEntry[];\n\t/** HTML template override. */\n\ttemplate?: string;\n\t/** Client entry path. */\n\tclientEntry?: string;\n\t/** Component to render when no route matches (from _404.tsx). */\n\tnotFoundComponent?: ComponentFn;\n}\n\n/**\n * Create a middleware that dispatches per-route middleware based on URL pattern matching.\n */\nfunction createRouteMiddlewareDispatcher(\n\tentries: RouteMiddlewareEntry[],\n): Middleware {\n\treturn async (ctx: MiddlewareContext) => {\n\t\tfor (const entry of entries) {\n\t\t\tif (matchPattern(entry.pattern, ctx.path)) {\n\t\t\t\tconst mw = Array.isArray(entry.middleware)\n\t\t\t\t\t? entry.middleware\n\t\t\t\t\t: [entry.middleware];\n\t\t\t\tfor (const fn of mw) {\n\t\t\t\t\tconst result = await fn(ctx);\n\t\t\t\t\tif (result) return result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n/**\n * URL pattern matcher supporting :param and :param* segments.\n *\n * Rules:\n * - Static segments must match exactly\n * - `:param` matches a single path segment\n * - `:param*` matches all remaining segments (must be last, and path must\n * have matched all preceding segments)\n * - Path length must match pattern length (unless catch-all)\n */\nexport function matchPattern(pattern: string, path: string): boolean {\n\tconst patternParts = pattern.split(\"/\").filter(Boolean);\n\tconst pathParts = path.split(\"/\").filter(Boolean);\n\n\tfor (let i = 0; i < patternParts.length; i++) {\n\t\tconst pp = patternParts[i]!;\n\n\t\t// Catch-all: matches remaining segments, but only if we've matched\n\t\t// all preceding segments up to this point\n\t\tif (pp.endsWith(\"*\")) {\n\t\t\t// All segments before the catch-all must have matched (we got here)\n\t\t\t// and there must be at least one remaining path segment\n\t\t\treturn i <= pathParts.length;\n\t\t}\n\n\t\t// No more path segments to match against\n\t\tif (i >= pathParts.length) return false;\n\n\t\t// Dynamic segment matches any single segment\n\t\tif (pp.startsWith(\":\")) continue;\n\n\t\t// Static segment must match exactly\n\t\tif (pp !== pathParts[i]) return false;\n\t}\n\n\t// All pattern parts consumed — path must also be fully consumed\n\treturn patternParts.length === pathParts.length;\n}\n\n/**\n * Create the SSR request handler for production.\n *\n * @example\n * import { routes } from \"virtual:zero/routes\"\n * import { routeMiddleware } from \"virtual:zero/route-middleware\"\n * import { createServer } from \"@pyreon/zero\"\n *\n * export default createServer({ routes, routeMiddleware, apiRoutes })\n */\nexport function createServer(options: CreateServerOptions) {\n\tconst config = options.config ?? {};\n\n\tconst allMiddleware: Middleware[] = [];\n\n\t// API routes run first — they short-circuit before SSR\n\tif (options.apiRoutes?.length) {\n\t\tallMiddleware.push(createApiMiddleware(options.apiRoutes));\n\t}\n\n\t// Per-route middleware runs next\n\tif (options.routeMiddleware?.length) {\n\t\tallMiddleware.push(\n\t\t\tcreateRouteMiddlewareDispatcher(options.routeMiddleware),\n\t\t);\n\t}\n\n\t// Then global middleware from config and options\n\tallMiddleware.push(...(config.middleware ?? []));\n\tallMiddleware.push(...(options.middleware ?? []));\n\n\tconst { App } = createApp({\n\t\troutes: options.routes,\n\t\trouterMode: \"history\",\n\t});\n\n\tconst handler = createHandler({\n\t\tApp,\n\t\troutes: options.routes,\n\t\tmiddleware: allMiddleware,\n\t\tmode: config.ssr?.mode ?? \"string\",\n\t\t...(options.template ? { template: options.template } : {}),\n\t\t...(options.clientEntry ? { clientEntry: options.clientEntry } : {}),\n\t});\n\n\t// Wrap handler with 404 detection when a notFoundComponent is provided\n\tif (!options.notFoundComponent) return handler;\n\n\tconst NotFound = options.notFoundComponent;\n\tconst routePatterns = flattenRoutePatterns(options.routes);\n\n\treturn async (req: Request) => {\n\t\tconst url = new URL(req.url);\n\t\tconst pathname = url.pathname;\n\n\t\t// Check if any defined route matches this path\n\t\tif (!routePatterns.some((pattern) => matchPattern(pattern, pathname))) {\n\t\t\tconst fullHtml = await render404Page(NotFound, options.template);\n\t\t\treturn new Response(fullHtml, {\n\t\t\t\tstatus: 404,\n\t\t\t\theaders: { \"Content-Type\": \"text/html; charset=utf-8\" },\n\t\t\t});\n\t\t}\n\n\t\treturn handler(req);\n\t};\n}\n\n/** Extract all URL patterns from a nested route tree. */\nfunction flattenRoutePatterns(routes: RouteRecord[], prefix = \"\"): string[] {\n\tconst patterns: string[] = [];\n\tfor (const route of routes) {\n\t\tconst fullPath =\n\t\t\troute.path === \"/\" && prefix ? prefix : `${prefix}${route.path}`;\n\t\tpatterns.push(fullPath);\n\t\tif (route.children) {\n\t\t\tpatterns.push(\n\t\t\t\t...flattenRoutePatterns(route.children as RouteRecord[], fullPath),\n\t\t\t);\n\t\t}\n\t}\n\treturn patterns;\n}\n","import type { ZeroConfig } from './types'\n\n/**\n * Define a Zero configuration.\n * Used in `zero.config.ts` at the project root.\n *\n * @example\n * import { defineConfig } from \"@pyreon/zero/config\"\n *\n * export default defineConfig({\n * mode: \"ssr\",\n * ssr: { mode: \"stream\" },\n * port: 3000,\n * })\n */\nexport function defineConfig(config: ZeroConfig): ZeroConfig {\n return config\n}\n\n/** Merge user config with defaults. */\nexport function resolveConfig(\n userConfig: ZeroConfig = {},\n): Required<Pick<ZeroConfig, 'mode' | 'base' | 'port' | 'adapter'>> & ZeroConfig {\n return {\n mode: 'ssr',\n base: '/',\n port: 3000,\n adapter: 'node',\n ...userConfig,\n ssr: {\n mode: 'string',\n ...userConfig.ssr,\n },\n }\n}\n","import type { ISRConfig } from './types'\n\n// ─── ISR Cache ───────────────────────────────────────────────────────────────\n\ninterface CacheEntry {\n html: string\n headers: Record<string, string>\n timestamp: number\n}\n\n/**\n * In-memory ISR cache with stale-while-revalidate semantics.\n *\n * Wraps an SSR handler and caches responses per URL path.\n * Serves stale content immediately while revalidating in the background.\n *\n * Bounded by `config.maxEntries` (default: 1000) with LRU eviction. The\n * `Map` preserves insertion order, so re-inserting an entry on every\n * serve (touching it) keeps the LRU order correct. Without the cap,\n * unbounded URL spaces like `/user/:id` would grow cache memory without\n * limit over the server's lifetime — a real leak in long-running\n * deployments.\n */\nexport function createISRHandler(\n handler: (req: Request) => Promise<Response>,\n config: ISRConfig,\n): (req: Request) => Promise<Response> {\n const cache = new Map<string, CacheEntry>()\n const revalidating = new Set<string>()\n const revalidateMs = config.revalidate * 1000\n const maxEntries = Math.max(1, config.maxEntries ?? 1000)\n\n function set(key: string, entry: CacheEntry): void {\n // LRU: re-inserting moves the key to the newest position. Then if we're\n // over the cap, drop the oldest (first in iteration order).\n if (cache.has(key)) cache.delete(key)\n cache.set(key, entry)\n while (cache.size > maxEntries) {\n const oldest = cache.keys().next().value\n if (oldest === undefined) break\n cache.delete(oldest)\n }\n }\n\n function touch(key: string): CacheEntry | undefined {\n const entry = cache.get(key)\n if (entry !== undefined) {\n cache.delete(key)\n cache.set(key, entry)\n }\n return entry\n }\n\n async function revalidate(url: URL) {\n const key = url.pathname\n if (revalidating.has(key)) return\n revalidating.add(key)\n\n try {\n const req = new Request(url.href, { method: 'GET' })\n const res = await handler(req)\n const html = await res.text()\n const headers: Record<string, string> = {}\n res.headers.forEach((v, k) => {\n headers[k] = v\n })\n\n set(key, { html, headers, timestamp: Date.now() })\n } catch {\n // Revalidation failed — stale cache entry remains valid\n } finally {\n revalidating.delete(key)\n }\n }\n\n return async (req: Request): Promise<Response> => {\n // Only cache GET requests\n if (req.method !== 'GET') {\n return handler(req)\n }\n\n const url = new URL(req.url)\n const key = url.pathname\n // `touch` moves the entry to the newest LRU position on read so\n // hot paths survive eviction even when the cap is small. `get`\n // wouldn't update ordering.\n const entry = touch(key)\n\n if (entry) {\n const age = Date.now() - entry.timestamp\n\n if (age > revalidateMs) {\n // Stale — serve cached but revalidate in background\n revalidate(url)\n }\n\n return new Response(entry.html, {\n status: 200,\n headers: {\n ...entry.headers,\n 'content-type': 'text/html; charset=utf-8',\n 'x-isr-cache': age > revalidateMs ? 'STALE' : 'HIT',\n 'x-isr-age': String(Math.round(age / 1000)),\n },\n })\n }\n\n // Cache miss — render, cache, and return\n const res = await handler(req)\n const html = await res.text()\n const headers: Record<string, string> = {}\n res.headers.forEach((v, k) => {\n headers[k] = v\n })\n\n cache.set(key, { html, headers, timestamp: Date.now() })\n\n return new Response(html, {\n status: 200,\n headers: {\n ...headers,\n 'content-type': 'text/html; charset=utf-8',\n 'x-isr-cache': 'MISS',\n },\n })\n }\n}\n","import type { AdapterBuildOptions } from '../types'\n\n/**\n * Validate that adapter build inputs exist before copying.\n * Throws with a clear error message if directories are missing.\n * @internal\n */\nexport async function validateBuildInputs(options: AdapterBuildOptions): Promise<void> {\n const { existsSync } = await import('node:fs')\n if (!existsSync(options.clientOutDir)) {\n throw new Error(`[Pyreon] Client build output not found: ${options.clientOutDir}. Run \"vite build\" first.`)\n }\n if (!existsSync(options.serverEntry)) {\n throw new Error(`[Pyreon] Server entry not found: ${options.serverEntry}. Run \"vite build --ssr\" first.`)\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Bun adapter — generates a standalone Bun.serve() entry.\n */\nexport function bunAdapter(): Adapter {\n return {\n name: 'bun',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy server and client builds\n await cp(options.clientOutDir, join(outDir, 'client'), {\n recursive: true,\n })\n await cp(join(options.serverEntry, '..'), join(outDir, 'server'), {\n recursive: true,\n })\n\n const port = options.config.port ?? 3000\n const serverEntry = `\nconst handler = (await import(\"./server/entry-server.js\")).default\nconst clientDir = new URL(\"./client/\", import.meta.url).pathname\n\nBun.serve({\n port: ${port},\n async fetch(req) {\n const url = new URL(req.url)\n\n // Try static files first\n if (req.method === \"GET\") {\n const filePath = clientDir + (url.pathname === \"/\" ? \"index.html\" : url.pathname)\n // Prevent path traversal — ensure resolved path stays within clientDir\n const resolved = Bun.resolveSync(filePath, \".\")\n if (!resolved.startsWith(Bun.resolveSync(clientDir, \".\"))) {\n return new Response(\"Forbidden\", { status: 403 })\n }\n const file = Bun.file(filePath)\n if (await file.exists()) {\n return new Response(file, {\n headers: {\n \"cache-control\": filePath.endsWith(\".js\") || filePath.endsWith(\".css\")\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\",\n },\n })\n }\n }\n\n // Fall through to SSR handler\n return handler(req)\n },\n})\n\nconsole.log(\"\\\\n ⚡ Zero production server running on http://localhost:${port}\\\\n\")\n`.trimStart()\n\n await writeFile(join(outDir, 'index.ts'), serverEntry)\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Cloudflare Pages adapter — generates output for Cloudflare Pages with Functions.\n *\n * Produces:\n * - Client assets in the output directory root (served as static)\n * - `_worker.js` — Cloudflare Pages Function for SSR\n *\n * Note: Cloudflare Pages Functions have a ~1MB module size limit.\n * For large apps, configure Vite's SSR build to bundle server code:\n * `ssr: { noExternal: true }` in vite.config.ts.\n *\n * Deploy with: `npx wrangler pages deploy ./dist`\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"cloudflare\",\n * })\n * ```\n */\nexport function cloudflareAdapter(): Adapter {\n return {\n name: 'cloudflare',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy client assets to root (Cloudflare serves static files from root)\n await cp(options.clientOutDir, outDir, { recursive: true })\n\n // Copy server build\n await cp(join(options.serverEntry, '..'), join(outDir, '_server'), {\n recursive: true,\n })\n\n // Generate Cloudflare Pages _worker.js (ES module format)\n const workerEntry = `\nimport handler from \"./_server/entry-server.js\"\n\nexport default {\n async fetch(request, env, ctx) {\n const url = new URL(request.url)\n\n // Let Cloudflare serve static assets (files with extensions)\n // This check is a fallback — Pages routes static files automatically\n const ext = url.pathname.split(\".\").pop()\n if (ext && ext !== url.pathname && !url.pathname.endsWith(\"/\")) {\n // Cloudflare Pages handles static assets automatically via its asset binding\n // Only reach here if the file doesn't exist — fall through to SSR\n }\n\n // SSR handler\n try {\n return await handler(request)\n } catch (err) {\n return new Response(\"Internal Server Error\", { status: 500 })\n }\n },\n}\n`.trimStart()\n\n await writeFile(join(outDir, '_worker.js'), workerEntry)\n\n // Cloudflare Pages config — _routes.json for routing\n const routesConfig = {\n version: 1,\n include: ['/*'],\n exclude: ['/assets/*', '/favicon.*', '/site.webmanifest', '/robots.txt', '/sitemap.xml'],\n }\n\n await writeFile(join(outDir, '_routes.json'), JSON.stringify(routesConfig, null, 2))\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Netlify adapter — generates output for Netlify Functions (v2).\n *\n * Produces:\n * - Client assets in `publish/` directory\n * - `netlify/functions/ssr.mjs` — Netlify Function for SSR\n * - `netlify.toml` — routing configuration\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"netlify\",\n * })\n * ```\n */\nexport function netlifyAdapter(): Adapter {\n return {\n name: 'netlify',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n const publishDir = join(outDir, 'publish')\n const functionsDir = join(outDir, 'netlify', 'functions')\n\n await mkdir(publishDir, { recursive: true })\n await mkdir(functionsDir, { recursive: true })\n\n // Copy client assets to publish/\n await cp(options.clientOutDir, publishDir, { recursive: true })\n\n // Copy server build to functions directory\n await cp(join(options.serverEntry, '..'), join(functionsDir, '_server'), {\n recursive: true,\n })\n\n // Generate Netlify Function (v2 format — ESM, Web-standard Request/Response)\n const funcEntry = `\nimport handler from \"./_server/entry-server.js\"\n\nexport default async function(req, context) {\n try {\n return await handler(req)\n } catch (err) {\n return new Response(\"Internal Server Error\", { status: 500 })\n }\n}\n\nexport const config = {\n path: \"/*\",\n preferStatic: true,\n}\n`.trimStart()\n\n await writeFile(join(functionsDir, 'ssr.mjs'), funcEntry)\n\n // Generate netlify.toml\n const toml = `\n[build]\n publish = \"publish\"\n functions = \"netlify/functions\"\n\n[[headers]]\n for = \"/assets/*\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n\n[[redirects]]\n from = \"/*\"\n to = \"/.netlify/functions/ssr\"\n status = 200\n conditions = {Role = [\"admin\", \"user\", \"\"]}\n`.trimStart()\n\n await writeFile(join(outDir, 'netlify.toml'), toml)\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Node.js adapter — generates a standalone server entry using node:http.\n */\nexport function nodeAdapter(): Adapter {\n return {\n name: 'node',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const outDir = options.outDir\n await mkdir(outDir, { recursive: true })\n\n // Copy server and client builds\n await cp(options.clientOutDir, join(outDir, 'client'), {\n recursive: true,\n })\n await cp(join(options.serverEntry, '..'), join(outDir, 'server'), {\n recursive: true,\n })\n\n // Generate standalone server entry\n const port = options.config.port ?? 3000\n const serverEntry = `\nimport { createServer } from \"node:http\"\nimport { readFile } from \"node:fs/promises\"\nimport { join, extname } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url))\nconst handler = (await import(\"./server/entry-server.js\")).default\nconst clientDir = join(__dirname, \"client\")\n\nconst MIME_TYPES = {\n \".html\": \"text/html\",\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".svg\": \"image/svg+xml\",\n \".woff2\": \"font/woff2\",\n \".woff\": \"font/woff\",\n \".ico\": \"image/x-icon\",\n}\n\nconst server = createServer(async (req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\")\n\n // Try to serve static files first\n if (req.method === \"GET\") {\n try {\n const filePath = join(clientDir, url.pathname === \"/\" ? \"index.html\" : url.pathname)\n // Prevent path traversal — ensure resolved path stays within clientDir\n const { resolve } = await import(\"node:path\")\n const resolved = resolve(filePath)\n if (!resolved.startsWith(resolve(clientDir))) {\n res.writeHead(403)\n res.end(\"Forbidden\")\n return\n }\n const ext = extname(filePath)\n if (ext && ext !== \".html\") {\n const data = await readFile(filePath)\n const mime = MIME_TYPES[ext] || \"application/octet-stream\"\n res.writeHead(200, {\n \"content-type\": mime,\n \"cache-control\": ext === \".js\" || ext === \".css\"\n ? \"public, max-age=31536000, immutable\"\n : \"public, max-age=3600\",\n })\n res.end(data)\n return\n }\n } catch {}\n }\n\n // Fall through to SSR handler\n const headers = {}\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) headers[key] = Array.isArray(value) ? value.join(\", \") : value\n }\n\n const request = new Request(url.href, {\n method: req.method,\n headers,\n })\n\n const response = await handler(request)\n const body = await response.text()\n\n const responseHeaders = {}\n response.headers.forEach((v, k) => { responseHeaders[k] = v })\n\n res.writeHead(response.status, responseHeaders)\n res.end(body)\n})\n\nserver.listen(${port}, () => {\n console.log(\"\\\\n ⚡ Zero production server running on http://localhost:${port}\\\\n\")\n})\n`.trimStart()\n\n await writeFile(join(outDir, 'index.js'), serverEntry)\n await writeFile(join(outDir, 'package.json'), JSON.stringify({ type: 'module' }, null, 2))\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\n\n/**\n * Static adapter — just copies the client build output.\n * Used with SSG mode where all pages are pre-rendered at build time.\n */\nexport function staticAdapter(): Adapter {\n return {\n name: 'static',\n async build(options: AdapterBuildOptions) {\n const { cp, mkdir } = await import('node:fs/promises')\n\n await mkdir(options.outDir, { recursive: true })\n await cp(options.clientOutDir, options.outDir, { recursive: true })\n },\n }\n}\n","import type { Adapter, AdapterBuildOptions } from '../types'\nimport { validateBuildInputs } from './validate'\n\n/**\n * Vercel adapter — generates output for Vercel's Build Output API v3.\n *\n * Produces a `.vercel/output` directory with:\n * - `static/` — client-side assets (JS, CSS, images)\n * - `functions/ssr.func/` — serverless function for SSR\n * - `config.json` — routing configuration\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { defineConfig } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * adapter: \"vercel\",\n * })\n * ```\n */\nexport function vercelAdapter(): Adapter {\n return {\n name: 'vercel',\n async build(options: AdapterBuildOptions) {\n await validateBuildInputs(options)\n const { writeFile, cp, mkdir } = await import('node:fs/promises')\n const { join } = await import('node:path')\n\n const vercelDir = join(options.outDir, '.vercel', 'output')\n const staticDir = join(vercelDir, 'static')\n const funcDir = join(vercelDir, 'functions', 'ssr.func')\n\n await mkdir(staticDir, { recursive: true })\n await mkdir(funcDir, { recursive: true })\n\n // Copy client assets to static/\n await cp(options.clientOutDir, staticDir, { recursive: true })\n\n // Copy server build to function directory\n await cp(join(options.serverEntry, '..'), funcDir, { recursive: true })\n\n // Generate serverless function entry\n const funcEntry = `\nexport default async function handler(req) {\n const handler = (await import(\"./entry-server.js\")).default\n return handler(req)\n}\n`.trimStart()\n\n await writeFile(join(funcDir, 'index.js'), funcEntry)\n\n // Function config\n await writeFile(\n join(funcDir, '.vc-config.json'),\n JSON.stringify(\n {\n runtime: 'nodejs20.x',\n handler: 'index.js',\n launcherType: 'Nodejs',\n },\n null,\n 2,\n ),\n )\n\n // Vercel Build Output config\n const config = {\n version: 3,\n routes: [\n // Serve static assets directly\n {\n src: '/assets/(.*)',\n headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },\n },\n // Favicon and manifest\n { src: '/(favicon\\\\..*|site\\\\.webmanifest|robots\\\\.txt|sitemap\\\\.xml)', dest: '/$1' },\n // All other routes → SSR function\n { src: '/(.*)', dest: '/ssr' },\n ],\n }\n\n await writeFile(join(vercelDir, 'config.json'), JSON.stringify(config, null, 2))\n },\n }\n}\n","export { bunAdapter } from './bun'\nexport { cloudflareAdapter } from './cloudflare'\nexport { netlifyAdapter } from './netlify'\nexport { nodeAdapter } from './node'\nexport { staticAdapter } from './static'\nexport { vercelAdapter } from './vercel'\n\nimport type { Adapter, ZeroConfig } from '../types'\nimport { bunAdapter } from './bun'\nimport { cloudflareAdapter } from './cloudflare'\nimport { netlifyAdapter } from './netlify'\nimport { nodeAdapter } from './node'\nimport { staticAdapter } from './static'\nimport { vercelAdapter } from './vercel'\n\n/**\n * Resolve the adapter from config.\n * Returns a built-in adapter or throws if unknown.\n */\nexport function resolveAdapter(config: ZeroConfig): Adapter {\n const name = config.adapter ?? 'node'\n\n switch (name) {\n case 'node':\n return nodeAdapter()\n case 'bun':\n return bunAdapter()\n case 'static':\n return staticAdapter()\n case 'vercel':\n return vercelAdapter()\n case 'cloudflare':\n return cloudflareAdapter()\n case 'netlify':\n return netlifyAdapter()\n default:\n throw new Error(`[Pyreon] Unknown adapter: \"${name}\". Use \"node\", \"bun\", \"static\", \"vercel\", \"cloudflare\", or \"netlify\".`)\n }\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Middleware composition ─────────────────────────────────────────────────\n//\n// Chains multiple middleware functions into a single middleware.\n// Each middleware runs in order. If any returns a Response, the chain\n// short-circuits and that Response is returned. If all return void,\n// the composed middleware returns void (continues to rendering).\n\n/**\n * Compose multiple middleware into a single middleware function.\n * Middleware runs sequentially — if any returns a Response, the chain stops.\n *\n * @example\n * import { compose } from \"@pyreon/zero/middleware\"\n * import { corsMiddleware } from \"@pyreon/zero/cors\"\n * import { rateLimitMiddleware } from \"@pyreon/zero/rate-limit\"\n *\n * const combined = compose(\n * corsMiddleware({ origin: \"*\" }),\n * rateLimitMiddleware({ max: 100 }),\n * cacheMiddleware(),\n * )\n */\nexport function compose(...middlewares: Middleware[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const mw of middlewares) {\n const result = await mw(ctx)\n if (result instanceof Response) return result\n }\n }\n}\n\n// ─── Shared request context ─────────────────────────────────────────────────\n//\n// Lightweight context bag attached to MiddlewareContext.locals so middleware\n// can communicate without coupling. Uses a namespaced key to avoid collisions\n// with user-defined locals.\n\nconst ZERO_CTX_KEY = '__zeroCtx'\n\n/**\n * Get the shared Zero context from a middleware context.\n * Creates one if it doesn't exist. Middleware can use this to\n * pass data to downstream middleware without polluting `ctx.locals`.\n *\n * @example\n * const authMiddleware: Middleware = (ctx) => {\n * const zctx = getContext(ctx)\n * zctx.userId = \"user_123\"\n * }\n *\n * const loggingMiddleware: Middleware = (ctx) => {\n * const zctx = getContext(ctx)\n * console.log(\"User:\", zctx.userId)\n * }\n */\nexport function getContext(ctx: MiddlewareContext): Record<string, unknown> {\n let zctx = ctx.locals[ZERO_CTX_KEY] as Record<string, unknown> | undefined\n if (!zctx) {\n zctx = {}\n ctx.locals[ZERO_CTX_KEY] = zctx\n }\n return zctx\n}\n","/**\n * Dev-only error overlay for SSR/loader errors.\n * Renders a styled HTML page with the error stack trace.\n */\nexport function renderErrorOverlay(error: Error): string {\n const title = escapeHtml(error.message || 'Unknown error')\n const stack = escapeHtml(error.stack || '')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>SSR Error — Pyreon Zero</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: ui-monospace, \"Cascadia Code\", \"Source Code Pro\", Menlo, Consolas, monospace;\n background: #1a1a2e;\n color: #e0e0e0;\n min-height: 100vh;\n padding: 2rem;\n }\n .overlay {\n max-width: 900px;\n margin: 0 auto;\n }\n .header {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n margin-bottom: 1.5rem;\n }\n .badge {\n background: #e74c3c;\n color: white;\n padding: 0.25rem 0.75rem;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n .label {\n color: #888;\n font-size: 0.85rem;\n }\n .message {\n font-size: 1.25rem;\n color: #ff6b6b;\n margin-bottom: 1.5rem;\n line-height: 1.5;\n word-break: break-word;\n }\n .stack {\n background: #16213e;\n border: 1px solid #2a2a4a;\n border-radius: 8px;\n padding: 1.25rem;\n overflow-x: auto;\n font-size: 0.8rem;\n line-height: 1.7;\n white-space: pre-wrap;\n word-break: break-all;\n }\n .stack .at { color: #888; }\n .stack .file { color: #4ecdc4; }\n .hint {\n margin-top: 1.5rem;\n padding: 1rem;\n background: #1e2a45;\n border-radius: 6px;\n border-left: 3px solid #3498db;\n font-size: 0.8rem;\n color: #aaa;\n line-height: 1.5;\n }\n </style>\n</head>\n<body>\n <div class=\"overlay\">\n <div class=\"header\">\n <span class=\"badge\">SSR Error</span>\n <span class=\"label\">Pyreon Zero — Dev Mode</span>\n </div>\n <div class=\"message\">${title}</div>\n <pre class=\"stack\">${formatStack(stack)}</pre>\n <div class=\"hint\">\n This error occurred during server-side rendering. Check the terminal for\n the full stack trace. This overlay is only shown in development.\n </div>\n </div>\n</body>\n</html>`\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction formatStack(stack: string): string {\n return stack\n .split('\\n')\n .map((line) => {\n if (line.includes('at ')) {\n const fileMatch = line.match(/\\(([^)]+)\\)/)\n if (fileMatch) {\n return line.replace(fileMatch[0], `(<span class=\"file\">${fileMatch[1]}</span>)`)\n }\n }\n return line\n })\n .join('\\n')\n}\n","import { readFile } from 'node:fs/promises'\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Plugin, ViteDevServer } from 'vite'\nimport { generateApiRouteModule } from './api-routes'\nimport { resolveConfig } from './config'\n\n/**\n * Scan node_modules/@pyreon/ to discover all installed Pyreon packages.\n * Returns package names to exclude from Vite's dep optimizer.\n */\nfunction scanPyreonPackages(root: string): string[] {\n const pyreonDir = join(root, 'node_modules', '@pyreon')\n if (!existsSync(pyreonDir)) return []\n\n try {\n return readdirSync(pyreonDir)\n .filter((name) => !name.startsWith('.'))\n .map((name) => `@pyreon/${name}`)\n } catch {\n return []\n }\n}\n\n/**\n * Resolve a package that isn't at the app's top-level `node_modules` but is\n * nested under another `@pyreon/*` package. Used to alias `@pyreon/runtime-server`\n * to the copy under `node_modules/@pyreon/zero/node_modules/@pyreon/runtime-server`\n * so `ssrLoadModule` works without requiring the app to declare it as a\n * direct dep.\n */\nfunction resolveNestedPackage(root: string, name: string): string | undefined {\n const direct = join(root, 'node_modules', name)\n if (existsSync(direct)) return direct\n const nested = join(root, 'node_modules', '@pyreon', 'zero', 'node_modules', name)\n if (existsSync(nested)) return nested\n return undefined\n}\nimport { matchPattern } from \"./entry-server\";\nimport { renderErrorOverlay } from \"./error-overlay\";\nimport {\n\tgenerateMiddlewareModule,\n\tgenerateRouteModuleFromRoutes,\n\tscanRouteFiles,\n\tscanRouteFilesWithExports,\n} from \"./fs-router\";\nimport { render404Page } from \"./not-found\";\nimport type { ZeroConfig } from \"./types\";\n\nconst VIRTUAL_ROUTES_ID = \"virtual:zero/routes\";\nconst RESOLVED_VIRTUAL_ROUTES_ID = `\\0${VIRTUAL_ROUTES_ID}`;\n\nconst VIRTUAL_MIDDLEWARE_ID = \"virtual:zero/route-middleware\";\nconst RESOLVED_VIRTUAL_MIDDLEWARE_ID = `\\0${VIRTUAL_MIDDLEWARE_ID}`;\n\nconst VIRTUAL_API_ROUTES_ID = \"virtual:zero/api-routes\";\nconst RESOLVED_VIRTUAL_API_ROUTES_ID = `\\0${VIRTUAL_API_ROUTES_ID}`;\n\n/**\n * Zero Vite plugin — adds file-based routing and zero-config conventions\n * on top of @pyreon/vite-plugin.\n *\n * @example\n * // vite.config.ts\n * import pyreon from \"@pyreon/vite-plugin\"\n * import zero from \"@pyreon/zero\"\n *\n * export default {\n * plugins: [pyreon(), zero()],\n * }\n */\nexport function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {\n\tconst config = resolveConfig(userConfig);\n\tlet routesDir: string;\n\tlet root: string;\n\n\tconst plugin: Plugin & { _zeroConfig: ZeroConfig } = {\n\t\tname: \"pyreon-zero\",\n\t\tenforce: \"pre\",\n\t\t_zeroConfig: userConfig,\n\n\t\tconfigResolved(resolvedConfig) {\n\t\t\troot = resolvedConfig.root;\n\t\t\troutesDir = `${root}/src/routes`;\n\t\t},\n\n\t\tresolveId(id) {\n\t\t\tif (id === VIRTUAL_ROUTES_ID) return RESOLVED_VIRTUAL_ROUTES_ID;\n\t\t\tif (id === VIRTUAL_MIDDLEWARE_ID) return RESOLVED_VIRTUAL_MIDDLEWARE_ID;\n\t\t\tif (id === VIRTUAL_API_ROUTES_ID) return RESOLVED_VIRTUAL_API_ROUTES_ID;\n\t\t},\n\n\t\tasync load(id) {\n\t\t\tif (id === RESOLVED_VIRTUAL_ROUTES_ID) {\n\t\t\t\ttry {\n\t\t\t\t\t// Detect each file's optional exports up front so the\n\t\t\t\t\t// generator emits the optimal shape:\n\t\t\t\t\t// • lazy() for routes that only export `default` (best code splitting)\n\t\t\t\t\t// • Direct mod.loader/.guard/.meta access for routes with metadata\n\t\t\t\t\t// • No spurious IMPORT_IS_UNDEFINED warnings from Rolldown\n\t\t\t\t\tconst routes = await scanRouteFilesWithExports(routesDir, config.mode);\n\t\t\t\t\treturn generateRouteModuleFromRoutes(routes, routesDir, {\n\t\t\t\t\t\tstaticImports: config.mode === 'ssg',\n\t\t\t\t\t});\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const routes = []`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (id === RESOLVED_VIRTUAL_MIDDLEWARE_ID) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await scanRouteFiles(routesDir);\n\t\t\t\t\treturn generateMiddlewareModule(files, routesDir);\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const routeMiddleware = []`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (id === RESOLVED_VIRTUAL_API_ROUTES_ID) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = await scanRouteFiles(routesDir);\n\t\t\t\t\treturn generateApiRouteModule(files, routesDir);\n\t\t\t\t} catch (_err) {\n\t\t\t\t\treturn `export const apiRoutes = []`;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tconfigureServer(server) {\n\t\t\t// Dev-mode SSR middleware — for mode: \"ssr\", actually render each\n\t\t\t// matched route server-side instead of serving the SPA shell.\n\t\t\t// Runs BEFORE the 404 handler so matched routes are SSR'd and\n\t\t\t// unmatched ones fall through to the 404 handler.\n\t\t\tif (config.mode === \"ssr\") {\n\t\t\t\tserver.middlewares.use((req, res, next) => {\n\t\t\t\t\tconst accept = req.headers.accept ?? \"\";\n\t\t\t\t\tif (!accept.includes(\"text/html\") && !accept.includes(\"*/*\"))\n\t\t\t\t\t\treturn next();\n\t\t\t\t\tconst pathname = req.url?.split(\"?\")[0] ?? \"/\";\n\t\t\t\t\tif (pathname.startsWith(\"/@\") || pathname.startsWith(\"/__\"))\n\t\t\t\t\t\treturn next();\n\t\t\t\t\tif (/\\.\\w+$/.test(pathname)) return next();\n\n\t\t\t\t\trenderSsr(server, root, req.originalUrl ?? pathname, pathname).then(\n\t\t\t\t\t\t(result) => {\n\t\t\t\t\t\t\tif (result === null) return next();\n\t\t\t\t\t\t\tres.statusCode = 200;\n\t\t\t\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\t\t\t\tres.setHeader(\"Content-Length\", Buffer.byteLength(result));\n\t\t\t\t\t\t\tres.end(result);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(err: unknown) => {\n\t\t\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\t\t\tserver.ssrFixStacktrace(error);\n\t\t\t\t\t\t\tconst html = renderErrorOverlay(error);\n\t\t\t\t\t\t\tres.statusCode = 500;\n\t\t\t\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\t\t\t\tres.setHeader(\"Content-Length\", Buffer.byteLength(html));\n\t\t\t\t\t\t\tres.end(html);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// 404 handler — check if the requested path matches any route.\n\t\t\t// If not, render the nearest _404.tsx component with a 404 status.\n\t\t\t// Uses a sync wrapper that calls the async handler, since Connect\n\t\t\t// middleware does not natively support async functions.\n\t\t\tserver.middlewares.use((req, res, next) => {\n\t\t\t\tconst accept = req.headers.accept ?? \"\";\n\t\t\t\t// Accept HTML requests and wildcard requests (fetch without explicit Accept header)\n\t\t\t\tif (!accept.includes(\"text/html\") && !accept.includes(\"*/*\"))\n\t\t\t\t\treturn next();\n\n\t\t\t\tconst pathname = req.url?.split(\"?\")[0] ?? \"/\";\n\n\t\t\t\t// Skip static assets, Vite internal requests, and file-like paths (with extensions)\n\t\t\t\tif (pathname.startsWith(\"/@\") || pathname.startsWith(\"/__\"))\n\t\t\t\t\treturn next();\n\t\t\t\tif (/\\.\\w+$/.test(pathname)) return next();\n\n\t\t\t\thandle404(server, routesDir, pathname, res).then(\n\t\t\t\t\t(handled) => {\n\t\t\t\t\t\tif (!handled) next();\n\t\t\t\t\t},\n\t\t\t\t\t(err) => {\n\t\t\t\t\t\t// oxlint-disable-next-line no-console\n\t\t\t\t\t\tconsole.error('[Pyreon] Error in 404 handler:', err);\n\t\t\t\t\t\tnext();\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\n\t\t\t// SSR error overlay — intercept HTML requests and catch SSR errors\n\t\t\t// This runs as a late middleware (return function) so it wraps\n\t\t\t// Vite's own SSR handling and catches rendering failures.\n\t\t\tserver.middlewares.use((req, res, next) => {\n\t\t\t\tconst accept = req.headers.accept ?? \"\";\n\t\t\t\tif (!accept.includes(\"text/html\")) return next();\n\n\t\t\t\tconst originalEnd = res.end.bind(res);\n\t\t\t\tlet errored = false;\n\n\t\t\t\tconst handleError = (err: unknown) => {\n\t\t\t\t\tif (errored) return;\n\t\t\t\t\terrored = true;\n\t\t\t\t\tconst error = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.ssrFixStacktrace(error);\n\t\t\t\t\tconst html = renderErrorOverlay(error);\n\t\t\t\t\tres.statusCode = 500;\n\t\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\t\tres.setHeader(\"Content-Length\", Buffer.byteLength(html));\n\t\t\t\t\toriginalEnd(html);\n\t\t\t\t};\n\n\t\t\t\tres.on(\"error\", handleError);\n\n\t\t\t\t// Wrap next() in try/catch to handle both sync and async errors.\n\t\t\t\t// Express-style middleware may throw synchronously or pass errors\n\t\t\t\t// through next(err), and Vite's SSR pipeline may reject promises.\n\t\t\t\ttry {\n\t\t\t\t\tconst result = next() as unknown;\n\t\t\t\t\t// Handle async errors from Vite's SSR pipeline\n\t\t\t\t\tif (\n\t\t\t\t\t\tresult &&\n\t\t\t\t\t\ttypeof (result as Promise<unknown>).catch === \"function\"\n\t\t\t\t\t) {\n\t\t\t\t\t\t(result as Promise<unknown>).catch(handleError);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\thandleError(err);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Watch routes directory for changes\n\t\t\tserver.watcher.add(`${routesDir}/**/*.{tsx,jsx,ts,js}`);\n\n\t\t\t// Invalidate virtual modules when route files change\n\t\t\tserver.watcher.on(\"all\", (event, path) => {\n\t\t\t\tif (\n\t\t\t\t\tpath.startsWith(routesDir) &&\n\t\t\t\t\t(event === \"add\" || event === \"unlink\")\n\t\t\t\t) {\n\t\t\t\t\tfor (const resolvedId of [\n\t\t\t\t\t\tRESOLVED_VIRTUAL_ROUTES_ID,\n\t\t\t\t\t\tRESOLVED_VIRTUAL_MIDDLEWARE_ID,\n\t\t\t\t\t\tRESOLVED_VIRTUAL_API_ROUTES_ID,\n\t\t\t\t\t]) {\n\t\t\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedId);\n\t\t\t\t\t\tif (mod) server.moduleGraph.invalidateModule(mod);\n\t\t\t\t\t}\n\t\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tconfig(userConfig) {\n\t\t\t// Discover all @pyreon/* packages installed in node_modules.\n\t\t\t// The \"bun\" export condition points to TS source — esbuild's\n\t\t\t// dep optimizer would compile them with the wrong JSX runtime.\n\t\t\tconst root = userConfig.root ?? process.cwd()\n\t\t\tconst pyreonExclude = scanPyreonPackages(root)\n\n\t\t\t// `@pyreon/runtime-server` is only imported by zero's dev SSR\n\t\t\t// middleware and the production server entry — apps rarely list it\n\t\t\t// as a direct dep. Resolve it to the copy nested under zero so\n\t\t\t// `ssrLoadModule(\"@pyreon/runtime-server\")` works uniformly.\n\t\t\tconst runtimeServerAlias = resolveNestedPackage(\n\t\t\t\troot,\n\t\t\t\t\"@pyreon/runtime-server\",\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tresolve: {\n\t\t\t\t\tconditions: ['bun'],\n\t\t\t\t\t...(runtimeServerAlias\n\t\t\t\t\t\t? { alias: { '@pyreon/runtime-server': runtimeServerAlias } }\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t\t// Vite's SSR module graph has its own resolver that defaults to the\n\t\t\t\t// \"node\" condition — which would pick the built `lib/index.js` for\n\t\t\t\t// every `@pyreon/*` package and bypass workspace source edits. Mirror\n\t\t\t\t// the client-side \"bun\" condition + alias so dev SSR uses `src/`.\n\t\t\t\tssr: {\n\t\t\t\t\tresolve: {\n\t\t\t\t\t\tconditions: ['bun'],\n\t\t\t\t\t\t...(runtimeServerAlias\n\t\t\t\t\t\t\t? { alias: { '@pyreon/runtime-server': runtimeServerAlias } }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\toptimizeDeps: {\n\t\t\t\t\texclude: pyreonExclude,\n\t\t\t\t},\n\t\t\t\tserver: {\n\t\t\t\t\tport: config.port,\n\t\t\t\t},\n\t\t\t\tdefine: {\n\t\t\t\t\t__ZERO_MODE__: JSON.stringify(config.mode),\n\t\t\t\t\t__ZERO_BASE__: JSON.stringify(config.base),\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n\n\treturn plugin;\n}\n\n/**\n * Check if the requested path matches any route. If not, render a 404 page.\n * Returns true if the 404 was handled (response sent), false otherwise.\n *\n * In dev mode, the _404.tsx component cannot be SSR-rendered because\n * the compiler emits _tpl() calls that require `document`. Instead,\n * we return a static 404 page. The actual component rendering happens\n * on the client side when the SPA loads.\n */\nasync function handle404(\n\tserver: import(\"vite\").ViteDevServer,\n\t_routesDir: string,\n\tpathname: string,\n\tres: import(\"http\").ServerResponse,\n): Promise<boolean> {\n\tconst mod = await server.ssrLoadModule(VIRTUAL_ROUTES_ID);\n\tconst routes = mod.routes as Array<{ path?: string; children?: unknown[] }>;\n\tconst patterns = flattenRoutePatterns(routes);\n\n\tif (patterns.some((pattern) => matchPattern(pattern, pathname))) {\n\t\treturn false; // Route matches — not a 404\n\t}\n\n\t// No route matched — return a 404.\n\t// In dev, we return a static page since the compiler emits _tpl() calls\n\t// that require document (unavailable in SSR). The _404.tsx component\n\t// renders on the client side after hydration.\n\tconst html = await render404Page(undefined);\n\n\tres.statusCode = 404;\n\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\tres.setHeader(\"Content-Length\", Buffer.byteLength(html));\n\tres.end(html);\n\treturn true;\n}\n\n/**\n * Dev-mode SSR render pipeline. Returns the composed HTML string, or `null`\n * if the URL doesn't match any known route (caller falls through to the 404\n * middleware). Mirrors the production `createServer` flow:\n * 1. Load virtual:zero/routes + app.ts via Vite's ssrLoadModule\n * 2. Create a per-request router bound to the request URL\n * 3. Pre-run loaders for the matched route(s)\n * 4. Render app tree with head tag collection\n * 5. Serialize loader data into `window.__PYREON_LOADER_DATA__`\n * 6. Inject everything into the user's transformed index.html (so Vite\n * still gets a chance to inject its HMR client + JSX runtime prelude)\n */\nasync function renderSsr(\n\tserver: ViteDevServer,\n\troot: string,\n\toriginalUrl: string,\n\tpathname: string,\n): Promise<string | null> {\n\t// Pattern check FIRST — otherwise SSR would try (and likely crash) on\n\t// asset paths that happened to accept text/html (e.g. curl-style).\n\tconst routesMod = await server.ssrLoadModule(VIRTUAL_ROUTES_ID);\n\tconst routes = routesMod.routes as Array<{\n\t\tpath?: string;\n\t\tchildren?: unknown[];\n\t}>;\n\tconst patterns = flattenRoutePatterns(routes);\n\tif (!patterns.some((pattern) => matchPattern(pattern, pathname))) {\n\t\treturn null;\n\t}\n\n\t// Read + transform index.html (Vite injects the HMR client / JSX prelude).\n\tlet template = await readFile(join(root, \"index.html\"), \"utf-8\");\n\ttemplate = await server.transformIndexHtml(originalUrl, template);\n\n\t// Framework modules load through Vite's SSR module graph so user code (which\n\t// imports the same packages) shares a single module instance — otherwise two\n\t// copies of `@pyreon/router` would hold separate `RouterContext` IDs and\n\t// `useContext` in RouterLink would miss the RouterProvider's value.\n\t// `@pyreon/runtime-server` isn't a direct dep of most apps, so zero's\n\t// `config()` hook registers an alias that points it at the copy under\n\t// zero's own `node_modules` — same path → same Vite module → same instance.\n\tconst [core, headPkg, headSsr, routerPkg, runtimeServer] = await Promise.all(\n\t\t[\n\t\t\tserver.ssrLoadModule(\"@pyreon/core\") as Promise<\n\t\t\t\ttypeof import(\"@pyreon/core\")\n\t\t\t>,\n\t\t\tserver.ssrLoadModule(\"@pyreon/head\") as Promise<\n\t\t\t\ttypeof import(\"@pyreon/head\")\n\t\t\t>,\n\t\t\tserver.ssrLoadModule(\"@pyreon/head/ssr\") as Promise<\n\t\t\t\ttypeof import(\"@pyreon/head/ssr\")\n\t\t\t>,\n\t\t\tserver.ssrLoadModule(\"@pyreon/router\") as Promise<\n\t\t\t\ttypeof import(\"@pyreon/router\")\n\t\t\t>,\n\t\t\tserver.ssrLoadModule(\"@pyreon/runtime-server\") as Promise<\n\t\t\t\ttypeof import(\"@pyreon/runtime-server\")\n\t\t\t>,\n\t\t],\n\t);\n\n\t// Build the SAME app tree the client will hydrate against. `entry-client`\n\t// imports `layout` from `_layout.tsx` and passes it explicitly to\n\t// `startClient` → `createApp`. We mirror that here: discover the user's\n\t// `_layout` (if present) via Vite's SSR module graph and pass it along.\n\t// Without this, SSR renders a different tree (no outer Layout wrapper)\n\t// and hydration mismatches at the very first nesting level — cascading\n\t// into duplicated mounts of every section below.\n\tlet userLayout: unknown\n\tfor (const ext of ['tsx', 'ts', 'jsx', 'js']) {\n\t\ttry {\n\t\t\tconst layoutMod = (await server.ssrLoadModule(\n\t\t\t\t`/src/routes/_layout.${ext}`,\n\t\t\t)) as { layout?: unknown; default?: unknown }\n\t\t\tuserLayout = layoutMod.layout ?? layoutMod.default\n\t\t\tif (userLayout) break\n\t\t} catch {\n\t\t\t// Try the next extension. If none exist, createApp uses DefaultLayout.\n\t\t}\n\t}\n\n\t// Use zero's own `createApp` rather than reassembling the tree by hand —\n\t// guarantees server and client agree on every wrapper component (any\n\t// future change to the App tree only needs to happen in one place).\n\t// Load via `ssrLoadModule` so app.ts shares Vite's SSR module graph with\n\t// the user's code: both end up importing the SAME `@pyreon/router` /\n\t// `@pyreon/core` / `@pyreon/head` instances, so contexts (RouterContext,\n\t// HeadContext, etc.) match between provider and consumer. A direct Node\n\t// `import(\"./app\")` would resolve those packages via Node's module graph,\n\t// producing duplicate context registries that never connect.\n\tconst appMod = (await server.ssrLoadModule(\n\t\t\"@pyreon/zero/server\",\n\t)) as typeof import(\"./server\")\n\ttype CreateAppLayout = NonNullable<\n\t\tParameters<typeof appMod.createApp>[0][\"layout\"]\n\t>\n\tconst { App, router: routerInst } = appMod.createApp({\n\t\troutes: routes as import(\"@pyreon/router\").RouteRecord[],\n\t\trouterMode: \"history\",\n\t\turl: pathname,\n\t\t...(userLayout ? { layout: userLayout as CreateAppLayout } : {}),\n\t})\n\n\t// `preload` loads lazy route components AND runs loaders for `pathname` so\n\t// the synchronous render pass produces final HTML — no loading fallbacks,\n\t// no `useLoaderData() === undefined`.\n\tawait routerInst.preload(pathname);\n\n\treturn runtimeServer.runWithRequestContext(async () => {\n\t\tconst app = core.h(App as Parameters<typeof core.h>[0], null);\n\n\t\tconst { html: appHtml, head } = await headSsr.renderWithHead(app);\n\t\tconst loaderData = routerPkg.serializeLoaderData(\n\t\t\trouterInst as Parameters<typeof routerPkg.serializeLoaderData>[0],\n\t\t);\n\t\tconst hasData = loaderData && Object.keys(loaderData).length > 0;\n\t\tconst loaderScript = hasData\n\t\t\t? `<script>window.__PYREON_LOADER_DATA__=${JSON.stringify(loaderData).replace(/<\\//g, \"<\\\\/\")}</script>`\n\t\t\t: \"\";\n\n\t\treturn template\n\t\t\t.replace(\"<!--pyreon-head-->\", head)\n\t\t\t.replace(\"<!--pyreon-app-->\", appHtml)\n\t\t\t.replace(\"<!--pyreon-scripts-->\", loaderScript);\n\t});\n}\n\n/** Extract all URL patterns from a nested route tree. */\nfunction flattenRoutePatterns(\n\troutes: Array<{ path?: string; children?: unknown[] }>,\n\tprefix = \"\",\n): string[] {\n\tconst patterns: string[] = [];\n\tfor (const route of routes) {\n\t\tif (!route.path) continue;\n\t\tconst fullPath =\n\t\t\troute.path === \"/\" && prefix ? prefix : `${prefix}${route.path}`;\n\t\tpatterns.push(fullPath);\n\t\tif (route.children) {\n\t\t\tpatterns.push(\n\t\t\t\t...flattenRoutePatterns(\n\t\t\t\t\troute.children as Array<{ path?: string; children?: unknown[] }>,\n\t\t\t\t\tfullPath,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn patterns;\n}\n","import { existsSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { Plugin } from 'vite'\n\nlet sharpWarned = false\nfunction warnSharpMissing() {\n if (sharpWarned) return\n sharpWarned = true\n // oxlint-disable-next-line no-console\n console.warn(\n '\\n[Pyreon] sharp not installed — favicons will not be generated. Install for full support: bun add -D sharp\\n',\n )\n}\n\n// ─── Favicon generation plugin ──────────────────────────────────────────────\n//\n// Generates all favicon formats from a single source file (SVG or PNG):\n// - favicon.ico (16x16 + 32x32 combined)\n// - favicon.svg (copied if source is SVG)\n// - apple-touch-icon.png (180x180)\n// - icon-192.png (for web manifest)\n// - icon-512.png (for web manifest)\n// - site.webmanifest\n//\n// Usage:\n// import { faviconPlugin } from \"@pyreon/zero\"\n// export default { plugins: [Pyreon] }\n\nexport interface FaviconLocaleConfig {\n /** Locale-specific source icon (SVG or PNG). */\n source: string\n /** Optional dark mode variant for this locale. */\n darkSource?: string\n}\n\nexport interface FaviconPluginConfig {\n /** Path to the source icon (SVG or PNG, at least 512x512 for PNG). */\n source: string\n /** Theme color for web manifest. Default: \"#ffffff\" */\n themeColor?: string\n /** Background color for web manifest. Default: \"#ffffff\" */\n backgroundColor?: string\n /** App name for web manifest. Uses package.json name if not set. */\n name?: string\n /** Generate web manifest. Default: true */\n manifest?: boolean\n /**\n * Dark mode favicon (SVG only).\n * When provided, the SVG favicon uses prefers-color-scheme media query\n * to switch between light and dark variants.\n */\n darkSource?: string\n /**\n * Locale-specific icon overrides. Each key is a locale code,\n * value is a source icon (and optional dark variant).\n * Locales not in this map use the base `source`.\n *\n * Generated files are placed under `/{locale}/` prefix:\n * /de/favicon.svg, /de/favicon-32x32.png, etc.\n *\n * @example\n * ```ts\n * faviconPlugin({\n * source: \"./icon.svg\",\n * locales: {\n * de: { source: \"./icon-de.svg\" },\n * cs: { source: \"./icon-cs.svg\" },\n * },\n * })\n * ```\n */\n locales?: Record<string, FaviconLocaleConfig>\n /**\n * Dev mode favicon — shown only during development to distinguish\n * dev tabs from production. Can be:\n * - A path to a separate icon file\n * - `true` to auto-generate a dev badge (grayscale + \"DEV\" overlay)\n *\n * @example\n * ```ts\n * faviconPlugin({\n * source: \"./icon.svg\",\n * devSource: \"./icon-dev.svg\", // custom dev icon\n * // OR\n * devSource: true, // auto-generate grayscale badge\n * })\n * ```\n */\n devSource?: string | boolean\n}\n\ninterface FaviconSize {\n size: number\n name: string\n}\n\nconst SIZES: FaviconSize[] = [\n { size: 16, name: 'favicon-16x16.png' },\n { size: 32, name: 'favicon-32x32.png' },\n { size: 180, name: 'apple-touch-icon.png' },\n { size: 192, name: 'icon-192.png' },\n { size: 512, name: 'icon-512.png' },\n]\n\n/**\n * Favicon generation Vite plugin.\n *\n * Generates all required favicon formats at build time from a single source.\n * In dev mode, serves the source directly.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { faviconPlugin } from \"@pyreon/zero\"\n *\n * export default {\n * plugins: [faviconPlugin({ source: \"./src/assets/icon.svg\" })],\n * }\n * ```\n */\nexport function faviconPlugin(config: FaviconPluginConfig): Plugin {\n const themeColor = config.themeColor ?? '#ffffff'\n const backgroundColor = config.backgroundColor ?? '#ffffff'\n const generateManifest = config.manifest !== false\n\n let root = ''\n let isBuild = false\n\n return {\n name: 'pyreon-zero-favicon',\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n root = resolvedConfig.root\n isBuild = resolvedConfig.command === 'build'\n },\n\n // Dev server: serve generated favicons on-the-fly\n configureServer(server) {\n const sourcePath = join(root, config.source)\n const darkPath = config.darkSource ? join(root, config.darkSource) : null\n const devSourcePath = typeof config.devSource === 'string'\n ? join(root, config.devSource)\n : null\n const autoDevBadge = config.devSource === true\n const devCache = new Map<string, Uint8Array>()\n\n /** Resolve source path for a request — handles dark variants and dev badge. */\n function resolveSourceForDev(baseName: string, defaultSource: string): string {\n // Dark variant: favicon-dark-32x32.png → use darkSource\n if (darkPath && baseName.includes('-dark-')) return darkPath\n // Light variant: favicon-light-32x32.png → use source\n if (baseName.includes('-light-')) return defaultSource\n return defaultSource\n }\n\n server.middlewares.use(async (req, res, next) => {\n const url = req.url ?? ''\n\n // Resolve locale-specific source\n const localeSource = resolveLocaleSource(url, config, root)\n const svgUrl = localeSource ? localeSource.url : url\n const svgPath = localeSource ? localeSource.sourcePath : sourcePath\n const isSvgSource = localeSource ? localeSource.source.endsWith('.svg') : config.source.endsWith('.svg')\n\n // Serve favicon.svg — in dev, add dev badge overlay if configured\n if (svgUrl.endsWith('/favicon.svg') && isSvgSource) {\n try {\n let content = await readFile(svgPath, 'utf-8')\n if (autoDevBadge) content = addDevBadgeToSvg(content)\n else if (devSourcePath && existsSync(devSourcePath)) {\n content = await readFile(devSourcePath, 'utf-8')\n }\n res.setHeader('Content-Type', 'image/svg+xml')\n res.end(content)\n return\n } catch { /* fall through */ }\n }\n\n // Serve generated PNGs on-demand — supports dark variants + dev badge\n const baseName = svgUrl.split('/').pop() ?? ''\n // Strip light-/dark- prefix for size matching\n const cleanName = baseName.replace(/-?(light|dark)-/, '-')\n const sizeMatch = SIZES.find((s) => s.name === cleanName || baseName === s.name)\n if (sizeMatch) {\n const resolvedSource = resolveSourceForDev(baseName, svgPath)\n const cacheKey = `${resolvedSource}:${sizeMatch.size}:${autoDevBadge}`\n let png = devCache.get(cacheKey)\n if (!png) {\n let result = await resizeToPng(resolvedSource, sizeMatch.size)\n if (result && autoDevBadge) {\n result = await addDevBadgeToPng(result, sizeMatch.size)\n }\n if (result) {\n png = result\n devCache.set(cacheKey, result)\n }\n }\n if (png) {\n res.setHeader('Content-Type', 'image/png')\n res.setHeader('Cache-Control', 'no-cache')\n res.end(Buffer.from(png))\n return\n }\n }\n\n // Serve generated ICO on-demand\n if (baseName === 'favicon.ico') {\n const cacheKey = `ico:${svgPath}`\n let ico: Uint8Array | undefined = devCache.get(cacheKey)\n if (!ico) {\n const result = await generateIco(svgPath)\n if (result) {\n ico = result\n devCache.set(cacheKey, result)\n }\n }\n if (ico) {\n res.setHeader('Content-Type', 'image/x-icon')\n res.setHeader('Cache-Control', 'no-cache')\n res.end(Buffer.from(ico))\n return\n }\n }\n\n // Serve manifest (supports /{locale}/site.webmanifest)\n if (baseName === 'site.webmanifest' && generateManifest) {\n const prefix = localeSource ? `/${localeSource.locale}` : ''\n const manifest = {\n name: config.name ?? 'App',\n short_name: config.name ?? 'App',\n icons: [\n { src: `${prefix}/icon-192.png`, sizes: '192x192', type: 'image/png' },\n { src: `${prefix}/icon-512.png`, sizes: '512x512', type: 'image/png' },\n ],\n theme_color: themeColor,\n background_color: backgroundColor,\n display: 'standalone',\n }\n res.setHeader('Content-Type', 'application/manifest+json')\n res.end(JSON.stringify(manifest, null, 2))\n return\n }\n\n next()\n })\n },\n\n // Inject favicon <link> tags into HTML\n transformIndexHtml() {\n const isSvg = config.source.endsWith('.svg')\n const hasDark = !!config.darkSource\n const tags: Array<{\n tag: string\n attrs: Record<string, string>\n injectTo: 'head'\n }> = []\n\n // SVG favicon (with prefers-color-scheme media query when dark variant exists)\n if (isSvg) {\n tags.push({\n tag: 'link',\n attrs: { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },\n injectTo: 'head',\n })\n }\n\n if (hasDark) {\n // Dual-variant PNG/ICO favicons — light active, dark hidden via media=\"not all\".\n // The themeScript and initTheme() swap these based on the resolved theme.\n const lightAttrs = { 'data-favicon-theme': 'light' }\n const darkAttrs = { 'data-favicon-theme': 'dark', media: 'not all' }\n\n tags.push(\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-light-32x32.png', ...lightAttrs }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-dark-32x32.png', ...darkAttrs }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-light-16x16.png', ...lightAttrs }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-dark-16x16.png', ...darkAttrs }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-light.png', ...lightAttrs }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-dark.png', ...darkAttrs }, injectTo: 'head' },\n )\n } else {\n // Single-variant (no dark mode)\n tags.push(\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }, injectTo: 'head' },\n { tag: 'link', attrs: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }, injectTo: 'head' },\n )\n }\n\n if (generateManifest) {\n tags.push({\n tag: 'link',\n attrs: { rel: 'manifest', href: '/site.webmanifest' },\n injectTo: 'head',\n })\n }\n\n tags.push({\n tag: 'meta',\n attrs: { name: 'theme-color', content: themeColor },\n injectTo: 'head',\n })\n\n // Auto-inject favicon swap script when dark variant exists.\n // This runs in the blocking <head> before any render — no flash.\n // Reads theme from localStorage or OS preference, then swaps\n // data-favicon-theme media attributes.\n if (hasDark) {\n tags.push({\n tag: 'script',\n attrs: {},\n injectTo: 'head',\n children: `(function(){try{var t=localStorage.getItem(\"zero-theme\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()`,\n } as any)\n }\n\n return tags\n },\n\n async generateBundle() {\n if (!isBuild) return\n\n // Generate favicons for the base (default) source\n await generateFaviconSet.call(this, root, config.source, config.darkSource, '', config, themeColor, backgroundColor, generateManifest)\n\n // Generate locale-specific favicon sets\n if (config.locales) {\n for (const [locale, localeConfig] of Object.entries(config.locales)) {\n await generateFaviconSet.call(this, root, localeConfig.source, localeConfig.darkSource, `${locale}/`, config, themeColor, backgroundColor, generateManifest)\n }\n }\n },\n }\n}\n\n/**\n * Wrap two SVGs into a single SVG that switches based on prefers-color-scheme.\n */\nfunction wrapSvgWithDarkMode(lightSvg: string, darkSvg: string): string {\n // Extract viewBox from light SVG\n const viewBoxMatch = lightSvg.match(/viewBox=\"([^\"]*)\"/)\n const viewBox = viewBoxMatch?.[1] ?? '0 0 32 32'\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"${viewBox}\">\n <style>\n :root { color-scheme: light dark; }\n @media (prefers-color-scheme: dark) { .light { display: none; } }\n @media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) { .dark { display: none; } }\n </style>\n <g class=\"light\">${stripSvgWrapper(lightSvg)}</g>\n <g class=\"dark\">${stripSvgWrapper(darkSvg)}</g>\n</svg>`\n}\n\nfunction stripSvgWrapper(svg: string): string {\n return svg\n .replace(/<svg[^>]*>/, '')\n .replace(/<\\/svg>\\s*$/, '')\n .trim()\n}\n\n/**\n * Resolve the source path for a locale-prefixed favicon URL.\n * Returns null if the URL is not locale-prefixed or locale has no override.\n */\nfunction resolveLocaleSource(\n url: string,\n config: FaviconPluginConfig,\n rootDir: string,\n): { locale: string; url: string; source: string; sourcePath: string } | null {\n if (!config.locales) return null\n\n for (const [locale, localeConfig] of Object.entries(config.locales)) {\n const prefix = `/${locale}/`\n if (url.startsWith(prefix)) {\n return {\n locale,\n url,\n source: localeConfig.source,\n sourcePath: join(rootDir, localeConfig.source),\n }\n }\n }\n return null\n}\n\n/**\n * Generate a complete favicon set (SVG, PNGs, ICO, manifest) with a file prefix.\n * Called once for base (prefix = '') and once per locale (prefix = '{locale}/').\n */\nasync function generateFaviconSet(\n this: any,\n rootDir: string,\n source: string,\n darkSource: string | undefined,\n prefix: string,\n config: FaviconPluginConfig,\n themeColor: string,\n backgroundColor: string,\n generateManifest: boolean,\n): Promise<void> {\n const sourcePath = join(rootDir, source)\n if (!existsSync(sourcePath)) {\n // oxlint-disable-next-line no-console\n console.warn(`[Pyreon] Source not found: ${sourcePath}`)\n return\n }\n\n const isSvg = source.endsWith('.svg')\n\n // Copy SVG as favicon.svg\n if (isSvg) {\n const svgContent = await readFile(sourcePath, 'utf-8')\n let finalSvg = svgContent\n\n if (darkSource) {\n const darkPath = join(rootDir, darkSource)\n if (existsSync(darkPath)) {\n const darkSvg = await readFile(darkPath, 'utf-8')\n finalSvg = wrapSvgWithDarkMode(svgContent, darkSvg)\n }\n }\n\n this.emitFile({\n type: 'asset',\n fileName: `${prefix}favicon.svg`,\n source: finalSvg,\n })\n }\n\n // Generate PNG sizes via sharp\n if (darkSource) {\n // Dual-variant: generate light + dark PNGs with prefixed names\n const darkPath = join(rootDir, darkSource)\n const darkExists = existsSync(darkPath)\n\n for (const { size, name } of SIZES) {\n // Light variant\n const lightName = name.replace(/^(favicon-)/, '$1light-').replace(/^(apple-touch-icon)/, '$1-light').replace(/^(icon-)/, '$1light-')\n const lightPng = await resizeToPng(sourcePath, size)\n if (lightPng) {\n this.emitFile({ type: 'asset', fileName: `${prefix}${lightName}`, source: lightPng })\n }\n\n // Dark variant\n if (darkExists) {\n const darkName = name.replace(/^(favicon-)/, '$1dark-').replace(/^(apple-touch-icon)/, '$1-dark').replace(/^(icon-)/, '$1dark-')\n const darkPng = await resizeToPng(darkPath, size)\n if (darkPng) {\n this.emitFile({ type: 'asset', fileName: `${prefix}${darkName}`, source: darkPng })\n }\n }\n }\n\n // Also generate standard names (used by manifest + external references)\n for (const { size, name } of SIZES) {\n const pngBuffer = await resizeToPng(sourcePath, size)\n if (pngBuffer) {\n this.emitFile({ type: 'asset', fileName: `${prefix}${name}`, source: pngBuffer })\n }\n }\n } else {\n // Single-variant\n for (const { size, name } of SIZES) {\n const pngBuffer = await resizeToPng(sourcePath, size)\n if (pngBuffer) {\n this.emitFile({ type: 'asset', fileName: `${prefix}${name}`, source: pngBuffer })\n }\n }\n }\n\n // Generate favicon.ico (16 + 32)\n const ico = await generateIco(sourcePath)\n if (ico) {\n this.emitFile({\n type: 'asset',\n fileName: `${prefix}favicon.ico`,\n source: ico,\n })\n }\n\n // Generate web manifest\n if (generateManifest) {\n const manifestPrefix = prefix ? `/${prefix.slice(0, -1)}` : ''\n const manifest = {\n name: config.name ?? 'App',\n short_name: config.name ?? 'App',\n icons: [\n { src: `${manifestPrefix}/icon-192.png`, sizes: '192x192', type: 'image/png' },\n { src: `${manifestPrefix}/icon-512.png`, sizes: '512x512', type: 'image/png' },\n ],\n theme_color: themeColor,\n background_color: backgroundColor,\n display: 'standalone',\n }\n\n this.emitFile({\n type: 'asset',\n fileName: `${prefix}site.webmanifest`,\n source: JSON.stringify(manifest, null, 2),\n })\n }\n}\n\n/**\n * Get favicon link tags for a specific locale.\n * Returns link objects suitable for `useHead()` or direct HTML injection.\n *\n * @example\n * ```ts\n * const links = faviconLinks(\"de\", { source: \"./icon.svg\", locales: { de: { source: \"./icon-de.svg\" } } })\n * // → [{ rel: \"icon\", type: \"image/svg+xml\", href: \"/de/favicon.svg\" }, ...]\n * ```\n */\nexport function faviconLinks(\n locale: string | undefined,\n config: FaviconPluginConfig,\n): Array<{ rel: string; type?: string; sizes?: string; href: string }> {\n const hasLocaleOverride = locale && config.locales?.[locale]\n const prefix = hasLocaleOverride ? `/${locale}` : ''\n const isSvg = (hasLocaleOverride ? config.locales![locale]!.source : config.source).endsWith('.svg')\n\n const links: Array<{ rel: string; type?: string; sizes?: string; href: string }> = []\n\n if (isSvg) {\n links.push({ rel: 'icon', type: 'image/svg+xml', href: `${prefix}/favicon.svg` })\n }\n\n links.push(\n { rel: 'icon', type: 'image/png', sizes: '32x32', href: `${prefix}/favicon-32x32.png` },\n { rel: 'icon', type: 'image/png', sizes: '16x16', href: `${prefix}/favicon-16x16.png` },\n { rel: 'apple-touch-icon', sizes: '180x180', href: `${prefix}/apple-touch-icon.png` },\n )\n\n if (config.manifest !== false) {\n links.push({ rel: 'manifest', href: `${prefix}/site.webmanifest` })\n }\n\n return links\n}\n\nasync function resizeToPng(input: string, size: number): Promise<Uint8Array | null> {\n try {\n const sharp = await import('sharp').then((m) => m.default ?? m)\n return await sharp(input).resize(size, size, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } } as any).png().toBuffer()\n } catch {\n warnSharpMissing()\n return null\n }\n}\n\nasync function generateIco(input: string): Promise<Uint8Array | null> {\n try {\n const sharp = await import('sharp').then((m) => m.default ?? m)\n const png16 = await sharp(input).resize(16, 16, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } } as any).png().toBuffer()\n const png32 = await sharp(input).resize(32, 32, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } } as any).png().toBuffer()\n\n // ICO format: header + directory entries + PNG data\n return createIcoFromPngs([\n { buffer: png16, size: 16 },\n { buffer: png32, size: 32 },\n ])\n } catch {\n warnSharpMissing()\n return null\n }\n}\n\nexport interface IcoEntry {\n buffer: Buffer\n size: number\n}\n\n/** @internal Exported for testing */\nexport function createIcoFromPngs(entries: IcoEntry[]): Uint8Array {\n const headerSize = 6\n const dirEntrySize = 16\n const dirSize = dirEntrySize * entries.length\n let dataOffset = headerSize + dirSize\n\n // ICO header\n const header = Buffer.alloc(headerSize)\n header.writeUInt16LE(0, 0) // reserved\n header.writeUInt16LE(1, 2) // type: icon\n header.writeUInt16LE(entries.length, 4) // count\n\n // Directory entries\n const dirEntries = Buffer.alloc(dirSize)\n const dataBuffers: Buffer[] = []\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!\n const offset = i * dirEntrySize\n dirEntries.writeUInt8(entry.size === 256 ? 0 : entry.size, offset) // width\n dirEntries.writeUInt8(entry.size === 256 ? 0 : entry.size, offset + 1) // height\n dirEntries.writeUInt8(0, offset + 2) // palette\n dirEntries.writeUInt8(0, offset + 3) // reserved\n dirEntries.writeUInt16LE(1, offset + 4) // color planes\n dirEntries.writeUInt16LE(32, offset + 6) // bits per pixel\n dirEntries.writeUInt32LE(entry.buffer.length, offset + 8) // size\n dirEntries.writeUInt32LE(dataOffset, offset + 12) // offset\n\n dataOffset += entry.buffer.length\n dataBuffers.push(entry.buffer)\n }\n\n return Buffer.concat([header, dirEntries, ...dataBuffers])\n}\n\n// ─── Dev badge helpers ──────────────────────────────────────────────────────\n\n/**\n * Add a \"DEV\" badge overlay to an SVG string.\n * Adds a small colored circle with \"DEV\" text in the bottom-right corner.\n */\nfunction addDevBadgeToSvg(svg: string): string {\n const viewBoxMatch = svg.match(/viewBox=\"([^\"]*)\"/)\n const viewBox = viewBoxMatch?.[1] ?? '0 0 32 32'\n const [, , w, h] = viewBox.split(' ').map(Number)\n const size = Math.min(w ?? 32, h ?? 32)\n const r = size * 0.28\n const cx = (w ?? 32) - r\n const cy = (h ?? 32) - r\n const fontSize = r * 0.85\n\n const badge = `<circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"#ef4444\" stroke=\"white\" stroke-width=\"${size * 0.03}\"/>` +\n `<text x=\"${cx}\" y=\"${cy}\" font-size=\"${fontSize}\" font-weight=\"bold\" fill=\"white\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"sans-serif\">D</text>`\n\n // Insert badge before closing </svg>\n return svg.replace(/<\\/svg>\\s*$/, `${badge}</svg>`)\n}\n\n/**\n * Add a \"DEV\" badge to a PNG buffer via sharp composite.\n * Composites a red circle with \"D\" in the bottom-right corner.\n */\nasync function addDevBadgeToPng(pngBuffer: Uint8Array, size: number): Promise<Uint8Array> {\n try {\n const sharp = await import('sharp').then((m) => m.default ?? m)\n const r = Math.round(size * 0.28)\n const d = r * 2\n const fontSize = Math.round(r * 0.85)\n\n const badgeSvg = `<svg width=\"${d}\" height=\"${d}\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"${r}\" cy=\"${r}\" r=\"${r}\" fill=\"#ef4444\"/>\n <text x=\"${r}\" y=\"${r}\" font-size=\"${fontSize}\" font-weight=\"bold\" fill=\"white\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"sans-serif\">D</text>\n </svg>`\n\n const badgePng = await sharp(Buffer.from(badgeSvg)).png().toBuffer()\n\n return await (sharp(Buffer.from(pngBuffer)) as any)\n .composite([{\n input: badgePng,\n gravity: 'southeast',\n }])\n .png()\n .toBuffer()\n } catch {\n // sharp not available — return original\n return pngBuffer\n }\n}\n","import type { Middleware } from '@pyreon/server'\nimport type { Plugin } from 'vite'\n\n// ─── SEO utilities ──────────────────────────────────────────────────────────\n//\n// Zero provides built-in SEO tooling:\n// - Automatic sitemap.xml generation from file-based routes\n// - Configurable robots.txt\n// - Structured data (JSON-LD) helpers\n// - Open Graph / Twitter Card meta helpers\n\nexport interface SitemapConfig {\n /** Base URL of the site (required). e.g. \"https://example.com\" */\n origin: string\n /** Paths to exclude from the sitemap. */\n exclude?: string[]\n /** Default change frequency. Default: \"weekly\" */\n changefreq?: ChangeFreq\n /** Default priority. Default: 0.7 */\n priority?: number\n /** Additional URLs to include (for dynamic routes). */\n additionalPaths?: SitemapEntry[]\n}\n\nexport interface SitemapEntry {\n path: string\n changefreq?: ChangeFreq\n priority?: number\n lastmod?: string\n}\n\nexport type ChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n\n/**\n * Generate a sitemap.xml string from route file paths.\n */\nexport function generateSitemap(routeFiles: string[], config: SitemapConfig): string {\n const { origin, exclude = [], changefreq = 'weekly', priority = 0.7 } = config\n\n const paths = routeFiles\n .filter((f) => {\n // Exclude layout, error, loading files\n const name = f\n .split('/')\n .pop()\n ?.replace(/\\.\\w+$/, '')\n return name !== '_layout' && name !== '_error' && name !== '_loading'\n })\n .map((f) => {\n // Convert file path to URL\n let path = f\n .replace(/\\.\\w+$/, '')\n .replace(/\\/index$/, '/')\n .replace(/^index$/, '/')\n\n // Skip dynamic routes — they need additionalPaths\n if (path.includes('[')) return null\n\n // Strip route groups\n path = path.replace(/\\([\\w-]+\\)\\//g, '')\n\n if (!path.startsWith('/')) path = `/${path}`\n return path\n })\n .filter((p): p is string => p !== null)\n .filter((p) => !exclude.some((e) => p.startsWith(e)))\n\n const allPaths: SitemapEntry[] = [\n ...paths.map((p) => ({ path: p, changefreq, priority })),\n ...(config.additionalPaths ?? []),\n ]\n\n const entries = allPaths\n .map((entry) => {\n const loc = `${origin}${entry.path === '/' ? '' : entry.path}`\n return ` <url>\n <loc>${escapeXml(loc)}</loc>\n <changefreq>${entry.changefreq ?? changefreq}</changefreq>\n <priority>${entry.priority ?? priority}</priority>${entry.lastmod ? `\\n <lastmod>${entry.lastmod}</lastmod>` : ''}\n </url>`\n })\n .join('\\n')\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries}\n</urlset>`\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n// ─── Robots.txt ─────────────────────────────────────────────────────────────\n\nexport interface RobotsConfig {\n /** Rules per user-agent. */\n rules?: RobotsRule[]\n /** Sitemap URL. */\n sitemap?: string\n /** Host directive. */\n host?: string\n}\n\nexport interface RobotsRule {\n userAgent: string\n allow?: string[]\n disallow?: string[]\n crawlDelay?: number\n}\n\n/**\n * Generate a robots.txt string.\n */\nexport function generateRobots(config: RobotsConfig = {}): string {\n const { rules = [{ userAgent: '*', allow: ['/'] }], sitemap, host } = config\n const lines: string[] = []\n\n for (const rule of rules) {\n lines.push(`User-agent: ${rule.userAgent}`)\n if (rule.allow) {\n for (const path of rule.allow) lines.push(`Allow: ${path}`)\n }\n if (rule.disallow) {\n for (const path of rule.disallow) lines.push(`Disallow: ${path}`)\n }\n if (rule.crawlDelay) lines.push(`Crawl-delay: ${rule.crawlDelay}`)\n lines.push('')\n }\n\n if (sitemap) lines.push(`Sitemap: ${sitemap}`)\n if (host) lines.push(`Host: ${host}`)\n\n return lines.join('\\n')\n}\n\n// ─── Structured data (JSON-LD) ──────────────────────────────────────────────\n\nexport type JsonLdType =\n | 'WebSite'\n | 'WebPage'\n | 'Article'\n | 'BlogPosting'\n | 'Product'\n | 'Organization'\n | 'Person'\n | 'BreadcrumbList'\n | 'FAQPage'\n | (string & {})\n\n/**\n * Generate a JSON-LD script tag string for structured data.\n *\n * @example\n * useHead({\n * script: [jsonLd({\n * \"@type\": \"WebSite\",\n * name: \"My Site\",\n * url: \"https://example.com\",\n * })],\n * })\n */\nexport function jsonLd(data: Record<string, unknown>): string {\n const ld = {\n '@context': 'https://schema.org',\n ...data,\n }\n return `<script type=\"application/ld+json\">${JSON.stringify(ld)}</script>`\n}\n\n// ─── SEO Vite plugin ────────────────────────────────────────────────────────\n\nexport interface SeoPluginConfig {\n /** Sitemap configuration. */\n sitemap?: SitemapConfig\n /** Robots.txt configuration. */\n robots?: RobotsConfig\n}\n\n/**\n * Zero SEO Vite plugin.\n * Generates sitemap.xml and robots.txt at build time.\n *\n * @example\n * import { seoPlugin } from \"@pyreon/zero/seo\"\n *\n * export default {\n * plugins: [\n * pyreon(),\n * zero(),\n * seoPlugin({\n * sitemap: { origin: \"https://example.com\" },\n * robots: { sitemap: \"https://example.com/sitemap.xml\" },\n * }),\n * ],\n * }\n */\nexport function seoPlugin(config: SeoPluginConfig = {}): Plugin {\n return {\n name: 'pyreon-zero-seo',\n apply: 'build',\n\n async generateBundle(_, _bundle) {\n // Generate sitemap.xml\n if (config.sitemap) {\n const { scanRouteFiles } = await import('./fs-router')\n const routesDir = `${process.cwd()}/src/routes`\n\n try {\n const files = await scanRouteFiles(routesDir)\n const sitemap = generateSitemap(files, config.sitemap)\n\n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n })\n } catch {\n // Sitemap generation failed — skip silently\n }\n }\n\n // Generate robots.txt\n if (config.robots) {\n const robots = generateRobots(config.robots)\n\n this.emitFile({\n type: 'asset',\n fileName: 'robots.txt',\n source: robots,\n })\n }\n },\n }\n}\n\n// ─── SEO middleware (serve sitemap/robots in dev) ────────────────────────────\n\n/**\n * SEO middleware for dev server.\n * Serves sitemap.xml and robots.txt dynamically during development.\n */\nexport function seoMiddleware(config: SeoPluginConfig = {}): Middleware {\n return async (ctx) => {\n if (ctx.url.pathname === '/robots.txt' && config.robots) {\n return new Response(generateRobots(config.robots), {\n headers: { 'Content-Type': 'text/plain' },\n })\n }\n\n if (ctx.url.pathname === '/sitemap.xml' && config.sitemap) {\n try {\n const { scanRouteFiles } = await import('./fs-router')\n const routesDir = `${process.cwd()}/src/routes`\n const files = await scanRouteFiles(routesDir)\n const sitemap = generateSitemap(files, config.sitemap)\n\n return new Response(sitemap, {\n headers: { 'Content-Type': 'application/xml' },\n })\n } catch {\n // Sitemap generation failed — continue to rendering\n }\n }\n }\n}\n","/**\n * OG Image generation plugin.\n *\n * Generates Open Graph images at build time from templates with\n * text overlays. Supports locale-specific text for i18n apps.\n * Uses sharp for image processing (same optional dep as favicon/image plugins).\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { ogImagePlugin } from \"@pyreon/zero/og-image\"\n *\n * export default {\n * plugins: [\n * ogImagePlugin({\n * locales: [\"en\", \"de\", \"cs\"],\n * templates: [{\n * name: \"default\",\n * background: \"./src/assets/og-bg.jpg\",\n * layers: [{\n * text: { en: \"Build faster\", de: \"Schneller bauen\", cs: \"Stavte rychleji\" },\n * y: \"40%\",\n * fontSize: 72,\n * }],\n * }],\n * }),\n * ],\n * }\n * ```\n */\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Plugin } from 'vite'\n\nlet sharpWarned = false\nfunction warnSharpMissing() {\n if (sharpWarned) return\n sharpWarned = true\n // oxlint-disable-next-line no-console\n console.warn(\n '\\n[Pyreon] sharp not installed — OG images will not be generated. Install for full support: bun add -D sharp\\n',\n )\n}\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface OgImageLayer {\n /**\n * Text content. Can be:\n * - A string (same for all locales)\n * - A record mapping locale → text\n * - A function receiving locale and returning text\n */\n text: string | Record<string, string> | ((locale: string) => string)\n /** X position — number (px) or string with % (e.g. \"50%\"). Default: \"50%\" */\n x?: number | string\n /** Y position — number (px) or string with % (e.g. \"40%\"). Default: \"50%\" */\n y?: number | string\n /** Font size in px. Default: 64 */\n fontSize?: number\n /** Font family. Default: \"sans-serif\" */\n fontFamily?: string\n /** Font weight. Default: \"bold\" */\n fontWeight?: string\n /** Text color. Default: \"#ffffff\" */\n color?: string\n /** Text anchor (alignment). Default: \"middle\" */\n textAnchor?: 'start' | 'middle' | 'end'\n /** Max width in px before wrapping. Default: 80% of image width. */\n maxWidth?: number\n}\n\nexport interface OgImageTemplate {\n /** Template name — used for output file naming. */\n name: string\n /**\n * Background: path to an image file, or a solid color config.\n *\n * @example \"./src/assets/og-bg.jpg\"\n * @example { color: \"#0066ff\", width: 1200, height: 630 }\n */\n background: string | { color: string; width?: number; height?: number }\n /** Output width. Default: 1200 */\n width?: number\n /** Output height. Default: 630 */\n height?: number\n /** Output format. Default: \"png\" */\n format?: 'png' | 'jpeg'\n /** JPEG quality (1-100). Default: 90 */\n quality?: number\n /** Text layers to overlay on the background. */\n layers?: OgImageLayer[]\n}\n\nexport interface OgImagePluginConfig {\n /** Templates to generate. */\n templates: OgImageTemplate[]\n /** Locales to generate for. When omitted, generates a single image per template. */\n locales?: string[]\n /** Output directory prefix. Default: \"og\" */\n outDir?: string\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction resolvePosition(value: number | string | undefined, dimension: number, fallback = '50%'): number {\n if (value === undefined) value = fallback\n if (typeof value === 'number') return value\n if (value.endsWith('%')) return Math.round((Number.parseFloat(value) / 100) * dimension)\n return Number.parseInt(value, 10) || 0\n}\n\nfunction resolveLayerText(layer: OgImageLayer, locale: string): string {\n if (typeof layer.text === 'string') return layer.text\n if (typeof layer.text === 'function') return layer.text(locale)\n return layer.text[locale] ?? layer.text[Object.keys(layer.text)[0] ?? ''] ?? ''\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Build an SVG overlay with text layers.\n * @internal Exported for testing.\n */\nexport function buildTextOverlaySvg(\n layers: OgImageLayer[],\n width: number,\n height: number,\n locale: string,\n): string {\n const textElements = layers.map((layer) => {\n const text = resolveLayerText(layer, locale)\n const x = resolvePosition(layer.x, width, '50%')\n const y = resolvePosition(layer.y, height, '50%')\n const fontSize = layer.fontSize ?? 64\n const fontFamily = layer.fontFamily ?? 'sans-serif'\n const fontWeight = layer.fontWeight ?? 'bold'\n const color = layer.color ?? '#ffffff'\n const anchor = layer.textAnchor ?? 'middle'\n const maxWidth = layer.maxWidth ?? Math.round(width * 0.8)\n\n // Word wrapping via tspan elements.\n // Width estimation: Latin chars ~0.55em, CJK chars ~1.0em, narrow chars ~0.35em.\n const words = text.split(' ')\n const lines: string[] = []\n let currentLine = ''\n\n const estimateWidth = (s: string): number => {\n let width = 0\n for (let i = 0; i < s.length; i++) {\n const code = s.charCodeAt(i)\n if (code >= 0x3000 && code <= 0x9FFF) {\n // CJK characters — full width\n width += fontSize * 1.0\n } else if (code <= 0x7E && 'iljft!|:;.,\\''.includes(s[i]!)) {\n // Narrow Latin characters\n width += fontSize * 0.35\n } else {\n // Regular Latin characters\n width += fontSize * 0.55\n }\n }\n return width\n }\n\n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word\n if (estimateWidth(testLine) > maxWidth && currentLine) {\n lines.push(currentLine)\n currentLine = word\n } else {\n currentLine = testLine\n }\n }\n if (currentLine) lines.push(currentLine)\n\n const tspans = lines\n .map((line, i) => {\n const dy = i === 0 ? '0' : `${fontSize * 1.2}`\n return `<tspan x=\"${x}\" dy=\"${dy}\">${escapeXml(line)}</tspan>`\n })\n .join('')\n\n return `<text x=\"${x}\" y=\"${y}\" font-size=\"${fontSize}\" font-family=\"${escapeXml(fontFamily)}\" font-weight=\"${fontWeight}\" fill=\"${color}\" text-anchor=\"${anchor}\" dominant-baseline=\"middle\">${tspans}</text>`\n })\n\n return `<svg width=\"${width}\" height=\"${height}\" xmlns=\"http://www.w3.org/2000/svg\">${textElements.join('')}</svg>`\n}\n\n/**\n * Render an OG image from a template for a specific locale.\n * @internal Exported for testing.\n */\nexport async function renderOgImage(\n template: OgImageTemplate,\n locale: string,\n rootDir: string,\n): Promise<Uint8Array | null> {\n try {\n const sharp = await import('sharp').then((m) => m.default ?? m)\n const width = template.width ?? 1200\n const height = template.height ?? 630\n\n let pipeline: any\n if (typeof template.background === 'string') {\n const bgPath = join(rootDir, template.background)\n pipeline = sharp(bgPath).resize(width, height, { fit: 'cover' })\n } else {\n pipeline = (sharp as any)({\n create: {\n width,\n height,\n channels: 4,\n background: template.background.color,\n },\n })\n }\n\n // Overlay text layers if any\n if (template.layers && template.layers.length > 0) {\n const svgOverlay = buildTextOverlaySvg(template.layers, width, height, locale)\n pipeline = pipeline.composite([{\n input: Buffer.from(svgOverlay),\n top: 0,\n left: 0,\n }])\n }\n\n if (template.format === 'jpeg') {\n return await pipeline.jpeg({ quality: template.quality ?? 90 }).toBuffer()\n }\n return await pipeline.png().toBuffer()\n } catch {\n warnSharpMissing()\n return null\n }\n}\n\n// ─── Path utility ───────────────────────────────────────────────────────────\n\n/**\n * Compute the OG image path for a template and locale.\n *\n * @example\n * ```ts\n * ogImagePath(\"default\", \"de\") // → \"/og/default-de.png\"\n * ogImagePath(\"default\") // → \"/og/default.png\"\n * ogImagePath(\"hero\", \"en\", \"images\") // → \"/images/hero-en.png\"\n * ```\n */\nexport function ogImagePath(\n templateName: string,\n locale?: string,\n outDir = 'og',\n format: 'png' | 'jpeg' = 'png',\n): string {\n const ext = format === 'jpeg' ? 'jpg' : 'png'\n const suffix = locale ? `-${locale}` : ''\n return `/${outDir}/${templateName}${suffix}.${ext}`\n}\n\n// ─── Vite plugin ────────────────────────────────────────────────────────────\n\n/**\n * OG image generation Vite plugin.\n *\n * Generates Open Graph images at build time. In dev, generates on-demand.\n * Requires `sharp` as an optional dependency.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { ogImagePlugin } from \"@pyreon/zero/og-image\"\n *\n * export default {\n * plugins: [\n * ogImagePlugin({\n * locales: [\"en\", \"de\"],\n * templates: [{\n * name: \"default\",\n * background: { color: \"#0066ff\" },\n * layers: [{ text: { en: \"Hello\", de: \"Hallo\" }, fontSize: 72 }],\n * }],\n * }),\n * ],\n * }\n * ```\n */\nexport function ogImagePlugin(config: OgImagePluginConfig): Plugin {\n const outDir = config.outDir ?? 'og'\n let root = ''\n let isBuild = false\n\n return {\n name: 'pyreon-zero-og-image',\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n root = resolvedConfig.root\n isBuild = resolvedConfig.command === 'build'\n },\n\n // Dev: generate on-demand\n configureServer(server) {\n const devCache = new Map<string, Uint8Array>()\n\n server.middlewares.use(async (req, res, next) => {\n const url = req.url ?? ''\n if (!url.startsWith(`/${outDir}/`)) return next()\n\n // Parse: /og/default-en.png → template=default, locale=en\n const fileName = url.slice(outDir.length + 2) // strip /{outDir}/\n const match = fileName.match(/^(.+?)(?:-([a-z]{2,5}))?\\.(png|jpe?g)$/)\n if (!match) return next()\n\n const [, templateName, locale, ext] = match\n const template = config.templates.find((t) => t.name === templateName)\n if (!template) return next()\n\n const resolvedLocale = locale ?? config.locales?.[0] ?? 'en'\n const cacheKey = `${templateName}:${resolvedLocale}`\n\n let buffer = devCache.get(cacheKey)\n if (!buffer) {\n const result = await renderOgImage(template, resolvedLocale, root)\n if (!result) return next()\n buffer = result\n devCache.set(cacheKey, result)\n }\n\n const contentType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : 'image/png'\n res.setHeader('Content-Type', contentType)\n res.setHeader('Cache-Control', 'no-cache')\n res.end(Buffer.from(buffer))\n })\n },\n\n // Build: generate all variants\n async generateBundle() {\n if (!isBuild) return\n\n for (const template of config.templates) {\n const locales = config.locales ?? [undefined]\n const format = template.format ?? 'png'\n const ext = format === 'jpeg' ? 'jpg' : 'png'\n\n for (const locale of locales) {\n // Validate background exists if it's a file path\n if (typeof template.background === 'string') {\n const bgPath = join(root, template.background)\n if (!existsSync(bgPath)) {\n // oxlint-disable-next-line no-console\n console.warn(`[Pyreon] Background not found: ${bgPath}`)\n continue\n }\n }\n\n const buffer = await renderOgImage(template, locale ?? 'en', root)\n if (!buffer) continue\n\n const suffix = locale ? `-${locale}` : ''\n this.emitFile({\n type: 'asset',\n fileName: `${outDir}/${template.name}${suffix}.${ext}`,\n source: buffer,\n })\n }\n }\n },\n }\n}\n","/**\n * AI integration utilities for Zero.\n *\n * - llms.txt auto-generation from routes and API routes\n * - JSON-LD auto-inference from route meta + Meta props\n * - AI plugin manifest (/.well-known/ai-plugin.json) from API routes\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { aiPlugin } from \"@pyreon/zero/ai\"\n *\n * export default {\n * plugins: [\n * aiPlugin({\n * name: \"My App\",\n * origin: \"https://example.com\",\n * description: \"A modern web application\",\n * }),\n * ],\n * }\n * ```\n */\nimport type { Plugin } from 'vite'\nimport { parseFileRoutes } from './fs-router'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface AiPluginConfig {\n /** App/API name. */\n name: string\n /** App description for AI agents. */\n description: string\n /** Base URL. e.g. \"https://example.com\" */\n origin: string\n /** Contact email (required by OpenAI plugin spec). */\n contactEmail?: string\n /** Legal info URL. */\n legalUrl?: string\n /** Logo URL for the plugin. */\n logoUrl?: string\n /** Routes directory relative to project root. Default: \"src/routes\" */\n routesDir?: string\n /** API routes directory relative to project root. Default: \"src/api\" */\n apiDir?: string\n /**\n * API route descriptions — map of pattern to description.\n * Used for llms.txt and ai-plugin.json.\n *\n * @example\n * ```ts\n * apiDescriptions: {\n * \"GET /api/posts\": \"List all blog posts, supports ?page=N&limit=N\",\n * \"GET /api/posts/:id\": \"Get a single post by ID\",\n * \"POST /api/posts\": \"Create a new post (requires auth)\",\n * }\n * ```\n */\n apiDescriptions?: Record<string, string>\n /**\n * Page descriptions — map of URL path to description.\n * Used for llms.txt. Falls back to route meta.title/description.\n */\n pageDescriptions?: Record<string, string>\n /**\n * Additional content to append to llms.txt.\n * Useful for authentication instructions, rate limits, etc.\n */\n llmsExtra?: string\n}\n\n// ─── llms.txt generation ────────────────────────────────────────────────────\n\n/**\n * Generate llms.txt content from route files and config.\n *\n * Format follows the llms.txt proposal:\n * ```\n * # {name}\n * > {description}\n *\n * ## Pages\n * - [/about](/about): About page\n *\n * ## API\n * - GET /api/posts: List posts\n * ```\n *\n * @internal Exported for testing.\n */\nexport function generateLlmsTxt(\n routeFiles: string[],\n apiFiles: string[],\n config: AiPluginConfig,\n): string {\n const lines: string[] = []\n\n // Header\n lines.push(`# ${config.name}`)\n lines.push(`> ${config.description}`)\n lines.push('')\n\n // Pages section\n const routes = parseFileRoutes(routeFiles)\n const pages = routes.filter(\n (r) => !r.isLayout && !r.isError && !r.isLoading && !r.isNotFound\n && !r.isCatchAll && !r.urlPath.includes(':'),\n )\n\n if (pages.length > 0) {\n lines.push('## Pages')\n lines.push('')\n for (const page of pages) {\n const desc = config.pageDescriptions?.[page.urlPath]\n const url = `${config.origin}${page.urlPath === '/' ? '' : page.urlPath}`\n if (desc) {\n lines.push(`- [${page.urlPath}](${url}): ${desc}`)\n } else {\n lines.push(`- [${page.urlPath}](${url})`)\n }\n }\n lines.push('')\n }\n\n // Dynamic routes (documented separately — AI needs to know about params)\n const dynamicRoutes = routes.filter(\n (r) => !r.isLayout && !r.isError && !r.isLoading && !r.isNotFound\n && (r.urlPath.includes(':') || r.isCatchAll),\n )\n if (dynamicRoutes.length > 0) {\n lines.push('## Dynamic Pages')\n lines.push('')\n for (const route of dynamicRoutes) {\n const desc = config.pageDescriptions?.[route.urlPath]\n if (desc) {\n lines.push(`- ${route.urlPath}: ${desc}`)\n } else {\n lines.push(`- ${route.urlPath}`)\n }\n }\n lines.push('')\n }\n\n // API section\n const apiPatterns = parseApiFiles(apiFiles)\n if (apiPatterns.length > 0 || config.apiDescriptions) {\n lines.push('## API Endpoints')\n lines.push('')\n\n // From apiDescriptions (most detailed — user-provided)\n if (config.apiDescriptions) {\n for (const [endpoint, desc] of Object.entries(config.apiDescriptions)) {\n lines.push(`- ${endpoint}: ${desc}`)\n }\n }\n\n // From auto-discovered API files (only those not already described)\n const describedPatterns = new Set(\n Object.keys(config.apiDescriptions ?? {}).map((k) => k.replace(/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\s+/, '')),\n )\n for (const pattern of apiPatterns) {\n if (!describedPatterns.has(pattern)) {\n lines.push(`- ${pattern}`)\n }\n }\n lines.push('')\n }\n\n // Extra content\n if (config.llmsExtra) {\n lines.push(config.llmsExtra)\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Generate llms-full.txt — expanded version with more detail.\n * Includes all route metadata and API descriptions.\n *\n * @internal Exported for testing.\n */\nexport function generateLlmsFullTxt(\n routeFiles: string[],\n apiFiles: string[],\n config: AiPluginConfig,\n): string {\n const lines: string[] = []\n\n lines.push(`# ${config.name} — Full Reference`)\n lines.push(`> ${config.description}`)\n lines.push('')\n lines.push(`Base URL: ${config.origin}`)\n lines.push('')\n\n // All pages with details\n const routes = parseFileRoutes(routeFiles)\n const pages = routes.filter(\n (r) => !r.isLayout && !r.isError && !r.isLoading && !r.isNotFound,\n )\n\n if (pages.length > 0) {\n lines.push('## All Routes')\n lines.push('')\n for (const page of pages) {\n const desc = config.pageDescriptions?.[page.urlPath] ?? ''\n const dynamic = page.urlPath.includes(':') ? ' (dynamic)' : ''\n const catchAll = page.isCatchAll ? ' (catch-all)' : ''\n lines.push(`### ${page.urlPath}${dynamic}${catchAll}`)\n if (desc) lines.push(desc)\n lines.push(`- File: ${page.filePath}`)\n lines.push(`- Render mode: ${page.renderMode}`)\n lines.push('')\n }\n }\n\n // API endpoints with full detail\n if (config.apiDescriptions) {\n lines.push('## API Reference')\n lines.push('')\n for (const [endpoint, desc] of Object.entries(config.apiDescriptions)) {\n lines.push(`### ${endpoint}`)\n lines.push(desc)\n lines.push('')\n }\n }\n\n if (config.llmsExtra) {\n lines.push('## Additional Information')\n lines.push('')\n lines.push(config.llmsExtra)\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n\n// ─── JSON-LD auto-inference ─────────────────────────────────────────────────\n\nexport interface InferJsonLdOptions {\n /** Page URL. */\n url: string\n /** Page title. */\n title?: string\n /** Page description. */\n description?: string\n /** Page image. */\n image?: string\n /** Site name. */\n siteName?: string\n /** Page type hint. */\n type?: 'website' | 'article' | 'product' | 'profile'\n /** Article metadata. */\n publishedTime?: string\n /** Article author. */\n author?: string\n /** Article tags. */\n tags?: string[]\n /** Breadcrumb path segments. */\n breadcrumbs?: Array<{ name: string; url: string }>\n}\n\n/**\n * Auto-infer JSON-LD structured data from page metadata.\n *\n * Returns an array of JSON-LD objects (multiple schemas can apply to one page).\n * For example, an article page gets both `Article` and `BreadcrumbList`.\n *\n * @example\n * ```tsx\n * const schemas = inferJsonLd({\n * url: \"https://example.com/blog/my-post\",\n * title: \"My Post\",\n * description: \"A great article\",\n * type: \"article\",\n * author: \"Vit Bokisch\",\n * publishedTime: \"2026-03-31\",\n * })\n * // → [Article schema, BreadcrumbList schema]\n * ```\n */\nexport function inferJsonLd(options: InferJsonLdOptions): Record<string, unknown>[] {\n const schemas: Record<string, unknown>[] = []\n\n // Base: WebPage or Article\n if (options.type === 'article') {\n const article: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: options.title,\n url: options.url,\n }\n if (options.description) article.description = options.description\n if (options.image) article.image = options.image\n if (options.publishedTime) article.datePublished = options.publishedTime\n if (options.author) {\n article.author = { '@type': 'Person', name: options.author }\n }\n if (options.tags && options.tags.length > 0) {\n article.keywords = options.tags.join(', ')\n }\n if (options.siteName) {\n article.publisher = { '@type': 'Organization', name: options.siteName }\n }\n schemas.push(article)\n } else if (options.type === 'product') {\n const product: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: options.title,\n url: options.url,\n }\n if (options.description) product.description = options.description\n if (options.image) product.image = options.image\n schemas.push(product)\n } else {\n const webpage: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'WebPage',\n name: options.title,\n url: options.url,\n }\n if (options.description) webpage.description = options.description\n if (options.image) webpage.thumbnailUrl = options.image\n schemas.push(webpage)\n }\n\n // BreadcrumbList from URL path or explicit breadcrumbs\n if (options.breadcrumbs && options.breadcrumbs.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: options.breadcrumbs.map((bc, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: bc.name,\n item: bc.url,\n })),\n })\n } else {\n // Auto-generate breadcrumbs from URL path\n const urlObj = safeParseUrl(options.url)\n if (urlObj) {\n const segments = urlObj.pathname.split('/').filter(Boolean)\n if (segments.length > 0) {\n const items = [\n { '@type': 'ListItem', position: 1, name: 'Home', item: urlObj.origin },\n ]\n let path = ''\n for (let i = 0; i < segments.length; i++) {\n path += `/${segments[i]}`\n items.push({\n '@type': 'ListItem',\n position: i + 2,\n name: capitalize(segments[i]!.replace(/-/g, ' ')),\n item: `${urlObj.origin}${path}`,\n })\n }\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items,\n })\n }\n }\n }\n\n return schemas\n}\n\n// ─── AI plugin manifest ─────────────────────────────────────────────────────\n\n/**\n * Generate an OpenAI-compatible AI plugin manifest.\n *\n * Follows the /.well-known/ai-plugin.json spec.\n *\n * @internal Exported for testing.\n */\nexport function generateAiPluginManifest(config: AiPluginConfig): Record<string, unknown> {\n return {\n schema_version: 'v1',\n name_for_human: config.name,\n name_for_model: config.name.toLowerCase().replace(/\\s+/g, '_').replace(/[^a-z0-9_]/g, ''),\n description_for_human: config.description,\n description_for_model: config.description,\n auth: { type: 'none' },\n api: {\n type: 'openapi',\n url: `${config.origin}/.well-known/openapi.yaml`,\n },\n logo_url: config.logoUrl ?? `${config.origin}/favicon.svg`,\n contact_email: config.contactEmail ?? '',\n legal_info_url: config.legalUrl ?? `${config.origin}/legal`,\n }\n}\n\n/**\n * Generate a minimal OpenAPI 3.0 spec from API route descriptions.\n *\n * @internal Exported for testing.\n */\nexport function generateOpenApiSpec(\n apiFiles: string[],\n config: AiPluginConfig,\n): Record<string, unknown> {\n const paths: Record<string, Record<string, unknown>> = {}\n\n // From user-provided descriptions\n if (config.apiDescriptions) {\n for (const [endpoint, desc] of Object.entries(config.apiDescriptions)) {\n const match = endpoint.match(/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\s+(.+)$/)\n if (match) {\n const method = match[1]!.toLowerCase()\n const path = match[2]!\n // Convert :param to {param} for OpenAPI\n const openApiPath = path.replace(/:(\\w+)/g, '{$1}')\n if (!paths[openApiPath]) paths[openApiPath] = {}\n paths[openApiPath][method] = {\n summary: desc,\n responses: { '200': { description: 'Success' } },\n }\n }\n }\n }\n\n // Auto-discovered API files (fill in gaps)\n for (const pattern of parseApiFiles(apiFiles)) {\n const openApiPath = pattern.replace(/:(\\w+)/g, '{$1}')\n if (!paths[openApiPath]) {\n paths[openApiPath] = {\n get: {\n summary: `${openApiPath} endpoint`,\n responses: { '200': { description: 'Success' } },\n },\n }\n }\n }\n\n return {\n openapi: '3.0.0',\n info: {\n title: config.name,\n description: config.description,\n version: '1.0.0',\n },\n servers: [{ url: config.origin }],\n paths,\n }\n}\n\n// ─── Vite plugin ────────────────────────────────────────────────────────────\n\n/**\n * AI integration Vite plugin.\n *\n * Generates at build time:\n * - `/llms.txt` — concise site summary for AI agents\n * - `/llms-full.txt` — detailed reference for AI agents\n * - `/.well-known/ai-plugin.json` — OpenAI plugin manifest\n * - `/.well-known/openapi.yaml` — minimal OpenAPI spec from API routes\n *\n * In dev, serves these files via middleware.\n *\n * @example\n * ```ts\n * import { aiPlugin } from \"@pyreon/zero/ai\"\n *\n * export default {\n * plugins: [\n * aiPlugin({\n * name: \"My App\",\n * origin: \"https://example.com\",\n * description: \"A modern web application\",\n * apiDescriptions: {\n * \"GET /api/posts\": \"List blog posts\",\n * \"GET /api/posts/:id\": \"Get post by ID\",\n * },\n * }),\n * ],\n * }\n * ```\n */\nexport function aiPlugin(config: AiPluginConfig): Plugin {\n let root = ''\n let isBuild = false\n let routeFiles: string[] = []\n let apiFiles: string[] = []\n\n return {\n name: 'pyreon-zero-ai',\n enforce: 'post',\n\n configResolved(resolvedConfig) {\n root = resolvedConfig.root\n isBuild = resolvedConfig.command === 'build'\n },\n\n async buildStart() {\n // Scan for route and API files\n try {\n const { join } = await import('node:path')\n\n const routesDir = join(root, config.routesDir ?? 'src/routes')\n const apiDir = join(root, config.apiDir ?? 'src/api')\n\n routeFiles = await scanDir(routesDir, routesDir)\n apiFiles = await scanDir(apiDir, apiDir)\n } catch {\n // Directories may not exist\n }\n },\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const url = req.url ?? ''\n\n if (url === '/llms.txt') {\n res.setHeader('Content-Type', 'text/plain; charset=utf-8')\n res.end(generateLlmsTxt(routeFiles, apiFiles, config))\n return\n }\n\n if (url === '/llms-full.txt') {\n res.setHeader('Content-Type', 'text/plain; charset=utf-8')\n res.end(generateLlmsFullTxt(routeFiles, apiFiles, config))\n return\n }\n\n if (url === '/.well-known/ai-plugin.json') {\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(generateAiPluginManifest(config), null, 2))\n return\n }\n\n if (url === '/.well-known/openapi.yaml' || url === '/.well-known/openapi.json') {\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(generateOpenApiSpec(apiFiles, config), null, 2))\n return\n }\n\n next()\n })\n },\n\n async generateBundle() {\n if (!isBuild) return\n\n this.emitFile({\n type: 'asset',\n fileName: 'llms.txt',\n source: generateLlmsTxt(routeFiles, apiFiles, config),\n })\n\n this.emitFile({\n type: 'asset',\n fileName: 'llms-full.txt',\n source: generateLlmsFullTxt(routeFiles, apiFiles, config),\n })\n\n this.emitFile({\n type: 'asset',\n fileName: '.well-known/ai-plugin.json',\n source: JSON.stringify(generateAiPluginManifest(config), null, 2),\n })\n\n this.emitFile({\n type: 'asset',\n fileName: '.well-known/openapi.json',\n source: JSON.stringify(generateOpenApiSpec(apiFiles, config), null, 2),\n })\n },\n }\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction parseApiFiles(files: string[]): string[] {\n return files\n .filter((f) => f.endsWith('.ts') || f.endsWith('.js'))\n .map((f) => {\n let path = f.replace(/\\.\\w+$/, '').replace(/\\/index$/, '')\n if (!path.startsWith('/')) path = `/${path}`\n // Convert [param] to :param\n path = path.replace(/\\[\\.\\.\\.(\\w+)\\]/g, ':$1*').replace(/\\[(\\w+)\\]/g, ':$1')\n return `/api${path === '/' ? '' : path}`\n })\n}\n\nasync function scanDir(dir: string, base: string): Promise<string[]> {\n const { readdir, stat } = await import('node:fs/promises')\n const { join, relative } = await import('node:path')\n\n try {\n const entries = await readdir(dir)\n const files: string[] = []\n for (const entry of entries) {\n const full = join(dir, entry)\n const s = await stat(full)\n if (s.isDirectory()) {\n files.push(...(await scanDir(full, base)))\n } else {\n files.push(relative(base, full))\n }\n }\n return files\n } catch {\n return []\n }\n}\n\nfunction safeParseUrl(url: string): URL | null {\n try {\n return new URL(url)\n } catch {\n return null\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n","import { createContext } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { Plugin } from 'vite'\n\n// ─── Localized routing ─────────────────────────────────────────────────────\n//\n// Adds locale-prefixed routes to Zero's file-system router:\n// - /about → /en/about, /de/about, /cs/about\n// - / → /en, /de, /cs (or default locale without prefix)\n// - Automatic locale detection from Accept-Language header\n// - Redirect to preferred locale\n// - hreflang link generation\n//\n// Usage:\n// import { i18nRouting } from \"@pyreon/zero\"\n// export default { plugins: [Pyreon], defaultLocale: \"en\" })] }\n\nexport interface I18nRoutingConfig {\n /** Supported locales. e.g. [\"en\", \"de\", \"cs\"] */\n locales: string[]\n /** Default locale — served without prefix (/ instead of /en/). */\n defaultLocale: string\n /** Redirect root to detected locale. Default: true */\n detectLocale?: boolean\n /** Cookie name to persist locale preference. Default: \"locale\" */\n cookieName?: string\n /** URL strategy. Default: \"prefix-except-default\" */\n strategy?: 'prefix' | 'prefix-except-default'\n}\n\nexport interface LocaleContext {\n /** Current locale code. e.g. \"en\", \"de\" */\n locale: string\n /** All supported locales. */\n locales: string[]\n /** Default locale. */\n defaultLocale: string\n /** Build a localized path. e.g. localePath(\"/about\", \"de\") → \"/de/about\" */\n localePath: (path: string, locale?: string) => string\n /** Get hreflang alternates for the current path. */\n alternates: () => Array<{ locale: string; url: string }>\n}\n\n/**\n * Detect preferred locale from Accept-Language header.\n */\nexport function detectLocaleFromHeader(\n acceptLanguage: string | null | undefined,\n locales: string[],\n defaultLocale: string,\n): string {\n if (!acceptLanguage) return defaultLocale\n\n // Parse Accept-Language: en-US,en;q=0.9,de;q=0.8\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return {\n lang: lang?.split('-')[0]?.toLowerCase() ?? '',\n quality: q ? Number.parseFloat(q) : 1,\n }\n })\n .sort((a, b) => b.quality - a.quality)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n }\n\n return defaultLocale\n}\n\n/**\n * Extract locale from a URL path.\n * Returns { locale, pathWithoutLocale }.\n */\nexport function extractLocaleFromPath(\n path: string,\n locales: string[],\n defaultLocale: string,\n): { locale: string; pathWithoutLocale: string } {\n const segments = path.split('/').filter(Boolean)\n const firstSegment = segments[0]?.toLowerCase()\n\n if (firstSegment && locales.includes(firstSegment)) {\n return {\n locale: firstSegment,\n pathWithoutLocale: '/' + segments.slice(1).join('/') || '/',\n }\n }\n\n return { locale: defaultLocale, pathWithoutLocale: path }\n}\n\n/**\n * Build a localized path.\n */\nexport function buildLocalePath(\n path: string,\n locale: string,\n defaultLocale: string,\n strategy: 'prefix' | 'prefix-except-default',\n): string {\n const clean = path === '/' ? '' : path\n if (strategy === 'prefix-except-default' && locale === defaultLocale) {\n return path\n }\n return `/${locale}${clean}`\n}\n\n/**\n * Create a LocaleContext for use in components and loaders.\n */\nexport function createLocaleContext(\n locale: string,\n path: string,\n config: I18nRoutingConfig,\n): LocaleContext {\n const strategy = config.strategy ?? 'prefix-except-default'\n\n return {\n locale,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n\n localePath(targetPath: string, targetLocale?: string) {\n return buildLocalePath(\n targetPath,\n targetLocale ?? locale,\n config.defaultLocale,\n strategy,\n )\n },\n\n alternates() {\n const { pathWithoutLocale } = extractLocaleFromPath(\n path,\n config.locales,\n config.defaultLocale,\n )\n return config.locales.map((loc) => ({\n locale: loc,\n url: buildLocalePath(pathWithoutLocale, loc, config.defaultLocale, strategy),\n }))\n },\n }\n}\n\n/**\n * I18n routing middleware for Zero's server.\n *\n * - Detects locale from URL prefix or Accept-Language header\n * - Redirects root to preferred locale (when detectLocale is true)\n * - Sets locale context for loaders and components\n *\n * @example\n * ```ts\n * // zero.config.ts\n * import { i18nRouting } from \"@pyreon/zero\"\n *\n * export default defineConfig({\n * plugins: [\n * i18nRouting({\n * locales: [\"en\", \"de\", \"cs\"],\n * defaultLocale: \"en\",\n * }),\n * ],\n * })\n * ```\n */\nexport function i18nRouting(config: I18nRoutingConfig): Plugin {\n const strategy = config.strategy ?? 'prefix-except-default'\n const detectEnabled = config.detectLocale !== false\n const cookieName = config.cookieName ?? 'locale'\n\n return {\n name: 'pyreon-zero-i18n-routing',\n\n // Route duplication is NOT handled here. The fs-router's `scanRouteFiles`\n // consumes the i18n config to duplicate routes per locale at build time.\n // This plugin only provides: (1) the server middleware for locale detection\n // and (2) the runtime hooks (useLocale, setLocale) for client-side use.\n configResolved() {},\n\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '/'\n\n // Skip static assets\n if (url.startsWith('/@') || url.startsWith('/__') || url.includes('.')) {\n return next()\n }\n\n const { locale } = extractLocaleFromPath(\n url,\n config.locales,\n config.defaultLocale,\n )\n\n // Redirect root to detected locale\n if (detectEnabled && url === '/') {\n const cookies = parseCookies(req.headers.cookie)\n const preferredFromCookie = cookies[cookieName]\n const preferredFromHeader = detectLocaleFromHeader(\n req.headers['accept-language'],\n config.locales,\n config.defaultLocale,\n )\n const preferred = preferredFromCookie && config.locales.includes(preferredFromCookie)\n ? preferredFromCookie\n : preferredFromHeader\n\n if (strategy === 'prefix' || preferred !== config.defaultLocale) {\n res.writeHead(302, { Location: `/${preferred}/` })\n res.end()\n return\n }\n }\n\n // Attach locale context to request for loaders\n ;(req as any).__locale = locale\n ;(req as any).__localeContext = createLocaleContext(locale, url, config)\n\n // Update the module-level signal so useLocale() returns the correct value\n localeSignal.set(locale)\n\n next()\n })\n },\n }\n}\n\nfunction parseCookies(header: string | undefined): Record<string, string> {\n if (!header) return {}\n const result: Record<string, string> = {}\n for (const pair of header.split(';')) {\n const [key, value] = pair.trim().split('=')\n if (key && value) result[key] = decodeURIComponent(value)\n }\n return result\n}\n\n// ─── Reactive locale hook ───────────────────────────────────────────────────\n\n/** @internal Context for the current locale. */\nexport const LocaleCtx = createContext<string>('en')\n\n/** Current locale signal — set by the server middleware or client-side detection. */\nexport const localeSignal = signal('en')\n\n/**\n * Read the current locale reactively.\n *\n * Returns the locale signal value directly — reactive in both SSR and CSR.\n * The server middleware sets `localeSignal` per-request, and client-side\n * `setLocale()` updates it as well.\n *\n * @example\n * ```tsx\n * const locale = useLocale() // \"en\", \"de\", etc.\n * ```\n */\nexport function useLocale(): string {\n return localeSignal()\n}\n\n/**\n * Set the locale client-side and update the URL.\n *\n * @example\n * ```tsx\n * <button onClick={() => setLocale('de')}>Deutsch</button>\n * ```\n */\nexport function setLocale(\n locale: string,\n config: I18nRoutingConfig,\n): void {\n localeSignal.set(locale)\n\n // Persist to cookie\n if (typeof document !== 'undefined') {\n document.cookie = `${config.cookieName ?? 'locale'}=${locale}; path=/; max-age=31536000`\n }\n\n // Navigate to localized URL — use pushState to avoid full page reload\n if (typeof window !== 'undefined') {\n const strategy = config.strategy ?? 'prefix-except-default'\n const { pathWithoutLocale } = extractLocaleFromPath(\n window.location.pathname,\n config.locales,\n config.defaultLocale,\n )\n const newPath = buildLocalePath(pathWithoutLocale, locale, config.defaultLocale, strategy)\n window.history.pushState(null, '', newPath)\n // Dispatch popstate so @pyreon/router picks up the URL change\n window.dispatchEvent(new PopStateEvent('popstate'))\n }\n}\n"],"mappings":"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"}
|
package/lib/testing.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","names":[],"sources":["../src/api-routes.ts","../src/testing.ts"],"sourcesContent":["import type { Middleware, MiddlewareContext } from '@pyreon/server'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** HTTP methods supported by API routes. */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'\n\n/** Context passed to API route handlers. */\nexport interface ApiContext {\n /** The incoming request. */\n request: Request\n /** Parsed URL. */\n url: URL\n /** URL path. */\n path: string\n /** Dynamic route parameters (e.g., { id: \"123\" }). */\n params: Record<string, string>\n /** Request headers. */\n headers: Headers\n}\n\n/** An API route handler function. */\nexport type ApiHandler = (ctx: ApiContext) => Response | Promise<Response>\n\n/** An API route module — exports named HTTP method handlers. */\nexport interface ApiRouteModule {\n GET?: ApiHandler\n POST?: ApiHandler\n PUT?: ApiHandler\n PATCH?: ApiHandler\n DELETE?: ApiHandler\n HEAD?: ApiHandler\n OPTIONS?: ApiHandler\n}\n\n/** A registered API route entry. */\nexport interface ApiRouteEntry {\n /** URL pattern (e.g., \"/api/posts/:id\"). */\n pattern: string\n /** The route module with method handlers. */\n module: ApiRouteModule\n}\n\n// ─── Pattern matching ────────────────────────────────────────────────────────\n\n/**\n * Match a URL path against an API route pattern.\n * Returns extracted params or null if no match.\n */\nexport function matchApiRoute(pattern: string, path: string): Record<string, string> | null {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n const params: Record<string, string> = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i]\n if (!pp) continue\n\n // Catch-all: :param*\n if (pp.endsWith('*')) {\n const paramName = pp.slice(1, -1)\n params[paramName] = pathParts.slice(i).join('/')\n return params\n }\n\n // No more path segments\n if (i >= pathParts.length) return null\n\n // Dynamic segment: :param\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = pathParts[i]!\n continue\n }\n\n // Static segment\n if (pp !== pathParts[i]) return null\n }\n\n return patternParts.length === pathParts.length ? params : null\n}\n\n// ─── Middleware ───────────────────────────────────────────────────────────────\n\nconst HTTP_METHODS: HttpMethod[] = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * Create a middleware that dispatches API route requests.\n * API routes are matched by URL pattern and HTTP method.\n */\nexport function createApiMiddleware(routes: ApiRouteEntry[]): Middleware {\n return async (ctx: MiddlewareContext) => {\n for (const route of routes) {\n const params = matchApiRoute(route.pattern, ctx.path)\n if (!params) continue\n\n const method = ctx.req.method.toUpperCase() as HttpMethod\n const handler = route.module[method]\n\n if (!handler) {\n // Route matched but method not supported\n const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(', ')\n return new Response(null, {\n status: 405,\n headers: {\n Allow: allowed,\n 'Content-Type': 'application/json',\n },\n })\n }\n\n return handler({\n request: ctx.req,\n url: ctx.url,\n path: ctx.path,\n params,\n headers: ctx.req.headers,\n })\n }\n }\n}\n\n// ─── Virtual module generation ───────────────────────────────────────────────\n\n/**\n * Detect whether a route file is an API route.\n * API routes are `.ts` or `.js` files inside an `api/` directory.\n */\nexport function isApiRoute(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/')\n return (\n normalized.startsWith('api/') &&\n (normalized.endsWith('.ts') || normalized.endsWith('.js')) &&\n !normalized.endsWith('.tsx') &&\n !normalized.endsWith('.jsx')\n )\n}\n\n/**\n * Convert an API route file path to a URL pattern.\n *\n * Examples:\n * \"api/posts.ts\" → \"/api/posts\"\n * \"api/posts/index.ts\" → \"/api/posts\"\n * \"api/posts/[id].ts\" → \"/api/posts/:id\"\n * \"api/[...path].ts\" → \"/api/:path*\"\n */\nexport function apiFilePathToPattern(filePath: string): string {\n let route = filePath\n // Remove extension\n for (const ext of ['.ts', '.js']) {\n if (route.endsWith(ext)) {\n route = route.slice(0, -ext.length)\n break\n }\n }\n\n const segments = route.split('/')\n const urlSegments: string[] = []\n\n for (const seg of segments) {\n if (seg === 'index') continue\n\n // Catch-all: [...param]\n const catchAll = seg.match(/^\\[\\.\\.\\.(\\w+)\\]$/)\n if (catchAll) {\n urlSegments.push(`:${catchAll[1]}*`)\n continue\n }\n\n // Dynamic: [param]\n const dynamic = seg.match(/^\\[(\\w+)\\]$/)\n if (dynamic) {\n urlSegments.push(`:${dynamic[1]}`)\n continue\n }\n\n urlSegments.push(seg)\n }\n\n return `/${urlSegments.join('/')}`\n}\n\n/**\n * Generate a virtual module that exports API route entries.\n * Each entry maps a URL pattern to a module with HTTP method handlers.\n */\nexport function generateApiRouteModule(files: string[], routesDir: string): string {\n const apiFiles = files.filter(isApiRoute)\n\n if (apiFiles.length === 0) {\n return 'export const apiRoutes = []\\n'\n }\n\n const imports: string[] = []\n const entries: string[] = []\n\n for (let i = 0; i < apiFiles.length; i++) {\n const name = `_api${i}`\n const file = apiFiles[i]\n if (!file) continue\n const fullPath = `${routesDir}/${file}`\n const pattern = apiFilePathToPattern(file)\n\n imports.push(`import * as ${name} from \"${fullPath}\"`)\n entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`)\n }\n\n return [...imports, '', 'export const apiRoutes = [', entries.join(',\\n'), ']'].join('\\n')\n}\n","import type { Middleware, MiddlewareContext } from '@pyreon/server'\nimport type { ApiHandler, ApiRouteEntry } from './api-routes'\nimport { createApiMiddleware } from './api-routes'\n\n// ─── Test helpers for Zero applications ─────────────────────────────────────\n\n/**\n * Create a mock MiddlewareContext for testing middleware.\n *\n * @example\n * import { createTestContext } from \"@pyreon/zero/testing\"\n *\n * const ctx = createTestContext(\"/api/posts\", { method: \"POST\", body: { title: \"Hello\" } })\n * const result = await myMiddleware(ctx)\n */\nexport function createTestContext(\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n): MiddlewareContext {\n const { method = 'GET', headers = {}, body } = options\n const url = new URL(`http://localhost${path}`)\n\n const requestHeaders: Record<string, string> = { ...headers }\n let requestBody: string | undefined\n\n if (body !== undefined) {\n requestHeaders['Content-Type'] = 'application/json'\n requestBody = JSON.stringify(body)\n }\n\n const req = new Request(url.toString(), {\n method,\n headers: requestHeaders,\n ...(requestBody != null ? { body: requestBody } : {}),\n })\n\n return {\n req,\n url,\n path,\n headers: new Headers(),\n locals: {},\n }\n}\n\n/**\n * Test a middleware by running it with a mock context and returning\n * the result along with the response headers it set.\n *\n * @example\n * import { testMiddleware } from \"@pyreon/zero/testing\"\n *\n * const { response, headers } = await testMiddleware(\n * corsMiddleware({ origin: \"*\" }),\n * \"/api/posts\"\n * )\n * expect(headers.get(\"Access-Control-Allow-Origin\")).toBe(\"*\")\n */\nexport async function testMiddleware(\n middleware: Middleware,\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n): Promise<{ response: Response | undefined; headers: Headers }> {\n const ctx = createTestContext(path, options)\n const response = (await middleware(ctx)) as Response | undefined\n return { response, headers: ctx.headers }\n}\n\n/**\n * Create a test server for API routes. Returns a function that\n * accepts Request objects and dispatches to the correct handler.\n *\n * @example\n * import { createTestApiServer } from \"@pyreon/zero/testing\"\n *\n * const server = createTestApiServer([\n * { pattern: \"/api/posts\", module: postsApi },\n * { pattern: \"/api/posts/:id\", module: postByIdApi },\n * ])\n *\n * const response = await server.request(\"/api/posts\")\n * expect(response.status).toBe(200)\n *\n * const data = await server.request(\"/api/posts\", { method: \"POST\", body: { title: \"Hi\" } })\n * expect(data.status).toBe(201)\n */\nexport function createTestApiServer(routes: ApiRouteEntry[]) {\n const middleware = createApiMiddleware(routes)\n\n return {\n async request(\n path: string,\n options: {\n method?: string\n headers?: Record<string, string>\n body?: unknown\n } = {},\n ): Promise<Response> {\n const ctx = createTestContext(path, options)\n const result = await middleware(ctx)\n if (!result) {\n return new Response('Not Found', { status: 404 })\n }\n return result\n },\n }\n}\n\n/**\n * Create a mock API handler for testing.\n * Records all calls and returns a configurable response.\n *\n * @example\n * import { createMockHandler } from \"@pyreon/zero/testing\"\n *\n * const handler = createMockHandler({ status: 200, body: { ok: true } })\n * // ... use handler in your API route module\n * expect(handler.calls).toHaveLength(1)\n * expect(handler.calls[0].params).toEqual({ id: \"123\" })\n */\nexport function createMockHandler(\n responseConfig: { status?: number; body?: unknown; headers?: Record<string, string> } = {},\n): ApiHandler & {\n calls: Array<{ path: string; params: Record<string, string> }>\n} {\n const { status = 200, body = null, headers = {} } = responseConfig\n const calls: Array<{ path: string; params: Record<string, string> }> = []\n\n const handler: ApiHandler = (ctx) => {\n calls.push({ path: ctx.path, params: ctx.params })\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json', ...headers },\n })\n }\n\n return Object.assign(handler, { calls })\n}\n"],"mappings":"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"}
|
package/lib/theme.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../../../core/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/**\n * Reactive signal tracking the OS color-scheme preference. Updated by the\n * `matchMedia('(prefers-color-scheme: dark)').change` event registered in\n * `initTheme`. Components reading `resolvedTheme()` subscribe to BOTH\n * `theme` and this signal, so a user toggling dark mode at the OS level\n * re-renders everything reactively — not just the `<html data-theme>`\n * attribute.\n *\n * SSR default is `_ssrDefault` (mutable via `setSSRThemeDefault`) so the\n * server-rendered theme can differ from the client's OS preference.\n */\nconst _osPrefersDark = signal<boolean>(false)\n\n/** SSR fallback when system preference can't be detected. Default: 'light'. */\nlet _ssrDefault: 'light' | 'dark' = 'light'\n\n/**\n * Set the default theme for SSR (when `matchMedia` is unavailable).\n * Call once at server startup before rendering.\n */\nexport function setSSRThemeDefault(value: 'light' | 'dark'): void {\n _ssrDefault = value\n}\n\n/**\n * Reactive read of the resolved theme. Subscribes to `theme` (explicit\n * user choice) and — when `theme === 'system'` — to `_osPrefersDark`\n * (OS color-scheme preference). Components using `resolvedTheme()`\n * inside JSX / effects / computeds re-render when either changes.\n */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return _ssrDefault\n return _osPrefersDark() ? 'dark' : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes. Seed the signal from the\n // current media-query state, then update reactively on each OS\n // preference flip. Components reading `resolvedTheme()` pick up the\n // change automatically (they subscribe to `_osPrefersDark` when\n // `theme === 'system'`).\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n _osPrefersDark.set(mq.matches)\n function onChange(e: MediaQueryListEvent) {\n _osPrefersDark.set(e.matches)\n }\n mq.addEventListener('change', onChange)\n\n // Re-apply when theme signal changes — updates data-theme + favicons\n const dispose = effect(() => {\n const mode = resolvedTheme()\n document.documentElement.dataset.theme = mode\n\n // Swap favicon variants (if dual-variant favicons are present)\n const faviconLinks = document.querySelectorAll<HTMLLinkElement>('[data-favicon-theme]')\n for (const link of faviconLinks) {\n link.media = link.dataset.faviconTheme === mode ? '' : 'not all'\n }\n })\n\n return () => {\n mq.removeEventListener('change', onChange)\n dispose?.dispose()\n }\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r;document.querySelectorAll(\"[data-favicon-theme]\").forEach(function(l){l.media=l.dataset.faviconTheme===r?\"\":\"not all\"})}catch(e){}})()`\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,cAAc,EAAE;AACtB,SAAS,EAAE,MAAM,OAAO,GAAG,UAAU;AACpC,QAAO;EACN;EACA,OAAO,SAAS;EAChB,UAAU,kBAAkB,SAAS;EACrC,KAAK,OAAO,OAAO;EACnB;;AAEF,SAAS,kBAAkB,UAAU;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,KAAI,MAAM,QAAQ,SAAS,GAAG,CAAE,QAAO,gBAAgB,SAAS;AAC1G,QAAO;;AAER,SAAS,gBAAgB,UAAU;CAClC,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,SAAS,SAAU,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,GAAG,gBAAgB,MAAM,CAAC;KACzF,QAAO,KAAK,MAAM;AACvB,QAAO;;;;;;;;;AAYR,SAAS,IAAI,MAAM,OAAO,KAAK;CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS;CAC9B,MAAM,eAAe,OAAO,OAAO;EAClC,GAAG;EACH;EACA,GAAG;AACJ,KAAI,OAAO,SAAS,WAAY,QAAO,EAAE,MAAM,aAAa,KAAK,IAAI;EACpE,GAAG;EACH;EACA,GAAG,aAAa;AACjB,QAAO,EAAE,MAAM,cAAc,GAAG,aAAa,KAAK,IAAI,EAAE,GAAG,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC;;AAE5G,MAAM,OAAO;;;;ACtCb,MAAM,cAAc;;AAGpB,MAAa,QAAQ,OAAc,SAAS;;;;;;;;;;;;AAa5C,MAAM,iBAAiB,OAAgB,MAAM;;AAG7C,IAAI,cAAgC;;;;;AAMpC,SAAgB,mBAAmB,OAA+B;AAChE,eAAc;;;;;;;;AAShB,SAAgB,gBAAkC;CAChD,MAAM,IAAI,OAAO;AACjB,KAAI,MAAM,UAAU;AAClB,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,gBAAgB,GAAG,SAAS;;AAErC,QAAO;;;AAIT,SAAgB,cAAc;AAE5B,UADgB,eAAe,KACV,SAAS,UAAU,OAAO;;;AAIjD,SAAgB,SAAS,GAAU;AACjC,OAAM,IAAI,EAAE;AACZ,KAAI,OAAO,aAAa,aAAa;AACnC,WAAS,gBAAgB,QAAQ,QAAQ,eAAe;AACxD,MAAI;AACF,gBAAa,QAAQ,aAAa,EAAE;UAC9B;;;;;;;AAUZ,SAAgB,YAAY;AAC1B,eAAc;AAEZ,MAAI;GACF,MAAM,SAAS,aAAa,QAAQ,YAAY;AAChD,OAAI,WAAW,WAAW,WAAW,UAAU,WAAW,SACxD,OAAM,IAAI,OAAO;UAEb;AAKR,WAAS,gBAAgB,QAAQ,QAAQ,eAAe;EAOxD,MAAM,KAAK,OAAO,WAAW,+BAA+B;AAC5D,iBAAe,IAAI,GAAG,QAAQ;EAC9B,SAAS,SAAS,GAAwB;AACxC,kBAAe,IAAI,EAAE,QAAQ;;AAE/B,KAAG,iBAAiB,UAAU,SAAS;EAGvC,MAAM,UAAU,aAAa;GAC3B,MAAM,OAAO,eAAe;AAC5B,YAAS,gBAAgB,QAAQ,QAAQ;GAGzC,MAAM,eAAe,SAAS,iBAAkC,uBAAuB;AACvF,QAAK,MAAM,QAAQ,aACjB,MAAK,QAAQ,KAAK,QAAQ,iBAAiB,OAAO,KAAK;IAEzD;AAEF,eAAa;AACX,MAAG,oBAAoB,UAAU,SAAS;AAC1C,YAAS,SAAS;;GAEpB;;;;;;;;;AAUJ,SAAgB,YAAY,OAAuD;AACjF,YAAW;AAEX,QACE,oBAAC,UAAD;EACE,OAAO,MAAM;EACb,OAAO,MAAM;EACb,SAAS;EACT,cAAW;EACX,OAAM;EACN,MAAK;kBAGH,eAAe,KAAK,SAClB,qBAAC,OAAD;GACE,OAAM;GACN,QAAO;GACP,SAAQ;GACR,MAAK;GACL,QAAO;GACP,gBAAa;GACb,kBAAe;GACf,mBAAgB;GAChB,eAAY;aATd;IAWE,oBAAC,UAAD;KAAQ,IAAG;KAAK,IAAG;KAAK,GAAE;KAAM;IAChC,oBAAC,QAAD;KAAM,IAAG;KAAK,IAAG;KAAI,IAAG;KAAK,IAAG;KAAM;IACtC,oBAAC,QAAD;KAAM,IAAG;KAAK,IAAG;KAAK,IAAG;KAAK,IAAG;KAAO;IACxC,oBAAC,QAAD;KAAM,IAAG;KAAO,IAAG;KAAO,IAAG;KAAO,IAAG;KAAS;IAChD,oBAAC,QAAD;KAAM,IAAG;KAAQ,IAAG;KAAQ,IAAG;KAAQ,IAAG;KAAU;IACpD,oBAAC,QAAD;KAAM,IAAG;KAAI,IAAG;KAAK,IAAG;KAAI,IAAG;KAAO;IACtC,oBAAC,QAAD;KAAM,IAAG;KAAK,IAAG;KAAK,IAAG;KAAK,IAAG;KAAO;IACxC,oBAAC,QAAD;KAAM,IAAG;KAAO,IAAG;KAAQ,IAAG;KAAO,IAAG;KAAU;IAClD,oBAAC,QAAD;KAAM,IAAG;KAAQ,IAAG;KAAO,IAAG;KAAQ,IAAG;KAAS;IAC9C;OAEN,oBAAC,OAAD;GACE,OAAM;GACN,QAAO;GACP,SAAQ;GACR,MAAK;GACL,QAAO;GACP,gBAAa;GACb,kBAAe;GACf,mBAAgB;GAChB,eAAY;aAEZ,oBAAC,QAAD,EAAM,GAAE,mDAAoD;GACxD;EAGH;;;;;;;;;;;;;AAeb,MAAa,cAAc,+CAA+C,YAAY"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"actions2.d.ts","names":[],"sources":["../../../src/actions.ts"],"mappings":";;;;UAKiB,aAAA;EAAA;EAEf,OAAA,EAAS,OAAA;;EAET,QAAA,EAAU,QAAA;EAAA;EAEV,IAAA;EAEgB;EAAhB,OAAA,EAAS,OAAA;AAAA;;KAIC,aAAA,iBAA8B,GAAA,EAAK,aAAA,KAAkB,CAAA,GAAI,OAAA,CAAQ,CAAA;;UAGnE,gBAAA;EACR,EAAA;EACA,OAAA,EAAS,aAAA;AAAA;;UAIM,MAAA;EATQ;EAAA,CAWtB,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAXmB;EAa7C,QAAA;AAAA;;;;;;;;;;;;AAb6E;;;;;iBAoC/D,YAAA,aAAA,CAA0B,OAAA,EAAS,aAAA,CAAc,CAAA,IAAK,MAAA,CAAO,CAAA;;iBAsC7D,oBAAA,CAAA,GAAwB,GAAA,SAAY,gBAAA;;AAjEpD;;;iBAyEgB,aAAA,CAAA;;;;;iBAUA,sBAAA,CAAA,IACd,GAAA,EAAK,iBAAA,KACF,QAAA,eAAuB,OAAA,CAAQ,QAAA"}
|
package/lib/types/ai.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai2.d.ts","names":[],"sources":["../../../src/ai.ts"],"mappings":";;;UA4BiB,cAAA;EAiEO;EA/DtB,IAAA;EA8DA;EA5DA,WAAA;EA6DA;EA3DA,MAAA;EA2DsB;EAzDtB,YAAA;EAmJiC;EAjJjC,QAAA;EAoJsB;EAlJtB,OAAA;EAiJA;EA/IA,SAAA;EAgJA;EA9IA,MAAA;EA8IsB;AAsDxB;;;;;;;;;;;;EAtLE,eAAA,GAAkB,MAAA;EA0MlB;;;;EArMA,gBAAA,GAAmB,MAAA;EAqMoB;AAsBzC;;;EAtNE,SAAA;AAAA;;;;;AAwTF;;;;;;;;;AAuBA;;;;iBAzTgB,eAAA,CACd,UAAA,YACA,QAAA,YACA,MAAA,EAAQ,cAAA;;;;;;AAuYV;iBA7SgB,mBAAA,CACd,UAAA,YACA,QAAA,YACA,MAAA,EAAQ,cAAA;AAAA,UAsDO,kBAAA;EAoPuC;EAlPtD,GAAA;EAkPuB;EAhPvB,KAAA;EAgPsD;EA9OtD,WAAA;;EAEA,KAAA;;EAEA,QAAA;;EAEA,IAAA;;EAEA,aAAA;;EAEA,MAAA;;EAEA,IAAA;;EAEA,WAAA,GAAc,KAAA;IAAQ,IAAA;IAAc,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;iBAsBtB,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,MAAA;;;;;;;;iBAkG1C,wBAAA,CAAyB,MAAA,EAAQ,cAAA,GAAiB,MAAA;;;;;;iBAuBlD,mBAAA,CACd,QAAA,YACA,MAAA,EAAQ,cAAA,GACP,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8Ea,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,MAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-routes2.d.ts","names":[],"sources":["../../../src/api-routes.ts"],"mappings":";;;;KAKY,UAAA;AAAZ;AAAA,UAGiB,UAAA;;EAEf,OAAA,EAAS,OAAA;EALW;EAOpB,GAAA,EAAK,GAAA;EAJoB;EAMzB,IAAA;EAJS;EAMT,MAAA,EAAQ,MAAA;EAAA;EAER,OAAA,EAAS,OAAA;AAAA;;KAIC,UAAA,IAAc,GAAA,EAAK,UAAA,KAAe,QAAA,GAAW,OAAA,CAAQ,QAAA;;UAGhD,cAAA;EACf,GAAA,GAAM,UAAA;EACN,IAAA,GAAO,UAAA;EACP,GAAA,GAAM,UAAA;EACN,KAAA,GAAQ,UAAA;EACR,MAAA,GAAS,UAAA;EACT,IAAA,GAAO,UAAA;EACP,OAAA,GAAU,UAAA;AAAA;AAVZ;AAAA,UAciB,aAAA;;EAEf,OAAA;EAhB4C;EAkB5C,MAAA,EAAQ,cAAA;AAAA;;;;;iBASM,aAAA,CAAc,OAAA,UAAiB,IAAA,WAAe,MAAA;;;;;iBAwC9C,mBAAA,CAAoB,MAAA,EAAQ,aAAA,KAAkB,UAAA;;;;;iBAsC9C,UAAA,CAAW,QAAA;;;;;;;;;;iBAmBX,oBAAA,CAAqB,QAAA;;;;;iBAwCrB,sBAAA,CAAuB,KAAA,YAAiB,SAAA"}
|
package/lib/types/cache.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache2.d.ts","names":[],"sources":["../../../src/cache.ts"],"mappings":";;;UAaiB,WAAA;;EAEf,SAAA;EAF0B;EAI1B,MAAA;EAMiB;EAJjB,KAAA;EAFA;EAIA,oBAAA;EAAA;EAEA,KAAA,GAAQ,SAAA;AAAA;AAAA,UAGO,SAAA;EAHE;EAKjB,KAAA;EAFwB;EAIxB,OAAA;AAAA;;iBAQc,SAAA,CAAU,OAAA,UAAiB,IAAA;;;;;AAiD3C;;;;;;;;;AA0BA;;;;;AAeA;;iBAzCgB,eAAA,CAAgB,MAAA,GAAQ,WAAA,GAAmB,UAAA;;;;;iBA0B3C,eAAA,CAAA,GAAmB,UAAA;;;;;;iBAenB,YAAA,CAAA,GAAgB,UAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client2.d.ts","names":[],"sources":["../../../src/client.ts"],"mappings":";;;;UASiB,kBAAA;;EAEf,MAAA,EAAQ,WAAA;EAFyB;EAIjC,MAAA,GAAS,WAAA;AAAA;;;;;;;AAsCX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,CAAY,OAAA,EAAS,kBAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compression2.d.ts","names":[],"sources":["../../../src/compression.ts"],"mappings":";;;UAIiB,iBAAA;;EAEf,SAAA;EAFgC;EAIhC,SAAA;AAAA;;AAgBF;;;;;;;;;AAyBA;;;iBAzBgB,qBAAA,CAAsB,MAAA,GAAQ,iBAAA,GAAyB,UAAA;;;;;;;;;iBAyBjD,gBAAA,CACpB,QAAA,EAAU,QAAA,EACV,QAAA,sBACA,SAAA,WACC,OAAA,CAAQ,QAAA;;iBAsCK,cAAA,CAAe,WAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/config.ts"],"mappings":";;;KA6CY,UAAA;AAAA,UAEK,SAAA;EA2CF;EAzCb,UAAA;EA4CI;;;;;AC9EN;EDyCE,UAAA;AAAA;AAAA,UAKe,UAAA;EC9CoB;EDgDnC,IAAA,GAAO,UAAA;EChDyC;EDmDhD,IAAA,GAAO,MAAA;ECnDmD;EDsD1D,GAAA;ICjD2B,wCDmDzB,IAAA;EAAA;ECjDa;EDqDf,GAAA;ICrDC,wDDuDC,KAAA,gCAAqC,OAAA;EAAA;ECvDuC;ED2D9E,GAAA,GAAM,SAAA;EC5DN;ED+DA,OAAA;EC9DU;EDiEV,IAAA;ECjEoE;EDoEpE,UAAA,GAAa,UAAA;ECpEiE;EDuE9E,IAAA;AAAA;;;;;;;AAhDF;;;;;AAEA;;;;iBChCgB,YAAA,CAAa,MAAA,EAAQ,UAAA,GAAa,UAAA;AD8ClD;AAAA,iBCzCgB,aAAA,CACd,UAAA,GAAY,UAAA,GACX,QAAA,CAAS,IAAA,CAAK,UAAA,2CAAqD,UAAA"}
|
package/lib/types/cors.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cors2.d.ts","names":[],"sources":["../../../src/cors.ts"],"mappings":";;;UAIiB,UAAA;;EAEf,MAAA,yBAA+B,MAAA;EAFN;EAIzB,OAAA;EAJyB;EAMzB,cAAA;EAJ+B;EAM/B,cAAA;EAFA;EAIA,WAAA;EAAA;EAEA,MAAA;AAAA;;AAqBF;;;;;;;;;;;;;;iBAAgB,cAAA,CAAe,MAAA,GAAQ,UAAA,GAAkB,UAAA"}
|
package/lib/types/csp.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"csp2.d.ts","names":[],"sources":["../../../src/csp.ts"],"mappings":";;;;;;;;;;;;AA8EA;;;;;;;;iBAhCgB,QAAA,CAAA;AAAA,UAMC,aAAA;EACf,UAAA;EACA,SAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;EACA,UAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;EACA,cAAA;EACA,UAAA;EACA,OAAA;EACA,WAAA;EAmH0D;EAjH1D,SAAA;;EAEA,QAAA;;EAEA,uBAAA;;EAEA,oBAAA;AAAA;AAAA,UAGe,SAAA;;EAEf,UAAA,EAAY,aAAA;;;;;;EAMZ,UAAA;AAAA;;;;;iBA2Bc,cAAA,CAAe,UAAA,EAAY,aAAA,EAAe,KAAA;;;;;;;;;;;;;;;;;;;;;;;;iBAqE1C,aAAA,CAAc,MAAA,EAAQ,SAAA,GAAY,UAAA"}
|
package/lib/types/env.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env2.d.ts","names":[],"sources":["../../../src/env.ts"],"mappings":";;AAsBA;;;;;;;;;;;AASA;;;;;;;;;UATiB,mBAAA;EAYf;EAVA,QAAA;EAWe;EATf,OAAA,GAAU,CAAA;EASM;EAPhB,WAAA;AAAA;AAAA,UAGe,YAAA;EACf,MAAA;EACA,KAAA,GAAQ,GAAA,sBAAyB,GAAA,aAAgB,CAAA;EACjD,QAAA;EACA,YAAA,GAAe,CAAA;AAAA;;AA2BjB;;iBAnBgB,GAAA,CAAI,OAAA,GAAU,mBAAA,WAA8B,YAAA;;;;iBAmB5C,GAAA,CAAI,OAAA,GAAU,mBAAA,WAA8B,YAAA;;;AAuB5D;iBAAgB,IAAA,CAAK,OAAA,GAAU,mBAAA,YAA+B,YAAA;;;;iBAsB9C,GAAA,CAAI,OAAA,GAAU,mBAAA,WAA8B,YAAA;;;;iBAwB5C,KAAA,kBAAA,CACd,MAAA,WAAiB,CAAA,IACjB,OAAA,GAAU,mBAAA,CAAoB,CAAA,IAC7B,YAAA,CAAa,CAAA;;KAmEX,WAAA,+BAED,iBAAA,GAAoB,iBAAA,GAAoB,kBAAA,GACxC,YAAA;;KAGC,UAAA,MACH,CAAA,SAAU,YAAA,YAAwB,CAAA,GAClC,CAAA,SAAU,iBAAA,YACV,CAAA,SAAU,iBAAA,YACV,CAAA,SAAU,kBAAA,aACV,CAAA,2BACA,CAAA,2BACA,CAAA;AAAA,KAGG,cAAA,oBACS,CAAA,GAAI,UAAA,CAAW,CAAA,CAAE,CAAA;;AAvF/B;;;;;;;;;;;;;;;;;;;;AAwBC;iBA0Fe,WAAA,WAAsB,MAAA,SAAe,WAAA,EAAA,CACnD,MAAA,EAAQ,CAAA,EACR,MAAA,GAAS,MAAA,+BACR,cAAA,CAAe,CAAA;;;;;;;;;;;;;iBAqCF,SAAA,CAAA,GAAa,MAAA;AAAA,iBACb,SAAA,WAAoB,MAAA,SAAe,WAAA,EAAA,CAAc,MAAA,EAAQ,CAAA,GAAI,cAAA,CAAe,CAAA;;;;;;;;;;;;;;;;;iBAwC5E,MAAA,GAAA,CAAU,KAAA,GAAQ,GAAA,aAAgB,CAAA,GAAI,YAAA,CAAa,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"favicon2.d.ts","names":[],"sources":["../../../src/favicon.ts"],"mappings":";;;UA6BiB,mBAAA;;EAEf,MAAA;EAFkC;EAIlC,UAAA;AAAA;AAAA,UAGe,mBAAA;EAAA;EAEf,MAAA;;EAEA,UAAA;EAFA;EAIA,eAAA;EAAA;EAEA,IAAA;EAEA;EAAA,QAAA;EA0BA;;;;;EApBA,UAAA;EAqEc;;;;;;;;;AA2YhB;;;;;;;;;;EA5bE,OAAA,GAAU,MAAA,SAAe,mBAAA;EA+b4B;;;AAmDvD;;;;;;;;;AAMA;;;;EAveE,SAAA;AAAA;;;;;;;;;;;;;;;;;iBAgCc,aAAA,CAAc,MAAA,EAAQ,mBAAA,GAAsB,MAAA;;;;;;;;;;;iBA2Y5C,YAAA,CACd,MAAA,sBACA,MAAA,EAAQ,mBAAA,GACP,KAAA;EAAQ,GAAA;EAAa,IAAA;EAAe,KAAA;EAAgB,IAAA;AAAA;AAAA,UAmDtC,QAAA;EACf,MAAA,EAAQ,MAAA;EACR,IAAA;AAAA;;iBAIc,iBAAA,CAAkB,OAAA,EAAS,QAAA,KAAa,UAAA"}
|
package/lib/types/font.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"font2.d.ts","names":[],"sources":["../../../src/font.ts"],"mappings":";;;UAciB,UAAA;;AAAjB;;;;;;;EASE,MAAA,GAAS,eAAA;EAUS;EARlB,KAAA,GAAQ,SAAA;EAFR;EAIA,OAAA,GAAU,WAAA;EAFV;EAIA,OAAA;EAFA;EAIA,QAAA;EAFA;EAIA,SAAA,GAAY,MAAA,SAAe,eAAA;AAAA;;UAIZ,gBAAA;EACf,MAAA;EACA,OAAA;EACA,MAAA;EACA,QAAA;AAAA;;UAIe,kBAAA;EACf,MAAA;EANA;EAQA,WAAA;EACA,MAAA;EACA,QAAA;AAAA;;KAIU,eAAA,GAAkB,gBAAA,GAAmB,kBAAA;AAAA,UAEhC,SAAA;EACf,MAAA;EACA,GAAA;EATA;EAWA,MAAA;EACA,KAAA;EACA,OAAA,GAAU,WAAA;AAAA;AAAA,KAGA,WAAA;;UAGK,eAAA;EAdkD;EAgBjE,QAAA;EAdwB;EAgBxB,UAAA;EAVqB;EAYrB,cAAA;EAhBA;EAkBA,eAAA;EAfA;EAiBA,eAAA;AAAA;AAAA,UAGQ,gBAAA;EACR,MAAA;EACA,MAAA;AAAA;AAAA,UAGQ,UAAA,SAAmB,gBAAA;EAC3B,QAAA;EACA,OAAA;AAAA;AAAA,UAGQ,YAAA,SAAqB,gBAAA;EAC7B,QAAA;EACA,WAAA;AAAA;AAAA,KAGG,YAAA,GAAe,UAAA,GAAa,YAAA;;;;iBAKjB,iBAAA,CAAkB,KAAA,EAAO,eAAA,GAAkB,YAAA;;AAtB1D;;;;;AAIO;iBA+CQ,iBAAA,CAAkB,KAAA,WAAgB,YAAA;;;;iBA8DlC,cAAA,CAAe,QAAA,EAAU,YAAA,IAAgB,OAAA,GAAS,WAAA;;;;AAxGzD;;;;;;;;;AAKI;;;;;AAQb;;;;;iBAoSgB,UAAA,CAAW,MAAA,GAAQ,UAAA,GAAkB,MAAA;;;;iBAgHrC,aAAA,CAAc,QAAA,EAAU,MAAA"}
|