Compare commits

...

5 Commits

Author SHA1 Message Date
Nathan Rajlich
0351f02dff Publish Canary
- @vercel/build-utils@2.14.1-canary.1
 - vercel@24.0.1-canary.1
 - @vercel/client@10.3.1-canary.1
 - @vercel/frameworks@0.6.1-canary.1
 - @vercel/go@1.3.1-canary.1
 - @vercel/node@1.13.1-canary.1
 - @vercel/python@2.2.1-canary.1
 - @vercel/ruby@1.3.1-canary.1
2022-02-22 12:13:21 -08:00
Yuanlin Lin
0d7fa2f912 [frameworks] Updated Umi.js logo (#7470)
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2022-02-22 19:59:32 +01:00
Nathan Rajlich
3b646880e7 [node] Use NodejsLambda class (#7436)
Updates `@vercel/node` to utilize the `NodejsLambda` class so that it doesn't need to explicitly bundle in the launcher/bridge files into the build result. This reduces the complexity of the Builder and is a cleaner separation of concerns. The `helpers.test.ts` file has been moved to `@vercel/node-bridge` so it's being removed in this PR as well.
2022-02-22 16:57:11 +00:00
Steven
350a0e5f36 [examples] Bump Next.js to 12.1.0 (#7458) 2022-02-18 18:53:26 -05:00
Steven
5c21d400bd Update dev test for image optimization with next@latest and python flask (#7454)
Related to https://github.com/vercel/next.js/pull/34431
2022-02-18 16:37:59 +00:00
21 changed files with 1719 additions and 7792 deletions

View File

@@ -23,6 +23,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local

View File

@@ -1,3 +1,6 @@
module.exports = {
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,12 @@
"lint": "next lint"
},
"dependencies": {
"next": "^12.0.8",
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"eslint": "8.7.0",
"eslint-config-next": "^12.0.8"
"eslint": "8.9.0",
"eslint-config-next": "12.1.0"
}
}

1633
examples/nextjs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.14.1-canary.0",
"version": "2.14.1-canary.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -30,7 +30,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.6.1-canary.0",
"@vercel/frameworks": "0.6.1-canary.1",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.0.1-canary.0",
"version": "24.0.1-canary.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,11 +43,11 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/go": "1.3.1-canary.0",
"@vercel/node": "1.13.1-canary.0",
"@vercel/python": "2.2.1-canary.0",
"@vercel/ruby": "1.3.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@vercel/go": "1.3.1-canary.1",
"@vercel/node": "1.13.1-canary.1",
"@vercel/python": "2.2.1-canary.1",
"@vercel/ruby": "1.3.1-canary.1",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -88,9 +88,9 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "10.3.1-canary.0",
"@vercel/client": "10.3.1-canary.1",
"@vercel/fetch-retry": "5.0.3",
"@vercel/frameworks": "0.6.1-canary.0",
"@vercel/frameworks": "0.6.1-canary.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.5",
"@zeit/fun": "0.11.2",

View File

@@ -1 +1,3 @@
Flask==1.0.3
Flask==2.0.1
werkzeug==2.0.1

View File

@@ -1703,13 +1703,11 @@ test(
fetchOpts('image/webp')
);
*/
await testPath(
200,
toUrl('/test.svg', 64, 70),
null,
expectHeader('image/svg+xml'),
fetchOpts('image/webp')
);
/*
* Disabled svg in https://github.com/vercel/next.js/pull/34431
* We can test for 400 status since config option is not enabled.
*/
await testPath(400, toUrl('/test.svg', 64, 70));
/* Disabled bmp because `next dev` bypasses
* and production will convert. Eventually
* we can enable once `next dev` supports it.

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.3.1-canary.0",
"version": "10.3.1-canary.1",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1 +1,19 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="24" fill="#1CA1FA"/><path d="M19.77 16.09h-2.625v11.086c0 3.656 2.613 6.234 6.843 6.234 4.254 0 6.856-2.578 6.856-6.234V16.09h-2.625v10.875c0 2.414-1.535 4.113-4.23 4.113-2.684 0-4.22-1.7-4.22-4.113V16.09z" fill="#fff"/></svg>
<svg width="48" height="48" viewBox="0 0 28 27" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-22.000000, -23.000000)">
<g transform="translate(23.000000, 24.000000)">
<path
d="M25.59375,11.968254 C25.59375,12.8799621 24.8542859,13.6190476 23.9421107,13.6190476 C23.0299354,13.6190476 2.97006457,13.6190476 2.05788934,13.6190476 C1.14571412,13.6190476 0.40625,12.8799621 0.40625,11.968254 C0.40625,11.368056 0.726725255,10.8426707 1.2059758,10.5537152 C0.964572543,10.2667081 0.819159836,9.89635858 0.819159836,9.49206349 C0.819159836,8.83899235 1.19858525,8.27449538 1.74912718,8.00677482 C1.68179398,7.82682944 1.64497951,7.63199747 1.64497951,7.42857143 C1.64497951,6.51686327 2.38444363,5.77777778 3.29661885,5.77777778 C3.37476958,5.77777778 3.45165256,5.78320283 3.52691139,5.79369671 C3.38061557,5.54732422 3.29661885,5.2596568 3.29661885,4.95238095 C3.29661885,4.04067279 4.03608297,3.3015873 4.9482582,3.3015873 C5.25569146,3.3015873 5.54350625,3.38554101 5.79000496,3.53176193 C5.7795057,3.45654163 5.77407787,3.37969801 5.77407787,3.3015873 C5.77407787,2.38987914 6.51354199,1.65079365 7.42571721,1.65079365 C7.73083366,1.65079365 8.0166262,1.73348677 8.26188409,1.87767313 C8.35426205,1.05344 9.0538542,0.412698413 9.90317623,0.412698413 C10.4521218,0.412698413 10.938518,0.680366532 11.2388423,1.09224331 C11.4679015,0.455411137 12.0775125,0 12.7935451,0 C13.5095776,0 14.1191887,0.455411137 14.3482478,1.09224331 C14.6485721,0.680366532 15.1349684,0.412698413 15.6839139,0.412698413 C16.533236,0.412698413 17.2328281,1.05344 17.3252061,1.87767313 C17.570464,1.73348677 17.8562565,1.65079365 18.161373,1.65079365 C19.0735482,1.65079365 19.8130123,2.38987914 19.8130123,3.3015873 C19.8130123,3.37969801 19.8075845,3.45654163 19.7970852,3.53176193 C20.0435839,3.38554101 20.3313987,3.3015873 20.638832,3.3015873 C21.5510072,3.3015873 22.2904713,4.04067279 22.2904713,4.95238095 C22.2904713,5.2596568 22.2064746,5.54732422 22.0601788,5.79369671 C22.1354376,5.78320283 22.2123206,5.77777778 22.2904713,5.77777778 C23.2026465,5.77777778 23.9421107,6.51686327 23.9421107,7.42857143 C23.9421107,7.63199747 23.9052962,7.82682944 23.837963,8.00677482 C24.3885049,8.27449538 24.7679303,8.83899235 24.7679303,9.49206349 C24.7679303,9.83184534 24.665222,10.1476506 24.489143,10.4101567 C25.1324705,10.635781 25.59375,11.2481763 25.59375,11.968254 Z"
stroke="#000000" stroke-width="0.8" fill="#FFFFFF" stroke-linejoin="round"></path>
<rect fill="#000000" x="8.53125" y="23.9365079" width="8.9375" height="2.06349206"></rect>
<path
d="M0,12.7936508 L26,12.7936508 C25.579783,19.7028245 19.9201192,25.1746032 13,25.1746032 C6.07988078,25.1746032 0.420216968,19.7028245 0,12.7936508 L0,12.7936508 Z"
stroke="#000000" stroke-width="0.8" fill="#1890FF"></path>
<path
d="M7.3125,7.01587302 C7.08813432,7.01587302 6.90625,6.83110164 6.90625,6.6031746 C6.90625,6.37524756 7.08813432,6.19047619 7.3125,6.19047619 C7.53686568,6.19047619 7.71875,6.37524756 7.71875,6.6031746 C7.71875,6.83110164 7.53686568,7.01587302 7.3125,7.01587302 Z M5.6875,9.07936508 C5.46313432,9.07936508 5.28125,8.89459371 5.28125,8.66666667 C5.28125,8.43873963 5.46313432,8.25396825 5.6875,8.25396825 C5.91186568,8.25396825 6.09375,8.43873963 6.09375,8.66666667 C6.09375,8.89459371 5.91186568,9.07936508 5.6875,9.07936508 Z M7.3125,10.7301587 C7.08813432,10.7301587 6.90625,10.5453874 6.90625,10.3174603 C6.90625,10.0895333 7.08813432,9.9047619 7.3125,9.9047619 C7.53686568,9.9047619 7.71875,10.0895333 7.71875,10.3174603 C7.71875,10.5453874 7.53686568,10.7301587 7.3125,10.7301587 Z M8.9375,7.42857143 C8.71313432,7.42857143 8.53125,7.24380006 8.53125,7.01587302 C8.53125,6.78794598 8.71313432,6.6031746 8.9375,6.6031746 C9.16186568,6.6031746 9.34375,6.78794598 9.34375,7.01587302 C9.34375,7.24380006 9.16186568,7.42857143 8.9375,7.42857143 Z M18.6875,9.07936508 C18.4631343,9.07936508 18.28125,8.89459371 18.28125,8.66666667 C18.28125,8.43873963 18.4631343,8.25396825 18.6875,8.25396825 C18.9118657,8.25396825 19.09375,8.43873963 19.09375,8.66666667 C19.09375,8.89459371 18.9118657,9.07936508 18.6875,9.07936508 Z"
fill="#000000"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.6.1-canary.0",
"version": "0.6.1-canary.1",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.3.1-canary.0",
"version": "1.3.1-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -24,7 +24,7 @@
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -6,7 +6,6 @@ const { join } = require('path');
async function main() {
const srcDir = join(__dirname, 'src');
const outDir = join(__dirname, 'dist');
const bridgeDir = join(__dirname, '../node-bridge');
// Start fresh
await fs.remove(outDir);
@@ -16,12 +15,6 @@ async function main() {
stdio: 'inherit',
});
// Copy bridge and launcher as-is
await Promise.all([
fs.copyFile(join(bridgeDir, 'bridge.js'), join(outDir, 'bridge.js')),
fs.copyFile(join(bridgeDir, 'launcher.js'), join(outDir, 'launcher.js')),
]);
// Copy type file for ts test
await fs.copyFile(
join(outDir, 'types.d.ts'),
@@ -33,52 +26,6 @@ async function main() {
await fs.remove(symlinkTarget);
await fs.symlink('symlinked-asset', symlinkTarget);
// Bundle helpers.ts with ncc
await fs.remove(join(outDir, 'helpers.js'));
const helpersDir = join(outDir, 'helpers');
await execa(
'ncc',
[
'build',
join(srcDir, 'helpers.ts'),
'-e',
'@vercel/node-bridge',
'-e',
'@vercel/build-utils',
'-e',
'typescript',
'-o',
helpersDir,
],
{ stdio: 'inherit' }
);
await fs.rename(join(helpersDir, 'index.js'), join(outDir, 'helpers.js'));
await fs.remove(helpersDir);
// Build source-map-support/register for source maps
const sourceMapSupportDir = join(outDir, 'source-map-support');
await execa(
'ncc',
[
'build',
join(__dirname, '../../node_modules/source-map-support/register'),
'-e',
'@vercel/node-bridge',
'-e',
'@vercel/build-utils',
'-e',
'typescript',
'-o',
sourceMapSupportDir,
],
{ stdio: 'inherit' }
);
await fs.rename(
join(sourceMapSupportDir, 'index.js'),
join(outDir, 'source-map-support.js')
);
await fs.remove(sourceMapSupportDir);
const mainDir = join(outDir, 'main');
await execa(
'ncc',
@@ -97,8 +44,11 @@ async function main() {
{ stdio: 'inherit' }
);
await fs.rename(join(mainDir, 'index.js'), join(outDir, 'index.js'));
await fs.remove(mainDir);
await fs.remove(join(outDir, 'example-import.js'));
await Promise.all([
fs.remove(mainDir),
fs.remove(join(outDir, 'example-import.js')),
fs.remove(join(outDir, 'example-import.d.ts')),
]);
}
main().catch(err => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.13.1-canary.0",
"version": "1.13.1-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -11,7 +11,6 @@
},
"scripts": {
"build": "node build",
"test-unit": "jest --env node --verbose --runInBand --bail test/helpers.test.js",
"test-integration-once": "jest --env node --verbose --runInBand --bail test/integration.test.js",
"prepublishOnly": "node build"
},
@@ -32,7 +31,7 @@
"@types/cookie": "0.3.3",
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.5",
"@vercel/node-bridge": "2.1.2-canary.0",

View File

@@ -88,8 +88,7 @@ if (!process.env.VERCEL_DEV_IS_ESM) {
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { Readable } from 'stream';
import type { Bridge } from '@vercel/node-bridge/bridge';
// @ts-ignore - copied to the `dist` output as-is
import { getVercelLauncher } from './launcher.js';
import { getVercelLauncher } from '@vercel/node-bridge/launcher.js';
function listen(server: Server, port: number, host: string): Promise<void> {
return new Promise(resolve => {
@@ -120,6 +119,10 @@ async function main() {
helpersPath: './helpers.js',
shouldAddHelpers,
useRequire,
// not used
bridgePath: '',
sourcemapSupportPath: '',
});
bridge = launcher();

View File

@@ -1,309 +0,0 @@
import {
VercelRequest,
VercelResponse,
VercelRequestCookies,
VercelRequestQuery,
VercelRequestBody,
} from './types';
import { Server } from 'http';
import type { Bridge } from '@vercel/node-bridge/bridge';
function getBodyParser(req: VercelRequest, body: Buffer) {
return function parseBody(): VercelRequestBody {
if (!req.headers['content-type']) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { parse: parseContentType } = require('content-type');
const { type } = parseContentType(req.headers['content-type']);
if (type === 'application/json') {
try {
const str = body.toString();
return str ? JSON.parse(str) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON');
}
}
if (type === 'application/octet-stream') {
return body;
}
if (type === 'application/x-www-form-urlencoded') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { parse: parseQS } = require('querystring');
// note: querystring.parse does not produce an iterable object
// https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
return parseQS(body.toString());
}
if (type === 'text/plain') {
return body.toString();
}
return undefined;
};
}
function getQueryParser({ url = '/' }: VercelRequest) {
return function parseQuery(): VercelRequestQuery {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { parse: parseURL } = require('url');
return parseURL(url, true).query;
};
}
function getCookieParser(req: VercelRequest) {
return function parseCookie(): VercelRequestCookies {
const header: undefined | string | string[] = req.headers.cookie;
if (!header) {
return {};
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { parse } = require('cookie');
return parse(Array.isArray(header) ? header.join(';') : header);
};
}
function status(res: VercelResponse, statusCode: number): VercelResponse {
res.statusCode = statusCode;
return res;
}
function redirect(
res: VercelResponse,
statusOrUrl: string | number,
url?: string
): VercelResponse {
if (typeof statusOrUrl === 'string') {
url = statusOrUrl;
statusOrUrl = 307;
}
if (typeof statusOrUrl !== 'number' || typeof url !== 'string') {
throw new Error(
`Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').`
);
}
res.writeHead(statusOrUrl, { Location: url }).end();
return res;
}
function setCharset(type: string, charset: string) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { parse, format } = require('content-type');
const parsed = parse(type);
parsed.parameters.charset = charset;
return format(parsed);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createETag(body: any, encoding: 'utf8' | undefined) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const etag = require('etag');
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
return etag(buf, { weak: true });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function send(
req: VercelRequest,
res: VercelResponse,
body: any
): VercelResponse {
let chunk: unknown = body;
let encoding: 'utf8' | undefined;
switch (typeof chunk) {
// string defaulting to html
case 'string':
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'text/html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'application/octet-stream');
}
} else {
return json(req, res, chunk);
}
break;
}
// write strings in utf-8
if (typeof chunk === 'string') {
encoding = 'utf8';
// reflect this in content-type
const type = res.getHeader('content-type');
if (typeof type === 'string') {
res.setHeader('content-type', setCharset(type, 'utf-8'));
}
}
// populate Content-Length
let len: number | undefined;
if (chunk !== undefined) {
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
len = chunk.length;
} else if (typeof chunk === 'string') {
if (chunk.length < 1000) {
// just calculate length small chunk
len = Buffer.byteLength(chunk, encoding);
} else {
// convert chunk to Buffer and calculate
const buf = Buffer.from(chunk, encoding);
len = buf.length;
chunk = buf;
encoding = undefined;
}
} else {
throw new Error(
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
);
}
if (len !== undefined) {
res.setHeader('content-length', len);
}
}
// populate ETag
let etag: string | undefined;
if (
!res.getHeader('etag') &&
len !== undefined &&
(etag = createETag(chunk, encoding))
) {
res.setHeader('etag', etag);
}
// strip irrelevant headers
if (204 === res.statusCode || 304 === res.statusCode) {
res.removeHeader('Content-Type');
res.removeHeader('Content-Length');
res.removeHeader('Transfer-Encoding');
chunk = '';
}
if (req.method === 'HEAD') {
// skip body for HEAD
res.end();
} else if (encoding) {
// respond with encoding
res.end(chunk, encoding);
} else {
// respond without encoding
res.end(chunk);
}
return res;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function json(
req: VercelRequest,
res: VercelResponse,
jsonBody: any
): VercelResponse {
const body = JSON.stringify(jsonBody);
// content-type
if (!res.getHeader('content-type')) {
res.setHeader('content-type', 'application/json; charset=utf-8');
}
return send(req, res, body);
}
export class ApiError extends Error {
readonly statusCode: number;
constructor(statusCode: number, message: string) {
super(message);
this.statusCode = statusCode;
}
}
export function sendError(
res: VercelResponse,
statusCode: number,
message: string
) {
res.statusCode = statusCode;
res.statusMessage = message;
res.end();
}
function setLazyProp<T>(req: VercelRequest, prop: string, getter: () => T) {
const opts = { configurable: true, enumerable: true };
const optsReset = { ...opts, writable: true };
Object.defineProperty(req, prop, {
...opts,
get: () => {
const value = getter();
// we set the property on the object to avoid recalculating it
Object.defineProperty(req, prop, { ...optsReset, value });
return value;
},
set: value => {
Object.defineProperty(req, prop, { ...optsReset, value });
},
});
}
export function createServerWithHelpers(
handler: (req: VercelRequest, res: VercelResponse) => void | Promise<void>,
bridge: Bridge
) {
const server = new Server(async (_req, _res) => {
const req = _req as VercelRequest;
const res = _res as VercelResponse;
try {
const reqId = req.headers['x-now-bridge-request-id'];
// don't expose this header to the client
delete req.headers['x-now-bridge-request-id'];
if (typeof reqId !== 'string') {
throw new ApiError(500, 'Internal Server Error');
}
const event = bridge.consumeEvent(reqId);
setLazyProp<VercelRequestCookies>(req, 'cookies', getCookieParser(req));
setLazyProp<VercelRequestQuery>(req, 'query', getQueryParser(req));
setLazyProp<VercelRequestBody>(
req,
'body',
getBodyParser(req, event.body)
);
res.status = statusCode => status(res, statusCode);
res.redirect = (statusOrUrl, url) => redirect(res, statusOrUrl, url);
res.send = body => send(req, res, body);
res.json = jsonBody => json(req, res, jsonBody);
await handler(req, res);
} catch (err) {
if (err instanceof ApiError) {
sendError(res, err.statusCode, err.message);
} else {
throw err;
}
}
});
return server;
}

View File

@@ -24,16 +24,13 @@ import {
File,
Files,
Meta,
PrepareCacheOptions,
BuildOptions,
Config,
StartDevServerOptions,
StartDevServerResult,
glob,
download,
FileBlob,
FileFsRef,
createLambda,
NodejsLambda,
runNpmInstall,
runPackageJsonScript,
getNodeVersion,
@@ -42,15 +39,15 @@ import {
debug,
isSymbolicLink,
walkParentDirs,
BuildV3,
PrepareCache,
StartDevServer,
} from '@vercel/build-utils';
// @ts-ignore - copied to the `dist` output as-is
import { makeVercelLauncher, makeAwsLauncher } from './launcher.js';
import { Register, register } from './typescript';
export { shouldServe };
export {
export type {
NowRequest,
NowResponse,
VercelRequest,
@@ -80,11 +77,6 @@ const tscPath = resolve(dirname(require_.resolve('typescript')), '../bin/tsc');
// eslint-disable-next-line no-useless-escape
const libPathRegEx = /^node_modules|[\/\\]node_modules[\/\\]/;
const LAUNCHER_FILENAME = '__launcher.js';
const BRIDGE_FILENAME = '__bridge.js';
const HELPERS_FILENAME = '__helpers.js';
const SOURCEMAP_SUPPORT_FILENAME = '__sourcemap_support.js';
async function downloadInstallAndBundle({
files,
entrypoint,
@@ -130,7 +122,6 @@ async function compile(
workPath: string,
baseDir: string,
entrypointPath: string,
entrypoint: string,
config: Config
): Promise<{
preparedFiles: Files;
@@ -230,7 +221,7 @@ async function compile(
);
for (const warning of warnings) {
if (warning && warning.stack) {
if (warning?.stack) {
debug(warning.stack.replace('Error: ', 'Warning: '));
}
}
@@ -342,18 +333,14 @@ export * from './types';
export const version = 3;
export async function build({
export const build: BuildV3 = async ({
files,
entrypoint,
workPath,
repoRootPath,
config = {},
meta = {},
}: BuildOptions) {
const shouldAddHelpers = !(
config.helpers === false || process.env.NODEJS_HELPERS === '0'
);
}) => {
const baseDir = repoRootPath || workPath;
const awsLambdaHandler = getAWSLambdaHandler(entrypoint, config);
@@ -379,71 +366,32 @@ export async function build({
workPath,
baseDir,
entrypointPath,
entrypoint,
config
);
debug(`Trace complete [${Date.now() - traceTime}ms]`);
const getFileName = (str: string) => `___vc/${str}`;
const shouldAddHelpers = !(
config.helpers === false || process.env.NODEJS_HELPERS === '0'
);
const launcher = awsLambdaHandler ? makeAwsLauncher : makeVercelLauncher;
const launcherSource = launcher({
entrypointPath: `../${renameTStoJS(relative(baseDir, entrypointPath))}`,
bridgePath: `./${BRIDGE_FILENAME}`,
helpersPath: `./${HELPERS_FILENAME}`,
sourcemapSupportPath: `./${SOURCEMAP_SUPPORT_FILENAME}`,
const lambda = new NodejsLambda({
files: preparedFiles,
handler: renameTStoJS(relative(baseDir, entrypointPath)),
runtime: nodeVersion.runtime,
shouldAddHelpers,
shouldAddSourcemapSupport,
awsLambdaHandler,
});
const launcherFiles: Files = {
[getFileName('package.json')]: new FileBlob({
data: JSON.stringify({ type: 'commonjs' }),
}),
[getFileName(LAUNCHER_FILENAME)]: new FileBlob({
data: launcherSource,
}),
[getFileName(BRIDGE_FILENAME)]: new FileFsRef({
fsPath: join(__dirname, 'bridge.js'),
}),
};
if (shouldAddSourcemapSupport) {
launcherFiles[getFileName(SOURCEMAP_SUPPORT_FILENAME)] = new FileFsRef({
fsPath: join(__dirname, 'source-map-support.js'),
});
}
if (shouldAddHelpers) {
launcherFiles[getFileName(HELPERS_FILENAME)] = new FileFsRef({
fsPath: join(__dirname, 'helpers.js'),
});
}
const lambda = await createLambda({
files: {
...preparedFiles,
...launcherFiles,
},
handler: `${getFileName(LAUNCHER_FILENAME).slice(0, -3)}.launcher`,
runtime: nodeVersion.runtime,
});
return { output: lambda };
}
};
export async function prepareCache({
workPath,
}: PrepareCacheOptions): Promise<Files> {
export const prepareCache: PrepareCache = async ({ workPath }) => {
const cache = await glob('node_modules/**', workPath);
return cache;
}
};
export async function startDevServer(
opts: StartDevServerOptions
): Promise<StartDevServerResult> {
export const startDevServer: StartDevServer = async opts => {
const { entrypoint, workPath, config, meta = {} } = opts;
const entryDir = join(workPath, dirname(entrypoint));
const projectTsConfig = await walkParentDirs({
@@ -501,7 +449,7 @@ export async function startDevServer(
const reason = signal ? `"${signal}" signal` : `exit code ${exitCode}`;
throw new Error(`\`node ${entrypoint}\` failed with ${reason}`);
}
}
};
async function doTypeCheck(
{ entrypoint, workPath, meta = {} }: StartDevServerOptions,

View File

@@ -1,842 +0,0 @@
const fetch = require('node-fetch');
const listen = require('test-listen');
const qs = require('querystring');
const { createServerWithHelpers } = require('../dist/helpers');
const mockListener = jest.fn();
const consumeEventMock = jest.fn();
const mockBridge = { consumeEvent: consumeEventMock };
let server;
let url;
async function fetchWithProxyReq(_url, opts = {}) {
if (opts.body) {
// eslint-disable-next-line
opts = { ...opts, body: Buffer.from(opts.body) };
}
consumeEventMock.mockImplementationOnce(() => opts);
return fetch(_url, {
...opts,
headers: { ...opts.headers, 'x-now-bridge-request-id': '2' },
});
}
beforeEach(async () => {
mockListener.mockClear();
consumeEventMock.mockClear();
mockListener.mockImplementation((req, res) => {
res.send('hello');
});
consumeEventMock.mockImplementation(() => ({}));
server = createServerWithHelpers(mockListener, mockBridge);
url = await listen(server);
});
afterEach(async () => {
await server.close();
});
describe('contract with @vercel/node-bridge', () => {
test('should call consumeEvent with the correct reqId', async () => {
await fetchWithProxyReq(`${url}/`);
expect(consumeEventMock).toHaveBeenLastCalledWith('2');
});
test('should not expose the request id header', async () => {
await fetchWithProxyReq(`${url}/`, { headers: { 'x-test-header': 'ok' } });
const [{ headers }] = mockListener.mock.calls[0];
expect(headers['x-now-bridge-request-id']).toBeUndefined();
expect(headers['x-test-header']).toBe('ok');
});
});
describe('all helpers', () => {
const nowHelpers = [
['query', 0],
['cookies', 0],
['body', 0],
['status', 1],
['redirect', 1],
['send', 1],
['json', 1],
];
test('should not recalculate req properties twice', async () => {
const spy = jest.fn(() => {});
const nowReqHelpers = nowHelpers.filter(([, i]) => i === 0);
mockListener.mockImplementation((req, res) => {
spy(...nowReqHelpers.map(h => req[h]));
spy(...nowReqHelpers.map(h => req[h]));
res.end();
});
await fetchWithProxyReq(`${url}/?who=bill`, {
method: 'POST',
body: JSON.stringify({ who: 'mike' }),
headers: { 'content-type': 'application/json', cookie: 'who=jim' },
});
// here we test that bodySpy is called twice with exactly the same arguments
for (let i = 0; i < 3; i += 1) {
expect(spy.mock.calls[0][i]).toBe(spy.mock.calls[1][i]);
}
});
test('should be able to overwrite request properties', async () => {
const spy = jest.fn(() => {});
mockListener.mockImplementation((...args) => {
nowHelpers.forEach(([prop, n]) => {
/* eslint-disable */
args[n][prop] = 'ok';
args[n][prop] = 'ok2';
spy(args[n][prop]);
});
args[1].end();
});
await fetchWithProxyReq(url);
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
});
test('should be able to reconfig request properties', async () => {
const spy = jest.fn(() => {});
mockListener.mockImplementation((...args) => {
nowHelpers.forEach(([prop, n]) => {
// eslint-disable-next-line
Object.defineProperty(args[n], prop, { value: 'ok' });
Object.defineProperty(args[n], prop, { value: 'ok2' });
spy(args[n][prop]);
});
args[1].end();
});
await fetchWithProxyReq(url);
nowHelpers.forEach((_, i) => expect(spy.mock.calls[i][0]).toBe('ok2'));
});
});
describe('req.query', () => {
test('req.query should reflect querystring in the url', async () => {
await fetchWithProxyReq(`${url}/?who=bill&where=us`);
expect(mockListener.mock.calls[0][0].query).toMatchObject({
who: 'bill',
where: 'us',
});
});
test('req.query should turn multiple params with same name into an array', async () => {
await fetchWithProxyReq(`${url}/?a=2&a=1`);
expect(mockListener.mock.calls[0][0].query).toMatchObject({
a: ['2', '1'],
});
});
test('req.query should be {} when there is no querystring', async () => {
await fetchWithProxyReq(url);
const [{ query }] = mockListener.mock.calls[0];
expect(Object.keys(query).length).toBe(0);
});
});
describe('req.cookies', () => {
test('req.cookies should reflect req.cookie header', async () => {
await fetchWithProxyReq(url, {
headers: {
cookie: 'who=bill; where=us',
},
});
expect(mockListener.mock.calls[0][0].cookies).toMatchObject({
who: 'bill',
where: 'us',
});
});
});
describe('req.body', () => {
test('req.body should be undefined by default', async () => {
await fetchWithProxyReq(url);
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
});
test('req.body should be undefined if content-type is not defined', async () => {
await fetchWithProxyReq(url, {
method: 'POST',
body: 'hello',
});
expect(mockListener.mock.calls[0][0].body).toBe(undefined);
});
test('req.body should be a string when content-type is `text/plain`', async () => {
await fetchWithProxyReq(url, {
method: 'POST',
body: 'hello',
headers: { 'content-type': 'text/plain' },
});
expect(mockListener.mock.calls[0][0].body).toBe('hello');
});
test('req.body should be a buffer when content-type is `application/octet-stream`', async () => {
await fetchWithProxyReq(url, {
method: 'POST',
body: 'hello',
headers: { 'content-type': 'application/octet-stream' },
});
const [{ body }] = mockListener.mock.calls[0];
const str = body.toString();
expect(Buffer.isBuffer(body)).toBe(true);
expect(str).toBe('hello');
});
test('req.body should be an object when content-type is `application/x-www-form-urlencoded`', async () => {
const obj = { who: 'mike' };
await fetchWithProxyReq(url, {
method: 'POST',
body: qs.encode(obj),
headers: { 'content-type': 'application/x-www-form-urlencoded' },
});
expect(mockListener.mock.calls[0][0].body).toMatchObject(obj);
});
test('req.body should be an object when content-type is `application/json`', async () => {
const json = {
who: 'bill',
where: 'us',
};
await fetchWithProxyReq(url, {
method: 'POST',
body: JSON.stringify(json),
headers: { 'content-type': 'application/json' },
});
expect(mockListener.mock.calls[0][0].body).toMatchObject(json);
});
test('should work when body is empty and content-type is `application/json`', async () => {
mockListener.mockImplementation((req, res) => {
console.log(req.body);
res.end();
});
const res = await fetchWithProxyReq(url, {
method: 'POST',
body: '',
headers: { 'content-type': 'application/json' },
});
expect(res.status).toBe(200);
expect(res.body).toMatchObject({});
});
test('should be able to try/catch parse errors', async () => {
const bodySpy = jest.fn(() => {});
mockListener.mockImplementation((req, res) => {
try {
if (req.body === undefined) res.status(400);
} catch (error) {
bodySpy(error);
} finally {
res.end();
}
});
await fetchWithProxyReq(url, {
method: 'POST',
body: '{"wrong":"json"',
headers: { 'content-type': 'application/json' },
});
expect(bodySpy).toHaveBeenCalled();
const [error] = bodySpy.mock.calls[0];
expect(error.message).toMatch(/invalid json/i);
expect(error.statusCode).toBe(400);
});
});
describe('res.status', () => {
test('res.status() should set the status code', async () => {
mockListener.mockImplementation((req, res) => {
res.status(404);
res.end();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(404);
});
test('res.status() should be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.status(404));
res.end();
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
});
describe('res.redirect', () => {
test('should redirect to login', async () => {
mockListener.mockImplementation((req, res) => {
res.redirect('/login');
res.end();
});
const res = await fetchWithProxyReq(url, { redirect: 'manual' });
expect(res.status).toBe(307);
expect(res.headers.get('location')).toBe(url + '/login');
});
test('should redirect with status code 301', async () => {
mockListener.mockImplementation((req, res) => {
res.redirect(301, '/login');
res.end();
});
const res = await fetchWithProxyReq(url, { redirect: 'manual' });
expect(res.status).toBe(301);
expect(res.headers.get('location')).toBe(url + '/login');
});
test('should show friendly error for invalid redirect', async () => {
let error;
mockListener.mockImplementation((req, res) => {
try {
res.redirect(307);
} catch (err) {
error = err;
}
res.end();
});
await fetchWithProxyReq(url, { redirect: 'manual' });
expect(error.message).toBe(
`Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').`
);
});
test('should show friendly error in case of passing null as first argument redirect', async () => {
let error;
mockListener.mockImplementation((req, res) => {
try {
res.redirect(null);
} catch (err) {
error = err;
}
res.end();
});
await fetchWithProxyReq(url, { redirect: 'manual' });
expect(error.message).toBe(
`Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').`
);
});
});
// tests based on expressjs test suite
// see https://github.com/expressjs/express/blob/master/test/res.send.js
describe('res.send', () => {
test('should be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.send('hello'));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
describe('res.send()', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
});
describe('.send(null)', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-length')).toBe('0');
expect(await res.text()).toBe('');
});
});
describe('.send(undefined)', () => {
test('should set body to ""', async () => {
mockListener.mockImplementation((req, res) => {
res.send(undefined);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(await res.text()).toBe('');
});
});
describe('.send(String)', () => {
test('should send as html', async () => {
mockListener.mockImplementation((req, res) => {
res.send('<p>hey</p>');
});
const res = await fetchWithProxyReq(url);
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
expect(await res.text()).toBe('<p>hey</p>');
});
test('should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send('½ + ¼ = ¾');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('should set ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Array(1000).join('-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
test('should override charset in Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
res.send('hey');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
});
describe('.send(Buffer)', () => {
test('should keep charset in Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
res.send(Buffer.from('hi'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe(
'text/plain; charset=iso-8859-1'
);
expect(await res.text()).toBe('hi');
});
test('should set Content-Length', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('½ + ¼ = ¾'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(Number(res.headers.get('content-length'))).toBe(12);
expect(await res.text()).toBe('½ + ¼ = ¾');
});
test('should send as octet-stream', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.from('hello'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('application/octet-stream');
expect((await res.buffer()).toString('hex')).toBe(
Buffer.from('hello').toString('hex')
);
});
test('should set ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Buffer.alloc(999, '-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(Buffer.from('hey'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
expect(await res.text()).toBe('hey');
});
test('should not override ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('ETag', '"foo"');
res.send(Buffer.from('hey'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('"foo"');
expect(await res.text()).toBe('hey');
});
});
describe('.send(Object)', () => {
test('should send as application/json', async () => {
mockListener.mockImplementation((req, res) => {
res.send({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
});
describe('when the request method is HEAD', () => {
test('should ignore the body', async () => {
mockListener.mockImplementation((req, res) => {
res.send('yay');
});
// TODO: fix this test
// node-fetch is automatically ignoring the body so this test will never fail
const res = await fetchWithProxyReq(url, { method: 'HEAD' });
expect(res.status).toBe(200);
expect((await res.buffer()).toString()).toBe('');
});
});
describe('when .statusCode is 204', () => {
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
mockListener.mockImplementation((req, res) => {
res.statusCode = 204;
res.setHeader('Transfer-Encoding', 'chunked');
res.send('foo');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(204);
expect(res.headers.get('Content-Type')).toBe(null);
expect(res.headers.get('Content-Length')).toBe(null);
expect(res.headers.get('Transfer-Encoding')).toBe(null);
expect(await res.text()).toBe('');
});
});
describe('when .statusCode is 304', () => {
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
mockListener.mockImplementation((req, res) => {
res.statusCode = 304;
res.setHeader('Transfer-Encoding', 'chunked');
res.send('foo');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(304);
expect(res.headers.get('Content-Type')).toBe(null);
expect(res.headers.get('Content-Length')).toBe(null);
expect(res.headers.get('Transfer-Encoding')).toBe(null);
expect(await res.text()).toBe('');
});
});
// test('should always check regardless of length', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.setHeader('ETag', etag);
// res.send('hey');
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(304);
// });
// test('should respond with 304 Not Modified when fresh', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.setHeader('ETag', etag);
// res.send(Array(1000).join('-'));
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(304);
// });
// test('should not perform freshness check unless 2xx or 304', async () => {
// const etag = '"asdf"';
// mockListener.mockImplementation((req, res) => {
// res.status(500);
// res.setHeader('ETag', etag);
// res.send('hey');
// });
// const res = await fetchWithProxyReq(url, {
// headers: { 'If-None-Match': etag },
// });
// expect(res.status).toBe(500);
// expect(await res.text()).toBe('hey');
// });
describe('etag', () => {
test('should send ETag', async () => {
mockListener.mockImplementation((req, res) => {
res.send('kajdslfkasdf');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('W/"c-IgR/L5SF7CJQff4wxKGF/vfPuZ0"');
});
test('should send ETag for empty string response', async () => {
mockListener.mockImplementation((req, res) => {
res.send('');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"');
});
test('should send ETag for long response', async () => {
mockListener.mockImplementation((req, res) => {
res.send(Array(1000).join('-'));
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
);
});
test('should not override ETag when manually set', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('etag', '"asdf"');
res.send('hello');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe('"asdf"');
});
test('should not send ETag for res.send()', async () => {
mockListener.mockImplementation((req, res) => {
res.send();
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('ETag')).toBe(null);
});
});
});
// tests based on expressjs test suite
// see https://github.com/expressjs/express/blob/master/test/res.json.js
describe('res.json', () => {
test('should send be chainable', async () => {
const spy = jest.fn();
mockListener.mockImplementation((req, res) => {
spy(res, res.json({ hello: 'world' }));
});
await fetchWithProxyReq(url);
const [a, b] = spy.mock.calls[0];
expect(a).toBe(b);
});
test('res.json() should send an empty body', async () => {
mockListener.mockImplementation((req, res) => {
res.json();
});
await fetchWithProxyReq(url);
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('');
});
describe('.json(object)', () => {
test('should not override previous Content-Types', async () => {
mockListener.mockImplementation((req, res) => {
res.setHeader('content-type', 'application/vnd.example+json');
res.json({ hello: 'world' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/vnd.example+json; charset=utf-8'
);
expect(await res.text()).toBe('{"hello":"world"}');
});
test('should set Content-Length and Content-Type', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ hello: '½ + ¼ = ¾' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(Number(res.headers.get('content-length'))).toBe(24);
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
});
describe('when given primitives', () => {
test('should respond with json for null', async () => {
mockListener.mockImplementation((req, res) => {
res.json(null);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('null');
});
test('should respond with json for Number', async () => {
mockListener.mockImplementation((req, res) => {
res.json(300);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('300');
});
test('should respond with json for String', async () => {
mockListener.mockImplementation((req, res) => {
res.json('str');
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('"str"');
});
});
test('should respond with json when given an array', async () => {
mockListener.mockImplementation((req, res) => {
res.json(['foo', 'bar', 'baz']);
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('["foo","bar","baz"]');
});
test('should respond with json when given an object', async () => {
mockListener.mockImplementation((req, res) => {
res.json({ name: 'tobi' });
});
const res = await fetchWithProxyReq(url);
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toBe(
'application/json; charset=utf-8'
);
expect(await res.text()).toBe('{"name":"tobi"}');
});
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/python",
"version": "2.2.1-canary.0",
"version": "2.2.1-canary.1",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@types/execa": "^0.9.0",
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@vercel/ncc": "0.24.0",
"execa": "^1.0.0",
"typescript": "4.3.4"

View File

@@ -1,7 +1,7 @@
{
"name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.1-canary.0",
"version": "1.3.1-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -22,7 +22,7 @@
"devDependencies": {
"@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0",
"@vercel/build-utils": "2.14.1-canary.0",
"@vercel/build-utils": "2.14.1-canary.1",
"@vercel/ncc": "0.24.0",
"execa": "2.0.4",
"fs-extra": "^7.0.1",