@pyreon/zero 0.12.1 → 0.12.3
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/actions.js +97 -0
- package/lib/actions.js.map +1 -0
- package/lib/ai.js +503 -0
- package/lib/ai.js.map +1 -0
- package/lib/api-routes.js +137 -0
- package/lib/api-routes.js.map +1 -0
- package/lib/compression.js +80 -0
- package/lib/compression.js.map +1 -0
- package/lib/cors.js +57 -0
- package/lib/cors.js.map +1 -0
- package/lib/csp.js +119 -0
- package/lib/csp.js.map +1 -0
- package/lib/env.js +217 -0
- package/lib/env.js.map +1 -0
- package/lib/favicon.js +424 -0
- package/lib/favicon.js.map +1 -0
- package/lib/i18n-routing.js +167 -0
- package/lib/i18n-routing.js.map +1 -0
- package/lib/index.js +1631 -179
- package/lib/index.js.map +1 -1
- package/lib/link.js +5 -0
- package/lib/link.js.map +1 -1
- package/lib/logger.js +78 -0
- package/lib/logger.js.map +1 -0
- package/lib/meta.js +336 -0
- package/lib/meta.js.map +1 -0
- package/lib/middleware.js +53 -0
- package/lib/middleware.js.map +1 -0
- package/lib/og-image.js +233 -0
- package/lib/og-image.js.map +1 -0
- package/lib/rate-limit.js +76 -0
- package/lib/rate-limit.js.map +1 -0
- package/lib/testing.js +179 -0
- package/lib/testing.js.map +1 -0
- package/lib/theme.js +11 -2
- package/lib/theme.js.map +1 -1
- package/lib/types/actions.d.ts +27 -24
- package/lib/types/actions.d.ts.map +1 -1
- package/lib/types/ai.d.ts +163 -0
- package/lib/types/ai.d.ts.map +1 -0
- package/lib/types/api-routes.d.ts +37 -33
- package/lib/types/api-routes.d.ts.map +1 -1
- package/lib/types/cache.d.ts +26 -22
- package/lib/types/cache.d.ts.map +1 -1
- package/lib/types/client.d.ts +13 -9
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/compression.d.ts +14 -10
- package/lib/types/compression.d.ts.map +1 -1
- package/lib/types/config.d.ts +39 -4
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/cors.d.ts +20 -16
- package/lib/types/cors.d.ts.map +1 -1
- package/lib/types/csp.d.ts +88 -0
- package/lib/types/csp.d.ts.map +1 -0
- package/lib/types/env.d.ts +118 -0
- package/lib/types/env.d.ts.map +1 -0
- package/lib/types/favicon.d.ts +70 -24
- package/lib/types/favicon.d.ts.map +1 -1
- package/lib/types/font.d.ts +68 -65
- package/lib/types/font.d.ts.map +1 -1
- package/lib/types/i18n-routing.d.ts +43 -37
- package/lib/types/i18n-routing.d.ts.map +1 -1
- package/lib/types/image-plugin.d.ts +49 -45
- package/lib/types/image-plugin.d.ts.map +1 -1
- package/lib/types/image.d.ts +47 -36
- package/lib/types/image.d.ts.map +1 -1
- package/lib/types/index.d.ts +1961 -46
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/link.d.ts +61 -56
- package/lib/types/link.d.ts.map +1 -1
- package/lib/types/logger.d.ts +57 -0
- package/lib/types/logger.d.ts.map +1 -0
- package/lib/types/meta.d.ts +180 -69
- package/lib/types/meta.d.ts.map +1 -1
- package/lib/types/middleware.d.ts +8 -4
- package/lib/types/middleware.d.ts.map +1 -1
- package/lib/types/og-image.d.ts +111 -0
- package/lib/types/og-image.d.ts.map +1 -0
- package/lib/types/rate-limit.d.ts +20 -16
- package/lib/types/rate-limit.d.ts.map +1 -1
- package/lib/types/script.d.ts +23 -19
- package/lib/types/script.d.ts.map +1 -1
- package/lib/types/seo.d.ts +47 -43
- package/lib/types/seo.d.ts.map +1 -1
- package/lib/types/testing.d.ts +64 -27
- package/lib/types/testing.d.ts.map +1 -1
- package/lib/types/theme.d.ts +22 -12
- package/lib/types/theme.d.ts.map +1 -1
- package/package.json +37 -12
- package/src/actions.ts +1 -3
- package/src/adapters/bun.ts +2 -0
- package/src/adapters/cloudflare.ts +84 -0
- package/src/adapters/index.ts +13 -1
- package/src/adapters/netlify.ts +86 -0
- package/src/adapters/node.ts +2 -0
- package/src/adapters/validate.ts +16 -0
- package/src/adapters/vercel.ts +86 -0
- package/src/ai.ts +623 -0
- package/src/compression.ts +19 -3
- package/src/csp.ts +207 -0
- package/src/entry-server.ts +28 -5
- package/src/env.ts +344 -0
- package/src/favicon.ts +221 -80
- package/src/index.ts +42 -2
- package/src/link.tsx +6 -0
- package/src/logger.ts +144 -0
- package/src/meta.tsx +124 -14
- package/src/og-image.ts +378 -0
- package/src/rate-limit.ts +11 -9
- package/src/theme.tsx +12 -1
- package/src/types.ts +1 -1
- package/src/vite-plugin.ts +5 -1
- package/lib/types/adapters/bun.d.ts +0 -6
- package/lib/types/adapters/bun.d.ts.map +0 -1
- package/lib/types/adapters/index.d.ts +0 -10
- package/lib/types/adapters/index.d.ts.map +0 -1
- package/lib/types/adapters/node.d.ts +0 -6
- package/lib/types/adapters/node.d.ts.map +0 -1
- package/lib/types/adapters/static.d.ts +0 -7
- package/lib/types/adapters/static.d.ts.map +0 -1
- package/lib/types/app.d.ts +0 -24
- package/lib/types/app.d.ts.map +0 -1
- package/lib/types/entry-server.d.ts +0 -37
- package/lib/types/entry-server.d.ts.map +0 -1
- package/lib/types/error-overlay.d.ts +0 -6
- package/lib/types/error-overlay.d.ts.map +0 -1
- package/lib/types/fs-router.d.ts +0 -47
- package/lib/types/fs-router.d.ts.map +0 -1
- package/lib/types/isr.d.ts +0 -9
- package/lib/types/isr.d.ts.map +0 -1
- package/lib/types/not-found.d.ts +0 -7
- package/lib/types/not-found.d.ts.map +0 -1
- package/lib/types/types.d.ts +0 -111
- package/lib/types/types.d.ts.map +0 -1
- package/lib/types/utils/use-intersection-observer.d.ts +0 -10
- package/lib/types/utils/use-intersection-observer.d.ts.map +0 -1
- package/lib/types/utils/with-headers.d.ts +0 -6
- package/lib/types/utils/with-headers.d.ts.map +0 -1
- package/lib/types/vite-plugin.d.ts +0 -17
- package/lib/types/vite-plugin.d.ts.map +0 -1
package/src/actions.ts
CHANGED
|
@@ -34,7 +34,6 @@ export interface Action<T = unknown> {
|
|
|
34
34
|
// ─── Registry ────────────────────────────────────────────────────────────────
|
|
35
35
|
|
|
36
36
|
const actionRegistry = new Map<string, RegisteredAction>()
|
|
37
|
-
let actionCounter = 0
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
39
|
* Define a server action. Returns a callable function that:
|
|
@@ -53,7 +52,7 @@ let actionCounter = 0
|
|
|
53
52
|
* const result = await createPost({ title: 'Hello', body: '...' })
|
|
54
53
|
*/
|
|
55
54
|
export function defineAction<T = unknown>(handler: ActionHandler<T>): Action<T> {
|
|
56
|
-
const id = `action_${
|
|
55
|
+
const id = `action_${crypto.randomUUID().slice(0, 8)}`
|
|
57
56
|
|
|
58
57
|
actionRegistry.set(id, { id, handler: handler as ActionHandler })
|
|
59
58
|
|
|
@@ -100,7 +99,6 @@ export function getRegisteredActions(): Map<string, RegisteredAction> {
|
|
|
100
99
|
*/
|
|
101
100
|
export function _resetActions(): void {
|
|
102
101
|
actionRegistry.clear()
|
|
103
|
-
actionCounter = 0
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
// ─── Server handler ──────────────────────────────────────────────────────────
|
package/src/adapters/bun.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Adapter, AdapterBuildOptions } from '../types'
|
|
2
|
+
import { validateBuildInputs } from './validate'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Bun adapter — generates a standalone Bun.serve() entry.
|
|
@@ -7,6 +8,7 @@ export function bunAdapter(): Adapter {
|
|
|
7
8
|
return {
|
|
8
9
|
name: 'bun',
|
|
9
10
|
async build(options: AdapterBuildOptions) {
|
|
11
|
+
await validateBuildInputs(options)
|
|
10
12
|
const { writeFile, cp, mkdir } = await import('node:fs/promises')
|
|
11
13
|
const { join } = await import('node:path')
|
|
12
14
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Adapter, AdapterBuildOptions } from '../types'
|
|
2
|
+
import { validateBuildInputs } from './validate'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cloudflare Pages adapter — generates output for Cloudflare Pages with Functions.
|
|
6
|
+
*
|
|
7
|
+
* Produces:
|
|
8
|
+
* - Client assets in the output directory root (served as static)
|
|
9
|
+
* - `_worker.js` — Cloudflare Pages Function for SSR
|
|
10
|
+
*
|
|
11
|
+
* Note: Cloudflare Pages Functions have a ~1MB module size limit.
|
|
12
|
+
* For large apps, configure Vite's SSR build to bundle server code:
|
|
13
|
+
* `ssr: { noExternal: true }` in vite.config.ts.
|
|
14
|
+
*
|
|
15
|
+
* Deploy with: `npx wrangler pages deploy ./dist`
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* // zero.config.ts
|
|
20
|
+
* import { defineConfig } from "@pyreon/zero"
|
|
21
|
+
*
|
|
22
|
+
* export default defineConfig({
|
|
23
|
+
* adapter: "cloudflare",
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function cloudflareAdapter(): Adapter {
|
|
28
|
+
return {
|
|
29
|
+
name: 'cloudflare',
|
|
30
|
+
async build(options: AdapterBuildOptions) {
|
|
31
|
+
await validateBuildInputs(options)
|
|
32
|
+
const { writeFile, cp, mkdir } = await import('node:fs/promises')
|
|
33
|
+
const { join } = await import('node:path')
|
|
34
|
+
|
|
35
|
+
const outDir = options.outDir
|
|
36
|
+
await mkdir(outDir, { recursive: true })
|
|
37
|
+
|
|
38
|
+
// Copy client assets to root (Cloudflare serves static files from root)
|
|
39
|
+
await cp(options.clientOutDir, outDir, { recursive: true })
|
|
40
|
+
|
|
41
|
+
// Copy server build
|
|
42
|
+
await cp(join(options.serverEntry, '..'), join(outDir, '_server'), {
|
|
43
|
+
recursive: true,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Generate Cloudflare Pages _worker.js (ES module format)
|
|
47
|
+
const workerEntry = `
|
|
48
|
+
import handler from "./_server/entry-server.js"
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
async fetch(request, env, ctx) {
|
|
52
|
+
const url = new URL(request.url)
|
|
53
|
+
|
|
54
|
+
// Let Cloudflare serve static assets (files with extensions)
|
|
55
|
+
// This check is a fallback — Pages routes static files automatically
|
|
56
|
+
const ext = url.pathname.split(".").pop()
|
|
57
|
+
if (ext && ext !== url.pathname && !url.pathname.endsWith("/")) {
|
|
58
|
+
// Cloudflare Pages handles static assets automatically via its asset binding
|
|
59
|
+
// Only reach here if the file doesn't exist — fall through to SSR
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// SSR handler
|
|
63
|
+
try {
|
|
64
|
+
return await handler(request)
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return new Response("Internal Server Error", { status: 500 })
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
`.trimStart()
|
|
71
|
+
|
|
72
|
+
await writeFile(join(outDir, '_worker.js'), workerEntry)
|
|
73
|
+
|
|
74
|
+
// Cloudflare Pages config — _routes.json for routing
|
|
75
|
+
const routesConfig = {
|
|
76
|
+
version: 1,
|
|
77
|
+
include: ['/*'],
|
|
78
|
+
exclude: ['/assets/*', '/favicon.*', '/site.webmanifest', '/robots.txt', '/sitemap.xml'],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await writeFile(join(outDir, '_routes.json'), JSON.stringify(routesConfig, null, 2))
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export { bunAdapter } from './bun'
|
|
2
|
+
export { cloudflareAdapter } from './cloudflare'
|
|
3
|
+
export { netlifyAdapter } from './netlify'
|
|
2
4
|
export { nodeAdapter } from './node'
|
|
3
5
|
export { staticAdapter } from './static'
|
|
6
|
+
export { vercelAdapter } from './vercel'
|
|
4
7
|
|
|
5
8
|
import type { Adapter, ZeroConfig } from '../types'
|
|
6
9
|
import { bunAdapter } from './bun'
|
|
10
|
+
import { cloudflareAdapter } from './cloudflare'
|
|
11
|
+
import { netlifyAdapter } from './netlify'
|
|
7
12
|
import { nodeAdapter } from './node'
|
|
8
13
|
import { staticAdapter } from './static'
|
|
14
|
+
import { vercelAdapter } from './vercel'
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Resolve the adapter from config.
|
|
@@ -21,7 +27,13 @@ export function resolveAdapter(config: ZeroConfig): Adapter {
|
|
|
21
27
|
return bunAdapter()
|
|
22
28
|
case 'static':
|
|
23
29
|
return staticAdapter()
|
|
30
|
+
case 'vercel':
|
|
31
|
+
return vercelAdapter()
|
|
32
|
+
case 'cloudflare':
|
|
33
|
+
return cloudflareAdapter()
|
|
34
|
+
case 'netlify':
|
|
35
|
+
return netlifyAdapter()
|
|
24
36
|
default:
|
|
25
|
-
throw new Error(`[zero] Unknown adapter: "${name}". Use "node", "bun", or "
|
|
37
|
+
throw new Error(`[zero] Unknown adapter: "${name}". Use "node", "bun", "static", "vercel", "cloudflare", or "netlify".`)
|
|
26
38
|
}
|
|
27
39
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Adapter, AdapterBuildOptions } from '../types'
|
|
2
|
+
import { validateBuildInputs } from './validate'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Netlify adapter — generates output for Netlify Functions (v2).
|
|
6
|
+
*
|
|
7
|
+
* Produces:
|
|
8
|
+
* - Client assets in `publish/` directory
|
|
9
|
+
* - `netlify/functions/ssr.mjs` — Netlify Function for SSR
|
|
10
|
+
* - `netlify.toml` — routing configuration
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // zero.config.ts
|
|
15
|
+
* import { defineConfig } from "@pyreon/zero"
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* adapter: "netlify",
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function netlifyAdapter(): Adapter {
|
|
23
|
+
return {
|
|
24
|
+
name: 'netlify',
|
|
25
|
+
async build(options: AdapterBuildOptions) {
|
|
26
|
+
await validateBuildInputs(options)
|
|
27
|
+
const { writeFile, cp, mkdir } = await import('node:fs/promises')
|
|
28
|
+
const { join } = await import('node:path')
|
|
29
|
+
|
|
30
|
+
const outDir = options.outDir
|
|
31
|
+
const publishDir = join(outDir, 'publish')
|
|
32
|
+
const functionsDir = join(outDir, 'netlify', 'functions')
|
|
33
|
+
|
|
34
|
+
await mkdir(publishDir, { recursive: true })
|
|
35
|
+
await mkdir(functionsDir, { recursive: true })
|
|
36
|
+
|
|
37
|
+
// Copy client assets to publish/
|
|
38
|
+
await cp(options.clientOutDir, publishDir, { recursive: true })
|
|
39
|
+
|
|
40
|
+
// Copy server build to functions directory
|
|
41
|
+
await cp(join(options.serverEntry, '..'), join(functionsDir, '_server'), {
|
|
42
|
+
recursive: true,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Generate Netlify Function (v2 format — ESM, Web-standard Request/Response)
|
|
46
|
+
const funcEntry = `
|
|
47
|
+
import handler from "./_server/entry-server.js"
|
|
48
|
+
|
|
49
|
+
export default async function(req, context) {
|
|
50
|
+
try {
|
|
51
|
+
return await handler(req)
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return new Response("Internal Server Error", { status: 500 })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const config = {
|
|
58
|
+
path: "/*",
|
|
59
|
+
preferStatic: true,
|
|
60
|
+
}
|
|
61
|
+
`.trimStart()
|
|
62
|
+
|
|
63
|
+
await writeFile(join(functionsDir, 'ssr.mjs'), funcEntry)
|
|
64
|
+
|
|
65
|
+
// Generate netlify.toml
|
|
66
|
+
const toml = `
|
|
67
|
+
[build]
|
|
68
|
+
publish = "publish"
|
|
69
|
+
functions = "netlify/functions"
|
|
70
|
+
|
|
71
|
+
[[headers]]
|
|
72
|
+
for = "/assets/*"
|
|
73
|
+
[headers.values]
|
|
74
|
+
Cache-Control = "public, max-age=31536000, immutable"
|
|
75
|
+
|
|
76
|
+
[[redirects]]
|
|
77
|
+
from = "/*"
|
|
78
|
+
to = "/.netlify/functions/ssr"
|
|
79
|
+
status = 200
|
|
80
|
+
conditions = {Role = ["admin", "user", ""]}
|
|
81
|
+
`.trimStart()
|
|
82
|
+
|
|
83
|
+
await writeFile(join(outDir, 'netlify.toml'), toml)
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/adapters/node.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Adapter, AdapterBuildOptions } from '../types'
|
|
2
|
+
import { validateBuildInputs } from './validate'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Node.js adapter — generates a standalone server entry using node:http.
|
|
@@ -7,6 +8,7 @@ export function nodeAdapter(): Adapter {
|
|
|
7
8
|
return {
|
|
8
9
|
name: 'node',
|
|
9
10
|
async build(options: AdapterBuildOptions) {
|
|
11
|
+
await validateBuildInputs(options)
|
|
10
12
|
const { writeFile, cp, mkdir } = await import('node:fs/promises')
|
|
11
13
|
const { join } = await import('node:path')
|
|
12
14
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AdapterBuildOptions } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate that adapter build inputs exist before copying.
|
|
5
|
+
* Throws with a clear error message if directories are missing.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export async function validateBuildInputs(options: AdapterBuildOptions): Promise<void> {
|
|
9
|
+
const { existsSync } = await import('node:fs')
|
|
10
|
+
if (!existsSync(options.clientOutDir)) {
|
|
11
|
+
throw new Error(`[zero:adapter] Client build output not found: ${options.clientOutDir}. Run "vite build" first.`)
|
|
12
|
+
}
|
|
13
|
+
if (!existsSync(options.serverEntry)) {
|
|
14
|
+
throw new Error(`[zero:adapter] Server entry not found: ${options.serverEntry}. Run "vite build --ssr" first.`)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Adapter, AdapterBuildOptions } from '../types'
|
|
2
|
+
import { validateBuildInputs } from './validate'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vercel adapter — generates output for Vercel's Build Output API v3.
|
|
6
|
+
*
|
|
7
|
+
* Produces a `.vercel/output` directory with:
|
|
8
|
+
* - `static/` — client-side assets (JS, CSS, images)
|
|
9
|
+
* - `functions/ssr.func/` — serverless function for SSR
|
|
10
|
+
* - `config.json` — routing configuration
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // zero.config.ts
|
|
15
|
+
* import { defineConfig } from "@pyreon/zero"
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* adapter: "vercel",
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function vercelAdapter(): Adapter {
|
|
23
|
+
return {
|
|
24
|
+
name: 'vercel',
|
|
25
|
+
async build(options: AdapterBuildOptions) {
|
|
26
|
+
await validateBuildInputs(options)
|
|
27
|
+
const { writeFile, cp, mkdir } = await import('node:fs/promises')
|
|
28
|
+
const { join } = await import('node:path')
|
|
29
|
+
|
|
30
|
+
const vercelDir = join(options.outDir, '.vercel', 'output')
|
|
31
|
+
const staticDir = join(vercelDir, 'static')
|
|
32
|
+
const funcDir = join(vercelDir, 'functions', 'ssr.func')
|
|
33
|
+
|
|
34
|
+
await mkdir(staticDir, { recursive: true })
|
|
35
|
+
await mkdir(funcDir, { recursive: true })
|
|
36
|
+
|
|
37
|
+
// Copy client assets to static/
|
|
38
|
+
await cp(options.clientOutDir, staticDir, { recursive: true })
|
|
39
|
+
|
|
40
|
+
// Copy server build to function directory
|
|
41
|
+
await cp(join(options.serverEntry, '..'), funcDir, { recursive: true })
|
|
42
|
+
|
|
43
|
+
// Generate serverless function entry
|
|
44
|
+
const funcEntry = `
|
|
45
|
+
export default async function handler(req) {
|
|
46
|
+
const handler = (await import("./entry-server.js")).default
|
|
47
|
+
return handler(req)
|
|
48
|
+
}
|
|
49
|
+
`.trimStart()
|
|
50
|
+
|
|
51
|
+
await writeFile(join(funcDir, 'index.js'), funcEntry)
|
|
52
|
+
|
|
53
|
+
// Function config
|
|
54
|
+
await writeFile(
|
|
55
|
+
join(funcDir, '.vc-config.json'),
|
|
56
|
+
JSON.stringify(
|
|
57
|
+
{
|
|
58
|
+
runtime: 'nodejs20.x',
|
|
59
|
+
handler: 'index.js',
|
|
60
|
+
launcherType: 'Nodejs',
|
|
61
|
+
},
|
|
62
|
+
null,
|
|
63
|
+
2,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Vercel Build Output config
|
|
68
|
+
const config = {
|
|
69
|
+
version: 3,
|
|
70
|
+
routes: [
|
|
71
|
+
// Serve static assets directly
|
|
72
|
+
{
|
|
73
|
+
src: '/assets/(.*)',
|
|
74
|
+
headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },
|
|
75
|
+
},
|
|
76
|
+
// Favicon and manifest
|
|
77
|
+
{ src: '/(favicon\\..*|site\\.webmanifest|robots\\.txt|sitemap\\.xml)', dest: '/$1' },
|
|
78
|
+
// All other routes → SSR function
|
|
79
|
+
{ src: '/(.*)', dest: '/ssr' },
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await writeFile(join(vercelDir, 'config.json'), JSON.stringify(config, null, 2))
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|