@vercel/node 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addHelpers = void 0;
4
+ const utils_1 = require("../utils");
5
+ const stream_1 = require("stream");
6
+ class ApiError extends Error {
7
+ constructor(statusCode, message) {
8
+ super(message);
9
+ this.statusCode = statusCode;
10
+ }
11
+ }
12
+ function getBodyParser(body, contentType) {
13
+ return function parseBody() {
14
+ const { parse: parseContentType } = require('content-type');
15
+ const { type } = parseContentType(contentType);
16
+ if (type === 'application/json') {
17
+ try {
18
+ const str = body.toString();
19
+ return str ? JSON.parse(str) : {};
20
+ }
21
+ catch (error) {
22
+ throw new ApiError(400, 'Invalid JSON');
23
+ }
24
+ }
25
+ if (type === 'application/octet-stream')
26
+ return body;
27
+ if (type === 'application/x-www-form-urlencoded') {
28
+ const { parse: parseQS } = require('querystring');
29
+ // note: querystring.parse does not produce an iterable object
30
+ // https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
31
+ return parseQS(body.toString());
32
+ }
33
+ if (type === 'text/plain')
34
+ return body.toString();
35
+ return undefined;
36
+ };
37
+ }
38
+ function getQueryParser({ url = '/' }) {
39
+ return function parseQuery() {
40
+ const { parse: parseURL } = require('url');
41
+ return parseURL(url, true).query;
42
+ };
43
+ }
44
+ function getCookieParser(req) {
45
+ return function parseCookie() {
46
+ const header = req.headers.cookie;
47
+ if (!header)
48
+ return {};
49
+ const { parse } = require('cookie');
50
+ return parse(Array.isArray(header) ? header.join(';') : header);
51
+ };
52
+ }
53
+ function status(res, statusCode) {
54
+ res.statusCode = statusCode;
55
+ return res;
56
+ }
57
+ function setCharset(type, charset) {
58
+ const { parse, format } = require('content-type');
59
+ const parsed = parse(type);
60
+ parsed.parameters.charset = charset;
61
+ return format(parsed);
62
+ }
63
+ function redirect(res, statusOrUrl, url) {
64
+ if (typeof statusOrUrl === 'string') {
65
+ url = statusOrUrl;
66
+ statusOrUrl = 307;
67
+ }
68
+ if (typeof statusOrUrl !== 'number' || typeof url !== 'string') {
69
+ 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').`);
70
+ }
71
+ res.writeHead(statusOrUrl, { Location: url }).end();
72
+ return res;
73
+ }
74
+ function setLazyProp(req, prop, getter) {
75
+ const opts = { configurable: true, enumerable: true };
76
+ const optsReset = { ...opts, writable: true };
77
+ Object.defineProperty(req, prop, {
78
+ ...opts,
79
+ get: () => {
80
+ const value = getter();
81
+ // we set the property on the object to avoid recalculating it
82
+ Object.defineProperty(req, prop, { ...optsReset, value });
83
+ return value;
84
+ },
85
+ set: value => {
86
+ Object.defineProperty(req, prop, { ...optsReset, value });
87
+ },
88
+ });
89
+ }
90
+ function createETag(body, encoding) {
91
+ const etag = require('etag');
92
+ const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
93
+ return etag(buf, { weak: true });
94
+ }
95
+ function json(req, res, jsonBody) {
96
+ const body = JSON.stringify(jsonBody);
97
+ if (!res.getHeader('content-type')) {
98
+ res.setHeader('content-type', 'application/json; charset=utf-8');
99
+ }
100
+ return send(req, res, body);
101
+ }
102
+ function send(req, res, body) {
103
+ let chunk = body;
104
+ let encoding;
105
+ switch (typeof chunk) {
106
+ // string defaulting to html
107
+ case 'string':
108
+ if (!res.getHeader('content-type')) {
109
+ res.setHeader('content-type', 'text/html');
110
+ }
111
+ break;
112
+ case 'boolean':
113
+ case 'number':
114
+ case 'object':
115
+ if (chunk === null) {
116
+ chunk = '';
117
+ }
118
+ else if (Buffer.isBuffer(chunk)) {
119
+ if (!res.getHeader('content-type')) {
120
+ res.setHeader('content-type', 'application/octet-stream');
121
+ }
122
+ }
123
+ else {
124
+ return json(req, res, chunk);
125
+ }
126
+ break;
127
+ }
128
+ // write strings in utf-8
129
+ if (typeof chunk === 'string') {
130
+ encoding = 'utf8';
131
+ // reflect this in content-type
132
+ const type = res.getHeader('content-type');
133
+ if (typeof type === 'string') {
134
+ res.setHeader('content-type', setCharset(type, 'utf-8'));
135
+ }
136
+ }
137
+ // populate Content-Length
138
+ let len;
139
+ if (chunk !== undefined) {
140
+ if (Buffer.isBuffer(chunk)) {
141
+ // get length of Buffer
142
+ len = chunk.length;
143
+ }
144
+ else if (typeof chunk === 'string') {
145
+ if (chunk.length < 1000) {
146
+ // just calculate length small chunk
147
+ len = Buffer.byteLength(chunk, encoding);
148
+ }
149
+ else {
150
+ // convert chunk to Buffer and calculate
151
+ const buf = Buffer.from(chunk, encoding);
152
+ len = buf.length;
153
+ chunk = buf;
154
+ encoding = undefined;
155
+ }
156
+ }
157
+ else {
158
+ throw new Error('`body` is not a valid string, object, boolean, number, Stream, or Buffer');
159
+ }
160
+ if (len !== undefined) {
161
+ res.setHeader('content-length', len);
162
+ }
163
+ }
164
+ // populate ETag
165
+ let etag;
166
+ if (!res.getHeader('etag') &&
167
+ len !== undefined &&
168
+ (etag = createETag(chunk, encoding))) {
169
+ res.setHeader('etag', etag);
170
+ }
171
+ // strip irrelevant headers
172
+ if (204 === res.statusCode || 304 === res.statusCode) {
173
+ res.removeHeader('Content-Type');
174
+ res.removeHeader('Content-Length');
175
+ res.removeHeader('Transfer-Encoding');
176
+ chunk = '';
177
+ }
178
+ if (req.method === 'HEAD') {
179
+ // skip body for HEAD
180
+ res.end();
181
+ }
182
+ else if (encoding) {
183
+ // respond with encoding
184
+ res.end(chunk, encoding);
185
+ }
186
+ else {
187
+ // respond without encoding
188
+ res.end(chunk);
189
+ }
190
+ return res;
191
+ }
192
+ function restoreBody(req, body) {
193
+ const replicateBody = new stream_1.PassThrough();
194
+ const on = replicateBody.on.bind(replicateBody);
195
+ const originalOn = req.on.bind(req);
196
+ req.read = replicateBody.read.bind(replicateBody);
197
+ req.on = req.addListener = (name, cb) =>
198
+ // @ts-expect-error
199
+ name === 'data' || name === 'end' ? on(name, cb) : originalOn(name, cb);
200
+ replicateBody.write(body);
201
+ replicateBody.end();
202
+ }
203
+ async function readBody(req) {
204
+ const body = (await (0, utils_1.serializeBody)(req)) || Buffer.from('');
205
+ restoreBody(req, body);
206
+ return body;
207
+ }
208
+ async function addHelpers(_req, _res) {
209
+ const req = _req;
210
+ const res = _res;
211
+ setLazyProp(req, 'cookies', getCookieParser(req));
212
+ setLazyProp(req, 'query', getQueryParser(req));
213
+ const contentType = req.headers['content-type'];
214
+ const body = contentType === undefined ? Buffer.from('') : await readBody(req);
215
+ setLazyProp(req, 'body', getBodyParser(body, contentType));
216
+ res.status = statusCode => status(res, statusCode);
217
+ res.redirect = (statusOrUrl, url) => redirect(res, statusOrUrl, url);
218
+ res.send = body => send(req, res, body);
219
+ res.json = jsonBody => json(req, res, jsonBody);
220
+ }
221
+ exports.addHelpers = addHelpers;
@@ -0,0 +1,65 @@
1
+ import { addHelpers } from './helpers.js';
2
+ import { createServer } from 'http';
3
+ import { serializeBody } from '../utils.js';
4
+ import { streamToBuffer } from '@vercel/build-utils';
5
+ import exitHook from 'exit-hook';
6
+ import fetch from 'node-fetch';
7
+ import asyncListen from 'async-listen';
8
+ import { isAbsolute } from 'path';
9
+ import { pathToFileURL } from 'url';
10
+ const { default: listen } = asyncListen;
11
+ async function createServerlessServer(userCode, options) {
12
+ const server = createServer(async (req, res) => {
13
+ if (options.shouldAddHelpers)
14
+ await addHelpers(req, res);
15
+ return userCode(req, res);
16
+ });
17
+ exitHook(() => server.close());
18
+ return { url: await listen(server) };
19
+ }
20
+ async function compileUserCode(entrypointPath) {
21
+ const id = isAbsolute(entrypointPath)
22
+ ? pathToFileURL(entrypointPath).href
23
+ : entrypointPath;
24
+ let fn = await import(id);
25
+ /**
26
+ * In some cases we might have nested default props due to TS => JS
27
+ */
28
+ for (let i = 0; i < 5; i++) {
29
+ if (fn.default)
30
+ fn = fn.default;
31
+ }
32
+ return fn;
33
+ }
34
+ export async function createServerlessEventHandler(entrypointPath, options) {
35
+ const userCode = await compileUserCode(entrypointPath);
36
+ const server = await createServerlessServer(userCode, options);
37
+ return async function (request) {
38
+ const url = new URL(request.url ?? '/', server.url);
39
+ // @ts-expect-error
40
+ const response = await fetch(url, {
41
+ body: await serializeBody(request),
42
+ headers: {
43
+ ...request.headers,
44
+ host: request.headers['x-forwarded-host'],
45
+ },
46
+ method: request.method,
47
+ redirect: 'manual',
48
+ });
49
+ let body;
50
+ if (options.mode === 'streaming') {
51
+ body = response.body;
52
+ }
53
+ else {
54
+ body = await streamToBuffer(response.body);
55
+ response.headers.delete('transfer-encoding');
56
+ response.headers.set('content-length', body.length);
57
+ }
58
+ return {
59
+ status: response.status,
60
+ headers: response.headers,
61
+ body,
62
+ encoding: 'utf8',
63
+ };
64
+ };
65
+ }
@@ -229,7 +229,7 @@ function register(opts = {}) {
229
229
  const diagnosticList = filterDiagnostics(diagnostics, ignoreDiagnostics);
230
230
  reportTSError(diagnosticList, config.options.noEmitOnError);
231
231
  if (output.emitSkipped) {
232
- throw new TypeError(`${path_1.relative(cwd, fileName)}: Emit skipped`);
232
+ throw new TypeError(`${(0, path_1.relative)(cwd, fileName)}: Emit skipped`);
233
233
  }
234
234
  // Throw an error when requiring `.d.ts` files.
235
235
  if (output.outputFiles.length === 0) {
@@ -237,7 +237,7 @@ function register(opts = {}) {
237
237
  'This is usually the result of a faulty configuration or import. ' +
238
238
  'Make sure there is a `.js`, `.json` or another executable extension and ' +
239
239
  'loader (attached before `ts-node`) available alongside ' +
240
- `\`${path_1.basename(fileName)}\`.`);
240
+ `\`${(0, path_1.basename)(fileName)}\`.`);
241
241
  }
