@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.
- package/dist/index.js +277 -123
- 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 =
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
|
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":
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
}
|