mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-23 09:59:12 +00:00
Compare commits
5 Commits
@vercel/py
...
@vercel/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0351f02dff | ||
|
|
0d7fa2f912 | ||
|
|
3b646880e7 | ||
|
|
350a0e5f36 | ||
|
|
5c21d400bd |
1
examples/nextjs/.gitignore
vendored
1
examples/nextjs/.gitignore
vendored
@@ -23,6 +23,7 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
module.exports = {
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
6477
examples/nextjs/package-lock.json
generated
6477
examples/nextjs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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
1633
examples/nextjs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
Flask==1.0.3
|
||||
Flask==2.0.1
|
||||
werkzeug==2.0.1
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
842
packages/node/test/helpers.test.js
vendored
842
packages/node/test/helpers.test.js
vendored
@@ -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"}');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user