@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.
- package/dist/index.js +122 -26
- 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 :
|
|
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) =>
|
|
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 "${
|
|
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
|
-
|
|
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
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
+
"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.
|
|
18
|
+
"@vercel/build-utils": "13.32.3"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|