@vercel/container 10.0.0 → 10.1.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.
Files changed (2) hide show
  1. package/dist/index.js +394 -159
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -62,8 +62,8 @@ async function generateProjectManifest({
62
62
  var diagnostics = (0, import_build_utils.createDiagnostics)("container");
63
63
 
64
64
  // src/index.ts
65
- var import_node_fs14 = require("fs");
66
- var import_node_path14 = __toESM(require("path"));
65
+ var import_node_fs15 = require("fs");
66
+ var import_node_path15 = __toESM(require("path"));
67
67
 
68
68
  // src/util.ts
69
69
  var import_build_utils2 = require("@vercel/build-utils");
@@ -106,7 +106,7 @@ function shortDigest(digest) {
106
106
  return digest.startsWith("sha256:") ? `${digest.slice(0, 19)}\u2026` : digest;
107
107
  }
108
108
  function delay(ms) {
109
- return new Promise((resolve2) => setTimeout(resolve2, ms));
109
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
110
110
  }
111
111
  async function withSpan(parent, name, attrs, fn) {
112
112
  if (!parent) {
@@ -136,7 +136,7 @@ function devImageTag(serviceName) {
136
136
  return `vercel-dev/${safe || "service"}:dev`;
137
137
  }
138
138
  function run(cmd, args, opts = {}) {
139
- return new Promise((resolve2, reject) => {
139
+ return new Promise((resolve4, reject) => {
140
140
  const child = (0, import_node_child_process.spawn)(cmd, args, {
141
141
  cwd: opts.cwd,
142
142
  env: opts.env,
@@ -171,7 +171,7 @@ function run(cmd, args, opts = {}) {
171
171
  });
172
172
  child.on("close", (code) => {
173
173
  if (code === 0) {
174
- resolve2({ stdout, stderr });
174
+ resolve4({ stdout, stderr });
175
175
  } else {
176
176
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
177
177
  const error = new Error(
@@ -746,7 +746,7 @@ var buildahEngine = {
746
746
  });
747
747
  const imageCount = stdout2.trim() ? stdout2.trim().split("\n").length : 0;
748
748
  info(
749
- imageCount > 0 ? `layer store: warm (${imageCount} image(s) present before build)` : "layer store: cold (no cached images; first build or cache miss)"
749
+ imageCount > 0 ? `layer store: warm (${imageCount} image(s) present before build)` : "layer store: cold (no images present before build)"
750
750
  );
751
751
  } catch (err) {
752
752
  debug(`could not read store warmth: ${err.message}`);
@@ -990,7 +990,7 @@ async function startDockerDaemon(span) {
990
990
  ].join("\n")
991
991
  );
992
992
  }
993
- await new Promise((resolve2) => setTimeout(resolve2, 500));
993
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
994
994
  }
995
995
  }
996
996
  async function stopDockerDaemon(daemon, span) {
@@ -1000,12 +1000,12 @@ async function stopDockerDaemon(daemon, span) {
1000
1000
  }
1001
1001
  step("Stopping Docker daemon");
1002
1002
  const stopTimeoutMs = Number(process.env.VERCEL_VCR_DOCKERD_STOP_TIMEOUT_MS) || 1e4;
1003
- await new Promise((resolve2) => {
1003
+ await new Promise((resolve4) => {
1004
1004
  let settled = false;
1005
1005
  const finish = () => {
1006
1006
  if (!settled) {
1007
1007
  settled = true;
1008
- resolve2();
1008
+ resolve4();
1009
1009
  }
1010
1010
  };
1011
1011
  child.once("exit", finish);
@@ -1206,16 +1206,155 @@ function selectContainerEngine() {
1206
1206
  }
1207
1207
 
1208
1208
  // src/buildpacks/build.ts
1209
- var import_node_fs10 = require("fs");
1209
+ var import_node_fs11 = require("fs");
1210
1210
  var import_node_os7 = require("os");
1211
- var import_node_path10 = require("path");
1211
+ var import_node_path11 = require("path");
1212
1212
 
1213
- // src/buildpacks/lifecycle/lifecycle.ts
1214
- var import_build_utils3 = require("@vercel/build-utils");
1213
+ // src/buildpacks/cache.ts
1215
1214
  var import_node_crypto2 = require("crypto");
1216
1215
  var import_node_fs4 = require("fs");
1217
- var import_node_os4 = require("os");
1218
1216
  var import_node_path4 = require("path");
1217
+ var BUILDPACK_CACHE_DIR = ".vercel/cache/buildpacks";
1218
+ var CNB_COMMITTED_DIR = "committed";
1219
+ var CNB_CACHE_METADATA_FILE = "io.buildpacks.lifecycle.cache.metadata";
1220
+ var IMAGE_ID_FILE = "image-id";
1221
+ function buildpackCacheEnabled() {
1222
+ return readString(process.env.VERCEL_ENABLE_BUILDPACK_CACHE) === "1";
1223
+ }
1224
+ function buildpackCacheDir(options) {
1225
+ const root = (0, import_node_path4.resolve)(options.repoRootPath || options.workPath);
1226
+ const app = (0, import_node_path4.relative)(root, (0, import_node_path4.resolve)(options.workPath)).split(import_node_path4.sep).join("/") || ".";
1227
+ const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([app, options.service?.name ?? null])).digest("hex").slice(0, 32);
1228
+ return (0, import_node_path4.join)(root, BUILDPACK_CACHE_DIR, key);
1229
+ }
1230
+ function restoreBuildpackCache(dir, imageId, cacheDir) {
1231
+ try {
1232
+ if (readRegularFile((0, import_node_path4.join)(dir, IMAGE_ID_FILE)) !== imageId) {
1233
+ return { status: "miss", reason: "base_image_changed" };
1234
+ }
1235
+ copyCommitted(dir, cacheDir);
1236
+ return { status: "hit", bytes: sizeOf((0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR)) };
1237
+ } catch (error) {
1238
+ (0, import_node_fs4.rmSync)((0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR), { recursive: true, force: true });
1239
+ const cold = error.code === "ENOENT";
1240
+ return { status: "miss", reason: cold ? "no_cache" : firstLine(error) };
1241
+ }
1242
+ }
1243
+ function saveBuildpackCache(dir, imageId, cacheDir) {
1244
+ const committed = (0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR);
1245
+ let metadata;
1246
+ try {
1247
+ assertDirectory(committed);
1248
+ metadata = readRegularFile((0, import_node_path4.join)(committed, CNB_CACHE_METADATA_FILE));
1249
+ } catch (error) {
1250
+ const missing = error.code === "ENOENT";
1251
+ return {
1252
+ status: "skipped",
1253
+ reason: missing ? "no_cache_written" : firstLine(error)
1254
+ };
1255
+ }
1256
+ try {
1257
+ if (isSaved(dir, imageId, metadata))
1258
+ return { status: "unchanged" };
1259
+ const bytes = sizeOf(committed);
1260
+ (0, import_node_fs4.rmSync)(dir, { recursive: true, force: true });
1261
+ copyCommitted(cacheDir, dir);
1262
+ (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(dir, IMAGE_ID_FILE), imageId);
1263
+ return { status: "saved", bytes };
1264
+ } catch (error) {
1265
+ (0, import_node_fs4.rmSync)(dir, { recursive: true, force: true });
1266
+ return { status: "skipped", reason: firstLine(error) };
1267
+ }
1268
+ }
1269
+ function isSaved(dir, imageId, metadata) {
1270
+ try {
1271
+ const committed = (0, import_node_path4.join)(dir, CNB_COMMITTED_DIR);
1272
+ assertDirectory(committed);
1273
+ return readRegularFile((0, import_node_path4.join)(dir, IMAGE_ID_FILE)) === imageId && readRegularFile((0, import_node_path4.join)(committed, CNB_CACHE_METADATA_FILE)) === metadata;
1274
+ } catch {
1275
+ return false;
1276
+ }
1277
+ }
1278
+ function copyCommitted(from, to) {
1279
+ const source = (0, import_node_path4.join)(from, CNB_COMMITTED_DIR);
1280
+ const target = (0, import_node_path4.join)(to, CNB_COMMITTED_DIR);
1281
+ assertDirectory(source);
1282
+ (0, import_node_fs4.mkdirSync)(target, { recursive: true });
1283
+ for (const entry of (0, import_node_fs4.readdirSync)(source, { withFileTypes: true })) {
1284
+ if (!entry.isFile())
1285
+ throw notRegularFile(entry.name);
1286
+ (0, import_node_fs4.copyFileSync)((0, import_node_path4.join)(source, entry.name), (0, import_node_path4.join)(target, entry.name));
1287
+ }
1288
+ }
1289
+ function assertDirectory(path4) {
1290
+ if (!(0, import_node_fs4.lstatSync)(path4).isDirectory()) {
1291
+ throw new Error(`${(0, import_node_path4.basename)(path4)} is not a directory`);
1292
+ }
1293
+ }
1294
+ function readRegularFile(path4) {
1295
+ if (!(0, import_node_fs4.lstatSync)(path4).isFile())
1296
+ throw notRegularFile((0, import_node_path4.basename)(path4));
1297
+ return (0, import_node_fs4.readFileSync)(path4, "utf8");
1298
+ }
1299
+ function notRegularFile(name) {
1300
+ return new Error(`${JSON.stringify(name)} is not a regular file`);
1301
+ }
1302
+ function sizeOf(dir) {
1303
+ return (0, import_node_fs4.readdirSync)(dir).reduce(
1304
+ (bytes, name) => bytes + (0, import_node_fs4.lstatSync)((0, import_node_path4.join)(dir, name)).size,
1305
+ 0
1306
+ );
1307
+ }
1308
+ function firstLine(error) {
1309
+ return error.message.split("\n")[0];
1310
+ }
1311
+ function formatBytes(bytes) {
1312
+ if (bytes < 1024)
1313
+ return `${bytes} B`;
1314
+ const units = ["KiB", "MiB", "GiB"];
1315
+ let value = bytes / 1024;
1316
+ let unit = 0;
1317
+ while (value >= 1024 && unit < units.length - 1) {
1318
+ value /= 1024;
1319
+ unit += 1;
1320
+ }
1321
+ return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
1322
+ }
1323
+ function describeRestore(outcome) {
1324
+ return outcome.status === "hit" ? `restored buildpack cache (${formatBytes(outcome.bytes)})` : `no buildpack cache to restore (${outcome.reason})`;
1325
+ }
1326
+ function describeSave(outcome) {
1327
+ switch (outcome.status) {
1328
+ case "saved":
1329
+ return `saved buildpack cache (${formatBytes(outcome.bytes)})`;
1330
+ case "unchanged":
1331
+ return "buildpack cache unchanged";
1332
+ default:
1333
+ return `buildpack cache not saved (${outcome.reason})`;
1334
+ }
1335
+ }
1336
+ function recordRestore(span, outcome, durationMs) {
1337
+ span?.setAttributes({
1338
+ "buildpack.cache.restore": outcome.status,
1339
+ "buildpack.cache.restore_ms": String(durationMs),
1340
+ ...outcome.status === "hit" ? { "buildpack.cache.restore_bytes": String(outcome.bytes) } : { "buildpack.cache.miss_reason": outcome.reason }
1341
+ });
1342
+ }
1343
+ function recordSave(span, outcome, durationMs) {
1344
+ span?.setAttributes({
1345
+ "buildpack.cache.save": outcome.status,
1346
+ "buildpack.cache.save_ms": String(durationMs),
1347
+ ...outcome.status === "saved" ? { "buildpack.cache.save_bytes": String(outcome.bytes) } : {},
1348
+ ...outcome.status === "skipped" ? { "buildpack.cache.skip_reason": outcome.reason } : {}
1349
+ });
1350
+ }
1351
+
1352
+ // src/buildpacks/lifecycle/lifecycle.ts
1353
+ var import_build_utils3 = require("@vercel/build-utils");
1354
+ var import_node_crypto3 = require("crypto");
1355
+ var import_node_fs5 = require("fs");
1356
+ var import_node_os4 = require("os");
1357
+ var import_node_path5 = require("path");
1219
1358
  var import_smol_toml = require("smol-toml");
1220
1359
  var import_tar = require("tar");
1221
1360
 
@@ -1230,8 +1369,8 @@ var distribution_default = {
1230
1369
  buildpacks: {
1231
1370
  ruby: {
1232
1371
  id: "vercel/ruby",
1233
- version: "0.2.0",
1234
- sha256: "d46d7d825b8e57636957125859c2637545892dc0f3cf93117b16c748bb4f35c5"
1372
+ version: "0.3.0",
1373
+ sha256: "7ee871c18ac1616658793d3d25639d7a1f65ade618f84c6596ef9e3f515b7202"
1235
1374
  }
1236
1375
  }
1237
1376
  };
@@ -1314,7 +1453,7 @@ async function withPrereleaseBuildpack(buildpack, archiveUrl) {
1314
1453
  );
1315
1454
  if (!entry) {
1316
1455
  throw new Error(
1317
- `Pre-release archive ${archive.url} does not match a buildpack in the ${buildpack.runtime} runtime.`
1456
+ `Pre-release archive ${archive.fileName} does not match a buildpack in the ${buildpack.runtime} runtime.`
1318
1457
  );
1319
1458
  }
1320
1459
  const checksumUrlObj = new URL(archive.url);
@@ -1323,17 +1462,17 @@ async function withPrereleaseBuildpack(buildpack, archiveUrl) {
1323
1462
  const response = await fetch(checksumUrl);
1324
1463
  if (!response.ok) {
1325
1464
  throw new Error(
1326
- `Failed to fetch pre-release checksum ${checksumUrl}: HTTP ${response.status} ${response.statusText}`
1465
+ `Failed to fetch pre-release checksum for ${archive.fileName}: HTTP ${response.status} ${response.statusText}`
1327
1466
  );
1328
1467
  }
1329
1468
  const fields = (await response.text()).trim().split(/\s+/);
1330
1469
  const [sha256, fileName] = fields;
1331
1470
  if (fields.length !== 2 || !SHA256_RE.test(sha256) || fileName !== archive.fileName) {
1332
1471
  throw new Error(
1333
- `Invalid pre-release checksum ${checksumUrl}: expected a SHA-256 checksum for ${archive.fileName}.`
1472
+ `Invalid pre-release checksum: expected a SHA-256 checksum for ${archive.fileName}.`
1334
1473
  );
1335
1474
  }
1336
- info(`Using pre-release ${entry.id}@${archive.version}: ${archive.url}`);
1475
+ info(`Using pre-release ${entry.id}@${archive.version}`);
1337
1476
  return {
1338
1477
  ...buildpack,
1339
1478
  buildpacks: buildpack.buildpacks.map(
@@ -1346,17 +1485,18 @@ async function withPrereleaseBuildpack(buildpack, archiveUrl) {
1346
1485
  var BUILD_USER_ID = 1001;
1347
1486
  var BUILD_GROUP_ID = 1001;
1348
1487
  var BUILD_USER = `${BUILD_USER_ID}:${BUILD_GROUP_ID}`;
1349
- var CNB_PLATFORM_API = "0.13";
1488
+ var CNB_PLATFORM_API = "0.15";
1350
1489
  var ORDER_MOUNT_DIR = "/platform/order";
1351
1490
  var ORDER_FILE = `${ORDER_MOUNT_DIR}/order.toml`;
1491
+ var CACHE_MOUNT_DIR = "/cache";
1352
1492
  var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1353
1493
  async function stageWorkspace({ files, workPath }, destinationDir) {
1354
1494
  const staged = /* @__PURE__ */ Object.create(null);
1355
1495
  const symlinks = /* @__PURE__ */ new Set();
1356
1496
  for (const [name, file] of Object.entries(files)) {
1357
1497
  const portable = name.replace(/\\/g, "/");
1358
- const normalized = import_node_path4.posix.normalize(portable).replace(/\/$/, "");
1359
- if (!normalized || normalized === "." || import_node_path4.win32.isAbsolute(name) || import_node_path4.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1498
+ const normalized = import_node_path5.posix.normalize(portable).replace(/\/$/, "");
1499
+ if (!normalized || normalized === "." || import_node_path5.win32.isAbsolute(name) || import_node_path5.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1360
1500
  throw new Error(`Invalid buildpack source path ${JSON.stringify(name)}.`);
1361
1501
  }
1362
1502
  staged[normalized] = file;
@@ -1369,15 +1509,15 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1369
1509
  if (!target) {
1370
1510
  throw new Error(`Cannot read buildpack source symlink ${name}.`);
1371
1511
  }
1372
- if ((0, import_node_path4.isAbsolute)(target)) {
1373
- target = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath, import_node_path4.posix.dirname(normalized)), target) || ".";
1374
- } else if (import_node_path4.win32.isAbsolute(target)) {
1512
+ if ((0, import_node_path5.isAbsolute)(target)) {
1513
+ target = (0, import_node_path5.relative)((0, import_node_path5.resolve)(workPath, import_node_path5.posix.dirname(normalized)), target) || ".";
1514
+ } else if (import_node_path5.win32.isAbsolute(target)) {
1375
1515
  throw new Error(
1376
1516
  `Buildpack source symlink ${name} has an absolute target.`
1377
1517
  );
1378
1518
  }
1379
1519
  target = target.replace(/\\/g, "/");
1380
- const resolved = import_node_path4.posix.join(import_node_path4.posix.dirname(normalized), target);
1520
+ const resolved = import_node_path5.posix.join(import_node_path5.posix.dirname(normalized), target);
1381
1521
  if (resolved === ".." || resolved.startsWith("../")) {
1382
1522
  throw new Error(
1383
1523
  `Buildpack source symlink ${name} escapes the service root. Ruby buildpack services must be self-contained.`
@@ -1387,7 +1527,7 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1387
1527
  symlinks.add(normalized);
1388
1528
  }
1389
1529
  for (const name of Object.keys(staged)) {
1390
- for (let parent = import_node_path4.posix.dirname(name); parent !== "."; parent = import_node_path4.posix.dirname(parent)) {
1530
+ for (let parent = import_node_path5.posix.dirname(name); parent !== "."; parent = import_node_path5.posix.dirname(parent)) {
1391
1531
  if (symlinks.has(parent)) {
1392
1532
  throw new Error(
1393
1533
  `Buildpack source ${name} is inside symlink ${parent}.`
@@ -1396,16 +1536,16 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1396
1536
  }
1397
1537
  const file = staged[name];
1398
1538
  if (file.type === "FileFsRef" && !(0, import_build_utils3.isSymbolicLink)(file.mode)) {
1399
- const sourcePath = (0, import_node_path4.resolve)(file.fsPath);
1400
- const sourceRelative = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath), sourcePath);
1401
- if (sourceRelative.startsWith(`..${import_node_path4.sep}`) || sourceRelative === ".." || (0, import_node_path4.isAbsolute)(sourceRelative)) {
1539
+ const sourcePath = (0, import_node_path5.resolve)(file.fsPath);
1540
+ const sourceRelative = (0, import_node_path5.relative)((0, import_node_path5.resolve)(workPath), sourcePath);
1541
+ if (sourceRelative.startsWith(`..${import_node_path5.sep}`) || sourceRelative === ".." || (0, import_node_path5.isAbsolute)(sourceRelative)) {
1402
1542
  continue;
1403
1543
  }
1404
- const sourceRoot = import_node_fs4.realpathSync.native(workPath);
1405
- const sourceTarget = import_node_fs4.realpathSync.native(sourcePath);
1406
- if (sourceTarget !== (0, import_node_path4.resolve)(sourceRoot, sourceRelative)) {
1407
- const target = (0, import_node_path4.relative)(sourceRoot, sourceTarget).split(import_node_path4.sep).join("/");
1408
- if (target === ".." || target.startsWith("../") || (0, import_node_path4.isAbsolute)(target) || !(target in staged)) {
1544
+ const sourceRoot = import_node_fs5.realpathSync.native(workPath);
1545
+ const sourceTarget = import_node_fs5.realpathSync.native(sourcePath);
1546
+ if (sourceTarget !== (0, import_node_path5.resolve)(sourceRoot, sourceRelative)) {
1547
+ const target = (0, import_node_path5.relative)(sourceRoot, sourceTarget).split(import_node_path5.sep).join("/");
1548
+ if (target === ".." || target.startsWith("../") || (0, import_node_path5.isAbsolute)(target) || !(target in staged)) {
1409
1549
  throw new Error(
1410
1550
  `Buildpack source ${name} follows a symlink to an excluded or outside-root file. Ruby buildpack services must be self-contained.`
1411
1551
  );
@@ -1414,17 +1554,17 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1414
1554
  }
1415
1555
  }
1416
1556
  await (0, import_build_utils3.download)(staged, destinationDir);
1417
- const root = import_node_fs4.realpathSync.native(destinationDir);
1557
+ const root = import_node_fs5.realpathSync.native(destinationDir);
1418
1558
  for (const name of symlinks) {
1419
1559
  let target;
1420
1560
  try {
1421
- target = (0, import_node_path4.relative)(root, import_node_fs4.realpathSync.native((0, import_node_path4.join)(destinationDir, name)));
1561
+ target = (0, import_node_path5.relative)(root, import_node_fs5.realpathSync.native((0, import_node_path5.join)(destinationDir, name)));
1422
1562
  } catch {
1423
1563
  throw new Error(
1424
1564
  `Buildpack source symlink ${name} has no target in the staged service.`
1425
1565
  );
1426
1566
  }
1427
- if (target === ".." || target.startsWith(`..${import_node_path4.sep}`) || (0, import_node_path4.isAbsolute)(target)) {
1567
+ if (target === ".." || target.startsWith(`..${import_node_path5.sep}`) || (0, import_node_path5.isAbsolute)(target)) {
1428
1568
  throw new Error(
1429
1569
  `Buildpack source symlink ${name} escapes the staged service.`
1430
1570
  );
@@ -1438,16 +1578,16 @@ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.t
1438
1578
  if (config.nodeHome) {
1439
1579
  environment.VERCEL_NODE_HOME = config.nodeHome;
1440
1580
  }
1441
- const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-env-"));
1442
- (0, import_node_fs4.chmodSync)(dir, 493);
1581
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-env-"));
1582
+ (0, import_node_fs5.chmodSync)(dir, 493);
1443
1583
  for (const [key, value] of Object.entries(environment)) {
1444
1584
  if (!ENV_NAME_RE.test(key)) {
1445
1585
  debug(`skipping build env var with an unsupported name: ${key}`);
1446
1586
  continue;
1447
1587
  }
1448
- const file = (0, import_node_path4.join)(dir, key);
1449
- (0, import_node_fs4.writeFileSync)(file, value);
1450
- (0, import_node_fs4.chmodSync)(file, 420);
1588
+ const file = (0, import_node_path5.join)(dir, key);
1589
+ (0, import_node_fs5.writeFileSync)(file, value);
1590
+ (0, import_node_fs5.chmodSync)(file, 420);
1451
1591
  }
1452
1592
  return dir;
1453
1593
  }
@@ -1499,11 +1639,11 @@ function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1499
1639
  }
1500
1640
  ]
1501
1641
  };
1502
- const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-order-"));
1503
- (0, import_node_fs4.chmodSync)(dir, 493);
1504
- const file = (0, import_node_path4.join)(dir, "order.toml");
1505
- (0, import_node_fs4.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
1506
- (0, import_node_fs4.chmodSync)(file, 420);
1642
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-order-"));
1643
+ (0, import_node_fs5.chmodSync)(dir, 493);
1644
+ const file = (0, import_node_path5.join)(dir, "order.toml");
1645
+ (0, import_node_fs5.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
1646
+ (0, import_node_fs5.chmodSync)(file, 420);
1507
1647
  return dir;
1508
1648
  }
1509
1649
  function cnbRegistryAuth(credentials) {
@@ -1519,74 +1659,78 @@ function distributionUrls(buildpack) {
1519
1659
  )
1520
1660
  };
1521
1661
  }
1662
+ function archiveName(archive) {
1663
+ return new URL(archive.url).pathname.split("/").at(-1) || "archive";
1664
+ }
1522
1665
  async function downloadPinnedArchive(archive, destinationPath) {
1523
1666
  const response = await fetch(archive.url);
1524
1667
  if (!response.ok) {
1525
1668
  throw new Error(
1526
- `Failed to fetch ${archive.url}: HTTP ${response.status} ${response.statusText}`
1669
+ `Failed to fetch ${archiveName(archive)}: HTTP ${response.status} ${response.statusText}`
1527
1670
  );
1528
1671
  }
1529
1672
  const contents = new Uint8Array(await response.arrayBuffer());
1530
- const checksum = (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
1673
+ const checksum = (0, import_node_crypto3.createHash)("sha256").update(contents).digest("hex");
1531
1674
  if (checksum !== archive.sha256) {
1532
1675
  throw new Error(
1533
- `Checksum mismatch for ${archive.url}: expected ${archive.sha256}, received ${checksum}.`
1676
+ `Checksum mismatch for ${archiveName(archive)}: expected ${archive.sha256}, received ${checksum}.`
1534
1677
  );
1535
1678
  }
1536
- (0, import_node_fs4.writeFileSync)(destinationPath, contents);
1679
+ (0, import_node_fs5.writeFileSync)(destinationPath, contents);
1537
1680
  }
1538
1681
  async function fetchPinnedArchive(archive, destinationDir) {
1539
- const archivePath = (0, import_node_path4.join)(destinationDir, `${archive.sha256}.tgz`);
1682
+ const archivePath = (0, import_node_path5.join)(destinationDir, `${archive.sha256}.tgz`);
1540
1683
  try {
1541
1684
  await downloadPinnedArchive(archive, archivePath);
1542
1685
  (0, import_tar.extract)({ file: archivePath, cwd: destinationDir, sync: true });
1543
1686
  } finally {
1544
- (0, import_node_fs4.rmSync)(archivePath, { force: true });
1687
+ (0, import_node_fs5.rmSync)(archivePath, { force: true });
1545
1688
  }
1546
1689
  }
1547
1690
  var LAYER_MEDIA_TYPE_RE = /(\.tar\.gzip|\.tar\+gzip|\.tar)$/;
1548
1691
  function readOciJson(path4) {
1549
- return JSON.parse((0, import_node_fs4.readFileSync)(path4, "utf8"));
1692
+ return JSON.parse((0, import_node_fs5.readFileSync)(path4, "utf8"));
1550
1693
  }
1551
1694
  function blobPath(layoutDir, digest) {
1552
1695
  const match = digest.match(/^sha256:([0-9a-f]{64})$/);
1553
1696
  if (!match) {
1554
1697
  throw new Error(`Unsupported OCI digest ${JSON.stringify(digest)}.`);
1555
1698
  }
1556
- return (0, import_node_path4.join)(layoutDir, "blobs", "sha256", match[1]);
1699
+ return (0, import_node_path5.join)(layoutDir, "blobs", "sha256", match[1]);
1557
1700
  }
1558
1701
  async function fetchBuildpackage(archive, cnbDir) {
1559
- const layoutDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "vercel-cnb-buildpackage-"));
1560
- const archivePath = (0, import_node_path4.join)(layoutDir, "buildpackage.cnb");
1702
+ const layoutDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os4.tmpdir)(), "vercel-cnb-buildpackage-"));
1703
+ const archivePath = (0, import_node_path5.join)(layoutDir, "buildpackage.cnb");
1561
1704
  try {
1562
1705
  await downloadPinnedArchive(archive, archivePath);
1563
1706
  await (0, import_tar.extract)({ file: archivePath, cwd: layoutDir });
1564
1707
  const index = readOciJson(
1565
- (0, import_node_path4.join)(layoutDir, "index.json")
1708
+ (0, import_node_path5.join)(layoutDir, "index.json")
1566
1709
  );
1710
+ const name = archiveName(archive);
1567
1711
  const manifestDigest = index.manifests?.[0]?.digest;
1568
1712
  if (!manifestDigest) {
1569
- throw new Error(`${archive.url} has no image manifest in index.json.`);
1713
+ throw new Error(`${name} has no image manifest in index.json.`);
1570
1714
  }
1571
1715
  const manifest = readOciJson(
1572
1716
  blobPath(layoutDir, manifestDigest)
1573
1717
  );
1574
1718
  if (!manifest.layers?.length) {
1575
- throw new Error(`${archive.url} has an image manifest with no layers.`);
1719
+ throw new Error(`${name} has an image manifest with no layers.`);
1576
1720
  }
1577
1721
  for (const layer of manifest.layers) {
1578
1722
  if (!layer.digest || !LAYER_MEDIA_TYPE_RE.test(layer.mediaType ?? "")) {
1579
1723
  throw new Error(
1580
- `${archive.url} has an unsupported layer media type ${JSON.stringify(
1724
+ `${name} has an unsupported layer media type ${JSON.stringify(
1581
1725
  layer.mediaType
1582
1726
  )}.`
1583
1727
  );
1584
1728
  }
1585
1729
  const layerPath = blobPath(layoutDir, layer.digest);
1586
- const actual = (0, import_node_crypto2.createHash)("sha256").update(new Uint8Array((0, import_node_fs4.readFileSync)(layerPath))).digest("hex");
1730
+ const actual = (0, import_node_crypto3.createHash)("sha256").update(new Uint8Array((0, import_node_fs5.readFileSync)(layerPath))).digest("hex");
1587
1731
  if (`sha256:${actual}` !== layer.digest) {
1588
1732
  throw new Error(
1589
- `${archive.url} layer ${layer.digest} hashes to sha256:${actual}.`
1733
+ `${name} layer ${layer.digest} hashes to sha256:${actual}.`
1590
1734
  );
1591
1735
  }
1592
1736
  await (0, import_tar.extract)({
@@ -1609,7 +1753,7 @@ async function fetchBuildpackage(archive, cnbDir) {
1609
1753
  });
1610
1754
  }
1611
1755
  } finally {
1612
- (0, import_node_fs4.rmSync)(layoutDir, { recursive: true, force: true });
1756
+ (0, import_node_fs5.rmSync)(layoutDir, { recursive: true, force: true });
1613
1757
  }
1614
1758
  }
1615
1759
  async function fetchDistribution(buildpack, cnbDir) {
@@ -1620,14 +1764,14 @@ async function fetchDistribution(buildpack, cnbDir) {
1620
1764
  )
1621
1765
  ]);
1622
1766
  for (const entry of buildpack.buildpacks) {
1623
- const directory = (0, import_node_path4.join)(
1767
+ const directory = (0, import_node_path5.join)(
1624
1768
  cnbDir,
1625
1769
  "buildpacks",
1626
1770
  entry.id.replace("/", "_"),
1627
1771
  entry.version
1628
1772
  );
1629
1773
  for (const file of ["buildpack.toml", "bin/detect", "bin/build"]) {
1630
- if (!(0, import_node_fs4.statSync)((0, import_node_path4.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
1774
+ if (!(0, import_node_fs5.statSync)((0, import_node_path5.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
1631
1775
  throw new Error(
1632
1776
  `Buildpack ${entry.id}@${entry.version} is missing ${file}.`
1633
1777
  );
@@ -1635,11 +1779,11 @@ async function fetchDistribution(buildpack, cnbDir) {
1635
1779
  }
1636
1780
  }
1637
1781
  }
1638
- function creatorArgs(image, imageRef) {
1782
+ function creatorArgs(image, imageRef, options = {}) {
1639
1783
  return [
1640
1784
  "-app=/workspace",
1641
1785
  `-order=${ORDER_FILE}`,
1642
- "-skip-restore",
1786
+ ...options.cacheDir ? [`-cache-dir=${options.cacheDir}`] : ["-skip-restore"],
1643
1787
  `-run-image=${image.runImage}`,
1644
1788
  "-report=/platform-output/report.toml",
1645
1789
  imageRef
@@ -1647,13 +1791,13 @@ function creatorArgs(image, imageRef) {
1647
1791
  }
1648
1792
 
1649
1793
  // src/buildpacks/lifecycle/buildah.ts
1650
- var import_node_fs5 = require("fs");
1651
- var import_node_crypto3 = require("crypto");
1794
+ var import_node_fs6 = require("fs");
1795
+ var import_node_crypto4 = require("crypto");
1652
1796
  var import_node_os5 = require("os");
1653
- var import_node_path5 = require("path");
1654
- async function runBuildah2(args, env) {
1797
+ var import_node_path6 = require("path");
1798
+ async function runBuildah2(args, env, quiet) {
1655
1799
  const storageArgs = await buildahStorageArgs();
1656
- return run("buildah", [...storageArgs, ...args], { env });
1800
+ return run("buildah", [...storageArgs, ...args], { env, quiet });
1657
1801
  }
1658
1802
  async function removeContainer(name) {
1659
1803
  try {
@@ -1664,6 +1808,47 @@ async function removeContainer(name) {
1664
1808
  );
1665
1809
  }
1666
1810
  }
1811
+ async function inspectBaseImageId(container) {
1812
+ const { stdout } = await runBuildah2(
1813
+ ["inspect", "--type", "container", container],
1814
+ void 0,
1815
+ true
1816
+ );
1817
+ const inspected = JSON.parse(stdout);
1818
+ const id = inspected.FromImageID || inspected.FromImageDigest;
1819
+ if (!id) {
1820
+ throw new Error(
1821
+ `buildah inspect did not report the base image of ${container}.`
1822
+ );
1823
+ }
1824
+ return id;
1825
+ }
1826
+ function cacheScratchParent() {
1827
+ if (isBuildContainer()) {
1828
+ const dir = "/vercel/.cnb";
1829
+ try {
1830
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
1831
+ return dir;
1832
+ } catch (error) {
1833
+ debug(
1834
+ `cannot use ${dir} for the buildpack cache: ${error.message}`
1835
+ );
1836
+ }
1837
+ }
1838
+ return (0, import_node_os5.tmpdir)();
1839
+ }
1840
+ function chownTree(root, uid, gid) {
1841
+ const stat = (0, import_node_fs6.lstatSync)(root);
1842
+ if (stat.isDirectory()) {
1843
+ (0, import_node_fs6.chmodSync)(root, 493);
1844
+ for (const entry of (0, import_node_fs6.readdirSync)(root)) {
1845
+ chownTree((0, import_node_path6.join)(root, entry), uid, gid);
1846
+ }
1847
+ } else if (stat.isFile()) {
1848
+ (0, import_node_fs6.chmodSync)(root, 420);
1849
+ }
1850
+ (0, import_node_fs6.lchownSync)(root, uid, gid);
1851
+ }
1667
1852
  var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1668
1853
  const urls = distributionUrls(buildpack);
1669
1854
  return withSpan(
@@ -1680,19 +1865,20 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1680
1865
  "image.ref": params.imageRef
1681
1866
  },
1682
1867
  async (lifecycleSpan) => {
1683
- const suffix = `${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
1868
+ const suffix = `${process.pid}-${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`;
1684
1869
  const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
1685
1870
  let cnbDir;
1686
1871
  let reportDir;
1687
1872
  let orderDir;
1688
1873
  let platformEnvDir;
1874
+ let cacheScratch;
1689
1875
  try {
1690
- reportDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-report-"));
1691
- const reportPath = (0, import_node_path5.join)(reportDir, "report.toml");
1692
- (0, import_node_fs5.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
1693
- (0, import_node_fs5.chmodSync)(reportDir, 493);
1694
- cnbDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-"));
1695
- (0, import_node_fs5.chmodSync)(cnbDir, 493);
1876
+ reportDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-report-"));
1877
+ const reportPath = (0, import_node_path6.join)(reportDir, "report.toml");
1878
+ (0, import_node_fs6.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
1879
+ (0, import_node_fs6.chmodSync)(reportDir, 493);
1880
+ cnbDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-"));
1881
+ (0, import_node_fs6.chmodSync)(cnbDir, 493);
1696
1882
  orderDir = writeOrderDir(buildpack);
1697
1883
  platformEnvDir = writePlatformEnvDir(params.buildEnv, {
1698
1884
  nodeHome: params.nodeToolchain?.home
@@ -1730,6 +1916,36 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1730
1916
  params.workPath,
1731
1917
  "/workspace"
1732
1918
  ]);
1919
+ let imageId;
1920
+ let cacheDir;
1921
+ if (params.cache) {
1922
+ const restoreStart = Date.now();
1923
+ step("Restoring the buildpack cache");
1924
+ let restored;
1925
+ try {
1926
+ imageId = await inspectBaseImageId(buildContainer);
1927
+ cacheScratch = (0, import_node_fs6.mkdtempSync)(
1928
+ (0, import_node_path6.join)(cacheScratchParent(), "vercel-cnb-cache-")
1929
+ );
1930
+ (0, import_node_fs6.chmodSync)(cacheScratch, 493);
1931
+ cacheDir = (0, import_node_path6.join)(cacheScratch, "cache");
1932
+ (0, import_node_fs6.mkdirSync)(cacheDir, { mode: 493 });
1933
+ restored = restoreBuildpackCache(
1934
+ params.cache.dir,
1935
+ imageId,
1936
+ cacheDir
1937
+ );
1938
+ chownTree(cacheDir, BUILD_USER_ID, BUILD_GROUP_ID);
1939
+ } catch (error) {
1940
+ cacheDir = void 0;
1941
+ restored = {
1942
+ status: "miss",
1943
+ reason: `setup_failed:${error.message.split("\n")[0]}`
1944
+ };
1945
+ }
1946
+ done(describeRestore(restored));
1947
+ recordRestore(lifecycleSpan, restored, Date.now() - restoreStart);
1948
+ }
1733
1949
  const platformEnvMount = [
1734
1950
  "--volume",
1735
1951
  `${platformEnvDir}:/platform/env:ro`
@@ -1738,6 +1954,7 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1738
1954
  "--volume",
1739
1955
  `${params.nodeToolchain.home}:${params.nodeToolchain.home}:ro`
1740
1956
  ] : [];
1957
+ const cacheMount = cacheDir ? ["--volume", `${cacheDir}:${CACHE_MOUNT_DIR}`] : [];
1741
1958
  const lifecycleEnv = {
1742
1959
  ...process.env,
1743
1960
  CNB_REGISTRY_AUTH: cnbRegistryAuth(params.credentials)
@@ -1770,14 +1987,17 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1770
1987
  `${orderDir}:${ORDER_MOUNT_DIR}:ro`,
1771
1988
  ...nodeToolchainMount,
1772
1989
  ...platformEnvMount,
1990
+ ...cacheMount,
1773
1991
  buildContainer,
1774
1992
  "--",
1775
1993
  "/cnb/lifecycle/creator",
1776
- ...creatorArgs(params.image, params.imageRef)
1994
+ ...creatorArgs(params.image, params.imageRef, {
1995
+ cacheDir: cacheDir ? CACHE_MOUNT_DIR : void 0
1996
+ })
1777
1997
  ],
1778
1998
  lifecycleEnv
1779
1999
  );
1780
- const digest = readReportDigest((0, import_node_fs5.readFileSync)(reportPath, "utf8"));
2000
+ const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
1781
2001
  if (!digest) {
1782
2002
  throw new Error(
1783
2003
  `${buildpack.runtime} buildpack lifecycle did not report a digest.`
@@ -1785,6 +2005,12 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1785
2005
  }
1786
2006
  done(`built and published ${params.imageRef}@${digest}`);
1787
2007
  lifecycleSpan?.setAttributes({ "image.digest": digest });
2008
+ if (params.cache && imageId && cacheDir) {
2009
+ const saveStart = Date.now();
2010
+ const save = saveBuildpackCache(params.cache.dir, imageId, cacheDir);
2011
+ done(`${describeSave(save)} in ${elapsed(saveStart)}`);
2012
+ recordSave(lifecycleSpan, save, Date.now() - saveStart);
2013
+ }
1788
2014
  return {
1789
2015
  imageRef: params.imageRef,
1790
2016
  digest,
@@ -1802,8 +2028,6 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1802
2028
  throw new Error(
1803
2029
  [
1804
2030
  `${buildpack.runtime} buildpack build failed via lifecycle/creator (${params.image.buildImage}).`,
1805
- `Lifecycle archive: ${urls.lifecycle}`,
1806
- ...urls.buildpacks.map((url) => `Buildpack archive: ${url}`),
1807
2031
  ...hint ? [`The lifecycle exited with code ${exitCode}: ${hint}.`] : [],
1808
2032
  "",
1809
2033
  `Underlying error: ${error.message}`,
@@ -1813,10 +2037,16 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1813
2037
  );
1814
2038
  } finally {
1815
2039
  await removeContainer(buildContainer);
1816
- for (const dir of [cnbDir, reportDir, orderDir, platformEnvDir]) {
2040
+ for (const dir of [
2041
+ cnbDir,
2042
+ reportDir,
2043
+ orderDir,
2044
+ platformEnvDir,
2045
+ cacheScratch
2046
+ ]) {
1817
2047
  if (dir) {
1818
2048
  try {
1819
- (0, import_node_fs5.rmSync)(dir, { recursive: true, force: true });
2049
+ (0, import_node_fs6.rmSync)(dir, { recursive: true, force: true });
1820
2050
  } catch {
1821
2051
  debug(`Could not remove Buildah build scratch directory ${dir}.`);
1822
2052
  }
@@ -1828,10 +2058,10 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1828
2058
  };
1829
2059
 
1830
2060
  // src/buildpacks/lifecycle/docker.ts
1831
- var import_node_crypto4 = require("crypto");
1832
- var import_node_fs6 = require("fs");
2061
+ var import_node_crypto5 = require("crypto");
2062
+ var import_node_fs7 = require("fs");
1833
2063
  var import_node_os6 = require("os");
1834
- var import_node_path6 = require("path");
2064
+ var import_node_path7 = require("path");
1835
2065
  function localBuildImageDockerfile(buildImage, nodeMajor) {
1836
2066
  const lines = [];
1837
2067
  if (nodeMajor !== void 0) {
@@ -1850,7 +2080,7 @@ function localBuildImageDockerfile(buildImage, nodeMajor) {
1850
2080
  `;
1851
2081
  }
1852
2082
  function localBuildImageTag(dockerfile) {
1853
- const hash = (0, import_node_crypto4.createHash)("sha256").update(dockerfile).digest("hex");
2083
+ const hash = (0, import_node_crypto5.createHash)("sha256").update(dockerfile).digest("hex");
1854
2084
  return `vercel-cnb-build:${hash.slice(0, 16)}`;
1855
2085
  }
1856
2086
  async function runDocker2(args, opts = {}) {
@@ -1876,9 +2106,9 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1876
2106
  let stageDir;
1877
2107
  const containers = [];
1878
2108
  try {
1879
- stageDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-"));
1880
- const cnbDir = (0, import_node_path6.join)(stageDir, "cnb");
1881
- (0, import_node_fs6.mkdirSync)(cnbDir, { mode: 493 });
2109
+ stageDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path7.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-"));
2110
+ const cnbDir = (0, import_node_path7.join)(stageDir, "cnb");
2111
+ (0, import_node_fs7.mkdirSync)(cnbDir, { mode: 493 });
1882
2112
  const orderDir = writeOrderDir(buildpack, stageDir);
1883
2113
  const platformEnvDir = writePlatformEnvDir(
1884
2114
  params.buildEnv,
@@ -1893,7 +2123,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1893
2123
  params.nodeToolchain?.major
1894
2124
  );
1895
2125
  const buildImageTag = localBuildImageTag(dockerfile);
1896
- const buildImageIdPath = (0, import_node_path6.join)(stageDir, "build-image.id");
2126
+ const buildImageIdPath = (0, import_node_path7.join)(stageDir, "build-image.id");
1897
2127
  step(
1898
2128
  `Preparing ${buildpack.runtime} ${params.image.version} build image` + (params.nodeToolchain ? ` with Node.js ${params.nodeToolchain.major}` : "")
1899
2129
  );
@@ -1911,11 +2141,11 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1911
2141
  ],
1912
2142
  { input: dockerfile }
1913
2143
  );
1914
- const buildImageId = (0, import_node_fs6.readFileSync)(buildImageIdPath, "utf8").trim();
2144
+ const buildImageId = (0, import_node_fs7.readFileSync)(buildImageIdPath, "utf8").trim();
1915
2145
  if (!/^sha256:[a-f0-9]{64}$/.test(buildImageId)) {
1916
2146
  throw new Error("Docker build did not report a valid image ID.");
1917
2147
  }
1918
- const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto4.randomBytes)(8).toString("hex")}`;
2148
+ const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto5.randomBytes)(8).toString("hex")}`;
1919
2149
  const initContainerName = `${containerName}-init`;
1920
2150
  containers.push(initContainerName);
1921
2151
  await runDocker2([
@@ -1999,13 +2229,13 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1999
2229
  `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
2000
2230
  );
2001
2231
  await runDocker2(["start", "--attach", containerName]);
2002
- const reportPath = (0, import_node_path6.join)(stageDir, "report.toml");
2232
+ const reportPath = (0, import_node_path7.join)(stageDir, "report.toml");
2003
2233
  await runDocker2([
2004
2234
  "cp",
2005
2235
  `${containerName}:/platform-output/report.toml`,
2006
2236
  reportPath
2007
2237
  ]);
2008
- const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
2238
+ const digest = readReportDigest((0, import_node_fs7.readFileSync)(reportPath, "utf8"));
2009
2239
  if (!digest) {
2010
2240
  throw new Error(
2011
2241
  `${buildpack.runtime} buildpack lifecycle did not report a digest.`
@@ -2049,7 +2279,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2049
2279
  }
2050
2280
  if (stageDir) {
2051
2281
  try {
2052
- (0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
2282
+ (0, import_node_fs7.rmSync)(stageDir, { recursive: true, force: true });
2053
2283
  } catch {
2054
2284
  debug(
2055
2285
  `Could not remove Docker build scratch directory ${stageDir}.`
@@ -2063,10 +2293,10 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2063
2293
 
2064
2294
  // src/buildpacks/node-toolchain.ts
2065
2295
  var import_build_utils4 = require("@vercel/build-utils");
2066
- var import_node_fs7 = require("fs");
2067
- var import_node_path7 = require("path");
2296
+ var import_node_fs8 = require("fs");
2297
+ var import_node_path8 = require("path");
2068
2298
  async function selectNodeMajor(workPath, config, meta) {
2069
- if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(workPath, "package.json"))) {
2299
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(workPath, "package.json"))) {
2070
2300
  return void 0;
2071
2301
  }
2072
2302
  const version2 = await (0, import_build_utils4.getNodeVersion)(workPath, void 0, config, meta);
@@ -2087,7 +2317,7 @@ async function resolveNodeToolchain(workPath, config, meta) {
2087
2317
  return void 0;
2088
2318
  }
2089
2319
  for (const executable of ["node", "npm"]) {
2090
- if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(toolchain.home, "bin", executable))) {
2320
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(toolchain.home, "bin", executable))) {
2091
2321
  throw new Error(
2092
2322
  `The selected Node.js ${toolchain.version} installation is missing ${toolchain.home}/bin/${executable} in the build environment.`
2093
2323
  );
@@ -2100,12 +2330,12 @@ async function resolveLocalNodeToolchain(workPath, config, meta) {
2100
2330
  }
2101
2331
 
2102
2332
  // src/buildpacks/registry.ts
2103
- var import_node_fs9 = require("fs");
2104
- var import_node_path9 = require("path");
2333
+ var import_node_fs10 = require("fs");
2334
+ var import_node_path10 = require("path");
2105
2335
 
2106
2336
  // src/buildpacks/ruby.ts
2107
- var import_node_fs8 = require("fs");
2108
- var import_node_path8 = require("path");
2337
+ var import_node_fs9 = require("fs");
2338
+ var import_node_path9 = require("path");
2109
2339
  var import_smol_toml2 = require("smol-toml");
2110
2340
  var DEFAULT_RUBY_VERSION = "3.4";
2111
2341
  var RUBY_IMAGE_REPOSITORY = "docker.io/library/ruby";
@@ -2130,10 +2360,10 @@ var ruby = {
2130
2360
  return { version: version2, buildImage: image, runImage: image };
2131
2361
  },
2132
2362
  validate({ workPath, buildEnv }) {
2133
- const gemfile = (0, import_node_path8.join)(workPath, "Gemfile");
2134
- if (!(0, import_node_fs8.existsSync)(gemfile))
2363
+ const gemfile = (0, import_node_path9.join)(workPath, "Gemfile");
2364
+ if (!(0, import_node_fs9.existsSync)(gemfile))
2135
2365
  return;
2136
- if (!GEMFILE_RAILS_GEM_RE.test((0, import_node_fs8.readFileSync)(gemfile, "utf8")))
2366
+ if (!GEMFILE_RAILS_GEM_RE.test((0, import_node_fs9.readFileSync)(gemfile, "utf8")))
2137
2367
  return;
2138
2368
  if (buildEnv?.SECRET_KEY_BASE)
2139
2369
  return;
@@ -2147,13 +2377,13 @@ function isValidVersion(version2) {
2147
2377
  }
2148
2378
  function selectRubyVersion(workPath) {
2149
2379
  for (const name of RUBY_VERSION_FILES) {
2150
- const file = (0, import_node_path8.join)(workPath, name);
2151
- if ((0, import_node_fs8.existsSync)(file)) {
2152
- return parseVersionFile(name, (0, import_node_fs8.readFileSync)(file, "utf8"));
2380
+ const file = (0, import_node_path9.join)(workPath, name);
2381
+ if ((0, import_node_fs9.existsSync)(file)) {
2382
+ return parseVersionFile(name, (0, import_node_fs9.readFileSync)(file, "utf8"));
2153
2383
  }
2154
2384
  }
2155
- const gemfile = (0, import_node_path8.join)(workPath, "Gemfile");
2156
- return (0, import_node_fs8.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs8.readFileSync)(gemfile, "utf8")) : void 0;
2385
+ const gemfile = (0, import_node_path9.join)(workPath, "Gemfile");
2386
+ return (0, import_node_fs9.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs9.readFileSync)(gemfile, "utf8")) : void 0;
2157
2387
  }
2158
2388
  function parseVersionFile(name, contents) {
2159
2389
  let version2;
@@ -2198,13 +2428,13 @@ function requestedBuildpack(config) {
2198
2428
  }
2199
2429
  function hasProjectMarkers(buildpack, workPath) {
2200
2430
  return buildpack.projectMarkers.some(
2201
- (name) => (0, import_node_fs9.existsSync)((0, import_node_path9.join)(workPath, name))
2431
+ (name) => (0, import_node_fs10.existsSync)((0, import_node_path10.join)(workPath, name))
2202
2432
  );
2203
2433
  }
2204
2434
 
2205
2435
  // src/buildpacks/build.ts
2206
2436
  async function buildAndPushBuildpack(params) {
2207
- const sourceDir = (0, import_node_fs10.mkdtempSync)((0, import_node_path10.join)((0, import_node_os7.tmpdir)(), "vercel-cnb-source-"));
2437
+ const sourceDir = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os7.tmpdir)(), "vercel-cnb-source-"));
2208
2438
  try {
2209
2439
  step("Staging buildpack source files");
2210
2440
  await stageWorkspace(params, sourceDir);
@@ -2249,6 +2479,12 @@ async function buildAndPushBuildpack(params) {
2249
2479
  "image.ref": target.imageRef,
2250
2480
  "registry.username": target.username
2251
2481
  });
2482
+ const cache = params.cache && engine.name === "buildah" ? { dir: buildpackCacheDir(params) } : void 0;
2483
+ if (cache)
2484
+ debug(`buildpack cache: ${cache.dir}`);
2485
+ buildSpan?.setAttributes({
2486
+ "buildpack.cache.enabled": String(Boolean(cache))
2487
+ });
2252
2488
  return engine.withRuntime(buildSpan, async () => {
2253
2489
  await withSpan(
2254
2490
  buildSpan,
@@ -2298,7 +2534,8 @@ async function buildAndPushBuildpack(params) {
2298
2534
  imageRef: target.imageRef,
2299
2535
  credentials,
2300
2536
  buildEnv: params.buildEnv,
2301
- nodeToolchain
2537
+ nodeToolchain,
2538
+ cache
2302
2539
  },
2303
2540
  buildSpan
2304
2541
  );
@@ -2314,7 +2551,7 @@ async function buildAndPushBuildpack(params) {
2314
2551
  );
2315
2552
  } finally {
2316
2553
  try {
2317
- (0, import_node_fs10.rmSync)(sourceDir, { recursive: true, force: true });
2554
+ (0, import_node_fs11.rmSync)(sourceDir, { recursive: true, force: true });
2318
2555
  } catch {
2319
2556
  debug(`Could not remove buildpack source snapshot ${sourceDir}.`);
2320
2557
  }
@@ -2322,8 +2559,8 @@ async function buildAndPushBuildpack(params) {
2322
2559
  }
2323
2560
 
2324
2561
  // src/image-source.ts
2325
- var import_node_fs11 = require("fs");
2326
- var import_node_path11 = __toESM(require("path"));
2562
+ var import_node_fs12 = require("fs");
2563
+ var import_node_path12 = __toESM(require("path"));
2327
2564
  var DETECT_SENTINEL = "<detect>";
2328
2565
  function resolveImageSource(options, context) {
2329
2566
  const { config, workPath, entrypoint } = options;
@@ -2337,8 +2574,8 @@ function resolveImageSource(options, context) {
2337
2574
  }
2338
2575
  const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
2339
2576
  const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
2340
- const dockerfilePath = import_node_path11.default.join(workPath, dockerfileRel);
2341
- const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs11.existsSync)(dockerfilePath);
2577
+ const dockerfilePath = import_node_path12.default.join(workPath, dockerfileRel);
2578
+ const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs12.existsSync)(dockerfilePath);
2342
2579
  if (hasDockerfile) {
2343
2580
  return { kind: "dockerfile", dockerfileRel, dockerfilePath };
2344
2581
  }
@@ -2356,9 +2593,9 @@ function resolveImageSource(options, context) {
2356
2593
 
2357
2594
  // src/dev.ts
2358
2595
  var import_node_child_process3 = require("child_process");
2359
- var import_node_fs12 = require("fs");
2596
+ var import_node_fs13 = require("fs");
2360
2597
  var import_node_os8 = require("os");
2361
- var import_node_path12 = __toESM(require("path"));
2598
+ var import_node_path13 = __toESM(require("path"));
2362
2599
  var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
2363
2600
  "TMPDIR",
2364
2601
  "TMP",
@@ -2388,8 +2625,8 @@ function isHostOnlyEnvVar(key) {
2388
2625
  return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
2389
2626
  }
2390
2627
  function writeEnvFile(env) {
2391
- const dir = (0, import_node_fs12.mkdtempSync)(import_node_path12.default.join((0, import_node_os8.tmpdir)(), "vercel-container-dev-env-"));
2392
- const file = import_node_path12.default.join(dir, "env");
2628
+ const dir = (0, import_node_fs13.mkdtempSync)(import_node_path13.default.join((0, import_node_os8.tmpdir)(), "vercel-container-dev-env-"));
2629
+ const file = import_node_path13.default.join(dir, "env");
2393
2630
  const lines = [];
2394
2631
  for (const [key, value] of Object.entries(env)) {
2395
2632
  if (value.includes("\n")) {
@@ -2397,7 +2634,7 @@ function writeEnvFile(env) {
2397
2634
  }
2398
2635
  lines.push(`${key}=${value}`);
2399
2636
  }
2400
- (0, import_node_fs12.writeFileSync)(file, `${lines.join("\n")}
2637
+ (0, import_node_fs13.writeFileSync)(file, `${lines.join("\n")}
2401
2638
  `);
2402
2639
  return file;
2403
2640
  }
@@ -2422,7 +2659,7 @@ function emit(out, line) {
2422
2659
  }
2423
2660
  }
2424
2661
  function runForwarded(cmd, args, out, opts = {}) {
2425
- return new Promise((resolve2, reject) => {
2662
+ return new Promise((resolve4, reject) => {
2426
2663
  const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2427
2664
  let stdout = "";
2428
2665
  let stderr = "";
@@ -2459,7 +2696,7 @@ function runForwarded(cmd, args, out, opts = {}) {
2459
2696
  });
2460
2697
  child.on("close", (code) => {
2461
2698
  if (code === 0) {
2462
- resolve2({ stdout });
2699
+ resolve4({ stdout });
2463
2700
  } else {
2464
2701
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
2465
2702
  reject(
@@ -2488,14 +2725,14 @@ async function resolveDevImage(options, out, span) {
2488
2725
  );
2489
2726
  }
2490
2727
  const { dockerfilePath } = source;
2491
- if (!(0, import_node_fs12.existsSync)(dockerfilePath)) {
2728
+ if (!(0, import_node_fs13.existsSync)(dockerfilePath)) {
2492
2729
  throw new Error(
2493
2730
  `Dockerfile not found at "${dockerfilePath}" for container service.`
2494
2731
  );
2495
2732
  }
2496
2733
  const serviceName = options.service?.name ?? "service";
2497
2734
  const tag = devImageTag(serviceName);
2498
- const contextDir = import_node_path12.default.dirname(dockerfilePath);
2735
+ const contextDir = import_node_path13.default.dirname(dockerfilePath);
2499
2736
  const buildArgFlags2 = [];
2500
2737
  const buildEnv = options.meta?.buildEnv ?? {};
2501
2738
  for (const [key, value] of Object.entries(buildEnv)) {
@@ -2700,7 +2937,7 @@ async function startContainer(options, reuseKey) {
2700
2937
  }
2701
2938
  });
2702
2939
  const cleanupEnvFile = () => {
2703
- (0, import_node_fs12.rmSync)(import_node_path12.default.dirname(envFilePath), { recursive: true, force: true });
2940
+ (0, import_node_fs13.rmSync)(import_node_path13.default.dirname(envFilePath), { recursive: true, force: true });
2704
2941
  };
2705
2942
  const shutdown = async () => {
2706
2943
  runningContainers.delete(reuseKey);
@@ -2733,7 +2970,7 @@ async function startContainer(options, reuseKey) {
2733
2970
  break;
2734
2971
  } catch (err) {
2735
2972
  lastErr = err;
2736
- await new Promise((resolve2) => setTimeout(resolve2, 250));
2973
+ await new Promise((resolve4) => setTimeout(resolve4, 250));
2737
2974
  }
2738
2975
  }
2739
2976
  if (hostPort === void 0) {
@@ -2777,29 +3014,24 @@ async function startContainer(options, reuseKey) {
2777
3014
 
2778
3015
  // src/prepare-cache.ts
2779
3016
  var import_build_utils5 = require("@vercel/build-utils");
2780
- var import_node_fs13 = require("fs");
2781
- var import_node_path13 = require("path");
2782
- var CACHE_ROOT = "/vercel";
2783
- var GRAPH_ROOT_REL = import_node_path13.posix.relative(CACHE_ROOT, BUILDAH_GRAPH_ROOT);
2784
- async function prepareCache(_options) {
2785
- if (process.env.VERCEL_VCR_DISABLE_LAYER_CACHE) {
2786
- debug("layer cache disabled (VERCEL_VCR_DISABLE_LAYER_CACHE)");
2787
- return {};
2788
- }
2789
- if (!isBuildContainer()) {
2790
- debug("skipping container layer cache (not in build container)");
2791
- return {};
2792
- }
2793
- if (!(0, import_node_fs13.existsSync)(BUILDAH_GRAPH_ROOT)) {
2794
- debug(`no buildah store to cache at ${BUILDAH_GRAPH_ROOT}`);
3017
+ var import_node_fs14 = require("fs");
3018
+ var import_node_path14 = require("path");
3019
+ async function prepareCache(options) {
3020
+ const root = (0, import_node_path14.resolve)(options.repoRootPath || options.workPath);
3021
+ if (!buildpackCacheEnabled()) {
3022
+ (0, import_node_fs14.rmSync)((0, import_node_path14.join)(root, BUILDPACK_CACHE_DIR), { recursive: true, force: true });
2795
3023
  return {};
2796
3024
  }
2797
3025
  const start = Date.now();
2798
- const files = await (0, import_build_utils5.glob)(`${GRAPH_ROOT_REL}/**`, CACHE_ROOT);
3026
+ const files = await (0, import_build_utils5.glob)(`${BUILDPACK_CACHE_DIR}/**`, root);
2799
3027
  const count = Object.keys(files).length;
2800
- info(
2801
- `cached container layer store: ${count} files from ${BUILDAH_GRAPH_ROOT} in ${Date.now() - start}ms`
2802
- );
3028
+ if (count > 0) {
3029
+ info(
3030
+ `cached buildpack layers: ${count} file${count === 1 ? "" : "s"} under ${BUILDPACK_CACHE_DIR} in ${Date.now() - start}ms`
3031
+ );
3032
+ } else {
3033
+ debug("no buildpack cache to persist");
3034
+ }
2803
3035
  return files;
2804
3036
  }
2805
3037
 
@@ -2997,11 +3229,14 @@ async function resolveImageHandler(options, source, span) {
2997
3229
  buildpack,
2998
3230
  files: options.files,
2999
3231
  workPath: options.workPath,
3232
+ repoRootPath: options.repoRootPath,
3233
+ service: options.service,
3000
3234
  repository: repository2,
3001
3235
  tag: tag2,
3002
3236
  config,
3003
3237
  meta,
3004
3238
  buildEnv: buildArgsFromEnv(meta?.buildEnv),
3239
+ cache: buildpackCacheEnabled(),
3005
3240
  parentSpan: span
3006
3241
  });
3007
3242
  }
@@ -3009,22 +3244,22 @@ async function resolveImageHandler(options, source, span) {
3009
3244
  if (meta?.isDev) {
3010
3245
  const serviceName2 = options.service?.name;
3011
3246
  const tag2 = devImageTag(
3012
- serviceName2 ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
3247
+ serviceName2 ?? import_node_path15.default.basename(dockerfileRel).split(".")[0]
3013
3248
  );
3014
3249
  span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
3015
3250
  return tag2;
3016
3251
  }
3017
- if (!(0, import_node_fs14.existsSync)(dockerfilePath)) {
3252
+ if (!(0, import_node_fs15.existsSync)(dockerfilePath)) {
3018
3253
  throw new Error(
3019
3254
  `Dockerfile not found at "${dockerfilePath}" for container service.`
3020
3255
  );
3021
3256
  }
3022
3257
  const serviceName = options.service?.name;
3023
3258
  const repository = sanitizeRepository(
3024
- serviceName ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
3259
+ serviceName ?? import_node_path15.default.basename(dockerfileRel).split(".")[0]
3025
3260
  );
3026
3261
  const tag = resolveImageTag();
3027
- const contextDir = import_node_path14.default.dirname(dockerfilePath);
3262
+ const contextDir = import_node_path15.default.dirname(dockerfilePath);
3028
3263
  const buildArgs = buildArgsFromEnv(meta?.buildEnv);
3029
3264
  span?.setAttributes({
3030
3265
  "container.mode": "build_and_push",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/container",
3
- "version": "10.0.0",
3
+ "version": "10.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "homepage": "https://vercel.com/docs",
@@ -31,7 +31,7 @@
31
31
  "build": "node ../../utils/build-builder.mjs",
32
32
  "type-check": "tsc --noEmit",
33
33
  "test": "vitest run --config ../../vitest.config.mts",
34
- "test-unit": "vitest run --config ../../vitest.config.mts test/unit.test.ts test/diagnostics.test.ts test/workspace.test.ts test/lifecycle-docker.test.ts test/ruby-version.test.ts test/docker-push.test.ts",
34
+ "test-unit": "vitest run --config ../../vitest.config.mts test/unit.test.ts test/diagnostics.test.ts test/workspace.test.ts test/lifecycle-docker.test.ts test/ruby-version.test.ts test/docker-push.test.ts test/cache.test.ts",
35
35
  "test-e2e": "vitest run --config ../../vitest.config.mts test/e2e.test.ts"
36
36
  }
37
37
  }