@vercel/rust 1.3.0 → 1.4.1

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.
Files changed (2) hide show
  1. package/dist/index.js +150 -38
  2. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -158,6 +158,7 @@ async function gatherExtraFiles(globMatcher, workPath) {
158
158
  // src/lib/start-dev-server.ts
159
159
  var import_child_process = require("child_process");
160
160
  var import_events = require("events");
161
+ var import_get_port = __toESM(require("get-port"));
161
162
  var import_build_utils5 = require("@vercel/build-utils");
162
163
 
163
164
  // src/lib/dev-build.ts
@@ -213,7 +214,7 @@ async function buildExecutableForDev(workPath, entrypoint) {
213
214
 
214
215
  // src/lib/dev-server.ts
215
216
  var import_build_utils4 = require("@vercel/build-utils");
216
- function createDevServerEnv(baseEnv, meta = {}) {
217
+ function createDevServerEnv(baseEnv, meta = {}, port) {
217
218
  const devEnv = {
218
219
  // Base environment
219
220
  ...Object.fromEntries(
@@ -225,6 +226,9 @@ function createDevServerEnv(baseEnv, meta = {}) {
225
226
  // Runtime environment from meta
226
227
  ...meta.env || {}
227
228
  };
229
+ if (typeof port === "number" && Number.isInteger(port)) {
230
+ devEnv.VERCEL_DEV_PORT = String(port);
231
+ }
228
232
  Object.keys(devEnv).forEach((key) => {
229
233
  if (devEnv[key] === void 0) {
230
234
  delete devEnv[key];
@@ -235,49 +239,147 @@ function createDevServerEnv(baseEnv, meta = {}) {
235
239
  }
236
240
 
237
241
  // src/lib/start-dev-server.ts
242
+ var SHUTDOWN_TIMEOUT = 35e3;
243
+ var ADDR_IN_USE_RE = /address (already )?in use|AddrInUse|EADDRINUSE/i;
244
+ var MAX_STDERR_CAPTURE = 8192;
245
+ var RUNNING_DEV_SERVERS = /* @__PURE__ */ new Set();
246
+ var cleanupHandlersInstalled = false;
247
+ function installGlobalCleanupHandlers() {
248
+ if (cleanupHandlersInstalled)
249
+ return;
250
+ cleanupHandlersInstalled = true;
251
+ const onSignal = () => {
252
+ for (const child of RUNNING_DEV_SERVERS) {
253
+ try {
254
+ child.kill("SIGTERM");
255
+ } catch (err) {
256
+ (0, import_build_utils5.debug)(`Error sending SIGTERM to Rust dev server on signal: ${err}`);
257
+ }
258
+ }
259
+ };
260
+ process.on("SIGINT", onSignal);
261
+ process.on("SIGTERM", onSignal);
262
+ process.on("SIGHUP", onSignal);
263
+ process.on("exit", () => {
264
+ for (const child of RUNNING_DEV_SERVERS) {
265
+ if (child.pid) {
266
+ try {
267
+ process.kill(child.pid, "SIGKILL");
268
+ } catch {
269
+ }
270
+ }
271
+ }
272
+ RUNNING_DEV_SERVERS.clear();
273
+ });
274
+ }
275
+ function trackDevServer(child) {
276
+ installGlobalCleanupHandlers();
277
+ RUNNING_DEV_SERVERS.add(child);
278
+ const untrack = () => {
279
+ RUNNING_DEV_SERVERS.delete(child);
280
+ };
281
+ child.once("exit", untrack);
282
+ child.once("close", untrack);
283
+ }
284
+ var RustDevServerError = class extends Error {
285
+ constructor(message) {
286
+ super(message);
287
+ this.name = "RustDevServerError";
288
+ }
289
+ };
290
+ function terminate(child) {
291
+ return new Promise((resolve) => {
292
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null) {
293
+ resolve();
294
+ return;
295
+ }
296
+ let timer;
297
+ let settled = false;
298
+ const done = () => {
299
+ if (settled)
300
+ return;
301
+ settled = true;
302
+ if (timer)
303
+ clearTimeout(timer);
304
+ resolve();
305
+ };
306
+ child.once("exit", done);
307
+ child.once("close", done);
308
+ try {
309
+ child.kill("SIGTERM");
310
+ } catch (err) {
311
+ (0, import_build_utils5.debug)(`Error sending SIGTERM to Rust dev server: ${err}`);
312
+ done();
313
+ return;
314
+ }
315
+ timer = setTimeout(() => {
316
+ (0, import_build_utils5.debug)(
317
+ `Rust dev server did not exit within ${SHUTDOWN_TIMEOUT}ms, sending SIGKILL`
318
+ );
319
+ try {
320
+ child.kill("SIGKILL");
321
+ } catch (err) {
322
+ (0, import_build_utils5.debug)(`Error sending SIGKILL to Rust dev server: ${err}`);
323
+ }
324
+ }, SHUTDOWN_TIMEOUT);
325
+ timer.unref?.();
326
+ });
327
+ }
238
328
  var startDevServer = async (opts) => {
239
- const { entrypoint, workPath, meta = {} } = opts;
329
+ const { entrypoint, workPath, meta = {}, onStdout, onStderr } = opts;
240
330
  try {
241
331
  await installRustToolchain();
242
332
  const executablePath = await buildExecutableForDev(workPath, entrypoint);
243
- (0, import_build_utils5.debug)(`Starting Rust dev server: ${executablePath}`);
244
- const devEnv = createDevServerEnv(process.env, meta);
333
+ const requestedPort = typeof meta.port === "number" ? meta.port : meta.env?.VERCEL_DEV_PORT ? Number(meta.env.VERCEL_DEV_PORT) : void 0;
334
+ const port = typeof requestedPort === "number" && Number.isInteger(requestedPort) ? requestedPort : await (0, import_get_port.default)();
335
+ (0, import_build_utils5.debug)(`Starting Rust dev server: ${executablePath} (port=${port})`);
336
+ const devEnv = createDevServerEnv(process.env, meta, port);
245
337
  const child = (0, import_child_process.spawn)(executablePath, [], {
246
338
  cwd: workPath,
247
339
  env: devEnv,
248
340
  stdio: ["pipe", "pipe", "pipe"]
249
341
  });
250
342
  if (!child.pid) {
251
- throw new Error("Failed to start dev server process");
343
+ throw new Error("Failed to start Rust dev server process");
252
344
  }
253
- (0, import_build_utils5.debug)(`Dev server process started with PID: ${child.pid}`);
345
+ trackDevServer(child);
346
+ (0, import_build_utils5.debug)(`Rust dev server process started with PID: ${child.pid}`);
254
347
  let buffer = "";
255
348
  let portEmitted = false;
349
+ let stderrTail = "";
256
350
  child.stdout?.on("data", (data) => {
257
- const output = data.toString();
258
- buffer += output;
259
- if (!portEmitted && buffer.includes("Dev server listening:")) {
260
- const portMatch = buffer.match(/Dev server listening: (\d+)/);
261
- if (portMatch) {
262
- const port = parseInt(portMatch[1], 10);
263
- (0, import_build_utils5.debug)(
264
- `Rust dev server detected port ${port}, emitting message event`
265
- );
266
- child.emit("message", { port }, null);
351
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
352
+ buffer += chunk.toString();
353
+ if (!portEmitted) {
354
+ const match = buffer.match(/Dev server listening:\s*(\d+)/);
355
+ if (match) {
267
356
  portEmitted = true;
357
+ const reportedPort = parseInt(match[1], 10);
358
+ (0, import_build_utils5.debug)(`Rust dev server reported ready on port ${reportedPort}`);
359
+ child.emit("message", { port: reportedPort }, null);
360
+ buffer = "";
268
361
  }
269
- buffer = "";
270
362
  }
271
- console.log(output);
363
+ if (onStdout) {
364
+ onStdout(chunk);
365
+ } else {
366
+ process.stdout.write(chunk.toString());
367
+ }
272
368
  });
273
369
  child.stderr?.on("data", (data) => {
274
- console.error(data.toString());
370
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
371
+ stderrTail = (stderrTail + chunk.toString()).slice(-MAX_STDERR_CAPTURE);
372
+ if (onStderr) {
373
+ onStderr(chunk);
374
+ } else {
375
+ process.stderr.write(chunk.toString());
376
+ }
275
377
  });
276
378
  child.on("error", (err) => {
277
- (0, import_build_utils5.debug)(`Dev server error: ${err}`);
379
+ (0, import_build_utils5.debug)(`Rust dev server error: ${err}`);
278
380
  });
279
- child.on("exit", (code, signal) => {
280
- (0, import_build_utils5.debug)(`Dev server exited with code ${code}, signal ${signal}`);
381
+ child.on("exit", (code, signal2) => {
382
+ (0, import_build_utils5.debug)(`Rust dev server exited with code ${code}, signal ${signal2}`);
281
383
  });
282
384
  const onMessage = (0, import_events.once)(child, "message");
283
385
  const onExit = (0, import_events.once)(child, "close");
@@ -287,31 +389,41 @@ var startDevServer = async (opts) => {
287
389
  return { state: "message", value: messageData };
288
390
  }),
289
391
  onExit.then((args) => {
290
- const [code, signal] = args;
291
- return { state: "exit", value: [code, signal] };
392
+ const [code, signal2] = args;
393
+ return { state: "exit", value: [code, signal2] };
292
394
  })
293
395
  ]);
294
396
  if (result.state === "message") {
295
- const { port } = result.value;
296
- (0, import_build_utils5.debug)(`Rust dev server ready on port ${port}`);
397
+ const readyPort = typeof result.value?.port === "number" ? result.value.port : port;
398
+ (0, import_build_utils5.debug)(`Rust dev server ready on port ${readyPort} (pid ${child.pid})`);
297
399
  if (!child.pid) {
298
400
  throw new Error("Child process has no PID");
299
401
  }
300
- const shutdown = async () => {
301
- try {
302
- process.kill(child.pid, "SIGTERM");
303
- } catch (err) {
304
- (0, import_build_utils5.debug)(`Error terminating Rust dev server: ${err}`);
305
- }
402
+ return {
403
+ port: readyPort,
404
+ pid: child.pid,
405
+ // Wait for exit so the port is released before `vercel dev` continues.
406
+ shutdown: () => terminate(child)
306
407
  };
307
- return { port, pid: child.pid, shutdown };
308
- } else {
309
- const [exitCode, signal] = result.value;
310
- const reason = signal ? `"${signal}" signal` : `exit code ${exitCode}`;
311
- throw new Error(`Rust dev server failed with ${reason}`);
312
408
  }
409
+ const [exitCode, signal] = result.value;
410
+ const reason = signal ? `"${signal}" signal` : `exit code ${exitCode}`;
411
+ const stderr = stderrTail.trim();
412
+ if (ADDR_IN_USE_RE.test(stderr)) {
413
+ throw new RustDevServerError(
414
+ `Rust dev server failed to bind port ${port} ("address already in use"). A previous dev server instance may not have shut down yet. Please retry, or ensure no other process is using that port.`
415
+ );
416
+ }
417
+ (0, import_build_utils5.debug)(
418
+ `Rust dev server exited before becoming ready (${reason}). Falling back to build-and-invoke mode.` + (stderr ? ` stderr:
419
+ ${stderr}` : "")
420
+ );
421
+ return null;
313
422
  } catch (error) {
314
- (0, import_build_utils5.debug)(`Failed to start dev server: ${error}`);
423
+ (0, import_build_utils5.debug)(`Failed to start Rust dev server: ${error}`);
424
+ if (error instanceof RustDevServerError) {
425
+ throw error;
426
+ }
315
427
  return null;
316
428
  }
317
429
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/rust",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/rust",
@@ -14,7 +14,8 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "smol-toml": "1.5.2",
17
- "execa": "5"
17
+ "execa": "5",
18
+ "get-port": "5.1.1"
18
19
  },
19
20
  "devDependencies": {
20
21
  "@types/ms": "^0.7.31",
@@ -26,8 +27,8 @@
26
27
  "prettier": "^2.8.4",
27
28
  "rimraf": "^4.1.1",
28
29
  "vitest": "2.0.3",
29
- "@vercel/build-utils": "13.26.2",
30
- "@vercel/routing-utils": "6.2.0"
30
+ "@vercel/build-utils": "14.0.1",
31
+ "@vercel/routing-utils": "6.4.1"
31
32
  },
32
33
  "scripts": {
33
34
  "build": "node ../../utils/build-builder.mjs",