@vercel/node 2.2.1-canary.1 → 2.3.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.
@@ -70,6 +70,16 @@ const static_config_1 = require("@vercel/static-config");
70
70
  const ts_morph_1 = require("ts-morph");
71
71
  const ncc_1 = __importDefault(require("@vercel/ncc"));
72
72
  const node_fetch_1 = __importDefault(require("node-fetch"));
73
+ function logError(error) {
74
+ console.error(error.message);
75
+ if (error.stack) {
76
+ // only show the stack trace if debug is enabled
77
+ // because it points to internals, not user code
78
+ const errorPrefixLength = 'Error: '.length;
79
+ const errorMessageLength = errorPrefixLength + error.message.length;
80
+ build_utils_1.debug(error.stack.substring(errorMessageLength + 1));
81
+ }
82
+ }
73
83
  function listen(server, port, host) {
74
84
  return new Promise(resolve => {
75
85
  server.listen(port, host, () => {
@@ -115,81 +125,146 @@ async function serializeRequest(message) {
115
125
  body,
116
126
  });
117
127
  }
118
- async function createEdgeEventHandler(entrypoint) {
119
- const buildResult = await ncc_1.default(entrypoint, { target: 'es2022' });
120
- const userCode = buildResult.code;
121
- const initialCode = `
122
- ${userCode};
128
+ async function compileUserCode(entrypoint) {
129
+ try {
130
+ const buildResult = await ncc_1.default(entrypoint, {
131
+ target: 'es2022',
132
+ });
133
+ const userCode = buildResult.code;
134
+ return `
135
+ ${userCode};
123
136
 
124
- addEventListener('fetch', async (event) => {
125
- let serializedRequest = await event.request.text();
126
- let requestDetails = JSON.parse(serializedRequest);
137
+ addEventListener('fetch', async (event) => {
138
+ try {
139
+ let serializedRequest = await event.request.text();
140
+ let requestDetails = JSON.parse(serializedRequest);
127
141
 
128
- let body;
142
+ let body;
129
143
 
130
- if (requestDetails.method !== 'GET' && requestDetails.method !== 'HEAD') {
131
- body = Uint8Array.from(atob(requestDetails.body), c => c.charCodeAt(0));
132
- }
144
+ if (requestDetails.method !== 'GET' && requestDetails.method !== 'HEAD') {
145
+ body = Uint8Array.from(atob(requestDetails.body), c => c.charCodeAt(0));
146
+ }
133
147
 
134
- let requestUrl = requestDetails.headers['x-forwarded-proto'] + '://' + requestDetails.headers['x-forwarded-host'] + requestDetails.url;
148
+ let requestUrl = requestDetails.headers['x-forwarded-proto'] + '://' + requestDetails.headers['x-forwarded-host'] + requestDetails.url;
135
149
 
136
- let request = new Request(requestUrl, {
137
- headers: requestDetails.headers,
138
- method: requestDetails.method,
139
- body: body
140
- });
150
+ let request = new Request(requestUrl, {
151
+ headers: requestDetails.headers,
152
+ method: requestDetails.method,
153
+ body: body
154
+ });
141
155
 
142
- event.request = request;
156
+ event.request = request;
143
157
 
144
- let edgeHandler = module.exports.default;
145
- let response = edgeHandler(event.request, event);
146
- return event.respondWith(response);
147
- })`;
148
- const edgeRuntime = new edge_runtime_1.EdgeRuntime({
149
- initialCode,
150
- extend: (context) => {
151
- Object.assign(context, {
152
- __dirname: '',
153
- module: {
154
- exports: {},
155
- },
156
- });
157
- return context;
158
- },
159
- });
160
- const server = await edge_runtime_1.runServer({ runtime: edgeRuntime });
161
- exit_hook_1.default(server.close);
158
+ let edgeHandler = module.exports.default;
159
+ if (!edgeHandler) {
160
+ throw new Error('No default export was found. Add a default export to handle requests.');
161
+ }
162
+
163
+ let response = await edgeHandler(event.request, event);
164
+
165
+ return event.respondWith(response);
166
+ } catch (error) {
167
+ // we can't easily show a meaningful stack trace
168
+ // so, stick to just the error message for now
169
+ event.respondWith(new Response(error.message, {
170
+ status: 500,
171
+ headers: {
172
+ 'x-vercel-failed': 'edge-wrapper'
173
+ }
174
+ }));
175
+ }
176
+ })`;
177
+ }
178
+ catch (error) {
179
+ // We can't easily show a meaningful stack trace from ncc -> edge-runtime.
180
+ // So, stick with just the message for now.
181
+ console.log(`Failed to instantiate edge runtime: ${error.message}`);
182
+ return undefined;
183
+ }
184
+ }
185
+ async function createEdgeRuntime(userCode) {
186
+ try {
187
+ if (!userCode) {
188
+ return undefined;
189
+ }
190
+ const edgeRuntime = new edge_runtime_1.EdgeRuntime({
191
+ initialCode: userCode,
192
+ extend: (context) => {
193
+ Object.assign(context, {
194
+ __dirname: '',
195
+ module: {
196
+ exports: {},
197
+ },
198
+ });
199
+ return context;
200
+ },
201
+ });
202
+ const server = await edge_runtime_1.runServer({ runtime: edgeRuntime });
203
+ exit_hook_1.default(server.close);
204
+ return server;
205
+ }
206
+ catch (error) {
207
+ // We can't easily show a meaningful stack trace from ncc -> edge-runtime.
208
+ // So, stick with just the message for now.
209
+ console.log(`Failed to instantiate edge runtime: ${error.message}`);
210
+ return undefined;
211
+ }
212
+ }
213
+ async function createEdgeEventHandler(entrypoint) {
214
+ const userCode = await compileUserCode(entrypoint);
215
+ const server = await createEdgeRuntime(userCode);
162
216
  return async function (request) {
217
+ if (!server) {
218
+ // this error state is already logged, but we have to wait until here to exit the process
219
+ // this matches the serverless function bridge launcher's behavior when
220
+ // an error is thrown in the function
221
+ process.exit(1);
222
+ }
163
223
  const response = await node_fetch_1.default(server.url, {
224
+ redirect: 'manual',
164
225
  method: 'post',
165
226
  body: await serializeRequest(request),
166
227
  });
228
+ const body = await response.text();
229
+ const isUserError = response.headers.get('x-vercel-failed') === 'edge-wrapper';
230
+ if (isUserError && response.status >= 500) {
231
+ // this error was "unhandled" from the user code's perspective
232
+ console.log(`Unhandled rejection: ${body}`);
233
+ // this matches the serverless function bridge launcher's behavior when
234
+ // an error is thrown in the function
235
+ process.exit(1);
236
+ }
167
237
  return {
168
238
  statusCode: response.status,
169
239
  headers: response.headers.raw(),
170
- body: await response.text(),
240
+ body,
171
241
  encoding: 'utf8',
172
242
  };
173
243
  };
174
244
  }
175
245
  const validRuntimes = ['experimental-edge'];
176
- function parseRuntime(entrypoint) {
246
+ function parseRuntime(entrypoint, entryPointPath) {
177
247
  const project = new ts_morph_1.Project();
178
- const staticConfig = static_config_1.getConfig(project, entrypoint);
248
+ const staticConfig = static_config_1.getConfig(project, entryPointPath);
179
249
  const runtime = staticConfig?.runtime;
180
250
  if (runtime && !validRuntimes.includes(runtime)) {
181
- throw new Error(`Invalid function runtime for "${entrypoint}": ${runtime}`);
251
+ throw new Error(`Invalid function runtime "${runtime}" for "${entrypoint}". Valid runtimes are: ${JSON.stringify(validRuntimes)}`);
182
252
  }
183
253
  return runtime;
184
254
  }
185
- async function createEventHandler(entrypoint, options) {
186
- const runtime = parseRuntime(entrypoint);
187
- if (runtime === 'experimental-edge') {
188
- return createEdgeEventHandler(entrypoint);
255
+ async function createEventHandler(entrypoint, config, options) {
256
+ const entryPointPath = path_1.join(process.cwd(), entrypoint);
257
+ const runtime = parseRuntime(entrypoint, entryPointPath);
258
+ // `middleware.js`/`middleware.ts` file is always run as
259
+ // an Edge Function, otherwise needs to be opted-in via
260
+ // `export const config = { runtime: 'experimental-edge' }`
261
+ if (config.middleware === true || runtime === 'experimental-edge') {
262
+ return createEdgeEventHandler(entryPointPath);
189
263
  }
190
- return createServerlessEventHandler(entrypoint, options);
264
+ return createServerlessEventHandler(entryPointPath, options);
191
265
  }
192
266
  let handleEvent;
267
+ let handlerEventError;
193
268
  async function main() {
194
269
  const config = JSON.parse(process.env.VERCEL_DEV_CONFIG || '{}');
195
270
  delete process.env.VERCEL_DEV_CONFIG;
@@ -198,8 +273,15 @@ async function main() {
198
273
  const shouldAddHelpers = !(config.helpers === false || buildEnv.NODEJS_HELPERS === '0');
199
274
  const proxyServer = http_1.createServer(onDevRequest);
200
275
  await listen(proxyServer, 0, '127.0.0.1');
201
- const entryPointPath = path_1.join(process.cwd(), entrypoint);
202
- handleEvent = await createEventHandler(entryPointPath, { shouldAddHelpers });
276
+ try {
277
+ handleEvent = await createEventHandler(entrypoint, config, {
278
+ shouldAddHelpers,
279
+ });
280
+ }
281
+ catch (error) {
282
+ logError(error);
283
+ handlerEventError = error;
284
+ }
203
285
  const address = proxyServer.address();
204
286
  if (typeof process.send === 'function') {
205
287
  process.send(address);
@@ -224,6 +306,12 @@ function rawBody(readable) {
224
306
  }
225
307
  exports.rawBody = rawBody;
226
308
  async function onDevRequest(req, res) {
309
+ if (handlerEventError) {
310
+ // this error state is already logged, but we have to wait until here to exit the process
311
+ // this matches the serverless function bridge launcher's behavior when
312
+ // an error is thrown in the function
313
+ process.exit(1);
314
+ }
227
315
  if (!handleEvent) {
228
316
  res.statusCode = 500;
229
317
  res.end('Bridge is not ready, please try again');
@@ -259,6 +347,6 @@ function fixConfigDev(config) {
259
347
  }
260
348
  exports.fixConfigDev = fixConfigDev;
261
349
  main().catch(err => {
262
- console.error(err);
350
+ logError(err);
263
351
  process.exit(1);
264
352
  });
package/dist/index.js CHANGED
@@ -307234,7 +307234,21 @@ const prepareCache = ({ repoRootPath, workPath }) => {
307234
307234
  exports.prepareCache = prepareCache;
307235
307235
  const startDevServer = async (opts) => {
307236
307236
  const { entrypoint, workPath, config, meta = {} } = opts;
307237
- const entryDir = path_1.join(workPath, path_1.dirname(entrypoint));
307237
+ const entrypointPath = path_1.join(workPath, entrypoint);
307238
+ if (config.middleware === true && typeof meta.requestUrl === 'string') {
307239
+ // TODO: static config is also parsed in `dev-server.ts`.
307240
+ // we should pass in this version as an env var instead.
307241
+ const project = new ts_morph_1.Project();
307242
+ const staticConfig = static_config_1.getConfig(project, entrypointPath);
307243
+ // Middleware is a catch-all for all paths unless a `matcher` property is defined
307244
+ const matchers = new RegExp(utils_1.getRegExpFromMatchers(staticConfig?.matcher));
307245
+ if (!matchers.test(meta.requestUrl)) {
307246
+ // If the "matchers" doesn't say to handle this
307247
+ // path then skip middleware invocation
307248
+ return null;
307249
+ }
307250
+ }
307251
+ const entryDir = path_1.dirname(entrypointPath);
307238
307252
  const projectTsConfig = await build_utils_1.walkParentDirs({
307239
307253
  base: workPath,
307240
307254
  start: entryDir,
@@ -307263,6 +307277,9 @@ const startDevServer = async (opts) => {
307263
307277
  },
307264
307278
  });
307265
307279
  const { pid } = child;
307280
+ if (!pid) {
307281
+ throw new Error(`Child Process has no "pid" when forking: "${devServerPath}"`);
307282
+ }
307266
307283
  const onMessage = once_1.default(child, 'message');
307267
307284
  const onExit = once_1.default.spread(child, 'exit');
307268
307285
  const result = await Promise.race([onMessage, onExit]);
@@ -307284,12 +307301,12 @@ const startDevServer = async (opts) => {
307284
307301
  // Got "exit" event from child process
307285
307302
  const [exitCode, signal] = result;
307286
307303
  const reason = signal ? `"${signal}" signal` : `exit code ${exitCode}`;
307287
- throw new Error(`\`node ${entrypoint}\` failed with ${reason}`);
307304
+ throw new Error(`Function \`${entrypoint}\` failed with ${reason}`);
307288
307305
  }
307289
307306
  };
307290
307307
  exports.startDevServer = startDevServer;
307291
307308
  async function doTypeCheck({ entrypoint, workPath, meta = {} }, projectTsConfig) {
307292
- const { devCacheDir = path_1.join(workPath, '.now', 'cache') } = meta;
307309
+ const { devCacheDir = path_1.join(workPath, '.vercel', 'cache') } = meta;
307293
307310
  const entrypointCacheDir = path_1.join(devCacheDir, 'node', entrypoint);
307294
307311
  // In order to type-check a single file, a standalone tsconfig
307295
307312
  // file needs to be created that inherits from the base one :(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/node",
3
- "version": "2.2.1-canary.1",
3
+ "version": "2.3.0",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@types/node": "*",
34
- "@vercel/build-utils": "4.1.1-canary.1",
34
+ "@vercel/build-utils": "4.2.0",
35
35
  "@vercel/ncc": "0.24.0",
36
36
  "@vercel/node-bridge": "3.0.0",
37
37
  "@vercel/static-config": "2.0.1",
@@ -60,5 +60,5 @@
60
60
  "source-map-support": "0.5.12",
61
61
  "test-listen": "1.1.0"
62
62
  },
63
- "gitHead": "998f6bf6e65301a19a400886bcc37b2dbbcdabe3"
63
+ "gitHead": "eed39913e1394477b224c38efe29429b17eeada6"
64
64
  }