@vercel/container 0.0.3 → 0.0.5

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 +122 -26
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  version: () => version
37
37
  });
38
38
  module.exports = __toCommonJS(src_exports);
39
+ var import_build_utils3 = require("@vercel/build-utils");
39
40
  var import_node_fs6 = require("fs");
40
41
  var import_node_path6 = __toESM(require("path"));
41
42
 
@@ -87,6 +88,17 @@ function isDockerfileRef(ref) {
87
88
  const base = (0, import_node_path.basename)(ref).toLowerCase();
88
89
  return base === "dockerfile" || base === "containerfile" || base === "dockerfile.vercel" || base === "containerfile.vercel";
89
90
  }
91
+ var DOCKERFILE_CANDIDATES = [
92
+ "Dockerfile.vercel",
93
+ "Containerfile.vercel"
94
+ ];
95
+ function findDockerfile(workPath) {
96
+ return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path.join)(workPath, name)));
97
+ }
98
+ function devImageTag(serviceName) {
99
+ const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
100
+ return `vercel-dev/${safe || "service"}:dev`;
101
+ }
90
102
  function run(cmd, args, opts = {}) {
91
103
  return new Promise((resolve, reject) => {
92
104
  const child = (0, import_node_child_process.spawn)(cmd, args, {
@@ -1147,10 +1159,6 @@ ${detail}` : "")
1147
1159
  });
1148
1160
  });
1149
1161
  }
1150
- function devImageTag(serviceName) {
1151
- const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
1152
- return `vercel-dev/${safe || "service"}:dev`;
1153
- }
1154
1162
  function normalizeCommand(command) {
1155
1163
  if (typeof command === "string") {
1156
1164
  return [command];
@@ -1163,7 +1171,7 @@ function normalizeCommand(command) {
1163
1171
  async function resolveDevImage(options, out, span) {
1164
1172
  const { config, workPath, entrypoint } = options;
1165
1173
  const entrypointRef = readString(entrypoint);
1166
- const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : void 0;
1174
+ const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
1167
1175
  const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
1168
1176
  const dockerfilePath = import_node_path4.default.join(workPath, dockerfileRel);
1169
1177
  const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs4.existsSync)(dockerfilePath);
@@ -1242,7 +1250,67 @@ function uniqueContainerName(serviceName) {
1242
1250
  const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
1243
1251
  return `vercel-dev-${safe || "service"}-${process.pid}-${Date.now().toString(36)}`;
1244
1252
  }
1253
+ async function assertDockerAvailable(out) {
1254
+ try {
1255
+ await runForwarded(
1256
+ "docker",
1257
+ ["info", "--format", "{{.ServerVersion}}"],
1258
+ out,
1259
+ {
1260
+ quiet: true
1261
+ }
1262
+ );
1263
+ } catch (err) {
1264
+ const message = err.message ?? "";
1265
+ if (/command not found/i.test(message)) {
1266
+ throw new Error(
1267
+ "Docker is required for `vercel dev` with containers, but the `docker` command was not found. Install Docker and ensure it is on your PATH."
1268
+ );
1269
+ }
1270
+ throw new Error(
1271
+ "Could not connect to the Docker daemon. Start Docker (e.g. open Docker Desktop) and run `vercel dev` again."
1272
+ );
1273
+ }
1274
+ }
1275
+ function containerExitMessage(exitCode, stderr) {
1276
+ const detail = stderr.trim().split("\n").slice(-5).join("\n");
1277
+ const looksLikeDaemonDown = exitCode === 125 || /cannot connect to the docker daemon/i.test(stderr);
1278
+ if (looksLikeDaemonDown) {
1279
+ return "Could not start the container: the Docker daemon is not running or is unreachable. Start Docker (e.g. open Docker Desktop) and try `vercel dev` again." + (detail ? `
1280
+
1281
+ Docker reported:
1282
+ ${detail}` : "");
1283
+ }
1284
+ return `The container exited (code ${exitCode}) before becoming ready.` + (detail ? `
1285
+
1286
+ Docker reported:
1287
+ ${detail}` : "");
1288
+ }
1289
+ var runningContainers = /* @__PURE__ */ new Map();
1290
+ var pendingContainers = /* @__PURE__ */ new Map();
1291
+ function containerReuseKey(options) {
1292
+ return options.service?.name ?? `root:${options.workPath}`;
1293
+ }
1245
1294
  async function startDevServer(options) {
1295
+ const reuseKey = containerReuseKey(options);
1296
+ const existing = runningContainers.get(reuseKey);
1297
+ if (existing && existing.isRunning()) {
1298
+ return existing.result;
1299
+ }
1300
+ if (existing) {
1301
+ runningContainers.delete(reuseKey);
1302
+ }
1303
+ const inFlight = pendingContainers.get(reuseKey);
1304
+ if (inFlight) {
1305
+ return inFlight;
1306
+ }
1307
+ const startPromise = startContainer(options, reuseKey).finally(() => {
1308
+ pendingContainers.delete(reuseKey);
1309
+ });
1310
+ pendingContainers.set(reuseKey, startPromise);
1311
+ return startPromise;
1312
+ }
1313
+ async function startContainer(options, reuseKey) {
1246
1314
  return withSpan(
1247
1315
  options.span,
1248
1316
  "container.dev.start",
@@ -1250,6 +1318,7 @@ async function startDevServer(options) {
1250
1318
  async (span) => {
1251
1319
  const { config, meta, onStdout, onStderr } = options;
1252
1320
  const out = { onStdout, onStderr };
1321
+ await assertDockerAvailable(out);
1253
1322
  const image = await withSpan(
1254
1323
  span,
1255
1324
  "container.dev.resolve_image",
@@ -1297,12 +1366,22 @@ async function startDevServer(options) {
1297
1366
  const child = (0, import_node_child_process3.spawn)("docker", args, {
1298
1367
  stdio: ["ignore", "pipe", "pipe"]
1299
1368
  });
1369
+ let runStderr = "";
1300
1370
  child.stdout?.on("data", (data) => onStdout?.(data));
1301
- child.stderr?.on("data", (data) => onStderr?.(data));
1371
+ child.stderr?.on("data", (data) => {
1372
+ runStderr += data.toString();
1373
+ onStderr?.(data);
1374
+ });
1375
+ child.on("error", (err) => {
1376
+ if (err.code === "ENOENT") {
1377
+ runStderr += "Command not found: `docker`. Ensure Docker is installed and on your PATH, and that the Docker daemon is running.";
1378
+ }
1379
+ });
1302
1380
  const cleanupEnvFile = () => {
1303
1381
  (0, import_node_fs4.rmSync)(import_node_path4.default.dirname(envFilePath), { recursive: true, force: true });
1304
1382
  };
1305
1383
  const shutdown = async () => {
1384
+ runningContainers.delete(reuseKey);
1306
1385
  try {
1307
1386
  await runForwarded("docker", ["stop", containerName], out, {
1308
1387
  quiet: true
@@ -1321,9 +1400,7 @@ async function startDevServer(options) {
1321
1400
  try {
1322
1401
  while (Date.now() < deadline) {
1323
1402
  if (child.exitCode !== null) {
1324
- throw new Error(
1325
- `Container "${options.service?.name}" exited (code ${child.exitCode}) before becoming ready.`
1326
- );
1403
+ throw new Error(containerExitMessage(child.exitCode, runStderr));
1327
1404
  }
1328
1405
  try {
1329
1406
  hostPort = await readMappedHostPort(
@@ -1339,7 +1416,7 @@ async function startDevServer(options) {
1339
1416
  }
1340
1417
  if (hostPort === void 0) {
1341
1418
  throw new Error(
1342
- `Timed out waiting for container "${options.service?.name}" to publish port ${containerPort}.` + (lastErr ? ` Last error: ${lastErr.message}` : "")
1419
+ `Timed out waiting for container "${containerName}" to publish port ${containerPort}.` + (lastErr ? ` Last error: ${lastErr.message}` : "")
1343
1420
  );
1344
1421
  }
1345
1422
  } catch (err) {
@@ -1352,11 +1429,26 @@ async function startDevServer(options) {
1352
1429
  "container.name": containerName
1353
1430
  });
1354
1431
  emit(out, `\u25B2 container container ready on localhost:${hostPort}`);
1355
- return {
1432
+ const result = {
1356
1433
  port: hostPort,
1357
1434
  pid: child.pid ?? 0,
1358
- shutdown
1435
+ shutdown,
1436
+ // The container is a long-running server; the dev server should keep it
1437
+ // alive across requests instead of tearing it down after each response.
1438
+ persistent: true
1359
1439
  };
1440
+ const running = {
1441
+ result,
1442
+ containerName,
1443
+ isRunning: () => child.exitCode === null
1444
+ };
1445
+ child.on("close", () => {
1446
+ if (runningContainers.get(reuseKey) === running) {
1447
+ runningContainers.delete(reuseKey);
1448
+ }
1449
+ });
1450
+ runningContainers.set(reuseKey, running);
1451
+ return result;
1360
1452
  }
1361
1453
  );
1362
1454
  }
@@ -1391,6 +1483,13 @@ async function prepareCache(_options) {
1391
1483
 
1392
1484
  // src/index.ts
1393
1485
  var version = 2;
1486
+ function resolveFunctionSourceFile(options) {
1487
+ const entrypoint = readString(options.entrypoint) ?? "";
1488
+ if (entrypoint === "<detect>") {
1489
+ return findDockerfile(options.workPath) ?? entrypoint;
1490
+ }
1491
+ return entrypoint;
1492
+ }
1394
1493
  function normalizeCommand2(command) {
1395
1494
  if (typeof command === "string") {
1396
1495
  return [command];
@@ -1400,12 +1499,6 @@ function normalizeCommand2(command) {
1400
1499
  }
1401
1500
  return void 0;
1402
1501
  }
1403
- var DOCKERFILE_CANDIDATES = ["Dockerfile.vercel", "Containerfile.vercel"];
1404
- function findDockerfile(workPath) {
1405
- return DOCKERFILE_CANDIDATES.find(
1406
- (name) => (0, import_node_fs6.existsSync)(import_node_path6.default.join(workPath, name))
1407
- );
1408
- }
1409
1502
  function sanitizeRepository(name) {
1410
1503
  const sanitized = name.toLowerCase().replace(/[^a-z0-9-_./]/g, "-").replace(/-+/g, "-").replace(/(^[-/.]+)|([-/.]+$)/g, "");
1411
1504
  return sanitized || "service";
@@ -1589,14 +1682,12 @@ async function resolveImageHandler(options, span) {
1589
1682
  return prebuiltImage;
1590
1683
  }
1591
1684
  if (meta?.isDev) {
1592
- if (prebuiltImage) {
1593
- span?.setAttributes({ "container.mode": "prebuilt_dev" });
1594
- info(`vercel dev: using prebuilt image ${prebuiltImage}`);
1595
- return prebuiltImage;
1596
- }
1597
- throw new Error(
1598
- '`vercel dev` cannot build container images from a Dockerfile. Specify a prebuilt "image" for local development.'
1685
+ const serviceName2 = options.service?.name;
1686
+ const tag2 = devImageTag(
1687
+ serviceName2 ?? import_node_path6.default.basename(dockerfileRel).split(".")[0]
1599
1688
  );
1689
+ span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
1690
+ return tag2;
1600
1691
  }
1601
1692
  if (!(0, import_node_fs6.existsSync)(dockerfilePath)) {
1602
1693
  throw new Error(
@@ -1643,6 +1734,10 @@ async function build(options) {
1643
1734
  { "service.name": options.service?.name },
1644
1735
  (span) => resolveImageHandler(options, span)
1645
1736
  );
1737
+ const lambdaOptions = await (0, import_build_utils3.getLambdaOptionsFromFunction)({
1738
+ sourceFile: resolveFunctionSourceFile(options),
1739
+ config: options.config
1740
+ });
1646
1741
  const command = normalizeCommand2(options.config.command);
1647
1742
  const routes = [
1648
1743
  { handle: "filesystem" },
@@ -1660,7 +1755,8 @@ async function build(options) {
1660
1755
  handler: image,
1661
1756
  runtime: "container",
1662
1757
  environment: {},
1663
- ...command ? { command } : {}
1758
+ ...command ? { command } : {},
1759
+ ...lambdaOptions
1664
1760
  }
1665
1761
  }
1666
1762
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/container",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "homepage": "https://vercel.com/docs",
@@ -15,7 +15,7 @@
15
15
  "devDependencies": {
16
16
  "@types/node": "20.11.0",
17
17
  "vitest": "2.0.3",
18
- "@vercel/build-utils": "13.32.1"
18
+ "@vercel/build-utils": "13.32.3"
19
19
  },
20
20
  "publishConfig": {
21
21
  "access": "public"