mirror of
https://github.com/withastro/astro.git
synced 2025-01-06 22:10:10 -05:00
fix(vercel): edge middleware (#9585)
* create vercel edge middleware remove getVercelOutput * handle node built-in modules * edge function to node fetch * adjust tests * add test * add changeset * function paths as constants * ensure node built-in modules are namespaced with `node:` * x-astro-path as constant * appease linter * add comments for ASTRO_PATH_HEADER and ASTRO_LOCALS_HEADER --------- Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
a50926a6b6
commit
05adaaa2d2
9 changed files with 161 additions and 84 deletions
5
.changeset/brown-parents-sniff.md
Normal file
5
.changeset/brown-parents-sniff.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@astrojs/vercel": patch
|
||||
---
|
||||
|
||||
Fixes an issue where edge middleware did not work.
|
|
@ -31,8 +31,6 @@ export async function getFilesFromFolder(dir: URL) {
|
|||
return files;
|
||||
}
|
||||
|
||||
export const getVercelOutput = (root: URL) => new URL('./.vercel/output/', root);
|
||||
|
||||
/**
|
||||
* Copies files into a folder keeping the folder structure intact.
|
||||
* The resulting file tree will start at the common ancestor.
|
||||
|
|
|
@ -8,14 +8,14 @@ import type {
|
|||
import { AstroError } from 'astro/errors';
|
||||
import glob from 'fast-glob';
|
||||
import { basename } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
getAstroImageConfig,
|
||||
getDefaultImageConfig,
|
||||
type DevImageService,
|
||||
type VercelImageConfig,
|
||||
} from '../image/shared.js';
|
||||
import { getVercelOutput, removeDir, writeJson } from '../lib/fs.js';
|
||||
import { removeDir, writeJson } from '../lib/fs.js';
|
||||
import { copyDependenciesToFunction } from '../lib/nft.js';
|
||||
import { getRedirects } from '../lib/redirects.js';
|
||||
import {
|
||||
|
@ -29,9 +29,25 @@ import {
|
|||
import { generateEdgeMiddleware } from './middleware.js';
|
||||
|
||||
const PACKAGE_NAME = '@astrojs/vercel/serverless';
|
||||
|
||||
/**
|
||||
* The edge function calls the node server at /_render,
|
||||
* with the original path as the value of this header.
|
||||
*/
|
||||
export const ASTRO_PATH_HEADER = 'x-astro-path';
|
||||
|
||||
/**
|
||||
* The edge function calls the node server at /_render,
|
||||
* with the locals serialized into this header.
|
||||
*/
|
||||
export const ASTRO_LOCALS_HEADER = 'x-astro-locals';
|
||||
export const VERCEL_EDGE_MIDDLEWARE_FILE = 'vercel-edge-middleware';
|
||||
|
||||
// Vercel routes the folder names to a path on the deployed website.
|
||||
// We attempt to avoid interfering by prefixing with an underscore.
|
||||
export const NODE_PATH = '_render';
|
||||
const MIDDLEWARE_PATH = '_middleware';
|
||||
|
||||
// https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version
|
||||
const SUPPORTED_NODE_VERSIONS: Record<
|
||||
string,
|
||||
|
@ -111,8 +127,8 @@ export interface VercelServerlessConfig {
|
|||
export default function vercelServerless({
|
||||
webAnalytics,
|
||||
speedInsights,
|
||||
includeFiles,
|
||||
excludeFiles = [],
|
||||
includeFiles: _includeFiles = [],
|
||||
excludeFiles: _excludeFiles = [],
|
||||
imageService,
|
||||
imagesConfig,
|
||||
devImageService = 'sharp',
|
||||
|
@ -130,9 +146,10 @@ export default function vercelServerless({
|
|||
}
|
||||
|
||||
let _config: AstroConfig;
|
||||
let buildTempFolder: URL;
|
||||
let serverEntry: string;
|
||||
let _buildTempFolder: URL;
|
||||
let _serverEntry: string;
|
||||
let _entryPoints: Map<RouteData, URL>;
|
||||
let _middlewareEntryPoint: URL | undefined;
|
||||
// Extra files to be merged with `includeFiles` during build
|
||||
const extraFilesToInclude: URL[] = [];
|
||||
|
||||
|
@ -162,13 +179,12 @@ export default function vercelServerless({
|
|||
if (command === 'build' && speedInsights?.enabled) {
|
||||
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
|
||||
}
|
||||
const outDir = getVercelOutput(config.root);
|
||||
|
||||
updateConfig({
|
||||
outDir,
|
||||
outDir: new URL('./.vercel/output/', config.root),
|
||||
build: {
|
||||
serverEntry: 'entry.mjs',
|
||||
client: new URL('./static/', outDir),
|
||||
server: new URL('./dist/', config.root),
|
||||
client: new URL('./.vercel/output/static/', config.root),
|
||||
server: new URL('./.vercel/output/_functions/', config.root),
|
||||
redirects: false,
|
||||
},
|
||||
vite: {
|
||||
|
@ -195,10 +211,12 @@ export default function vercelServerless({
|
|||
`\tYou can set functionPerRoute: false to prevent surpassing the limit.\n`
|
||||
);
|
||||
}
|
||||
|
||||
setAdapter(getAdapter({ functionPerRoute, edgeMiddleware }));
|
||||
|
||||
_config = config;
|
||||
buildTempFolder = config.build.server;
|
||||
serverEntry = config.build.serverEntry;
|
||||
_buildTempFolder = config.build.server;
|
||||
_serverEntry = config.build.serverEntry;
|
||||
|
||||
if (config.output === 'static') {
|
||||
throw new AstroError(
|
||||
|
@ -208,20 +226,7 @@ export default function vercelServerless({
|
|||
},
|
||||
'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => {
|
||||
_entryPoints = entryPoints;
|
||||
if (middlewareEntryPoint) {
|
||||
const outPath = fileURLToPath(buildTempFolder);
|
||||
const vercelEdgeMiddlewareHandlerPath = new URL(
|
||||
VERCEL_EDGE_MIDDLEWARE_FILE,
|
||||
_config.srcDir
|
||||
);
|
||||
const bundledMiddlewarePath = await generateEdgeMiddleware(
|
||||
middlewareEntryPoint,
|
||||
outPath,
|
||||
vercelEdgeMiddlewareHandlerPath
|
||||
);
|
||||
// let's tell the adapter that we need to save this file
|
||||
extraFilesToInclude.push(bundledMiddlewarePath);
|
||||
}
|
||||
_middlewareEntryPoint = middlewareEntryPoint;
|
||||
},
|
||||
'astro:build:done': async ({ routes, logger }) => {
|
||||
// Merge any includes from `vite.assetsInclude
|
||||
|
@ -240,9 +245,14 @@ export default function vercelServerless({
|
|||
mergeGlobbedIncludes(_config.vite.assetsInclude);
|
||||
}
|
||||
|
||||
const routeDefinitions: { src: string; dest: string }[] = [];
|
||||
const filesToInclude = includeFiles?.map((file) => new URL(file, _config.root)) || [];
|
||||
filesToInclude.push(...extraFilesToInclude);
|
||||
const routeDefinitions: Array<{
|
||||
src: string
|
||||
dest: string
|
||||
middlewarePath?: string
|
||||
}> = [];
|
||||
|
||||
const includeFiles = _includeFiles.map((file) => new URL(file, _config.root)).concat(extraFilesToInclude);
|
||||
const excludeFiles = _excludeFiles.map((file) => new URL(file, _config.root));
|
||||
|
||||
const runtime = getRuntime(process, logger);
|
||||
|
||||
|
@ -267,7 +277,7 @@ export default function vercelServerless({
|
|||
config: _config,
|
||||
logger,
|
||||
NTF_CACHE,
|
||||
includeFiles: filesToInclude,
|
||||
includeFiles,
|
||||
excludeFiles,
|
||||
maxDuration,
|
||||
});
|
||||
|
@ -278,24 +288,28 @@ export default function vercelServerless({
|
|||
}
|
||||
} else {
|
||||
await createFunctionFolder({
|
||||
functionName: 'render',
|
||||
functionName: NODE_PATH,
|
||||
runtime,
|
||||
entry: new URL(serverEntry, buildTempFolder),
|
||||
entry: new URL(_serverEntry, _buildTempFolder),
|
||||
config: _config,
|
||||
logger,
|
||||
NTF_CACHE,
|
||||
includeFiles: filesToInclude,
|
||||
includeFiles,
|
||||
excludeFiles,
|
||||
maxDuration,
|
||||
});
|
||||
const dest = _middlewareEntryPoint ? MIDDLEWARE_PATH : NODE_PATH;
|
||||
for (const route of routes) {
|
||||
if (route.prerender) continue;
|
||||
routeDefinitions.push({
|
||||
src: route.pattern.source,
|
||||
dest: 'render',
|
||||
});
|
||||
if (!route.prerender) routeDefinitions.push({ src: route.pattern.source, dest });
|
||||
}
|
||||
}
|
||||
if (_middlewareEntryPoint) {
|
||||
await createMiddlewareFolder({
|
||||
functionName: MIDDLEWARE_PATH,
|
||||
entry: _middlewareEntryPoint,
|
||||
config: _config,
|
||||
});
|
||||
}
|
||||
const fourOhFourRoute = routes.find((route) => route.pathname === '/404');
|
||||
// Output configuration
|
||||
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
|
||||
|
@ -314,7 +328,9 @@ export default function vercelServerless({
|
|||
? [
|
||||
{
|
||||
src: '/.*',
|
||||
dest: fourOhFourRoute.prerender ? '/404.html' : 'render',
|
||||
dest: fourOhFourRoute.prerender ? '/404.html'
|
||||
: _middlewareEntryPoint ? MIDDLEWARE_PATH
|
||||
: NODE_PATH,
|
||||
status: 404,
|
||||
},
|
||||
]
|
||||
|
@ -337,7 +353,7 @@ export default function vercelServerless({
|
|||
});
|
||||
|
||||
// Remove temporary folder
|
||||
await removeDir(buildTempFolder);
|
||||
await removeDir(_buildTempFolder);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -345,6 +361,31 @@ export default function vercelServerless({
|
|||
|
||||
type Runtime = `nodejs${string}.x`;
|
||||
|
||||
interface CreateMiddlewareFolderArgs {
|
||||
config: AstroConfig
|
||||
entry: URL
|
||||
functionName: string
|
||||
}
|
||||
|
||||
async function createMiddlewareFolder({
|
||||
functionName,
|
||||
entry,
|
||||
config,
|
||||
}: CreateMiddlewareFolderArgs) {
|
||||
const functionFolder = new URL(`./functions/${functionName}.func/`, config.outDir);
|
||||
|
||||
await generateEdgeMiddleware(
|
||||
entry,
|
||||
new URL(VERCEL_EDGE_MIDDLEWARE_FILE, config.srcDir),
|
||||
new URL('./middleware.mjs', functionFolder),
|
||||
)
|
||||
|
||||
await writeJson(new URL(`./.vc-config.json`, functionFolder), {
|
||||
runtime: 'edge',
|
||||
entrypoint: 'middleware.mjs',
|
||||
});
|
||||
}
|
||||
|
||||
interface CreateFunctionFolderArgs {
|
||||
functionName: string;
|
||||
runtime: Runtime;
|
||||
|
@ -353,7 +394,7 @@ interface CreateFunctionFolderArgs {
|
|||
logger: AstroIntegrationLogger;
|
||||
NTF_CACHE: any;
|
||||
includeFiles: URL[];
|
||||
excludeFiles: string[];
|
||||
excludeFiles: URL[];
|
||||
maxDuration: number | undefined;
|
||||
}
|
||||
|
||||
|
@ -379,7 +420,7 @@ async function createFunctionFolder({
|
|||
entry,
|
||||
outDir: functionFolder,
|
||||
includeFiles,
|
||||
excludeFiles: excludeFiles.map((file) => new URL(file, config.root)),
|
||||
excludeFiles,
|
||||
logger,
|
||||
},
|
||||
NTF_CACHE
|
||||
|
@ -393,7 +434,7 @@ async function createFunctionFolder({
|
|||
// https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration
|
||||
await writeJson(vcConfig, {
|
||||
runtime,
|
||||
handler,
|
||||
handler: handler.replaceAll("\\","/"),
|
||||
launcherType: 'Nodejs',
|
||||
maxDuration,
|
||||
supportsResponseStreaming: true,
|
||||
|
@ -411,15 +452,18 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru
|
|||
`\tYour project will use Node.js 18 as the runtime instead.\n` +
|
||||
`\tConsider switching your local version to 18.\n`
|
||||
);
|
||||
return 'nodejs18.x';
|
||||
}
|
||||
if (support.status === 'current') {
|
||||
return `nodejs${major}.x`;
|
||||
} else if (support?.status === 'beta') {
|
||||
}
|
||||
if (support.status === 'beta') {
|
||||
logger.warn(
|
||||
`Your project is being built for Node.js ${major} as the runtime, which is currently in beta for Vercel Serverless Functions.`
|
||||
);
|
||||
return `nodejs${major}.x`;
|
||||
} else if (support.status === 'deprecated') {
|
||||
}
|
||||
if (support.status === 'deprecated') {
|
||||
const removeDate = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }).format(
|
||||
support.removal
|
||||
);
|
||||
|
@ -430,14 +474,6 @@ function getRuntime(process: NodeJS.Process, logger: AstroIntegrationLogger): Ru
|
|||
`\tConsider upgrading your local version to 18.\n`
|
||||
);
|
||||
return `nodejs${major}.x`;
|
||||
} else {
|
||||
logger.warn(
|
||||
`\n` +
|
||||
`\tThe local Node.js version (${major}) is not supported by Vercel Serverless Functions.\n` +
|
||||
`\tYour project will use Node.js 18 as the runtime instead.\n` +
|
||||
`\tConsider switching your local version to 18.\n`
|
||||
);
|
||||
return 'nodejs18.x';
|
||||
}
|
||||
return `nodejs${major}.x`;
|
||||
return 'nodejs18.x';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { SSRManifest } from 'astro';
|
||||
import { applyPolyfills, NodeApp } from 'astro/app/node';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { ASTRO_LOCALS_HEADER } from './adapter.js';
|
||||
import { ASTRO_PATH_HEADER, ASTRO_LOCALS_HEADER } from './adapter.js';
|
||||
|
||||
applyPolyfills();
|
||||
|
||||
|
@ -10,6 +10,10 @@ export const createExports = (manifest: SSRManifest) => {
|
|||
const handler = async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const clientAddress = req.headers['x-forwarded-for'] as string | undefined;
|
||||
const localsHeader = req.headers[ASTRO_LOCALS_HEADER];
|
||||
const realPath = req.headers[ASTRO_PATH_HEADER];
|
||||
if (typeof realPath === 'string') {
|
||||
req.url = realPath;
|
||||
}
|
||||
const locals =
|
||||
typeof localsHeader === 'string'
|
||||
? JSON.parse(localsHeader)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { ASTRO_LOCALS_HEADER } from './adapter.js';
|
||||
import { builtinModules } from 'node:module';
|
||||
import { ASTRO_LOCALS_HEADER, ASTRO_PATH_HEADER, NODE_PATH } from './adapter.js';
|
||||
|
||||
/**
|
||||
* It generates the Vercel Edge Middleware file.
|
||||
|
@ -16,16 +16,12 @@ import { ASTRO_LOCALS_HEADER } from './adapter.js';
|
|||
*/
|
||||
export async function generateEdgeMiddleware(
|
||||
astroMiddlewareEntryPointPath: URL,
|
||||
outPath: string,
|
||||
vercelEdgeMiddlewareHandlerPath: URL
|
||||
vercelEdgeMiddlewareHandlerPath: URL,
|
||||
outPath: URL,
|
||||
): Promise<URL> {
|
||||
const entryPointPathURLAsString = JSON.stringify(
|
||||
fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/')
|
||||
);
|
||||
|
||||
const code = edgeMiddlewareTemplate(entryPointPathURLAsString, vercelEdgeMiddlewareHandlerPath);
|
||||
const code = edgeMiddlewareTemplate(astroMiddlewareEntryPointPath, vercelEdgeMiddlewareHandlerPath);
|
||||
// https://vercel.com/docs/concepts/functions/edge-middleware#create-edge-middleware
|
||||
const bundledFilePath = join(outPath, 'middleware.mjs');
|
||||
const bundledFilePath = fileURLToPath(outPath);
|
||||
const esbuild = await import('esbuild');
|
||||
await esbuild.build({
|
||||
stdin: {
|
||||
|
@ -36,17 +32,27 @@ export async function generateEdgeMiddleware(
|
|||
platform: 'browser',
|
||||
// https://runtime-keys.proposal.wintercg.org/#edge-light
|
||||
conditions: ['edge-light', 'worker', 'browser'],
|
||||
external: ['astro/middleware'],
|
||||
outfile: bundledFilePath,
|
||||
allowOverwrite: true,
|
||||
format: 'esm',
|
||||
bundle: true,
|
||||
minify: false,
|
||||
// ensure node built-in modules are namespaced with `node:`
|
||||
plugins: [{
|
||||
name: 'esbuild-namespace-node-built-in-modules',
|
||||
setup(build) {
|
||||
const filter = new RegExp(builtinModules.map((mod) => `(^${mod}$)`).join('|'));
|
||||
build.onResolve({ filter }, (args) => ({ path: 'node:' + args.path, external: true }));
|
||||
},
|
||||
}]
|
||||
});
|
||||
return pathToFileURL(bundledFilePath);
|
||||
}
|
||||
|
||||
function edgeMiddlewareTemplate(middlewarePath: string, vercelEdgeMiddlewareHandlerPath: URL) {
|
||||
function edgeMiddlewareTemplate(astroMiddlewareEntryPointPath: URL, vercelEdgeMiddlewareHandlerPath: URL) {
|
||||
const middlewarePath = JSON.stringify(
|
||||
fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/')
|
||||
);
|
||||
const filePathEdgeMiddleware = fileURLToPath(vercelEdgeMiddlewareHandlerPath);
|
||||
let handlerTemplateImport = '';
|
||||
let handlerTemplateCall = '{}';
|
||||
|
@ -61,20 +67,20 @@ function edgeMiddlewareTemplate(middlewarePath: string, vercelEdgeMiddlewareHand
|
|||
import { onRequest } from ${middlewarePath};
|
||||
import { createContext, trySerializeLocals } from 'astro/middleware';
|
||||
export default async function middleware(request, context) {
|
||||
const url = new URL(request.url);
|
||||
const ctx = createContext({
|
||||
request,
|
||||
params: {}
|
||||
});
|
||||
ctx.locals = ${handlerTemplateCall};
|
||||
const next = async () => {
|
||||
const response = await fetch(url, {
|
||||
const { origin } = new URL(request.url);
|
||||
const next = () =>
|
||||
fetch(new URL('${NODE_PATH}', request.url), {
|
||||
headers: {
|
||||
${JSON.stringify(ASTRO_LOCALS_HEADER)}: trySerializeLocals(ctx.locals)
|
||||
...Object.fromEntries(request.headers.entries()),
|
||||
'${ASTRO_PATH_HEADER}': request.url.replace(origin, ''),
|
||||
'${ASTRO_LOCALS_HEADER}': trySerializeLocals(ctx.locals)
|
||||
}
|
||||
});
|
||||
return response;
|
||||
};
|
||||
})
|
||||
|
||||
return onRequest(ctx, next);
|
||||
}`;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
type DevImageService,
|
||||
type VercelImageConfig,
|
||||
} from '../image/shared.js';
|
||||
import { emptyDir, getVercelOutput, writeJson } from '../lib/fs.js';
|
||||
import { emptyDir, writeJson } from '../lib/fs.js';
|
||||
import { isServerLikeOutput } from '../lib/prerender.js';
|
||||
import { getRedirects } from '../lib/redirects.js';
|
||||
import {
|
||||
|
@ -79,7 +79,7 @@ export default function vercelStatic({
|
|||
if (command === 'build' && speedInsights?.enabled) {
|
||||
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
|
||||
}
|
||||
const outDir = new URL('./static/', getVercelOutput(config.root));
|
||||
const outDir = new URL('./.vercel/output/static/', config.root);
|
||||
updateConfig({
|
||||
outDir,
|
||||
build: {
|
||||
|
@ -110,12 +110,12 @@ export default function vercelStatic({
|
|||
// Ensure to have `.vercel/output` empty.
|
||||
// This is because, when building to static, outDir = .vercel/output/static/,
|
||||
// so .vercel/output itself won't get cleaned.
|
||||
await emptyDir(getVercelOutput(_config.root));
|
||||
await emptyDir(new URL('./.vercel/output/', _config.root));
|
||||
},
|
||||
'astro:build:done': async ({ routes }) => {
|
||||
// Output configuration
|
||||
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
|
||||
await writeJson(new URL(`./config.json`, getVercelOutput(_config.root)), {
|
||||
await writeJson(new URL('./.vercel/output/config.json', _config.root), {
|
||||
version: 3,
|
||||
routes: [
|
||||
...getRedirects(routes, _config),
|
||||
|
|
|
@ -3,6 +3,34 @@ import chaiJestSnapshot from 'chai-jest-snapshot';
|
|||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Vercel edge middleware', () => {
|
||||
/** @type {import('../../../astro/test/test-utils.js').Fixture} */
|
||||
let build;
|
||||
before(async () => {
|
||||
build = await loadFixture({
|
||||
root: './fixtures/middleware-with-edge-file/',
|
||||
});
|
||||
await build.build();
|
||||
});
|
||||
|
||||
it('an edge function is created', async () => {
|
||||
const contents = await build.readFile(
|
||||
'../.vercel/output/functions/_middleware.func/.vc-config.json'
|
||||
);
|
||||
expect(JSON.parse(contents)).to.deep.include({
|
||||
"runtime": "edge",
|
||||
"entrypoint": "middleware.mjs"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('deployment config points to the middleware edge function', async () => {
|
||||
const contents = await build.readFile(
|
||||
'../.vercel/output/config.json'
|
||||
);
|
||||
const { routes } = JSON.parse(contents);
|
||||
expect(routes.some(route => route.dest === '_middleware')).to.be.true;
|
||||
});
|
||||
|
||||
// TODO: The path here seems to be inconsistent?
|
||||
it.skip('with edge handle file, should successfully build the middleware', async () => {
|
||||
const fixture = await loadFixture({
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('maxDuration', () => {
|
|||
|
||||
it('makes it to vercel function configuration', async () => {
|
||||
const vcConfig = JSON.parse(
|
||||
await fixture.readFile('../.vercel/output/functions/render.func/.vc-config.json')
|
||||
await fixture.readFile('../.vercel/output/functions/_render.func/.vc-config.json')
|
||||
);
|
||||
expect(vcConfig).to.deep.include({ maxDuration: 60 });
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { loadFixture } from './test-utils.js';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('maxDuration', () => {
|
||||
describe('streaming', () => {
|
||||
/** @type {import('./test-utils.js').Fixture} */
|
||||
let fixture;
|
||||
|
||||
|
@ -14,7 +14,7 @@ describe('maxDuration', () => {
|
|||
|
||||
it('makes it to vercel function configuration', async () => {
|
||||
const vcConfig = JSON.parse(
|
||||
await fixture.readFile('../.vercel/output/functions/render.func/.vc-config.json')
|
||||
await fixture.readFile('../.vercel/output/functions/_render.func/.vc-config.json')
|
||||
);
|
||||
expect(vcConfig).to.deep.include({ supportsResponseStreaming: true });
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue