@vercel/container 0.0.2 → 0.0.4
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 +113 -36
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -83,6 +83,21 @@ function toTag(value) {
|
|
|
83
83
|
function readString(value) {
|
|
84
84
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
85
85
|
}
|
|
86
|
+
function isDockerfileRef(ref) {
|
|
87
|
+
const base = (0, import_node_path.basename)(ref).toLowerCase();
|
|
88
|
+
return base === "dockerfile" || base === "containerfile" || base === "dockerfile.vercel" || base === "containerfile.vercel";
|
|
89
|
+
}
|
|
90
|
+
var DOCKERFILE_CANDIDATES = [
|
|
91
|
+
"Dockerfile.vercel",
|
|
92
|
+
"Containerfile.vercel"
|
|
93
|
+
];
|
|
94
|
+
function findDockerfile(workPath) {
|
|
95
|
+
return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path.join)(workPath, name)));
|
|
96
|
+
}
|
|
97
|
+
function devImageTag(serviceName) {
|
|
98
|
+
const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
|
|
99
|
+
return `vercel-dev/${safe || "service"}:dev`;
|
|
100
|
+
}
|
|
86
101
|
function run(cmd, args, opts = {}) {
|
|
87
102
|
return new Promise((resolve, reject) => {
|
|
88
103
|
const child = (0, import_node_child_process.spawn)(cmd, args, {
|
|
@@ -1143,14 +1158,6 @@ ${detail}` : "")
|
|
|
1143
1158
|
});
|
|
1144
1159
|
});
|
|
1145
1160
|
}
|
|
1146
|
-
function devImageTag(serviceName) {
|
|
1147
|
-
const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
|
|
1148
|
-
return `vercel-dev/${safe || "service"}:dev`;
|
|
1149
|
-
}
|
|
1150
|
-
function isDockerfileRef(ref) {
|
|
1151
|
-
const base = import_node_path4.default.basename(ref).toLowerCase();
|
|
1152
|
-
return base === "dockerfile" || base === "containerfile" || base.endsWith(".dockerfile");
|
|
1153
|
-
}
|
|
1154
1161
|
function normalizeCommand(command) {
|
|
1155
1162
|
if (typeof command === "string") {
|
|
1156
1163
|
return [command];
|
|
@@ -1163,7 +1170,7 @@ function normalizeCommand(command) {
|
|
|
1163
1170
|
async function resolveDevImage(options, out, span) {
|
|
1164
1171
|
const { config, workPath, entrypoint } = options;
|
|
1165
1172
|
const entrypointRef = readString(entrypoint);
|
|
1166
|
-
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef :
|
|
1173
|
+
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
|
|
1167
1174
|
const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
|
|
1168
1175
|
const dockerfilePath = import_node_path4.default.join(workPath, dockerfileRel);
|
|
1169
1176
|
const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs4.existsSync)(dockerfilePath);
|
|
@@ -1242,7 +1249,67 @@ function uniqueContainerName(serviceName) {
|
|
|
1242
1249
|
const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
|
|
1243
1250
|
return `vercel-dev-${safe || "service"}-${process.pid}-${Date.now().toString(36)}`;
|
|
1244
1251
|
}
|
|
1252
|
+
async function assertDockerAvailable(out) {
|
|
1253
|
+
try {
|
|
1254
|
+
await runForwarded(
|
|
1255
|
+
"docker",
|
|
1256
|
+
["info", "--format", "{{.ServerVersion}}"],
|
|
1257
|
+
out,
|
|
1258
|
+
{
|
|
1259
|
+
quiet: true
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
const message = err.message ?? "";
|
|
1264
|
+
if (/command not found/i.test(message)) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
"Docker is required for `vercel dev` with containers, but the `docker` command was not found. Install Docker and ensure it is on your PATH."
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
throw new Error(
|
|
1270
|
+
"Could not connect to the Docker daemon. Start Docker (e.g. open Docker Desktop) and run `vercel dev` again."
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function containerExitMessage(exitCode, stderr) {
|
|
1275
|
+
const detail = stderr.trim().split("\n").slice(-5).join("\n");
|
|
1276
|
+
const looksLikeDaemonDown = exitCode === 125 || /cannot connect to the docker daemon/i.test(stderr);
|
|
1277
|
+
if (looksLikeDaemonDown) {
|
|
1278
|
+
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 ? `
|
|
1279
|
+
|
|
1280
|
+
Docker reported:
|
|
1281
|
+
${detail}` : "");
|
|
1282
|
+
}
|
|
1283
|
+
return `The container exited (code ${exitCode}) before becoming ready.` + (detail ? `
|
|
1284
|
+
|
|
1285
|
+
Docker reported:
|
|
1286
|
+
${detail}` : "");
|
|
1287
|
+
}
|
|
1288
|
+
var runningContainers = /* @__PURE__ */ new Map();
|
|
1289
|
+
var pendingContainers = /* @__PURE__ */ new Map();
|
|
1290
|
+
function containerReuseKey(options) {
|
|
1291
|
+
return options.service?.name ?? `root:${options.workPath}`;
|
|
1292
|
+
}
|
|
1245
1293
|
async function startDevServer(options) {
|
|
1294
|
+
const reuseKey = containerReuseKey(options);
|
|
1295
|
+
const existing = runningContainers.get(reuseKey);
|
|
1296
|
+
if (existing && existing.isRunning()) {
|
|
1297
|
+
return existing.result;
|
|
1298
|
+
}
|
|
1299
|
+
if (existing) {
|
|
1300
|
+
runningContainers.delete(reuseKey);
|
|
1301
|
+
}
|
|
1302
|
+
const inFlight = pendingContainers.get(reuseKey);
|
|
1303
|
+
if (inFlight) {
|
|
1304
|
+
return inFlight;
|
|
1305
|
+
}
|
|
1306
|
+
const startPromise = startContainer(options, reuseKey).finally(() => {
|
|
1307
|
+
pendingContainers.delete(reuseKey);
|
|
1308
|
+
});
|
|
1309
|
+
pendingContainers.set(reuseKey, startPromise);
|
|
1310
|
+
return startPromise;
|
|
1311
|
+
}
|
|
1312
|
+
async function startContainer(options, reuseKey) {
|
|
1246
1313
|
return withSpan(
|
|
1247
1314
|
options.span,
|
|
1248
1315
|
"container.dev.start",
|
|
@@ -1250,6 +1317,7 @@ async function startDevServer(options) {
|
|
|
1250
1317
|
async (span) => {
|
|
1251
1318
|
const { config, meta, onStdout, onStderr } = options;
|
|
1252
1319
|
const out = { onStdout, onStderr };
|
|
1320
|
+
await assertDockerAvailable(out);
|
|
1253
1321
|
const image = await withSpan(
|
|
1254
1322
|
span,
|
|
1255
1323
|
"container.dev.resolve_image",
|
|
@@ -1297,12 +1365,22 @@ async function startDevServer(options) {
|
|
|
1297
1365
|
const child = (0, import_node_child_process3.spawn)("docker", args, {
|
|
1298
1366
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1299
1367
|
});
|
|
1368
|
+
let runStderr = "";
|
|
1300
1369
|
child.stdout?.on("data", (data) => onStdout?.(data));
|
|
1301
|
-
child.stderr?.on("data", (data) =>
|
|
1370
|
+
child.stderr?.on("data", (data) => {
|
|
1371
|
+
runStderr += data.toString();
|
|
1372
|
+
onStderr?.(data);
|
|
1373
|
+
});
|
|
1374
|
+
child.on("error", (err) => {
|
|
1375
|
+
if (err.code === "ENOENT") {
|
|
1376
|
+
runStderr += "Command not found: `docker`. Ensure Docker is installed and on your PATH, and that the Docker daemon is running.";
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1302
1379
|
const cleanupEnvFile = () => {
|
|
1303
1380
|
(0, import_node_fs4.rmSync)(import_node_path4.default.dirname(envFilePath), { recursive: true, force: true });
|
|
1304
1381
|
};
|
|
1305
1382
|
const shutdown = async () => {
|
|
1383
|
+
runningContainers.delete(reuseKey);
|
|
1306
1384
|
try {
|
|
1307
1385
|
await runForwarded("docker", ["stop", containerName], out, {
|
|
1308
1386
|
quiet: true
|
|
@@ -1321,9 +1399,7 @@ async function startDevServer(options) {
|
|
|
1321
1399
|
try {
|
|
1322
1400
|
while (Date.now() < deadline) {
|
|
1323
1401
|
if (child.exitCode !== null) {
|
|
1324
|
-
throw new Error(
|
|
1325
|
-
`Container "${options.service?.name}" exited (code ${child.exitCode}) before becoming ready.`
|
|
1326
|
-
);
|
|
1402
|
+
throw new Error(containerExitMessage(child.exitCode, runStderr));
|
|
1327
1403
|
}
|
|
1328
1404
|
try {
|
|
1329
1405
|
hostPort = await readMappedHostPort(
|
|
@@ -1339,7 +1415,7 @@ async function startDevServer(options) {
|
|
|
1339
1415
|
}
|
|
1340
1416
|
if (hostPort === void 0) {
|
|
1341
1417
|
throw new Error(
|
|
1342
|
-
`Timed out waiting for container "${
|
|
1418
|
+
`Timed out waiting for container "${containerName}" to publish port ${containerPort}.` + (lastErr ? ` Last error: ${lastErr.message}` : "")
|
|
1343
1419
|
);
|
|
1344
1420
|
}
|
|
1345
1421
|
} catch (err) {
|
|
@@ -1352,11 +1428,26 @@ async function startDevServer(options) {
|
|
|
1352
1428
|
"container.name": containerName
|
|
1353
1429
|
});
|
|
1354
1430
|
emit(out, `\u25B2 container container ready on localhost:${hostPort}`);
|
|
1355
|
-
|
|
1431
|
+
const result = {
|
|
1356
1432
|
port: hostPort,
|
|
1357
1433
|
pid: child.pid ?? 0,
|
|
1358
|
-
shutdown
|
|
1434
|
+
shutdown,
|
|
1435
|
+
// The container is a long-running server; the dev server should keep it
|
|
1436
|
+
// alive across requests instead of tearing it down after each response.
|
|
1437
|
+
persistent: true
|
|
1359
1438
|
};
|
|
1439
|
+
const running = {
|
|
1440
|
+
result,
|
|
1441
|
+
containerName,
|
|
1442
|
+
isRunning: () => child.exitCode === null
|
|
1443
|
+
};
|
|
1444
|
+
child.on("close", () => {
|
|
1445
|
+
if (runningContainers.get(reuseKey) === running) {
|
|
1446
|
+
runningContainers.delete(reuseKey);
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
runningContainers.set(reuseKey, running);
|
|
1450
|
+
return result;
|
|
1360
1451
|
}
|
|
1361
1452
|
);
|
|
1362
1453
|
}
|
|
@@ -1400,18 +1491,6 @@ function normalizeCommand2(command) {
|
|
|
1400
1491
|
}
|
|
1401
1492
|
return void 0;
|
|
1402
1493
|
}
|
|
1403
|
-
function isDockerfileRef2(ref) {
|
|
1404
|
-
const base = import_node_path6.default.basename(ref).toLowerCase();
|
|
1405
|
-
return base === "dockerfile" || base === "containerfile" || base.endsWith(".dockerfile") || // Vercel-specific container opt-in markers, used to deploy a project as a
|
|
1406
|
-
// container even when another framework (e.g. Next.js) is also present.
|
|
1407
|
-
base === "dockerfile.vercel" || base === "containerfile.vercel";
|
|
1408
|
-
}
|
|
1409
|
-
var DOCKERFILE_CANDIDATES = ["Dockerfile.vercel", "Containerfile.vercel"];
|
|
1410
|
-
function findDockerfile(workPath) {
|
|
1411
|
-
return DOCKERFILE_CANDIDATES.find(
|
|
1412
|
-
(name) => (0, import_node_fs6.existsSync)(import_node_path6.default.join(workPath, name))
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
1494
|
function sanitizeRepository(name) {
|
|
1416
1495
|
const sanitized = name.toLowerCase().replace(/[^a-z0-9-_./]/g, "-").replace(/-+/g, "-").replace(/(^[-/.]+)|([-/.]+$)/g, "");
|
|
1417
1496
|
return sanitized || "service";
|
|
@@ -1575,7 +1654,7 @@ async function buildAndPushImage(params) {
|
|
|
1575
1654
|
async function resolveImageHandler(options, span) {
|
|
1576
1655
|
const { config, workPath, entrypoint, meta } = options;
|
|
1577
1656
|
const entrypointRef = readString(entrypoint);
|
|
1578
|
-
const dockerfileConfigured = entrypointRef &&
|
|
1657
|
+
const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
|
|
1579
1658
|
const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
|
|
1580
1659
|
const dockerfilePath = import_node_path6.default.join(workPath, dockerfileRel);
|
|
1581
1660
|
const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs6.existsSync)(dockerfilePath);
|
|
@@ -1595,14 +1674,12 @@ async function resolveImageHandler(options, span) {
|
|
|
1595
1674
|
return prebuiltImage;
|
|
1596
1675
|
}
|
|
1597
1676
|
if (meta?.isDev) {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
return prebuiltImage;
|
|
1602
|
-
}
|
|
1603
|
-
throw new Error(
|
|
1604
|
-
'`vercel dev` cannot build container images from a Dockerfile. Specify a prebuilt "image" for local development.'
|
|
1677
|
+
const serviceName2 = options.service?.name;
|
|
1678
|
+
const tag2 = devImageTag(
|
|
1679
|
+
serviceName2 ?? import_node_path6.default.basename(dockerfileRel).split(".")[0]
|
|
1605
1680
|
);
|
|
1681
|
+
span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
|
|
1682
|
+
return tag2;
|
|
1606
1683
|
}
|
|
1607
1684
|
if (!(0, import_node_fs6.existsSync)(dockerfilePath)) {
|
|
1608
1685
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/container",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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.2"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|