@vercel/container 8.2.1 → 8.3.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 +277 -123
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -105,6 +105,9 @@ function elapsed(since) {
105
105
  function shortDigest(digest) {
106
106
  return digest.startsWith("sha256:") ? `${digest.slice(0, 19)}\u2026` : digest;
107
107
  }
108
+ function delay(ms) {
109
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
110
+ }
108
111
  async function withSpan(parent, name, attrs, fn) {
109
112
  if (!parent) {
110
113
  return fn(void 0);
@@ -517,7 +520,7 @@ function formatVcrAuthError(registry, username, detail) {
517
520
  // src/engines/types.ts
518
521
  var VCR_REGISTRY = process.env.VERCEL_VCR_REGISTRY || "vcr.vercel.com";
519
522
  var TARGET_PLATFORM = "linux/amd64";
520
- var DIGEST_RE = /sha256:[a-f0-9]{64}/;
523
+ var DIGEST_RE = /^sha256:[a-f0-9]{64}$/;
521
524
  function buildArgFlags(params) {
522
525
  const flags = [];
523
526
  for (const [key, value] of Object.entries(params.buildArgs ?? {})) {
@@ -526,6 +529,161 @@ function buildArgFlags(params) {
526
529
  return flags;
527
530
  }
528
531
 
532
+ // src/registry.ts
533
+ async function resolveRegistryTarget(params) {
534
+ const token = await withSpan(
535
+ params.span,
536
+ "container.mint_oidc",
537
+ {},
538
+ (s) => resolveOidcTokenForBuild(s)
539
+ );
540
+ const claims = decodeOidcClaims(token);
541
+ debug(`registry token: ${tokenFingerprint(token)}`);
542
+ debugTokenClaims("OIDC token claims", token);
543
+ const username = claims.owner_id;
544
+ if (!username) {
545
+ throw new Error(
546
+ "VERCEL_OIDC_TOKEN is missing the `owner_id` (team id) claim required to authenticate to the container registry."
547
+ );
548
+ }
549
+ const fullRepository = [claims.owner, claims.project, params.repository].join(
550
+ "/"
551
+ );
552
+ return {
553
+ token,
554
+ claims,
555
+ username,
556
+ fullRepository,
557
+ imageRef: `${VCR_REGISTRY}/${fullRepository}:${params.tag}`
558
+ };
559
+ }
560
+ var MANIFEST_MEDIA_TYPES = [
561
+ "application/vnd.oci.image.manifest.v1+json",
562
+ "application/vnd.oci.image.index.v1+json",
563
+ "application/vnd.docker.distribution.manifest.v2+json",
564
+ "application/vnd.docker.distribution.manifest.list.v2+json"
565
+ ].join(", ");
566
+ var MANIFEST_LOOKUP_ATTEMPTS = 3;
567
+ var MANIFEST_LOOKUP_TIMEOUT_MS = 1e4;
568
+ async function resolvePushedImageDigest(params) {
569
+ const fail = (reason) => new Error(
570
+ `Image ${params.imageRef} was pushed, but its registry digest could not be resolved: ${reason}`
571
+ );
572
+ const ref = parseTaggedImageRef(params.imageRef, params.registry);
573
+ if (!ref) {
574
+ throw fail("Expected a tagged image reference in the push registry.");
575
+ }
576
+ const url = `https://${params.registry}/v2/${ref.repository}/manifests/${encodeURIComponent(ref.tag)}`;
577
+ const authorization = `Basic ${Buffer.from(
578
+ `${params.username}:${params.token}`
579
+ ).toString("base64")}`;
580
+ let failure = "Registry request failed.";
581
+ for (let attempt = 1; attempt <= MANIFEST_LOOKUP_ATTEMPTS; attempt++) {
582
+ debug(
583
+ `manifest lookup (${attempt}/${MANIFEST_LOOKUP_ATTEMPTS}): HEAD ${url}`
584
+ );
585
+ let response;
586
+ try {
587
+ response = await fetch(url, {
588
+ method: "HEAD",
589
+ headers: { authorization, accept: MANIFEST_MEDIA_TYPES },
590
+ redirect: "manual",
591
+ signal: AbortSignal.timeout(MANIFEST_LOOKUP_TIMEOUT_MS)
592
+ });
593
+ } catch (err) {
594
+ failure = `Registry request failed: ${describeFetchError(err)}`;
595
+ }
596
+ if (response?.ok) {
597
+ const digest = response.headers.get("docker-content-digest");
598
+ if (!digest || !DIGEST_RE.test(digest)) {
599
+ throw fail("Registry returned a missing or invalid manifest digest.");
600
+ }
601
+ return digest;
602
+ }
603
+ if (response) {
604
+ failure = `Registry returned HTTP ${response.status}.`;
605
+ const retryable = response.status === 404 || response.status === 429 || response.status >= 500;
606
+ if (!retryable) {
607
+ throw fail(failure);
608
+ }
609
+ }
610
+ debug(`manifest lookup failed: ${failure}`);
611
+ if (attempt < MANIFEST_LOOKUP_ATTEMPTS) {
612
+ await delay(500 * 2 ** (attempt - 1));
613
+ }
614
+ }
615
+ throw fail(failure);
616
+ }
617
+ function parseTaggedImageRef(imageRef, registry) {
618
+ const prefix = `${registry}/`;
619
+ if (!imageRef.startsWith(prefix)) {
620
+ return void 0;
621
+ }
622
+ const imagePath = imageRef.slice(prefix.length);
623
+ const tagSeparator = imagePath.lastIndexOf(":");
624
+ if (tagSeparator <= imagePath.lastIndexOf("/") || tagSeparator === imagePath.length - 1) {
625
+ return void 0;
626
+ }
627
+ return {
628
+ repository: imagePath.slice(0, tagSeparator),
629
+ tag: imagePath.slice(tagSeparator + 1)
630
+ };
631
+ }
632
+ function describeFetchError(err) {
633
+ const error = err;
634
+ const cause = error.cause?.message;
635
+ return cause ? `${error.message} (${cause})` : error.message;
636
+ }
637
+ async function ensureRepository(repository, token, claims, span) {
638
+ if (repository.includes("/")) {
639
+ debug(`skipping repository auto-create (fully-qualified "${repository}")`);
640
+ span?.setAttributes({ "repository.create_result": "skipped_qualified" });
641
+ return;
642
+ }
643
+ const teamId = claims.owner_id;
644
+ const projectId = claims.project_id;
645
+ if (!teamId || !projectId) {
646
+ debug(
647
+ `skipping repository auto-create (missing ${!teamId ? "team id" : "project id"})`
648
+ );
649
+ span?.setAttributes({
650
+ "repository.create_result": "skipped_missing_ids"
651
+ });
652
+ return;
653
+ }
654
+ span?.setAttributes({ "team.id": teamId, "project.id": projectId });
655
+ const apiUrl = (readString(process.env.VERCEL_API_URL) ?? "https://api.vercel.com").replace(/\/+$/, "");
656
+ const url = `${apiUrl}/v1/vcr/repository?teamId=${encodeURIComponent(teamId)}`;
657
+ const body = JSON.stringify({ name: repository, projectId });
658
+ step(`Ensuring registry repository "${repository}"`);
659
+ debug(`repository create: POST ${url}`);
660
+ try {
661
+ const res = await fetch(url, {
662
+ method: "POST",
663
+ headers: {
664
+ authorization: `Bearer ${token}`,
665
+ "content-type": "application/json"
666
+ },
667
+ body
668
+ });
669
+ span?.setAttributes({ "repository.create_status": toTag(res.status) });
670
+ if (res.ok) {
671
+ span?.setAttributes({ "repository.create_result": "created" });
672
+ done(`created repository "${repository}"`);
673
+ } else if (res.status === 409) {
674
+ span?.setAttributes({ "repository.create_result": "already_exists" });
675
+ done(`repository "${repository}" already exists`);
676
+ } else {
677
+ span?.setAttributes({ "repository.create_result": "unexpected_status" });
678
+ done("continuing \u2014 push will validate the repository");
679
+ }
680
+ } catch (err) {
681
+ debug(`repository auto-create failed: ${err.message}`);
682
+ span?.setAttributes({ "repository.create_result": "error" });
683
+ done("continuing \u2014 push will validate the repository");
684
+ }
685
+ }
686
+
529
687
  // src/engines/buildah.ts
530
688
  async function runBuildah(args, opts = {}) {
531
689
  const storageArgs = await buildahStorageArgs();
@@ -693,6 +851,7 @@ ${stderr}`);
693
851
  `pushing ${params.imageRef} ` + (zstdEnabled ? "with zstd compression (level=3, force, oci)" : "with default compression (zstd disabled)")
694
852
  );
695
853
  const pushStart = Date.now();
854
+ let digest;
696
855
  try {
697
856
  await runBuildah([
698
857
  "push",
@@ -701,12 +860,7 @@ ${stderr}`);
701
860
  digestFile,
702
861
  params.imageRef
703
862
  ]);
704
- const digest = (0, import_node_fs3.readFileSync)(digestFile, "utf8").trim();
705
- const resolved = digest.match(/sha256:[a-f0-9]{64}/)?.[0] ?? (digest || void 0);
706
- debug(
707
- `push completed in ${Date.now() - pushStart}ms` + (resolved ? ` (digest ${resolved})` : "")
708
- );
709
- return resolved;
863
+ digest = readDigestFile(digestFile);
710
864
  } catch (err) {
711
865
  const message = err.message;
712
866
  if (/denied|forbidden|unauthorized|not found|401|403|404/i.test(message)) {
@@ -726,8 +880,25 @@ ${stderr}`);
726
880
  } finally {
727
881
  (0, import_node_fs3.rmSync)(digestDir, { recursive: true, force: true });
728
882
  }
883
+ debug(`push completed in ${Date.now() - pushStart}ms`);
884
+ if (DIGEST_RE.test(digest)) {
885
+ debug(`pushed digest ${digest}`);
886
+ return digest;
887
+ }
888
+ debug(
889
+ `--digestfile did not contain a digest (${JSON.stringify(digest)}) \u2014 resolving registry manifest`
890
+ );
891
+ return resolvePushedImageDigest(params);
729
892
  }
730
893
  };
894
+ function readDigestFile(path4) {
895
+ try {
896
+ return (0, import_node_fs3.readFileSync)(path4, "utf8").trim();
897
+ } catch (err) {
898
+ debug(`could not read --digestfile: ${err.message}`);
899
+ return "";
900
+ }
901
+ }
731
902
 
732
903
  // src/engines/docker.ts
733
904
  var import_node_child_process2 = require("child_process");
@@ -989,22 +1160,12 @@ ${version2.trim()}`);
989
1160
  }
990
1161
  },
991
1162
  async push(params) {
1163
+ let output;
992
1164
  try {
993
1165
  info(`pushing ${params.imageRef}`);
994
1166
  const pushStart = Date.now();
995
- const { stdout } = await runDocker(["push", params.imageRef]);
1167
+ output = await runDocker(["push", params.imageRef]);
996
1168
  debug(`push completed in ${Date.now() - pushStart}ms`);
997
- let resolvedDigest = stdout.match(/sha256:[a-f0-9]{64}/)?.[0];
998
- if (!resolvedDigest) {
999
- debug("digest not found in push output \u2014 inspecting RepoDigests");
1000
- const inspect = await run(
1001
- "docker",
1002
- ["inspect", "--format", "{{index .RepoDigests 0}}", params.imageRef],
1003
- { quiet: true }
1004
- );
1005
- resolvedDigest = inspect.stdout.match(/sha256:[a-f0-9]{64}/)?.[0];
1006
- }
1007
- return resolvedDigest;
1008
1169
  } catch (err) {
1009
1170
  const message = err.message;
1010
1171
  if (/denied|forbidden|unauthorized|not found|401|403|404/i.test(message)) {
@@ -1022,6 +1183,13 @@ ${version2.trim()}`);
1022
1183
  }
1023
1184
  throw err;
1024
1185
  }
1186
+ const summary = /^[^\s:]+: digest: (sha256:[a-f0-9]{64}) size: \d+\s*$/m;
1187
+ const digest = output.stdout.match(summary)?.[1] ?? output.stderr.match(summary)?.[1];
1188
+ if (digest) {
1189
+ return digest;
1190
+ }
1191
+ debug("digest not found in push summary \u2014 resolving registry manifest");
1192
+ return resolvePushedImageDigest(params);
1025
1193
  }
1026
1194
  };
1027
1195
 
@@ -1042,84 +1210,6 @@ var import_node_fs10 = require("fs");
1042
1210
  var import_node_os7 = require("os");
1043
1211
  var import_node_path10 = require("path");
1044
1212
 
1045
- // src/registry.ts
1046
- async function resolveRegistryTarget(params) {
1047
- const token = await withSpan(
1048
- params.span,
1049
- "container.mint_oidc",
1050
- {},
1051
- (s) => resolveOidcTokenForBuild(s)
1052
- );
1053
- const claims = decodeOidcClaims(token);
1054
- debug(`registry token: ${tokenFingerprint(token)}`);
1055
- debugTokenClaims("OIDC token claims", token);
1056
- const username = claims.owner_id;
1057
- if (!username) {
1058
- throw new Error(
1059
- "VERCEL_OIDC_TOKEN is missing the `owner_id` (team id) claim required to authenticate to the container registry."
1060
- );
1061
- }
1062
- const fullRepository = [claims.owner, claims.project, params.repository].join(
1063
- "/"
1064
- );
1065
- return {
1066
- token,
1067
- claims,
1068
- username,
1069
- fullRepository,
1070
- imageRef: `${VCR_REGISTRY}/${fullRepository}:${params.tag}`
1071
- };
1072
- }
1073
- async function ensureRepository(repository, token, claims, span) {
1074
- if (repository.includes("/")) {
1075
- debug(`skipping repository auto-create (fully-qualified "${repository}")`);
1076
- span?.setAttributes({ "repository.create_result": "skipped_qualified" });
1077
- return;
1078
- }
1079
- const teamId = claims.owner_id;
1080
- const projectId = claims.project_id;
1081
- if (!teamId || !projectId) {
1082
- debug(
1083
- `skipping repository auto-create (missing ${!teamId ? "team id" : "project id"})`
1084
- );
1085
- span?.setAttributes({
1086
- "repository.create_result": "skipped_missing_ids"
1087
- });
1088
- return;
1089
- }
1090
- span?.setAttributes({ "team.id": teamId, "project.id": projectId });
1091
- const apiUrl = (readString(process.env.VERCEL_API_URL) ?? "https://api.vercel.com").replace(/\/+$/, "");
1092
- const url = `${apiUrl}/v1/vcr/repository?teamId=${encodeURIComponent(teamId)}`;
1093
- const body = JSON.stringify({ name: repository, projectId });
1094
- step(`Ensuring registry repository "${repository}"`);
1095
- debug(`repository create: POST ${url}`);
1096
- try {
1097
- const res = await fetch(url, {
1098
- method: "POST",
1099
- headers: {
1100
- authorization: `Bearer ${token}`,
1101
- "content-type": "application/json"
1102
- },
1103
- body
1104
- });
1105
- span?.setAttributes({ "repository.create_status": toTag(res.status) });
1106
- if (res.ok) {
1107
- span?.setAttributes({ "repository.create_result": "created" });
1108
- done(`created repository "${repository}"`);
1109
- } else if (res.status === 409) {
1110
- span?.setAttributes({ "repository.create_result": "already_exists" });
1111
- done(`repository "${repository}" already exists`);
1112
- } else {
1113
- span?.setAttributes({ "repository.create_result": "unexpected_status" });
1114
- done("continuing \u2014 push will validate the repository");
1115
- }
1116
- } catch (err) {
1117
- debug(`repository auto-create failed: ${err.message}`);
1118
- span?.setAttributes({ "repository.create_result": "error" });
1119
- done("continuing \u2014 push will validate the repository");
1120
- }
1121
- }
1122
-
1123
1213
  // src/buildpacks/lifecycle/lifecycle.ts
1124
1214
  var import_build_utils3 = require("@vercel/build-utils");
1125
1215
  var import_node_crypto2 = require("crypto");
@@ -1154,6 +1244,28 @@ var LIFECYCLE = {
1154
1244
  url: `${BUILDPACK_DIST_BASE_URL}/lifecycle/${LIFECYCLE_VERSION}/lifecycle-v${LIFECYCLE_VERSION}-linux-x86-64.tgz`,
1155
1245
  sha256: distribution.lifecycle.sha256
1156
1246
  };
1247
+ var PRERELEASE_URL_ENV = "VERCEL_BUILDPACK_PRERELEASE_URL";
1248
+ var SHA256_RE = /^[0-9a-f]{64}$/;
1249
+ var ARTIFACT_PREFIX = "vercel-";
1250
+ var ARTIFACT_SUFFIX = ".cnb";
1251
+ var ARTIFACT_NAME_RE = /^[A-Za-z0-9_-]+$/;
1252
+ var ARTIFACT_VERSION_RE = /^\d+\.\d+\.\d+$/;
1253
+ function artifactFileName(name, version2) {
1254
+ return `${ARTIFACT_PREFIX}${name}-${version2}${ARTIFACT_SUFFIX}`;
1255
+ }
1256
+ function parseArtifactFileName(fileName) {
1257
+ if (!fileName.startsWith(ARTIFACT_PREFIX) || !fileName.endsWith(ARTIFACT_SUFFIX)) {
1258
+ return void 0;
1259
+ }
1260
+ const inner = fileName.slice(ARTIFACT_PREFIX.length, -ARTIFACT_SUFFIX.length);
1261
+ const separator = inner.lastIndexOf("-");
1262
+ const name = separator > 0 ? inner.slice(0, separator) : "";
1263
+ const version2 = separator > 0 ? inner.slice(separator + 1) : "";
1264
+ if (!ARTIFACT_NAME_RE.test(name) || !ARTIFACT_VERSION_RE.test(version2)) {
1265
+ return void 0;
1266
+ }
1267
+ return { name, version: version2 };
1268
+ }
1157
1269
  function vercelBuildpack(name) {
1158
1270
  const pinned = distribution.buildpacks[name];
1159
1271
  if (!pinned) {
@@ -1166,10 +1278,69 @@ function vercelBuildpack(name) {
1166
1278
  function vercelBuildpackArtifact(buildpack) {
1167
1279
  const { name, version: version2 } = buildpack;
1168
1280
  return {
1169
- url: `${BUILDPACK_DIST_BASE_URL}/buildpacks/${name}/${version2}/vercel-${name}-${version2}.cnb`,
1281
+ url: buildpack.url ?? `${BUILDPACK_DIST_BASE_URL}/buildpacks/${name}/${version2}/${artifactFileName(name, version2)}`,
1170
1282
  sha256: buildpack.sha256
1171
1283
  };
1172
1284
  }
1285
+ function parseArchiveUrl(value) {
1286
+ let url;
1287
+ try {
1288
+ url = new URL(value);
1289
+ } catch {
1290
+ throw new Error(`${PRERELEASE_URL_ENV} must be an https .cnb archive URL.`);
1291
+ }
1292
+ const fileName = url.pathname.split("/").at(-1) ?? "";
1293
+ const artifact = parseArtifactFileName(fileName);
1294
+ if (url.protocol !== "https:" || !artifact) {
1295
+ throw new Error(
1296
+ `${PRERELEASE_URL_ENV} must be a plain https URL ending in vercel-<name>-<version>.cnb.`
1297
+ );
1298
+ }
1299
+ return {
1300
+ url: url.href,
1301
+ fileName,
1302
+ name: artifact.name,
1303
+ version: artifact.version
1304
+ };
1305
+ }
1306
+ function getPrereleaseUrl(env = process.env) {
1307
+ const value = env[PRERELEASE_URL_ENV]?.trim();
1308
+ return value ? parseArchiveUrl(value).url : void 0;
1309
+ }
1310
+ async function withPrereleaseBuildpack(buildpack, archiveUrl) {
1311
+ const archive = parseArchiveUrl(archiveUrl);
1312
+ const entry = buildpack.buildpacks.find(
1313
+ (candidate) => candidate.name === archive.name && candidate.id === `vercel/${archive.name}`
1314
+ );
1315
+ if (!entry) {
1316
+ throw new Error(
1317
+ `Pre-release archive ${archive.url} does not match a buildpack in the ${buildpack.runtime} runtime.`
1318
+ );
1319
+ }
1320
+ const checksumUrlObj = new URL(archive.url);
1321
+ checksumUrlObj.pathname += ".sha256";
1322
+ const checksumUrl = checksumUrlObj.href;
1323
+ const response = await fetch(checksumUrl);
1324
+ if (!response.ok) {
1325
+ throw new Error(
1326
+ `Failed to fetch pre-release checksum ${checksumUrl}: HTTP ${response.status} ${response.statusText}`
1327
+ );
1328
+ }
1329
+ const fields = (await response.text()).trim().split(/\s+/);
1330
+ const [sha256, fileName] = fields;
1331
+ if (fields.length !== 2 || !SHA256_RE.test(sha256) || fileName !== archive.fileName) {
1332
+ throw new Error(
1333
+ `Invalid pre-release checksum ${checksumUrl}: expected a SHA-256 checksum for ${archive.fileName}.`
1334
+ );
1335
+ }
1336
+ info(`Using pre-release ${entry.id}@${archive.version}: ${archive.url}`);
1337
+ return {
1338
+ ...buildpack,
1339
+ buildpacks: buildpack.buildpacks.map(
1340
+ (candidate) => candidate === entry ? { ...entry, version: archive.version, sha256, url: archive.url } : candidate
1341
+ )
1342
+ };
1343
+ }
1173
1344
 
1174
1345
  // src/buildpacks/lifecycle/lifecycle.ts
1175
1346
  var BUILD_USER_ID = 1001;
@@ -1340,22 +1511,6 @@ function cnbRegistryAuth(credentials) {
1340
1511
  const basic = Buffer.from(`${username}:${token}`).toString("base64");
1341
1512
  return JSON.stringify({ [registry]: `Basic ${basic}` });
1342
1513
  }
1343
- function assertPublishedArchive(name, archive) {
1344
- if (/^0{64}$/.test(archive.sha256)) {
1345
- throw new Error(
1346
- `The ${name} archive has not been published. Publish it and update its checksum in distribution.json.`
1347
- );
1348
- }
1349
- }
1350
- function assertPublishedDistribution(buildpack) {
1351
- assertPublishedArchive("CNB lifecycle", LIFECYCLE);
1352
- for (const entry of buildpack.buildpacks) {
1353
- assertPublishedArchive(
1354
- `${entry.id}@${entry.version} buildpack`,
1355
- vercelBuildpackArtifact(entry)
1356
- );
1357
- }
1358
- }
1359
1514
  function distributionUrls(buildpack) {
1360
1515
  return {
1361
1516
  lifecycle: LIFECYCLE.url,
@@ -1525,7 +1680,6 @@ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1525
1680
  "image.ref": params.imageRef
1526
1681
  },
1527
1682
  async (lifecycleSpan) => {
1528
- assertPublishedDistribution(buildpack);
1529
1683
  const suffix = `${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
1530
1684
  const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
1531
1685
  let cnbDir;
@@ -1719,7 +1873,6 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1719
1873
  "image.ref": params.imageRef
1720
1874
  },
1721
1875
  async (lifecycleSpan) => {
1722
- assertPublishedDistribution(buildpack);
1723
1876
  let stageDir;
1724
1877
  const containers = [];
1725
1878
  try {
@@ -2048,7 +2201,9 @@ async function buildAndPushBuildpack(params) {
2048
2201
  `The ${params.buildpack.runtime} buildpack was selected, but no supported project marker was found in the service sources. Add ${params.buildpack.projectMarkers.join(" or ")}, or add a Dockerfile.vercel to control the image build.`
2049
2202
  );
2050
2203
  }
2051
- const image = await params.buildpack.resolveImage(sourceDir);
2204
+ const prereleaseUrl = getPrereleaseUrl();
2205
+ const buildpack = prereleaseUrl ? await withPrereleaseBuildpack(params.buildpack, prereleaseUrl) : params.buildpack;
2206
+ const image = await buildpack.resolveImage(sourceDir);
2052
2207
  const nodeToolchain = engine.name === "buildah" ? await resolveNodeToolchain(sourceDir, params.config, params.meta) : await resolveLocalNodeToolchain(
2053
2208
  sourceDir,
2054
2209
  params.config,
@@ -2058,9 +2213,10 @@ async function buildAndPushBuildpack(params) {
2058
2213
  params.parentSpan,
2059
2214
  "container.buildpack.build_and_push",
2060
2215
  {
2061
- "buildpack.runtime": params.buildpack.runtime,
2216
+ "buildpack.runtime": buildpack.runtime,
2062
2217
  "buildpack.runtime_version": image.version,
2063
2218
  "buildpack.node_version": nodeToolchain?.version,
2219
+ "buildpack.prerelease_url": prereleaseUrl,
2064
2220
  "container.engine": engine.name,
2065
2221
  "container.repository": params.repository
2066
2222
  },
@@ -2118,7 +2274,7 @@ async function buildAndPushBuildpack(params) {
2118
2274
  );
2119
2275
  const runLifecycle = engine.name === "buildah" ? buildAndPushWithLifecycle : buildAndPushWithLifecycleDocker;
2120
2276
  const result = await runLifecycle(
2121
- params.buildpack,
2277
+ buildpack,
2122
2278
  {
2123
2279
  workPath: sourceDir,
2124
2280
  image,
@@ -2767,16 +2923,14 @@ async function buildAndPushImage(params) {
2767
2923
  { "image.ref": imageRef },
2768
2924
  () => engine.push(buildParams)
2769
2925
  );
2770
- done(
2771
- digest ? `pushed ${shortDigest(digest)} in ${elapsed(pushStart)}` : `pushed in ${elapsed(pushStart)}`
2772
- );
2926
+ done(`pushed ${shortDigest(digest)} in ${elapsed(pushStart)}`);
2773
2927
  await withSpan(
2774
2928
  buildSpan,
2775
2929
  "container.report_storage",
2776
2930
  { "container.engine": engine.name },
2777
2931
  (s) => engine.reportStorage?.(s) ?? Promise.resolve()
2778
2932
  );
2779
- const resolvedRef = digest ? `${VCR_REGISTRY}/${fullRepository}@${digest}` : imageRef;
2933
+ const resolvedRef = `${VCR_REGISTRY}/${fullRepository}@${digest}`;
2780
2934
  buildSpan?.setAttributes({
2781
2935
  "image.digest": digest,
2782
2936
  "image.resolved_ref": resolvedRef
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/container",
3
- "version": "8.2.1",
3
+ "version": "8.3.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",
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",
35
35
  "test-e2e": "vitest run --config ../../vitest.config.mts test/e2e.test.ts"
36
36
  }
37
37
  }