From da1808e0fc9b239ffbe463e8cc67ff1fdb4b7f76 Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Tue, 17 Mar 2026 15:37:04 +0000 Subject: [PATCH 1/6] feat(vercel): allow overriding function config by route --- src/presets/vercel/types.ts | 18 ++++++ src/presets/vercel/utils.ts | 114 +++++++++++++++++++++++++++++++----- test/presets/vercel.test.ts | 72 +++++++++++++++++++++++ 3 files changed, 190 insertions(+), 14 deletions(-) diff --git a/src/presets/vercel/types.ts b/src/presets/vercel/types.ts index 4c8bd608f2..7f92d6fd8b 100644 --- a/src/presets/vercel/types.ts +++ b/src/presets/vercel/types.ts @@ -147,6 +147,24 @@ export interface VercelOptions { * @see https://vercel.com/docs/cron-jobs */ cronHandlerRoute?: string; + + /** + * Per-route function configuration overrides. + * + * Keys are route patterns (e.g., `/api/queues/*`, `/api/slow-routes/**`). + * Values are partial {@link VercelServerlessFunctionConfig} objects. + * + * @example + * ```ts + * routeFunctionConfig: { + * '/api/my-slow-routes/**': { maxDuration: 3600 }, + * '/api/queues/fulfill-order': { + * experimentalTriggers: [{ type: 'queue/v2beta', topic: 'orders' }], + * }, + * } + * ``` + */ + routeFunctionConfig?: Record; } /** diff --git a/src/presets/vercel/utils.ts b/src/presets/vercel/utils.ts index 02447c8a0e..6dfc771939 100644 --- a/src/presets/vercel/utils.ts +++ b/src/presets/vercel/utils.ts @@ -3,6 +3,7 @@ import { defu } from "defu"; import { writeFile } from "../_utils/fs.ts"; import type { Nitro, NitroRouteRules } from "nitro/types"; import { dirname, relative, resolve } from "pathe"; +import { createRouter, addRoute, findRoute } from "rou3"; import { joinURL, withLeadingSlash, withoutLeadingSlash } from "ufo"; import type { PrerenderFunctionConfig, @@ -48,15 +49,26 @@ export async function generateFunctionFiles(nitro: Nitro) { const buildConfig = generateBuildConfig(nitro, o11Routes); await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); - const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); - const functionConfig: VercelServerlessFunctionConfig = { + const baseFunctionConfig: VercelServerlessFunctionConfig = { handler: "index.mjs", launcherType: "Nodejs", shouldAddHelpers: false, supportsResponseStreaming: true, ...nitro.options.vercel?.functions, }; - await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)); + const functionConfigPath = resolve(nitro.options.output.serverDir, ".vc-config.json"); + await writeFile(functionConfigPath, JSON.stringify(baseFunctionConfig, null, 2)); + + // Build rou3 router for routeFunctionConfig matching + const routeFunctionConfig = nitro.options.vercel?.routeFunctionConfig; + const hasRouteFunctionConfig = routeFunctionConfig && Object.keys(routeFunctionConfig).length > 0; + let routeFuncRouter: ReturnType> | undefined; + if (hasRouteFunctionConfig) { + routeFuncRouter = createRouter(); + for (const [pattern, overrides] of Object.entries(routeFunctionConfig)) { + addRoute(routeFuncRouter, "", pattern, overrides); + } + } // Write ISR functions for (const [key, value] of Object.entries(nitro.options.routeRules)) { @@ -70,11 +82,23 @@ export async function generateFunctionFiles(nitro: Nitro) { normalizeRouteDest(key) + ISR_SUFFIX ); await fsp.mkdir(dirname(funcPrefix), { recursive: true }); - await fsp.symlink( - "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), - funcPrefix + ".func", - "junction" - ); + + const match = routeFuncRouter && findRoute(routeFuncRouter, "", key); + if (match) { + await createFunctionDirWithCustomConfig( + funcPrefix + ".func", + nitro.options.output.serverDir, + baseFunctionConfig, + match.data + ); + } else { + await fsp.symlink( + "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), + funcPrefix + ".func", + "junction" + ); + } + await writePrerenderConfig( funcPrefix + ".prerender-config.json", value.isr, @@ -82,6 +106,25 @@ export async function generateFunctionFiles(nitro: Nitro) { ); } + // Write routeFunctionConfig custom function directories + const createdFuncDirs = new Set(); + if (hasRouteFunctionConfig) { + for (const [pattern, overrides] of Object.entries(routeFunctionConfig!)) { + const funcDir = resolve( + nitro.options.output.serverDir, + "..", + normalizeRouteDest(pattern) + ".func" + ); + await createFunctionDirWithCustomConfig( + funcDir, + nitro.options.output.serverDir, + baseFunctionConfig, + overrides + ); + createdFuncDirs.add(funcDir); + } + } + // Write observability routes if (o11Routes.length === 0) { return; @@ -94,12 +137,29 @@ export async function generateFunctionFiles(nitro: Nitro) { continue; // #3563 } const funcPrefix = resolve(nitro.options.output.serverDir, "..", route.dest); - await fsp.mkdir(dirname(funcPrefix), { recursive: true }); - await fsp.symlink( - "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), - funcPrefix + ".func", - "junction" - ); + const funcDir = funcPrefix + ".func"; + + // Skip if already created by routeFunctionConfig + if (createdFuncDirs.has(funcDir)) { + continue; + } + + const match = routeFuncRouter && findRoute(routeFuncRouter, "", route.src); + if (match) { + await createFunctionDirWithCustomConfig( + funcDir, + nitro.options.output.serverDir, + baseFunctionConfig, + match.data + ); + } else { + await fsp.mkdir(dirname(funcPrefix), { recursive: true }); + await fsp.symlink( + "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), + funcDir, + "junction" + ); + } } } @@ -273,6 +333,13 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { ), }; }), + // Route function config routes + ...(nitro.options.vercel?.routeFunctionConfig + ? Object.keys(nitro.options.vercel.routeFunctionConfig).map((pattern) => ({ + src: normalizeRouteSrc(pattern), + dest: withLeadingSlash(normalizeRouteDest(pattern)), + })) + : []), // Observability routes ...(o11Routes || []).map((route) => ({ src: joinURL(nitro.options.baseURL, route.src), @@ -512,6 +579,25 @@ function normalizeRouteDest(route: string) { ); } +async function createFunctionDirWithCustomConfig( + funcDir: string, + serverDir: string, + baseFunctionConfig: VercelServerlessFunctionConfig, + overrides: VercelServerlessFunctionConfig +) { + await fsp.mkdir(funcDir, { recursive: true }); + const entries = await fsp.readdir(serverDir); + for (const entry of entries) { + if (entry === ".vc-config.json") { + continue; + } + const target = "./" + relative(funcDir, resolve(serverDir, entry)); + await fsp.symlink(target, resolve(funcDir, entry), "junction"); + } + const mergedConfig = defu(overrides, baseFunctionConfig); + await writeFile(resolve(funcDir, ".vc-config.json"), JSON.stringify(mergedConfig, null, 2)); +} + async function writePrerenderConfig( filename: string, isrConfig: NitroRouteRules["isr"], diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index e61e808760..e5797fcfe0 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -531,6 +531,78 @@ describe("nitro:preset:vercel:bun", async () => { }); }); +describe("nitro:preset:vercel:route-function-config", async () => { + const ctx = await setupTest("vercel", { + outDirSuffix: "-route-func-config", + config: { + preset: "vercel", + vercel: { + routeFunctionConfig: { + "/api/hello": { + maxDuration: 300, + }, + "/api/echo": { + experimentalTriggers: [{ type: "queue/v2beta", topic: "orders" }], + }, + }, + }, + }, + }); + + it("should create custom function directory (not symlink)", async () => { + const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); + const stat = await fsp.lstat(funcDir); + expect(stat.isDirectory()).toBe(true); + expect(stat.isSymbolicLink()).toBe(false); + }); + + it("should write merged .vc-config.json with overrides", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/api/hello.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.maxDuration).toBe(300); + expect(config.handler).toBe("index.mjs"); + expect(config.launcherType).toBe("Nodejs"); + expect(config.supportsResponseStreaming).toBe(true); + }); + + it("should write custom config with arbitrary fields", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/api/echo.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.experimentalTriggers).toEqual([{ type: "queue/v2beta", topic: "orders" }]); + expect(config.handler).toBe("index.mjs"); + }); + + it("should symlink files inside custom function directory to __server.func", async () => { + const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); + const entries = await fsp.readdir(funcDir, { withFileTypes: true }); + const indexEntry = entries.find((e) => e.name === "index.mjs"); + expect(indexEntry).toBeDefined(); + const indexStat = await fsp.lstat(resolve(funcDir, "index.mjs")); + expect(indexStat.isSymbolicLink()).toBe(true); + }); + + it("should add routing entries for custom function routes in config.json", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "config.json"), "utf8") + .then((r) => JSON.parse(r)); + const routes = config.routes as { src: string; dest: string }[]; + const helloRoute = routes.find((r) => r.dest === "/api/hello" && r.src === "/api/hello"); + expect(helloRoute).toBeDefined(); + const echoRoute = routes.find((r) => r.dest === "/api/echo" && r.src === "/api/echo"); + expect(echoRoute).toBeDefined(); + }); + + it("should keep base __server.func with standard config", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/__server.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.maxDuration).toBeUndefined(); + expect(config.handler).toBe("index.mjs"); + }); +}); + describe.skip("nitro:preset:vercel:bun-verceljson", async () => { const vercelJsonPath = join(fixtureDir, "vercel.json"); From 24d92dc2eec2f3f28a642401c8f719418f46aff8 Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Wed, 18 Mar 2026 12:26:00 +0000 Subject: [PATCH 2/6] fix: factor in base url with function route matching --- src/presets/vercel/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/vercel/utils.ts b/src/presets/vercel/utils.ts index 6dfc771939..e4f71f4c7d 100644 --- a/src/presets/vercel/utils.ts +++ b/src/presets/vercel/utils.ts @@ -336,7 +336,7 @@ function generateBuildConfig(nitro: Nitro, o11Routes?: ObservabilityRoute[]) { // Route function config routes ...(nitro.options.vercel?.routeFunctionConfig ? Object.keys(nitro.options.vercel.routeFunctionConfig).map((pattern) => ({ - src: normalizeRouteSrc(pattern), + src: joinURL(nitro.options.baseURL, normalizeRouteSrc(pattern)), dest: withLeadingSlash(normalizeRouteDest(pattern)), })) : []), From ba05eb0f65b61290d8b51a28f4a2e33265608d2b Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Wed, 18 Mar 2026 12:30:10 +0000 Subject: [PATCH 3/6] fix: replace arrays rather than merge --- src/presets/vercel/utils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/presets/vercel/utils.ts b/src/presets/vercel/utils.ts index e4f71f4c7d..6d443f12f2 100644 --- a/src/presets/vercel/utils.ts +++ b/src/presets/vercel/utils.ts @@ -595,6 +595,11 @@ async function createFunctionDirWithCustomConfig( await fsp.symlink(target, resolve(funcDir, entry), "junction"); } const mergedConfig = defu(overrides, baseFunctionConfig); + for (const [key, value] of Object.entries(overrides)) { + if (Array.isArray(value)) { + (mergedConfig as Record)[key] = value; + } + } await writeFile(resolve(funcDir, ".vc-config.json"), JSON.stringify(mergedConfig, null, 2)); } From 2758d9b43fd61a683e50c11e819f8908f337cd42 Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Wed, 18 Mar 2026 13:17:48 +0000 Subject: [PATCH 4/6] docs(vercel): function config overrides --- docs/2.deploy/20.providers/vercel.md | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/2.deploy/20.providers/vercel.md b/docs/2.deploy/20.providers/vercel.md index e835edf324..dcb333efee 100644 --- a/docs/2.deploy/20.providers/vercel.md +++ b/docs/2.deploy/20.providers/vercel.md @@ -55,6 +55,35 @@ Alternatively, Nitro also detects Bun automatically if you specify a `bunVersion } ``` +## Per-route function configuration + +Use `vercel.routeFunctionConfig` to override [serverless function settings](https://vercel.com/docs/build-output-api/primitives#serverless-function-configuration) for specific routes. Each key is a route pattern and its value is a partial function configuration object that gets merged with the base `vercel.functions` config. + +This is useful when certain routes need different resource limits, regions, or features like [Vercel Queues triggers](https://vercel.com/docs/queues). + +```ts [nitro.config.ts] +import { defineNitroConfig } from "nitro/config"; + +export default defineNitroConfig({ + vercel: { + routeFunctionConfig: { + "/api/heavy-computation": { + maxDuration: 800, + memory: 4096, + }, + "/api/regional": { + regions: ["lhr1", "cdg1"], + }, + "/api/queues/process-order": { + experimentalTriggers: [{ type: "queue/v2beta", topic: "orders" }], + }, + }, + }, +}); +``` + +Route patterns support wildcards via [rou3](https://github.com/h3js/rou3) matching (e.g., `/api/slow/**` matches all routes under `/api/slow/`). + ## Proxy route rules Nitro automatically optimizes `proxy` route rules on Vercel by generating [CDN-level rewrites](https://vercel.com/docs/rewrites) at build time. This means matching requests are proxied at the edge without invoking a serverless function, reducing latency and cost. From af5775ab8a16ec08568c0c3780c642df1ee9a72e Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Wed, 18 Mar 2026 13:26:53 +0000 Subject: [PATCH 5/6] Update docs/2.deploy/20.providers/vercel.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/2.deploy/20.providers/vercel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/2.deploy/20.providers/vercel.md b/docs/2.deploy/20.providers/vercel.md index dcb333efee..586ba05275 100644 --- a/docs/2.deploy/20.providers/vercel.md +++ b/docs/2.deploy/20.providers/vercel.md @@ -57,7 +57,7 @@ Alternatively, Nitro also detects Bun automatically if you specify a `bunVersion ## Per-route function configuration -Use `vercel.routeFunctionConfig` to override [serverless function settings](https://vercel.com/docs/build-output-api/primitives#serverless-function-configuration) for specific routes. Each key is a route pattern and its value is a partial function configuration object that gets merged with the base `vercel.functions` config. +Use `vercel.routeFunctionConfig` to override [serverless function settings](https://vercel.com/docs/build-output-api/primitives#serverless-function-configuration) for specific routes. Each key is a route pattern and its value is a partial function configuration object that gets merged with the base `vercel.functions` config. Note: array properties (e.g., `regions`) from route config will replace the base config arrays rather than merging them. This is useful when certain routes need different resource limits, regions, or features like [Vercel Queues triggers](https://vercel.com/docs/queues). From 0b8d4c551c6b0c32605d6b5ac0bae3b6a32ed8e0 Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Wed, 18 Mar 2026 14:10:48 +0000 Subject: [PATCH 6/6] test(vercel): move to main fixture --- test/fixture/nitro.config.ts | 10 +++ test/presets/vercel.test.ts | 161 +++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 74 deletions(-) diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts index 53fdb7b36b..a315a842b4 100644 --- a/test/fixture/nitro.config.ts +++ b/test/fixture/nitro.config.ts @@ -4,6 +4,16 @@ import { dirname, resolve } from "node:path"; import { existsSync } from "node:fs"; export default defineConfig({ + vercel: { + routeFunctionConfig: { + "/api/hello": { + maxDuration: 300, + }, + "/api/echo": { + experimentalTriggers: [{ type: "queue/v2beta", topic: "orders" }], + }, + }, + }, compressPublicAssets: true, compatibilityDate: "latest", serverDir: "server", diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index e5797fcfe0..95ac42178d 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -167,6 +167,14 @@ describe("nitro:preset:vercel:web", async () => { "dest": "/rules/swr-ttl/[...]-isr?__isr_route=$__isr_route", "src": "(?<__isr_route>/rules/swr-ttl/(?:.*))", }, + { + "dest": "/api/hello", + "src": "/api/hello", + }, + { + "dest": "/api/echo", + "src": "/api/echo", + }, { "dest": "/wasm/static-import", "src": "/wasm/static-import", @@ -420,9 +428,47 @@ describe("nitro:preset:vercel:web", async () => { "functions/_vercel", "functions/api/cached.func (symlink)", "functions/api/db.func (symlink)", - "functions/api/echo.func (symlink)", + "functions/api/echo.func/.vc-config.json", + "functions/api/echo.func/_...name_.mjs (symlink)", + "functions/api/echo.func/_...name_.mjs.map (symlink)", + "functions/api/echo.func/_...param_.mjs (symlink)", + "functions/api/echo.func/_...param_.mjs.map (symlink)", + "functions/api/echo.func/_...slug_.mjs (symlink)", + "functions/api/echo.func/_...slug_.mjs.map (symlink)", + "functions/api/echo.func/_chunks (symlink)", + "functions/api/echo.func/_id_.mjs (symlink)", + "functions/api/echo.func/_id_.mjs.map (symlink)", + "functions/api/echo.func/_libs (symlink)", + "functions/api/echo.func/_routes (symlink)", + "functions/api/echo.func/_tasks (symlink)", + "functions/api/echo.func/_test-id_.mjs (symlink)", + "functions/api/echo.func/_test-id_.mjs.map (symlink)", + "functions/api/echo.func/_virtual (symlink)", + "functions/api/echo.func/index.mjs (symlink)", + "functions/api/echo.func/index.mjs.map (symlink)", + "functions/api/echo.func/node_modules (symlink)", + "functions/api/echo.func/package.json (symlink)", "functions/api/headers.func (symlink)", - "functions/api/hello.func (symlink)", + "functions/api/hello.func/.vc-config.json", + "functions/api/hello.func/_...name_.mjs (symlink)", + "functions/api/hello.func/_...name_.mjs.map (symlink)", + "functions/api/hello.func/_...param_.mjs (symlink)", + "functions/api/hello.func/_...param_.mjs.map (symlink)", + "functions/api/hello.func/_...slug_.mjs (symlink)", + "functions/api/hello.func/_...slug_.mjs.map (symlink)", + "functions/api/hello.func/_chunks (symlink)", + "functions/api/hello.func/_id_.mjs (symlink)", + "functions/api/hello.func/_id_.mjs.map (symlink)", + "functions/api/hello.func/_libs (symlink)", + "functions/api/hello.func/_routes (symlink)", + "functions/api/hello.func/_tasks (symlink)", + "functions/api/hello.func/_test-id_.mjs (symlink)", + "functions/api/hello.func/_test-id_.mjs.map (symlink)", + "functions/api/hello.func/_virtual (symlink)", + "functions/api/hello.func/index.mjs (symlink)", + "functions/api/hello.func/index.mjs.map (symlink)", + "functions/api/hello.func/node_modules (symlink)", + "functions/api/hello.func/package.json (symlink)", "functions/api/hey.func (symlink)", "functions/api/kebab.func (symlink)", "functions/api/meta/test.func (symlink)", @@ -478,6 +524,45 @@ describe("nitro:preset:vercel:web", async () => { ] `); }); + + it("should create custom function directory for routeFunctionConfig (not symlink)", async () => { + const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); + const stat = await fsp.lstat(funcDir); + expect(stat.isDirectory()).toBe(true); + expect(stat.isSymbolicLink()).toBe(false); + }); + + it("should write merged .vc-config.json with routeFunctionConfig overrides", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/api/hello.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.maxDuration).toBe(300); + expect(config.handler).toBe("index.mjs"); + expect(config.launcherType).toBe("Nodejs"); + expect(config.supportsResponseStreaming).toBe(true); + }); + + it("should write routeFunctionConfig with arbitrary fields", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/api/echo.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.experimentalTriggers).toEqual([{ type: "queue/v2beta", topic: "orders" }]); + expect(config.handler).toBe("index.mjs"); + }); + + it("should symlink files inside routeFunctionConfig directory to __server.func", async () => { + const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); + const indexStat = await fsp.lstat(resolve(funcDir, "index.mjs")); + expect(indexStat.isSymbolicLink()).toBe(true); + }); + + it("should keep base __server.func without routeFunctionConfig overrides", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "functions/__server.func/.vc-config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config.maxDuration).toBeUndefined(); + expect(config.handler).toBe("index.mjs"); + }); } ); }); @@ -531,78 +616,6 @@ describe("nitro:preset:vercel:bun", async () => { }); }); -describe("nitro:preset:vercel:route-function-config", async () => { - const ctx = await setupTest("vercel", { - outDirSuffix: "-route-func-config", - config: { - preset: "vercel", - vercel: { - routeFunctionConfig: { - "/api/hello": { - maxDuration: 300, - }, - "/api/echo": { - experimentalTriggers: [{ type: "queue/v2beta", topic: "orders" }], - }, - }, - }, - }, - }); - - it("should create custom function directory (not symlink)", async () => { - const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); - const stat = await fsp.lstat(funcDir); - expect(stat.isDirectory()).toBe(true); - expect(stat.isSymbolicLink()).toBe(false); - }); - - it("should write merged .vc-config.json with overrides", async () => { - const config = await fsp - .readFile(resolve(ctx.outDir, "functions/api/hello.func/.vc-config.json"), "utf8") - .then((r) => JSON.parse(r)); - expect(config.maxDuration).toBe(300); - expect(config.handler).toBe("index.mjs"); - expect(config.launcherType).toBe("Nodejs"); - expect(config.supportsResponseStreaming).toBe(true); - }); - - it("should write custom config with arbitrary fields", async () => { - const config = await fsp - .readFile(resolve(ctx.outDir, "functions/api/echo.func/.vc-config.json"), "utf8") - .then((r) => JSON.parse(r)); - expect(config.experimentalTriggers).toEqual([{ type: "queue/v2beta", topic: "orders" }]); - expect(config.handler).toBe("index.mjs"); - }); - - it("should symlink files inside custom function directory to __server.func", async () => { - const funcDir = resolve(ctx.outDir, "functions/api/hello.func"); - const entries = await fsp.readdir(funcDir, { withFileTypes: true }); - const indexEntry = entries.find((e) => e.name === "index.mjs"); - expect(indexEntry).toBeDefined(); - const indexStat = await fsp.lstat(resolve(funcDir, "index.mjs")); - expect(indexStat.isSymbolicLink()).toBe(true); - }); - - it("should add routing entries for custom function routes in config.json", async () => { - const config = await fsp - .readFile(resolve(ctx.outDir, "config.json"), "utf8") - .then((r) => JSON.parse(r)); - const routes = config.routes as { src: string; dest: string }[]; - const helloRoute = routes.find((r) => r.dest === "/api/hello" && r.src === "/api/hello"); - expect(helloRoute).toBeDefined(); - const echoRoute = routes.find((r) => r.dest === "/api/echo" && r.src === "/api/echo"); - expect(echoRoute).toBeDefined(); - }); - - it("should keep base __server.func with standard config", async () => { - const config = await fsp - .readFile(resolve(ctx.outDir, "functions/__server.func/.vc-config.json"), "utf8") - .then((r) => JSON.parse(r)); - expect(config.maxDuration).toBeUndefined(); - expect(config.handler).toBe("index.mjs"); - }); -}); - describe.skip("nitro:preset:vercel:bun-verceljson", async () => { const vercelJsonPath = join(fixtureDir, "vercel.json");