242
242
  const file = {
243
243
  code: output.outputFiles[1].text,
@@ -265,7 +265,7 @@ function register(opts = {}) {
265
265
  */
266
266
  function readConfig(configFileName) {
267
267
  let config = { compilerOptions: {} };
268
- const basePath = normalizeSlashes(path_1.dirname(configFileName));
268
+ const basePath = normalizeSlashes((0, path_1.dirname)(configFileName));
269
269
  // Read project configuration when available.
270
270
  if (configFileName) {
271
271
  const result = ts.readConfigFile(configFileName, readFile);
@@ -307,7 +307,7 @@ function register(opts = {}) {
307
307
  const output = {
308
308
  code: value,
309
309
  map: Object.assign(JSON.parse(sourceMap), {
310
- file: path_1.basename(fileName),
310
+ file: (0, path_1.basename)(fileName),
311
311
  sources: [fileName],
312
312
  }),
313
313
  };
package/dist/utils.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isEdgeRuntime = exports.EdgeRuntimes = exports.logError = exports.entrypointToOutputPath = exports.getRegExpFromMatchers = void 0;
4
- const path_1 = require("path");
5
- const path_to_regexp_1 = require("path-to-regexp");
3
+ exports.serializeBody = exports.isEdgeRuntime = exports.EdgeRuntimes = exports.logError = exports.entrypointToOutputPath = exports.getRegExpFromMatchers = void 0;
6
4
  const build_utils_1 = require("@vercel/build-utils");
5
+ const path_to_regexp_1 = require("path-to-regexp");
6
+ const path_1 = require("path");
7
7
  function getRegExpFromMatchers(matcherOrMatchers) {
8
8
  if (!matcherOrMatchers) {
9
9
  return '^/.*$';
@@ -22,9 +22,9 @@ function getRegExpFromMatcher(matcher, index, allMatchers) {
22
22
  if (!matcher.startsWith('/')) {
23
23
  throw new Error(`Middleware's \`config.matcher\` values must start with "/". Received: ${matcher}`);
24
24
  }
25
- const regExps = [path_to_regexp_1.pathToRegexp(matcher).source];
25
+ const regExps = [(0, path_to_regexp_1.pathToRegexp)(matcher).source];
26
26
  if (matcher === '/' && !allMatchers.includes('/index')) {
27
- regExps.push(path_to_regexp_1.pathToRegexp('/index').source);
27
+ regExps.push((0, path_to_regexp_1.pathToRegexp)('/index').source);
28
28
  }
29
29
  return regExps;
30
30
  }
@@ -39,7 +39,7 @@ function getRegExpFromMatcher(matcher, index, allMatchers) {
39
39
  */
40
40
  function entrypointToOutputPath(entrypoint, zeroConfig) {
41
41
  if (zeroConfig) {
42
- const ext = path_1.extname(entrypoint);
42
+ const ext = (0, path_1.extname)(entrypoint);
43
43
  return entrypoint.slice(0, entrypoint.length - ext.length);
44
44
  }
45
45
  return entrypoint;
@@ -52,7 +52,7 @@ function logError(error) {
52
52
  // because it points to internals, not user code
53
53
  const errorPrefixLength = 'Error: '.length;
54
54
  const errorMessageLength = errorPrefixLength + error.message.length;
55
- build_utils_1.debug(error.stack.substring(errorMessageLength + 1));
55
+ (0, build_utils_1.debug)(error.stack.substring(errorMessageLength + 1));
56
56
  }
57
57
  }
58
58
  exports.logError = logError;
@@ -66,3 +66,9 @@ function isEdgeRuntime(runtime) {
66
66
  Object.values(EdgeRuntimes).includes(runtime));
67
67
  }
68
68
  exports.isEdgeRuntime = isEdgeRuntime;
69
+ async function serializeBody(request) {
70
+ return request.method !== 'GET' && request.method !== 'HEAD'
71
+ ? await (0, build_utils_1.streamToBuffer)(request)
72
+ : undefined;
73
+ }
74
+ exports.serializeBody = serializeBody;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/node",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build": "node build",
14
- "test": "jest --env node --verbose --bail --runInBand",
14
+ "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --env node --verbose --bail --runInBand",
15
15
  "test-unit": "pnpm test test/unit",
16
16
  "test-e2e": "pnpm test test/integration"
17
17
  },
@@ -21,17 +21,19 @@
21
21
  "dependencies": {
22
22
  "@edge-runtime/vm": "2.0.0",
23
23
  "@types/node": "14.18.33",
24
- "@vercel/build-utils": "6.7.1",
25
- "@vercel/node-bridge": "4.0.1",
26
- "@vercel/static-config": "2.0.16",
27
- "edge-runtime": "2.0.0",
24
+ "@types/node-fetch": "2.6.3",
25
+ "@vercel/build-utils": "6.7.2",
26
+ "@vercel/error-utils": "1.0.8",
27
+ "@vercel/static-config": "2.0.17",
28
+ "async-listen": "1.2.0",
29
+ "edge-runtime": "2.1.4",
28
30
  "esbuild": "0.14.47",
29
31
  "exit-hook": "2.2.1",
30
- "node-fetch": "2.6.7",
32
+ "node-fetch": "2.6.9",
31
33
  "path-to-regexp": "6.2.1",
32
34
  "ts-morph": "12.0.0",
33
35
  "ts-node": "10.9.1",
34
- "typescript": "4.3.4"
36
+ "typescript": "4.9.5"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@babel/core": "7.5.0",
@@ -41,18 +43,18 @@
41
43
  "@types/content-type": "1.1.3",
42
44
  "@types/cookie": "0.3.3",
43
45
  "@types/etag": "1.8.0",
44
- "@types/jest": "27.4.1",
45
- "@types/node-fetch": "^2.6.1",
46
+ "@types/jest": "29.5.0",
46
47
  "@types/test-listen": "1.1.0",
47
48
  "@vercel/ncc": "0.24.0",
48
49
  "@vercel/nft": "0.22.5",
49
50
  "content-type": "1.0.4",
50
51
  "cookie": "0.4.0",
52
+ "cross-env": "7.0.3",
51
53
  "etag": "1.8.1",
52
54
  "execa": "3.2.0",
53
55
  "fs-extra": "11.1.0",
54
56
  "source-map-support": "0.5.12",
55
57
  "test-listen": "1.1.0"
56
58
  },
57
- "gitHead": "a63b9d960bec291e152308c30074843aab802c8d"
59
+ "gitHead": "2de365f9cfea3ce283d2bf855507c71209f1e3d8"
58
60
  }
@@ -1,4 +0,0 @@
1
- /// <reference types="node" />
2
- import { IncomingMessage } from 'http';
3
- import { VercelProxyResponse } from '@vercel/node-bridge/types';
4
- export declare function createEdgeEventHandler(entrypointFullPath: string, entrypointRelativePath: string, isMiddleware: boolean, isZeroConfig?: boolean): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>>;
@@ -1,15 +0,0 @@
1
- import type { Plugin } from 'esbuild';
2
- export declare class NodeCompatBindings {
3
- private bindings;
4
- use(modulePath: `node:${string}`): string;
5
- getContext(): Record<string, unknown>;
6
- }
7
- /**
8
- * Allows to enable Node.js compatibility by detecting namespaced `node:`
9
- * imports and producing metadata to bind global variables for each.
10
- * It requires from the consumer to add the imports.
11
- */
12
- export declare function createNodeCompatPlugin(): {
13
- plugin: Plugin;
14
- bindings: NodeCompatBindings;
15
- };
@@ -1,21 +0,0 @@
1
- import type { Plugin } from 'esbuild';
2
- export declare class WasmAssets {
3
- private readonly assets;
4
- /**
5
- * Declare a WebAssembly binding
6
- */
7
- declare(filePath: string): Promise<string>;
8
- /**
9
- * Get an object with the context needed to execute the code
10
- * built with the plugin
11
- */
12
- getContext(): Promise<Record<string, WebAssembly.Module>>;
13
- /**
14
- * Allow to iterate easily
15
- */
16
- [Symbol.iterator](): IterableIterator<[string, string]>;
17
- }
18
- export declare function createEdgeWasmPlugin(): {
19
- plugin: Plugin;
20
- wasmAssets: WasmAssets;
21
- };
@@ -1,34 +0,0 @@
1
- /// <reference types="node" />
2
- import { Config, Meta } from '@vercel/build-utils';
3
- import { ChildProcess } from 'child_process';
4
- export declare function forkDevServer(options: {
5
- tsConfig: any;
6
- config: Config;
7
- maybeTranspile: boolean;
8
- workPath: string | undefined;
9
- isTypeScript: boolean;
10
- isEsm: boolean;
11
- require_: NodeRequire;
12
- entrypoint: string;
13
- meta: Meta;
14
- /**
15
- * A path to the dev-server path. This is used in tests.
16
- */
17
- devServerPath?: string;
18
- }): ChildProcess & {
19
- pid: number;
20
- };
21
- /**
22
- * When launching a dev-server, we want to know its state.
23
- * This function will be used to know whether it was exited (due to some error),
24
- * or it is listening to new requests, and we can start proxying requests.
25
- */
26
- export declare function readMessage(child: ChildProcess): Promise<{
27
- state: 'message';
28
- value: {
29
- port: number;
30
- };
31
- } | {
32
- state: 'exit';
33
- value: [number, string | null];
34
- }>;
@@ -1,7 +0,0 @@
1
- /// <reference types="node" />
2
- import { IncomingMessage } from 'http';
3
- import { VercelProxyResponse } from '@vercel/node-bridge/types';
4
- export declare function createServerlessEventHandler(entrypoint: string, options: {
5
- shouldAddHelpers: boolean;
6
- useRequire: boolean;
7
- }): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>>;
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createServerlessEventHandler = void 0;
4
- const launcher_js_1 = require("@vercel/node-bridge/launcher.js");
5
- function rawBody(readable) {
6
- return new Promise((resolve, reject) => {
7
- let bytes = 0;
8
- const chunks = [];
9
- readable.on('error', reject);
10
- readable.on('data', chunk => {
11
- chunks.push(chunk);
12
- bytes += chunk.length;
13
- });
14
- readable.on('end', () => {
15
- resolve(Buffer.concat(chunks, bytes));
16
- });
17
- });
18
- }
19
- async function createServerlessEventHandler(entrypoint, options) {
20
- const launcher = launcher_js_1.getVercelLauncher({
21
- entrypointPath: entrypoint,
22
- helpersPath: './helpers.js',
23
- shouldAddHelpers: options.shouldAddHelpers,
24
- useRequire: options.useRequire,
25
- // not used
26
- bridgePath: '',
27
- sourcemapSupportPath: '',
28
- });
29
- const bridge = launcher();
30
- return async function (request) {
31
- const body = await rawBody(request);
32
- const event = {
33
- Action: 'Invoke',
34
- body: JSON.stringify({
35
- method: request.method,
36
- path: request.url,
37
- headers: request.headers,
38
- encoding: 'base64',
39
- body: body.toString('base64'),
40
- }),
41
- };
42
- return bridge.launcher(event, {
43
- callbackWaitsForEmptyEventLoop: false,
44
- });
45
- };
46
- }
47
- exports.createServerlessEventHandler = createServerlessEventHandler;