@vercel/container 7.0.3 → 8.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 +1356 -259
  2. package/package.json +8 -5
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ __export(src_exports, {
38
38
  version: () => version
39
39
  });
40
40
  module.exports = __toCommonJS(src_exports);
41
- var import_build_utils4 = require("@vercel/build-utils");
41
+ var import_build_utils6 = require("@vercel/build-utils");
42
42
 
43
43
  // src/diagnostics.ts
44
44
  var import_build_utils = require("@vercel/build-utils");
@@ -62,17 +62,25 @@ async function generateProjectManifest({
62
62
  var diagnostics = (0, import_build_utils.createDiagnostics)("container");
63
63
 
64
64
  // src/index.ts
65
- var import_node_fs6 = require("fs");
66
- var import_node_path6 = __toESM(require("path"));
65
+ var import_node_fs14 = require("fs");
66
+ var import_node_path14 = __toESM(require("path"));
67
67
 
68
68
  // src/util.ts
69
69
  var import_build_utils2 = require("@vercel/build-utils");
70
70
  var import_node_child_process = require("child_process");
71
- var import_node_crypto = require("crypto");
72
71
  var import_node_fs = require("fs");
73
72
  var import_node_os = require("os");
74
73
  var import_node_path = require("path");
75
74
  var DEBUG = Boolean((0, import_build_utils2.getPlatformEnv)("BUILDER_DEBUG"));
75
+ function normalizeCommand(command) {
76
+ if (typeof command === "string") {
77
+ return [command];
78
+ }
79
+ if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
80
+ return command;
81
+ }
82
+ return void 0;
83
+ }
76
84
  function write(line) {
77
85
  process.stderr.write(`${line}
78
86
  `);
@@ -125,9 +133,10 @@ function devImageTag(serviceName) {
125
133
  return `vercel-dev/${safe || "service"}:dev`;
126
134
  }
127
135
  function run(cmd, args, opts = {}) {
128
- return new Promise((resolve, reject) => {
136
+ return new Promise((resolve2, reject) => {
129
137
  const child = (0, import_node_child_process.spawn)(cmd, args, {
130
138
  cwd: opts.cwd,
139
+ env: opts.env,
131
140
  stdio: [opts.input !== void 0 ? "pipe" : "ignore", "pipe", "pipe"]
132
141
  });
133
142
  let stdout = "";
@@ -159,15 +168,15 @@ function run(cmd, args, opts = {}) {
159
168
  });
160
169
  child.on("close", (code) => {
161
170
  if (code === 0) {
162
- resolve({ stdout, stderr });
171
+ resolve2({ stdout, stderr });
163
172
  } else {
164
173
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
165
- reject(
166
- new Error(
167
- `\`${cmd} ${args.join(" ")}\` exited with code ${code}` + (detail ? `
174
+ const error = new Error(
175
+ `\`${cmd} ${args.join(" ")}\` exited with code ${code}` + (detail ? `
168
176
  ${detail}` : "")
169
- )
170
177
  );
178
+ error.exitCode = code ?? void 0;
179
+ reject(error);
171
180
  }
172
181
  });
173
182
  if (opts.input !== void 0) {
@@ -179,59 +188,6 @@ function extractField(text, label) {
179
188
  const match = text.match(new RegExp(`^\\s*${label}:\\s*(.+)$`, "m"));
180
189
  return match?.[1]?.trim();
181
190
  }
182
- function tokenFingerprint(token) {
183
- if (!token)
184
- return "absent";
185
- const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
186
- return `present(len=${token.length}, sha256=${sha})`;
187
- }
188
- function debugTokenClaims(label, token) {
189
- if (!DEBUG)
190
- return;
191
- if (!token) {
192
- debug(`${label}: <absent>`);
193
- return;
194
- }
195
- try {
196
- const payload = token.split(".")[1];
197
- if (!payload) {
198
- debug(`${label}: <not a JWT>`);
199
- return;
200
- }
201
- const claims = JSON.parse(
202
- Buffer.from(payload, "base64url").toString("utf8")
203
- );
204
- const safe = {
205
- iss: claims.iss,
206
- aud: claims.aud,
207
- sub: claims.sub,
208
- scope: claims.scope,
209
- owner: claims.owner,
210
- owner_id: claims.owner_id,
211
- project: claims.project,
212
- project_id: claims.project_id,
213
- exp: typeof claims.exp === "number" ? `${new Date(claims.exp * 1e3).toISOString()} (in ${Math.round(
214
- (claims.exp * 1e3 - Date.now()) / 1e3
215
- )}s)` : claims.exp
216
- };
217
- debug(`${label}: ${JSON.stringify(safe)}`);
218
- } catch (err) {
219
- debug(`${label}: <unparseable claims> (${err.message})`);
220
- }
221
- }
222
- function decodeOidcClaims(token) {
223
- if (!token)
224
- return {};
225
- try {
226
- const payload = token.split(".")[1];
227
- if (!payload)
228
- return {};
229
- const json = Buffer.from(payload, "base64url").toString("utf8");
230
- return JSON.parse(json);
231
- } catch {
232
- return {};
233
- }
234
- }
235
191
  function isBuildContainer() {
236
192
  return Boolean(readString(process.env.VERCEL_BUILD_IMAGE));
237
193
  }
@@ -401,6 +357,7 @@ Continuing (set VERCEL_VCR_STRICT_STORAGE=1 to fail builds).`
401
357
  }
402
358
 
403
359
  // src/oidc.ts
360
+ var import_node_crypto = require("crypto");
404
361
  function parseOidcToken(token) {
405
362
  const parts = token.split(".");
406
363
  if (parts.length !== 3) {
@@ -415,6 +372,59 @@ function parseOidcToken(token) {
415
372
  throw new Error("VERCEL_OIDC_TOKEN has an unreadable JWT payload.");
416
373
  }
417
374
  }
375
+ function decodeOidcClaims(token) {
376
+ if (!token)
377
+ return {};
378
+ try {
379
+ const payload = token.split(".")[1];
380
+ if (!payload)
381
+ return {};
382
+ const json = Buffer.from(payload, "base64url").toString("utf8");
383
+ return JSON.parse(json);
384
+ } catch {
385
+ return {};
386
+ }
387
+ }
388
+ function tokenFingerprint(token) {
389
+ if (!token)
390
+ return "absent";
391
+ const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
392
+ return `present(len=${token.length}, sha256=${sha})`;
393
+ }
394
+ function debugTokenClaims(label, token) {
395
+ if (!DEBUG)
396
+ return;
397
+ if (!token) {
398
+ debug(`${label}: <absent>`);
399
+ return;
400
+ }
401
+ try {
402
+ const payload = token.split(".")[1];
403
+ if (!payload) {
404
+ debug(`${label}: <not a JWT>`);
405
+ return;
406
+ }
407
+ const claims = JSON.parse(
408
+ Buffer.from(payload, "base64url").toString("utf8")
409
+ );
410
+ const safe = {
411
+ iss: claims.iss,
412
+ aud: claims.aud,
413
+ sub: claims.sub,
414
+ scope: claims.scope,
415
+ owner: claims.owner,
416
+ owner_id: claims.owner_id,
417
+ project: claims.project,
418
+ project_id: claims.project_id,
419
+ exp: typeof claims.exp === "number" ? `${new Date(claims.exp * 1e3).toISOString()} (in ${Math.round(
420
+ (claims.exp * 1e3 - Date.now()) / 1e3
421
+ )}s)` : claims.exp
422
+ };
423
+ debug(`${label}: ${JSON.stringify(safe)}`);
424
+ } catch (err) {
425
+ debug(`${label}: <unparseable claims> (${err.message})`);
426
+ }
427
+ }
418
428
  function resolveProjectContext(token) {
419
429
  const claims = parseOidcToken(token);
420
430
  return {
@@ -507,6 +517,7 @@ function formatVcrAuthError(registry, username, detail) {
507
517
  // src/engines/types.ts
508
518
  var VCR_REGISTRY = process.env.VERCEL_VCR_REGISTRY || "vcr.vercel.com";
509
519
  var TARGET_PLATFORM = "linux/amd64";
520
+ var DIGEST_RE = /sha256:[a-f0-9]{64}/;
510
521
  function buildArgFlags(params) {
511
522
  const flags = [];
512
523
  for (const [key, value] of Object.entries(params.buildArgs ?? {})) {
@@ -808,7 +819,7 @@ async function startDockerDaemon(span) {
808
819
  ].join("\n")
809
820
  );
810
821
  }
811
- await new Promise((resolve) => setTimeout(resolve, 500));
822
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
812
823
  }
813
824
  }
814
825
  async function stopDockerDaemon(daemon, span) {
@@ -818,12 +829,12 @@ async function stopDockerDaemon(daemon, span) {
818
829
  }
819
830
  step("Stopping Docker daemon");
820
831
  const stopTimeoutMs = Number(process.env.VERCEL_VCR_DOCKERD_STOP_TIMEOUT_MS) || 1e4;
821
- await new Promise((resolve) => {
832
+ await new Promise((resolve2) => {
822
833
  let settled = false;
823
834
  const finish = () => {
824
835
  if (!settled) {
825
836
  settled = true;
826
- resolve();
837
+ resolve2();
827
838
  }
828
839
  };
829
840
  child.once("exit", finish);
@@ -1026,7 +1037,39 @@ function selectContainerEngine() {
1026
1037
  return isBuildContainer() ? buildahEngine : dockerEngine;
1027
1038
  }
1028
1039
 
1040
+ // src/buildpacks/build.ts
1041
+ var import_node_fs10 = require("fs");
1042
+ var import_node_os7 = require("os");
1043
+ var import_node_path10 = require("path");
1044
+
1029
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
+ }
1030
1073
  async function ensureRepository(repository, token, claims, span) {
1031
1074
  if (repository.includes("/")) {
1032
1075
  debug(`skipping repository auto-create (fully-qualified "${repository}")`);
@@ -1077,95 +1120,1156 @@ async function ensureRepository(repository, token, claims, span) {
1077
1120
  }
1078
1121
  }
1079
1122
 
1080
- // src/dev.ts
1081
- var import_node_child_process3 = require("child_process");
1123
+ // src/buildpacks/lifecycle/lifecycle.ts
1124
+ var import_build_utils3 = require("@vercel/build-utils");
1125
+ var import_node_crypto2 = require("crypto");
1082
1126
  var import_node_fs4 = require("fs");
1083
1127
  var import_node_os4 = require("os");
1084
- var import_node_path4 = __toESM(require("path"));
1085
- var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
1086
- "TMPDIR",
1087
- "TMP",
1088
- "TEMP",
1089
- "HOME",
1090
- "PATH",
1091
- "PWD",
1092
- "OLDPWD",
1093
- "SHELL",
1094
- "SHLVL",
1095
- "USER",
1096
- "LOGNAME",
1097
- "TERM",
1098
- "TERM_PROGRAM",
1099
- "TERM_PROGRAM_VERSION",
1100
- "TERM_SESSION_ID",
1101
- "COLORTERM",
1102
- "LANG",
1103
- "LC_ALL",
1104
- "LC_CTYPE",
1105
- "COMMAND_MODE",
1106
- "SECURITYSESSIONID",
1107
- "__CF_USER_TEXT_ENCODING",
1108
- "__CFBundleIdentifier"
1109
- ]);
1110
- function isHostOnlyEnvVar(key) {
1111
- return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
1128
+ var import_node_path4 = require("path");
1129
+ var import_smol_toml = require("smol-toml");
1130
+ var import_tar = require("tar");
1131
+
1132
+ // src/buildpacks/distribution.json
1133
+ var distribution_default = {
1134
+ $comment: "Pinned lifecycle and buildpack archives",
1135
+ baseUrl: "https://lwnhvkbwb2b5igkl.public.blob.vercel-storage.com",
1136
+ lifecycle: {
1137
+ version: "0.21.18",
1138
+ sha256: "45e4ac394fe8194b909002571c25479dcb39f2ecabecad4105621369c279c78e"
1139
+ },
1140
+ buildpacks: {
1141
+ ruby: {
1142
+ id: "vercel/ruby",
1143
+ version: "0.1.0",
1144
+ sha256: "e204724b690ee3f64d28455d06efd3c7fce9189057ba4d3f702e27c24b8bdee5"
1145
+ }
1146
+ }
1147
+ };
1148
+
1149
+ // src/buildpacks/distribution.ts
1150
+ var distribution = distribution_default;
1151
+ var BUILDPACK_DIST_BASE_URL = distribution.baseUrl;
1152
+ var LIFECYCLE_VERSION = distribution.lifecycle.version;
1153
+ var LIFECYCLE = {
1154
+ url: `${BUILDPACK_DIST_BASE_URL}/lifecycle/${LIFECYCLE_VERSION}/lifecycle-v${LIFECYCLE_VERSION}-linux-x86-64.tgz`,
1155
+ sha256: distribution.lifecycle.sha256
1156
+ };
1157
+ function vercelBuildpack(name) {
1158
+ const pinned = distribution.buildpacks[name];
1159
+ if (!pinned) {
1160
+ throw new Error(
1161
+ `distribution.json does not pin a "${name}" buildpack; add it under "buildpacks".`
1162
+ );
1163
+ }
1164
+ return { name, ...pinned };
1112
1165
  }
1113
- function writeEnvFile(env) {
1114
- const dir = (0, import_node_fs4.mkdtempSync)(import_node_path4.default.join((0, import_node_os4.tmpdir)(), "vercel-container-dev-env-"));
1115
- const file = import_node_path4.default.join(dir, "env");
1116
- const lines = [];
1117
- for (const [key, value] of Object.entries(env)) {
1118
- if (value.includes("\n")) {
1166
+ function vercelBuildpackArtifact(buildpack) {
1167
+ const { name, version: version2 } = buildpack;
1168
+ return {
1169
+ url: `${BUILDPACK_DIST_BASE_URL}/buildpacks/${name}/${version2}/vercel-${name}-${version2}.cnb`,
1170
+ sha256: buildpack.sha256
1171
+ };
1172
+ }
1173
+
1174
+ // src/buildpacks/lifecycle/lifecycle.ts
1175
+ var BUILD_USER_ID = 1001;
1176
+ var BUILD_GROUP_ID = 1001;
1177
+ var BUILD_USER = `${BUILD_USER_ID}:${BUILD_GROUP_ID}`;
1178
+ var CNB_PLATFORM_API = "0.13";
1179
+ var ORDER_MOUNT_DIR = "/platform/order";
1180
+ var ORDER_FILE = `${ORDER_MOUNT_DIR}/order.toml`;
1181
+ var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1182
+ async function stageWorkspace({ files, workPath }, destinationDir) {
1183
+ const staged = /* @__PURE__ */ Object.create(null);
1184
+ const symlinks = /* @__PURE__ */ new Set();
1185
+ for (const [name, file] of Object.entries(files)) {
1186
+ const portable = name.replace(/\\/g, "/");
1187
+ const normalized = import_node_path4.posix.normalize(portable).replace(/\/$/, "");
1188
+ if (!normalized || normalized === "." || import_node_path4.win32.isAbsolute(name) || import_node_path4.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1189
+ throw new Error(`Invalid buildpack source path ${JSON.stringify(name)}.`);
1190
+ }
1191
+ staged[normalized] = file;
1192
+ if (!(0, import_build_utils3.isSymbolicLink)(file.mode))
1119
1193
  continue;
1194
+ let target = (0, import_build_utils3.getSymlinkTarget)(file);
1195
+ if (target === null && file.type === "FileRef") {
1196
+ target = (await (0, import_build_utils3.streamToBuffer)(await file.toStreamAsync())).toString();
1197
+ }
1198
+ if (!target) {
1199
+ throw new Error(`Cannot read buildpack source symlink ${name}.`);
1200
+ }
1201
+ if ((0, import_node_path4.isAbsolute)(target)) {
1202
+ target = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath, import_node_path4.posix.dirname(normalized)), target) || ".";
1203
+ } else if (import_node_path4.win32.isAbsolute(target)) {
1204
+ throw new Error(
1205
+ `Buildpack source symlink ${name} has an absolute target.`
1206
+ );
1207
+ }
1208
+ target = target.replace(/\\/g, "/");
1209
+ const resolved = import_node_path4.posix.join(import_node_path4.posix.dirname(normalized), target);
1210
+ if (resolved === ".." || resolved.startsWith("../")) {
1211
+ throw new Error(
1212
+ `Buildpack source symlink ${name} escapes the service root. Ruby buildpack services must be self-contained.`
1213
+ );
1214
+ }
1215
+ staged[normalized] = new import_build_utils3.FileBlob({ data: target, mode: file.mode });
1216
+ symlinks.add(normalized);
1217
+ }
1218
+ for (const name of Object.keys(staged)) {
1219
+ for (let parent = import_node_path4.posix.dirname(name); parent !== "."; parent = import_node_path4.posix.dirname(parent)) {
1220
+ if (symlinks.has(parent)) {
1221
+ throw new Error(
1222
+ `Buildpack source ${name} is inside symlink ${parent}.`
1223
+ );
1224
+ }
1225
+ }
1226
+ const file = staged[name];
1227
+ if (file.type === "FileFsRef" && !(0, import_build_utils3.isSymbolicLink)(file.mode)) {
1228
+ const sourcePath = (0, import_node_path4.resolve)(file.fsPath);
1229
+ const sourceRelative = (0, import_node_path4.relative)((0, import_node_path4.resolve)(workPath), sourcePath);
1230
+ if (sourceRelative.startsWith(`..${import_node_path4.sep}`) || sourceRelative === ".." || (0, import_node_path4.isAbsolute)(sourceRelative)) {
1231
+ continue;
1232
+ }
1233
+ const sourceRoot = import_node_fs4.realpathSync.native(workPath);
1234
+ const sourceTarget = import_node_fs4.realpathSync.native(sourcePath);
1235
+ if (sourceTarget !== (0, import_node_path4.resolve)(sourceRoot, sourceRelative)) {
1236
+ const target = (0, import_node_path4.relative)(sourceRoot, sourceTarget).split(import_node_path4.sep).join("/");
1237
+ if (target === ".." || target.startsWith("../") || (0, import_node_path4.isAbsolute)(target) || !(target in staged)) {
1238
+ throw new Error(
1239
+ `Buildpack source ${name} follows a symlink to an excluded or outside-root file. Ruby buildpack services must be self-contained.`
1240
+ );
1241
+ }
1242
+ }
1243
+ }
1244
+ }
1245
+ await (0, import_build_utils3.download)(staged, destinationDir);
1246
+ const root = import_node_fs4.realpathSync.native(destinationDir);
1247
+ for (const name of symlinks) {
1248
+ let target;
1249
+ try {
1250
+ target = (0, import_node_path4.relative)(root, import_node_fs4.realpathSync.native((0, import_node_path4.join)(destinationDir, name)));
1251
+ } catch {
1252
+ throw new Error(
1253
+ `Buildpack source symlink ${name} has no target in the staged service.`
1254
+ );
1255
+ }
1256
+ if (target === ".." || target.startsWith(`..${import_node_path4.sep}`) || (0, import_node_path4.isAbsolute)(target)) {
1257
+ throw new Error(
1258
+ `Buildpack source symlink ${name} escapes the staged service.`
1259
+ );
1120
1260
  }
1121
- lines.push(`${key}=${value}`);
1122
1261
  }
1123
- (0, import_node_fs4.writeFileSync)(file, `${lines.join("\n")}
1124
- `);
1125
- return file;
1126
1262
  }
1127
- function useContainerHost(endpoint) {
1128
- try {
1129
- const url = new URL(endpoint);
1130
- if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]") {
1131
- url.hostname = "host.docker.internal";
1132
- return url.toString();
1263
+ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.tmpdir)()) {
1264
+ const environment = { ...buildEnv };
1265
+ delete environment.VERCEL_NODE_HOME;
1266
+ delete environment.VERCEL_COMMAND;
1267
+ if (config.nodeHome) {
1268
+ environment.VERCEL_NODE_HOME = config.nodeHome;
1269
+ }
1270
+ const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-env-"));
1271
+ (0, import_node_fs4.chmodSync)(dir, 493);
1272
+ for (const [key, value] of Object.entries(environment)) {
1273
+ if (!ENV_NAME_RE.test(key)) {
1274
+ debug(`skipping build env var with an unsupported name: ${key}`);
1275
+ continue;
1133
1276
  }
1277
+ const file = (0, import_node_path4.join)(dir, key);
1278
+ (0, import_node_fs4.writeFileSync)(file, value);
1279
+ (0, import_node_fs4.chmodSync)(file, 420);
1280
+ }
1281
+ return dir;
1282
+ }
1283
+ function readReportDigest(report) {
1284
+ let parsed;
1285
+ try {
1286
+ parsed = (0, import_smol_toml.parse)(report);
1134
1287
  } catch {
1288
+ return void 0;
1135
1289
  }
1136
- return endpoint;
1290
+ const digest = parsed?.image?.digest;
1291
+ if (typeof digest !== "string" || !DIGEST_RE.test(digest)) {
1292
+ return void 0;
1293
+ }
1294
+ return digest;
1137
1295
  }
1138
- function emit(out, line) {
1139
- if (out.onStderr) {
1140
- out.onStderr(Buffer.from(`${line}
1141
- `));
1142
- } else {
1143
- process.stderr.write(`${line}
1144
- `);
1296
+ function describeCreatorExitCode(code) {
1297
+ if (code === void 0)
1298
+ return void 0;
1299
+ if (code === 20)
1300
+ return "no buildpack detected the app";
1301
+ if (code === 21) {
1302
+ return "no buildpack detected the app and at least one errored during detection";
1303
+ }
1304
+ if (code >= 22 && code <= 29)
1305
+ return "the detect phase failed";
1306
+ if (code >= 30 && code <= 39)
1307
+ return "the analyze phase failed";
1308
+ if (code >= 40 && code <= 49)
1309
+ return "the restore phase failed";
1310
+ if (code === 51) {
1311
+ return "a buildpack failed while building the app (e.g. installing dependencies) \u2014 its error is in the build output above";
1312
+ }
1313
+ if (code >= 50 && code <= 59)
1314
+ return "the build phase failed";
1315
+ if (code >= 60 && code <= 69) {
1316
+ return "the export phase failed to write the image";
1145
1317
  }
1318
+ return void 0;
1146
1319
  }
1147
- function runForwarded(cmd, args, out, opts = {}) {
1148
- return new Promise((resolve, reject) => {
1149
- const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1150
- let stdout = "";
1151
- let stderr = "";
1152
- child.stdout?.on("data", (chunk) => {
1153
- stdout += chunk.toString();
1154
- if (!opts.quiet) {
1155
- if (out.onStdout) {
1156
- out.onStdout(chunk);
1157
- } else {
1158
- process.stderr.write(chunk.toString());
1159
- }
1320
+ function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1321
+ const order = {
1322
+ order: [
1323
+ {
1324
+ group: buildpack.buildpacks.map((entry) => ({
1325
+ id: entry.id,
1326
+ version: entry.version
1327
+ }))
1160
1328
  }
1161
- });
1162
- child.stderr?.on("data", (chunk) => {
1163
- stderr += chunk.toString();
1164
- if (!opts.quiet) {
1165
- if (out.onStderr) {
1166
- out.onStderr(chunk);
1167
- } else {
1168
- process.stderr.write(chunk.toString());
1329
+ ]
1330
+ };
1331
+ const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)(parentDir, "vercel-cnb-order-"));
1332
+ (0, import_node_fs4.chmodSync)(dir, 493);
1333
+ const file = (0, import_node_path4.join)(dir, "order.toml");
1334
+ (0, import_node_fs4.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
1335
+ (0, import_node_fs4.chmodSync)(file, 420);
1336
+ return dir;
1337
+ }
1338
+ function cnbRegistryAuth(credentials) {
1339
+ const { registry, username, token } = credentials;
1340
+ const basic = Buffer.from(`${username}:${token}`).toString("base64");
1341
+ return JSON.stringify({ [registry]: `Basic ${basic}` });
1342
+ }
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
+ function distributionUrls(buildpack) {
1360
+ return {
1361
+ lifecycle: LIFECYCLE.url,
1362
+ buildpacks: buildpack.buildpacks.map(
1363
+ (entry) => vercelBuildpackArtifact(entry).url
1364
+ )
1365
+ };
1366
+ }
1367
+ async function downloadPinnedArchive(archive, destinationPath) {
1368
+ const response = await fetch(archive.url);
1369
+ if (!response.ok) {
1370
+ throw new Error(
1371
+ `Failed to fetch ${archive.url}: HTTP ${response.status} ${response.statusText}`
1372
+ );
1373
+ }
1374
+ const contents = new Uint8Array(await response.arrayBuffer());
1375
+ const checksum = (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
1376
+ if (checksum !== archive.sha256) {
1377
+ throw new Error(
1378
+ `Checksum mismatch for ${archive.url}: expected ${archive.sha256}, received ${checksum}.`
1379
+ );
1380
+ }
1381
+ (0, import_node_fs4.writeFileSync)(destinationPath, contents);
1382
+ }
1383
+ async function fetchPinnedArchive(archive, destinationDir) {
1384
+ const archivePath = (0, import_node_path4.join)(destinationDir, `${archive.sha256}.tgz`);
1385
+ try {
1386
+ await downloadPinnedArchive(archive, archivePath);
1387
+ (0, import_tar.extract)({ file: archivePath, cwd: destinationDir, sync: true });
1388
+ } finally {
1389
+ (0, import_node_fs4.rmSync)(archivePath, { force: true });
1390
+ }
1391
+ }
1392
+ var LAYER_MEDIA_TYPE_RE = /(\.tar\.gzip|\.tar\+gzip|\.tar)$/;
1393
+ function readOciJson(path4) {
1394
+ return JSON.parse((0, import_node_fs4.readFileSync)(path4, "utf8"));
1395
+ }
1396
+ function blobPath(layoutDir, digest) {
1397
+ const match = digest.match(/^sha256:([0-9a-f]{64})$/);
1398
+ if (!match) {
1399
+ throw new Error(`Unsupported OCI digest ${JSON.stringify(digest)}.`);
1400
+ }
1401
+ return (0, import_node_path4.join)(layoutDir, "blobs", "sha256", match[1]);
1402
+ }
1403
+ async function fetchBuildpackage(archive, cnbDir) {
1404
+ const layoutDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "vercel-cnb-buildpackage-"));
1405
+ const archivePath = (0, import_node_path4.join)(layoutDir, "buildpackage.cnb");
1406
+ try {
1407
+ await downloadPinnedArchive(archive, archivePath);
1408
+ await (0, import_tar.extract)({ file: archivePath, cwd: layoutDir });
1409
+ const index = readOciJson(
1410
+ (0, import_node_path4.join)(layoutDir, "index.json")
1411
+ );
1412
+ const manifestDigest = index.manifests?.[0]?.digest;
1413
+ if (!manifestDigest) {
1414
+ throw new Error(`${archive.url} has no image manifest in index.json.`);
1415
+ }
1416
+ const manifest = readOciJson(
1417
+ blobPath(layoutDir, manifestDigest)
1418
+ );
1419
+ if (!manifest.layers?.length) {
1420
+ throw new Error(`${archive.url} has an image manifest with no layers.`);
1421
+ }
1422
+ for (const layer of manifest.layers) {
1423
+ if (!layer.digest || !LAYER_MEDIA_TYPE_RE.test(layer.mediaType ?? "")) {
1424
+ throw new Error(
1425
+ `${archive.url} has an unsupported layer media type ${JSON.stringify(
1426
+ layer.mediaType
1427
+ )}.`
1428
+ );
1429
+ }
1430
+ const layerPath = blobPath(layoutDir, layer.digest);
1431
+ const actual = (0, import_node_crypto2.createHash)("sha256").update(new Uint8Array((0, import_node_fs4.readFileSync)(layerPath))).digest("hex");
1432
+ if (`sha256:${actual}` !== layer.digest) {
1433
+ throw new Error(
1434
+ `${archive.url} layer ${layer.digest} hashes to sha256:${actual}.`
1435
+ );
1436
+ }
1437
+ await (0, import_tar.extract)({
1438
+ file: layerPath,
1439
+ cwd: cnbDir,
1440
+ strict: true,
1441
+ filter: (_path, entry) => {
1442
+ if (!("path" in entry))
1443
+ return false;
1444
+ const match = entry.path.match(/^\/?cnb\/(buildpacks\/.*)$/);
1445
+ if (!match || match[1].split("/").includes("..")) {
1446
+ return false;
1447
+ }
1448
+ entry.path = match[1];
1449
+ if (entry.type === "Link" && entry.linkpath) {
1450
+ entry.linkpath = entry.linkpath.replace(/^\/?cnb\//, "");
1451
+ }
1452
+ return true;
1453
+ }
1454
+ });
1455
+ }
1456
+ } finally {
1457
+ (0, import_node_fs4.rmSync)(layoutDir, { recursive: true, force: true });
1458
+ }
1459
+ }
1460
+ async function fetchDistribution(buildpack, cnbDir) {
1461
+ await Promise.all([
1462
+ fetchPinnedArchive(LIFECYCLE, cnbDir),
1463
+ ...buildpack.buildpacks.map(
1464
+ (entry) => fetchBuildpackage(vercelBuildpackArtifact(entry), cnbDir)
1465
+ )
1466
+ ]);
1467
+ for (const entry of buildpack.buildpacks) {
1468
+ const directory = (0, import_node_path4.join)(
1469
+ cnbDir,
1470
+ "buildpacks",
1471
+ entry.id.replace("/", "_"),
1472
+ entry.version
1473
+ );
1474
+ for (const file of ["buildpack.toml", "bin/detect", "bin/build"]) {
1475
+ if (!(0, import_node_fs4.statSync)((0, import_node_path4.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
1476
+ throw new Error(
1477
+ `Buildpack ${entry.id}@${entry.version} is missing ${file}.`
1478
+ );
1479
+ }
1480
+ }
1481
+ }
1482
+ }
1483
+ function creatorArgs(image, imageRef) {
1484
+ return [
1485
+ "-app=/workspace",
1486
+ `-order=${ORDER_FILE}`,
1487
+ "-skip-restore",
1488
+ `-run-image=${image.runImage}`,
1489
+ "-report=/platform-output/report.toml",
1490
+ imageRef
1491
+ ];
1492
+ }
1493
+
1494
+ // src/buildpacks/lifecycle/buildah.ts
1495
+ var import_node_fs5 = require("fs");
1496
+ var import_node_crypto3 = require("crypto");
1497
+ var import_node_os5 = require("os");
1498
+ var import_node_path5 = require("path");
1499
+ async function runBuildah2(args, env) {
1500
+ const storageArgs = await buildahStorageArgs();
1501
+ return run("buildah", [...storageArgs, ...args], { env });
1502
+ }
1503
+ async function removeContainer(name) {
1504
+ try {
1505
+ await runBuildah2(["rm", name]);
1506
+ } catch (error) {
1507
+ debug(
1508
+ `could not remove Buildah CNB container ${name}: ${error.message}`
1509
+ );
1510
+ }
1511
+ }
1512
+ var buildAndPushWithLifecycle = async (buildpack, params, span) => {
1513
+ const urls = distributionUrls(buildpack);
1514
+ return withSpan(
1515
+ span,
1516
+ "container.buildpack.lifecycle_registry_build",
1517
+ {
1518
+ "buildpack.runtime": buildpack.runtime,
1519
+ "buildpack.runtime_version": params.image.version,
1520
+ "buildpack.build_image": params.image.buildImage,
1521
+ "buildpack.run_image": params.image.runImage,
1522
+ "buildpack.lifecycle_url": urls.lifecycle,
1523
+ "buildpack.archive_url": urls.buildpacks.join(","),
1524
+ "buildpack.node_version": params.nodeToolchain?.version,
1525
+ "image.ref": params.imageRef
1526
+ },
1527
+ async (lifecycleSpan) => {
1528
+ assertPublishedDistribution(buildpack);
1529
+ const suffix = `${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
1530
+ const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
1531
+ let cnbDir;
1532
+ let reportDir;
1533
+ let orderDir;
1534
+ let platformEnvDir;
1535
+ try {
1536
+ reportDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-report-"));
1537
+ const reportPath = (0, import_node_path5.join)(reportDir, "report.toml");
1538
+ (0, import_node_fs5.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
1539
+ (0, import_node_fs5.chmodSync)(reportDir, 493);
1540
+ cnbDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-"));
1541
+ (0, import_node_fs5.chmodSync)(cnbDir, 493);
1542
+ orderDir = writeOrderDir(buildpack);
1543
+ platformEnvDir = writePlatformEnvDir(params.buildEnv, {
1544
+ nodeHome: params.nodeToolchain?.home
1545
+ });
1546
+ step("Fetching the CNB lifecycle and buildpacks...");
1547
+ await fetchDistribution(buildpack, cnbDir);
1548
+ done("fetched the CNB lifecycle and buildpacks");
1549
+ step(
1550
+ `Preparing ${buildpack.runtime} ${params.image.version} stock image and Vercel buildpacks`
1551
+ );
1552
+ await runBuildah2([
1553
+ "from",
1554
+ "--pull=always",
1555
+ "--platform",
1556
+ TARGET_PLATFORM,
1557
+ "--name",
1558
+ buildContainer,
1559
+ params.image.buildImage
1560
+ ]);
1561
+ await runBuildah2([
1562
+ "run",
1563
+ "--network",
1564
+ "host",
1565
+ buildContainer,
1566
+ "--",
1567
+ "sh",
1568
+ "-c",
1569
+ `groupadd -g ${BUILD_GROUP_ID} cnb && useradd -m -u ${BUILD_USER_ID} -g ${BUILD_GROUP_ID} cnb && mkdir -p /layers /platform /workspace && chown -R ${BUILD_USER} /layers /platform /workspace /home/cnb`
1570
+ ]);
1571
+ await runBuildah2([
1572
+ "copy",
1573
+ "--chown",
1574
+ BUILD_USER,
1575
+ buildContainer,
1576
+ params.workPath,
1577
+ "/workspace"
1578
+ ]);
1579
+ const platformEnvMount = [
1580
+ "--volume",
1581
+ `${platformEnvDir}:/platform/env:ro`
1582
+ ];
1583
+ const nodeToolchainMount = params.nodeToolchain ? [
1584
+ "--volume",
1585
+ `${params.nodeToolchain.home}:${params.nodeToolchain.home}:ro`
1586
+ ] : [];
1587
+ const lifecycleEnv = {
1588
+ ...process.env,
1589
+ CNB_REGISTRY_AUTH: cnbRegistryAuth(params.credentials)
1590
+ };
1591
+ step(
1592
+ `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
1593
+ );
1594
+ await runBuildah2(
1595
+ [
1596
+ "run",
1597
+ "--network",
1598
+ "host",
1599
+ "--user",
1600
+ BUILD_USER,
1601
+ "--env",
1602
+ "HOME=/home/cnb",
1603
+ "--env",
1604
+ `CNB_USER_ID=${BUILD_USER_ID}`,
1605
+ "--env",
1606
+ `CNB_GROUP_ID=${BUILD_GROUP_ID}`,
1607
+ "--env",
1608
+ `CNB_PLATFORM_API=${CNB_PLATFORM_API}`,
1609
+ "--env",
1610
+ "CNB_REGISTRY_AUTH",
1611
+ "--volume",
1612
+ `${cnbDir}:/cnb:ro`,
1613
+ "--volume",
1614
+ `${reportDir}:/platform-output`,
1615
+ "--volume",
1616
+ `${orderDir}:${ORDER_MOUNT_DIR}:ro`,
1617
+ ...nodeToolchainMount,
1618
+ ...platformEnvMount,
1619
+ buildContainer,
1620
+ "--",
1621
+ "/cnb/lifecycle/creator",
1622
+ ...creatorArgs(params.image, params.imageRef)
1623
+ ],
1624
+ lifecycleEnv
1625
+ );
1626
+ const digest = readReportDigest((0, import_node_fs5.readFileSync)(reportPath, "utf8"));
1627
+ if (!digest) {
1628
+ throw new Error(
1629
+ `${buildpack.runtime} buildpack lifecycle did not report a digest.`
1630
+ );
1631
+ }
1632
+ done(`built and published ${params.imageRef}@${digest}`);
1633
+ lifecycleSpan?.setAttributes({ "image.digest": digest });
1634
+ return {
1635
+ imageRef: params.imageRef,
1636
+ digest,
1637
+ buildImage: params.image.buildImage,
1638
+ runImage: params.image.runImage,
1639
+ runtimeVersion: params.image.version
1640
+ };
1641
+ } catch (error) {
1642
+ const exitCode = error.exitCode;
1643
+ const hint = describeCreatorExitCode(exitCode);
1644
+ lifecycleSpan?.setAttributes({
1645
+ ...exitCode !== void 0 ? { "buildpack.lifecycle.exit_code": String(exitCode) } : {},
1646
+ ...hint ? { "buildpack.lifecycle.failure_phase": hint } : {}
1647
+ });
1648
+ throw new Error(
1649
+ [
1650
+ `${buildpack.runtime} buildpack build failed via lifecycle/creator (${params.image.buildImage}).`,
1651
+ `Lifecycle archive: ${urls.lifecycle}`,
1652
+ ...urls.buildpacks.map((url) => `Buildpack archive: ${url}`),
1653
+ ...hint ? [`The lifecycle exited with code ${exitCode}: ${hint}.`] : [],
1654
+ "",
1655
+ `Underlying error: ${error.message}`,
1656
+ "",
1657
+ `Buildpack project is ${params.workPath}`
1658
+ ].join("\n")
1659
+ );
1660
+ } finally {
1661
+ await removeContainer(buildContainer);
1662
+ for (const dir of [cnbDir, reportDir, orderDir, platformEnvDir]) {
1663
+ if (dir) {
1664
+ try {
1665
+ (0, import_node_fs5.rmSync)(dir, { recursive: true, force: true });
1666
+ } catch {
1667
+ debug(`Could not remove Buildah build scratch directory ${dir}.`);
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+ }
1673
+ );
1674
+ };
1675
+
1676
+ // src/buildpacks/lifecycle/docker.ts
1677
+ var import_node_crypto4 = require("crypto");
1678
+ var import_node_fs6 = require("fs");
1679
+ var import_node_os6 = require("os");
1680
+ var import_node_path6 = require("path");
1681
+ function localBuildImageDockerfile(buildImage, nodeMajor) {
1682
+ const lines = [];
1683
+ if (nodeMajor !== void 0) {
1684
+ lines.push(
1685
+ `FROM docker.io/library/node:${nodeMajor}-bookworm-slim AS node`
1686
+ );
1687
+ }
1688
+ lines.push(`FROM ${buildImage}`);
1689
+ if (nodeMajor !== void 0) {
1690
+ lines.push(`COPY --from=node /usr/local /node${nodeMajor}`);
1691
+ }
1692
+ lines.push(
1693
+ `RUN groupadd --gid ${BUILD_GROUP_ID} cnb && useradd --create-home --uid ${BUILD_USER_ID} --gid ${BUILD_GROUP_ID} cnb && mkdir -p /layers /platform /workspace && chown -R ${BUILD_USER} /layers /workspace /home/cnb`
1694
+ );
1695
+ return `${lines.join("\n")}
1696
+ `;
1697
+ }
1698
+ function localBuildImageTag(dockerfile) {
1699
+ const hash = (0, import_node_crypto4.createHash)("sha256").update(dockerfile).digest("hex");
1700
+ return `vercel-cnb-build:${hash.slice(0, 16)}`;
1701
+ }
1702
+ async function runDocker2(args, opts = {}) {
1703
+ debug(`exec: docker ${args.join(" ")}`);
1704
+ return run("docker", args, opts);
1705
+ }
1706
+ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
1707
+ const urls = distributionUrls(buildpack);
1708
+ return withSpan(
1709
+ span,
1710
+ "container.buildpack.lifecycle_docker_build",
1711
+ {
1712
+ "buildpack.runtime": buildpack.runtime,
1713
+ "buildpack.runtime_version": params.image.version,
1714
+ "buildpack.build_image": params.image.buildImage,
1715
+ "buildpack.run_image": params.image.runImage,
1716
+ "buildpack.lifecycle_url": urls.lifecycle,
1717
+ "buildpack.archive_url": urls.buildpacks.join(","),
1718
+ "buildpack.node_version": params.nodeToolchain?.version,
1719
+ "image.ref": params.imageRef
1720
+ },
1721
+ async (lifecycleSpan) => {
1722
+ assertPublishedDistribution(buildpack);
1723
+ let stageDir;
1724
+ const containers = [];
1725
+ try {
1726
+ stageDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-"));
1727
+ const cnbDir = (0, import_node_path6.join)(stageDir, "cnb");
1728
+ (0, import_node_fs6.mkdirSync)(cnbDir, { mode: 493 });
1729
+ const orderDir = writeOrderDir(buildpack, stageDir);
1730
+ const platformEnvDir = writePlatformEnvDir(
1731
+ params.buildEnv,
1732
+ { nodeHome: params.nodeToolchain?.home },
1733
+ stageDir
1734
+ );
1735
+ step("Fetching the CNB lifecycle and buildpacks...");
1736
+ await fetchDistribution(buildpack, cnbDir);
1737
+ done("fetched the CNB lifecycle and buildpacks");
1738
+ const dockerfile = localBuildImageDockerfile(
1739
+ params.image.buildImage,
1740
+ params.nodeToolchain?.major
1741
+ );
1742
+ const buildImageTag = localBuildImageTag(dockerfile);
1743
+ const buildImageIdPath = (0, import_node_path6.join)(stageDir, "build-image.id");
1744
+ step(
1745
+ `Preparing ${buildpack.runtime} ${params.image.version} build image` + (params.nodeToolchain ? ` with Node.js ${params.nodeToolchain.major}` : "")
1746
+ );
1747
+ await runDocker2(
1748
+ [
1749
+ "build",
1750
+ "--pull",
1751
+ "--platform",
1752
+ TARGET_PLATFORM,
1753
+ "--tag",
1754
+ buildImageTag,
1755
+ "--iidfile",
1756
+ buildImageIdPath,
1757
+ "-"
1758
+ ],
1759
+ { input: dockerfile }
1760
+ );
1761
+ const buildImageId = (0, import_node_fs6.readFileSync)(buildImageIdPath, "utf8").trim();
1762
+ if (!/^sha256:[a-f0-9]{64}$/.test(buildImageId)) {
1763
+ throw new Error("Docker build did not report a valid image ID.");
1764
+ }
1765
+ const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto4.randomBytes)(8).toString("hex")}`;
1766
+ const initContainerName = `${containerName}-init`;
1767
+ containers.push(initContainerName);
1768
+ await runDocker2([
1769
+ "create",
1770
+ "--name",
1771
+ initContainerName,
1772
+ "--platform",
1773
+ TARGET_PLATFORM,
1774
+ "--user",
1775
+ "0:0",
1776
+ "--network",
1777
+ "none",
1778
+ "--volume",
1779
+ "/workspace",
1780
+ "--volume",
1781
+ "/layers",
1782
+ "--volume",
1783
+ "/platform-output",
1784
+ "--volume",
1785
+ "/cnb",
1786
+ "--volume",
1787
+ "/platform",
1788
+ "--entrypoint",
1789
+ "/bin/sh",
1790
+ buildImageId,
1791
+ "-c",
1792
+ `chown -R -h ${BUILD_USER} /workspace /layers /platform-output && chown -R -h 0:0 /cnb /platform`
1793
+ ]);
1794
+ step("Copying the project into the build workspace");
1795
+ await runDocker2([
1796
+ "cp",
1797
+ `${params.workPath}/.`,
1798
+ `${initContainerName}:/workspace`
1799
+ ]);
1800
+ for (const [source, destination] of [
1801
+ [`${cnbDir}/.`, "/cnb"],
1802
+ [orderDir, ORDER_MOUNT_DIR],
1803
+ [platformEnvDir, "/platform/env"]
1804
+ ]) {
1805
+ await runDocker2([
1806
+ "cp",
1807
+ source,
1808
+ `${initContainerName}:${destination}`
1809
+ ]);
1810
+ }
1811
+ await runDocker2(["start", "--attach", initContainerName]);
1812
+ const lifecycleEnv = {
1813
+ ...process.env,
1814
+ CNB_REGISTRY_AUTH: cnbRegistryAuth(params.credentials)
1815
+ };
1816
+ containers.push(containerName);
1817
+ await runDocker2(
1818
+ [
1819
+ "create",
1820
+ "--name",
1821
+ containerName,
1822
+ "--platform",
1823
+ TARGET_PLATFORM,
1824
+ "--user",
1825
+ BUILD_USER,
1826
+ "--env",
1827
+ "HOME=/home/cnb",
1828
+ "--env",
1829
+ `CNB_USER_ID=${BUILD_USER_ID}`,
1830
+ "--env",
1831
+ `CNB_GROUP_ID=${BUILD_GROUP_ID}`,
1832
+ "--env",
1833
+ `CNB_PLATFORM_API=${CNB_PLATFORM_API}`,
1834
+ "--env",
1835
+ "CNB_REGISTRY_AUTH",
1836
+ "--volumes-from",
1837
+ initContainerName,
1838
+ "--entrypoint",
1839
+ "/cnb/lifecycle/creator",
1840
+ buildImageId,
1841
+ ...creatorArgs(params.image, params.imageRef)
1842
+ ],
1843
+ { env: lifecycleEnv }
1844
+ );
1845
+ step(
1846
+ `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
1847
+ );
1848
+ await runDocker2(["start", "--attach", containerName]);
1849
+ const reportPath = (0, import_node_path6.join)(stageDir, "report.toml");
1850
+ await runDocker2([
1851
+ "cp",
1852
+ `${containerName}:/platform-output/report.toml`,
1853
+ reportPath
1854
+ ]);
1855
+ const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
1856
+ if (!digest) {
1857
+ throw new Error(
1858
+ `${buildpack.runtime} buildpack lifecycle did not report a digest.`
1859
+ );
1860
+ }
1861
+ done(`built and published ${params.imageRef}@${digest}`);
1862
+ lifecycleSpan?.setAttributes({ "image.digest": digest });
1863
+ return {
1864
+ imageRef: params.imageRef,
1865
+ digest,
1866
+ buildImage: params.image.buildImage,
1867
+ runImage: params.image.runImage,
1868
+ runtimeVersion: params.image.version
1869
+ };
1870
+ } catch (error) {
1871
+ const exitCode = error.exitCode;
1872
+ const hint = describeCreatorExitCode(exitCode);
1873
+ lifecycleSpan?.setAttributes({
1874
+ ...exitCode !== void 0 ? { "buildpack.lifecycle.exit_code": String(exitCode) } : {},
1875
+ ...hint ? { "buildpack.lifecycle.failure_phase": hint } : {}
1876
+ });
1877
+ throw new Error(
1878
+ [
1879
+ `${buildpack.runtime} buildpack build failed via docker (${params.image.buildImage}).`,
1880
+ ...hint ? [`The lifecycle exited with code ${exitCode}: ${hint}.`] : [],
1881
+ "",
1882
+ `Underlying error: ${error.message}`,
1883
+ "",
1884
+ `Buildpack project is ${params.workPath}`
1885
+ ].join("\n")
1886
+ );
1887
+ } finally {
1888
+ for (const container of containers.reverse()) {
1889
+ try {
1890
+ await runDocker2(["rm", "--force", "--volumes", container], {
1891
+ quiet: true
1892
+ });
1893
+ } catch {
1894
+ debug(`Could not remove Docker build container ${container}.`);
1895
+ }
1896
+ }
1897
+ if (stageDir) {
1898
+ try {
1899
+ (0, import_node_fs6.rmSync)(stageDir, { recursive: true, force: true });
1900
+ } catch {
1901
+ debug(
1902
+ `Could not remove Docker build scratch directory ${stageDir}.`
1903
+ );
1904
+ }
1905
+ }
1906
+ }
1907
+ }
1908
+ );
1909
+ };
1910
+
1911
+ // src/buildpacks/node-toolchain.ts
1912
+ var import_build_utils4 = require("@vercel/build-utils");
1913
+ var import_node_fs7 = require("fs");
1914
+ var import_node_path7 = require("path");
1915
+ async function selectNodeMajor(workPath, config, meta) {
1916
+ if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(workPath, "package.json"))) {
1917
+ return void 0;
1918
+ }
1919
+ const version2 = await (0, import_build_utils4.getNodeVersion)(workPath, void 0, config, meta);
1920
+ if ((0, import_build_utils4.isBunVersion)(version2)) {
1921
+ throw new Error(
1922
+ "Buildpack asset builds require Node.js. Bun is not supported yet; configure engines.node or use Dockerfile.vercel."
1923
+ );
1924
+ }
1925
+ return {
1926
+ home: `/node${version2.major}`,
1927
+ version: version2.range,
1928
+ major: version2.major
1929
+ };
1930
+ }
1931
+ async function resolveNodeToolchain(workPath, config, meta) {
1932
+ const toolchain = await selectNodeMajor(workPath, config, meta);
1933
+ if (!toolchain) {
1934
+ return void 0;
1935
+ }
1936
+ for (const executable of ["node", "npm"]) {
1937
+ if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(toolchain.home, "bin", executable))) {
1938
+ throw new Error(
1939
+ `The selected Node.js ${toolchain.version} installation is missing ${toolchain.home}/bin/${executable} in the build environment.`
1940
+ );
1941
+ }
1942
+ }
1943
+ return toolchain;
1944
+ }
1945
+ async function resolveLocalNodeToolchain(workPath, config, meta) {
1946
+ return selectNodeMajor(workPath, config, meta);
1947
+ }
1948
+
1949
+ // src/buildpacks/registry.ts
1950
+ var import_node_fs9 = require("fs");
1951
+ var import_node_path9 = require("path");
1952
+
1953
+ // src/buildpacks/ruby.ts
1954
+ var import_node_fs8 = require("fs");
1955
+ var import_node_path8 = require("path");
1956
+ var import_smol_toml2 = require("smol-toml");
1957
+ var DEFAULT_RUBY_VERSION = "3.4";
1958
+ var RUBY_IMAGE_REPOSITORY = "docker.io/library/ruby";
1959
+ var RUBY_VERSION_DOCS = "https://vercel.link/ruby-version";
1960
+ var RUBY_VERSION_FILES = [
1961
+ ".ruby-version",
1962
+ ".tool-versions",
1963
+ "mise.toml"
1964
+ ];
1965
+ var VERSION_FILE_RE = /^ruby[\s-]*(?:=\s*)?["']?([^\s#"']+)["']?/m;
1966
+ var VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?$/;
1967
+ var GEMFILE_RUBY_CALL_RE = /^\s*ruby\b(?!\s*:)/;
1968
+ var GEMFILE_RUBY_LINE_RE = /^\s*ruby(?:\s*\(\s*("[^"]*"|'[^']*')\s*\)|\s*("[^"]*"|'[^']*'))\s*(?:#.*)?$/;
1969
+ var ruby = {
1970
+ runtime: "ruby",
1971
+ projectMarkers: ["Gemfile"],
1972
+ buildpacks: [vercelBuildpack("ruby")],
1973
+ async resolveImage(workPath) {
1974
+ const version2 = selectRubyVersion(workPath) ?? DEFAULT_RUBY_VERSION;
1975
+ const image = `${RUBY_IMAGE_REPOSITORY}:${version2}`;
1976
+ return { version: version2, buildImage: image, runImage: image };
1977
+ }
1978
+ };
1979
+ function isValidVersion(version2) {
1980
+ return VERSION_RE.test(version2) && version2.split(".").every((n) => Number.isSafeInteger(+n));
1981
+ }
1982
+ function selectRubyVersion(workPath) {
1983
+ for (const name of RUBY_VERSION_FILES) {
1984
+ const file = (0, import_node_path8.join)(workPath, name);
1985
+ if ((0, import_node_fs8.existsSync)(file)) {
1986
+ return parseVersionFile(name, (0, import_node_fs8.readFileSync)(file, "utf8"));
1987
+ }
1988
+ }
1989
+ const gemfile = (0, import_node_path8.join)(workPath, "Gemfile");
1990
+ return (0, import_node_fs8.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs8.readFileSync)(gemfile, "utf8")) : void 0;
1991
+ }
1992
+ function parseVersionFile(name, contents) {
1993
+ let version2;
1994
+ if (name === "mise.toml") {
1995
+ try {
1996
+ const { tools } = (0, import_smol_toml2.parse)(contents);
1997
+ version2 = typeof tools === "object" && "ruby" in tools ? tools.ruby : void 0;
1998
+ } catch (error) {
1999
+ throw new Error(`Invalid ${name}: ${error.message}`);
2000
+ }
2001
+ } else {
2002
+ version2 = VERSION_FILE_RE.exec(contents)?.[1] ?? contents.trim();
2003
+ }
2004
+ if (typeof version2 !== "string" || !isValidVersion(version2)) {
2005
+ throw new Error(
2006
+ `Invalid Ruby version in ${name}: ${JSON.stringify(version2)}. Expected a numeric major.minor or major.minor.patch version such as 3.4.1; only MRI is supported. Learn more: ${RUBY_VERSION_DOCS}`
2007
+ );
2008
+ }
2009
+ return version2;
2010
+ }
2011
+ function parseGemfileRubyVersion(contents) {
2012
+ const lines = contents.split(/\r?\n/).filter((line2) => GEMFILE_RUBY_CALL_RE.test(line2));
2013
+ if (lines.length === 0)
2014
+ return void 0;
2015
+ const [line] = lines;
2016
+ const match = lines.length === 1 ? GEMFILE_RUBY_LINE_RE.exec(line) : null;
2017
+ const version2 = (match?.[1] ?? match?.[2])?.slice(1, -1);
2018
+ if (version2 === void 0 || !isValidVersion(version2)) {
2019
+ throw new Error(
2020
+ `Unsupported ruby declaration in Gemfile: ${JSON.stringify(line.trim())}. Pin Ruby with a .ruby-version, .tool-versions, or mise.toml file, or declare an exact version such as ruby "3.4.1". Version operators, engine:, patchlevel:, file:, and computed values are not supported. Learn more: ${RUBY_VERSION_DOCS}`
2021
+ );
2022
+ }
2023
+ return [...version2.split("."), "0"].slice(0, 3).join(".");
2024
+ }
2025
+
2026
+ // src/buildpacks/registry.ts
2027
+ var BUILDPACKS = [ruby];
2028
+ function requestedBuildpack(config) {
2029
+ if (!config)
2030
+ return void 0;
2031
+ return BUILDPACKS.find((bp) => bp.runtime === config.buildpack);
2032
+ }
2033
+ function hasProjectMarkers(buildpack, workPath) {
2034
+ return buildpack.projectMarkers.some(
2035
+ (name) => (0, import_node_fs9.existsSync)((0, import_node_path9.join)(workPath, name))
2036
+ );
2037
+ }
2038
+
2039
+ // src/buildpacks/build.ts
2040
+ async function buildAndPushBuildpack(params) {
2041
+ const sourceDir = (0, import_node_fs10.mkdtempSync)((0, import_node_path10.join)((0, import_node_os7.tmpdir)(), "vercel-cnb-source-"));
2042
+ try {
2043
+ step("Staging buildpack source files");
2044
+ await stageWorkspace(params, sourceDir);
2045
+ const engine = selectContainerEngine();
2046
+ if (!hasProjectMarkers(params.buildpack, sourceDir)) {
2047
+ throw new Error(
2048
+ `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
+ );
2050
+ }
2051
+ const image = await params.buildpack.resolveImage(sourceDir);
2052
+ const nodeToolchain = engine.name === "buildah" ? await resolveNodeToolchain(sourceDir, params.config, params.meta) : await resolveLocalNodeToolchain(
2053
+ sourceDir,
2054
+ params.config,
2055
+ params.meta
2056
+ );
2057
+ return await withSpan(
2058
+ params.parentSpan,
2059
+ "container.buildpack.build_and_push",
2060
+ {
2061
+ "buildpack.runtime": params.buildpack.runtime,
2062
+ "buildpack.runtime_version": image.version,
2063
+ "buildpack.node_version": nodeToolchain?.version,
2064
+ "container.engine": engine.name,
2065
+ "container.repository": params.repository
2066
+ },
2067
+ async (buildSpan) => {
2068
+ const target = await resolveRegistryTarget({
2069
+ repository: params.repository,
2070
+ tag: params.tag,
2071
+ span: buildSpan
2072
+ });
2073
+ buildSpan?.setAttributes({
2074
+ "container.repository": target.fullRepository,
2075
+ "image.tag": params.tag,
2076
+ "image.ref": target.imageRef,
2077
+ "registry.username": target.username
2078
+ });
2079
+ return engine.withRuntime(buildSpan, async () => {
2080
+ await withSpan(
2081
+ buildSpan,
2082
+ "container.ensure_toolchain_ready",
2083
+ { "container.engine": engine.name },
2084
+ (s) => engine.ensureReady(s)
2085
+ );
2086
+ await withSpan(
2087
+ buildSpan,
2088
+ "container.verify_storage",
2089
+ { "container.engine": engine.name },
2090
+ (s) => engine.verifyStorage?.(s) ?? Promise.resolve()
2091
+ );
2092
+ const credentials = {
2093
+ registry: VCR_REGISTRY,
2094
+ username: target.username,
2095
+ token: target.token
2096
+ };
2097
+ step(`Authenticating to ${VCR_REGISTRY} as ${target.username}`);
2098
+ await withSpan(
2099
+ buildSpan,
2100
+ "container.registry_login",
2101
+ {
2102
+ "container.registry": VCR_REGISTRY,
2103
+ "registry.username": target.username
2104
+ },
2105
+ () => engine.login(credentials)
2106
+ );
2107
+ done("authenticated");
2108
+ await withSpan(
2109
+ buildSpan,
2110
+ "container.ensure_repository",
2111
+ { "container.repository": params.repository },
2112
+ (s) => ensureRepository(
2113
+ params.repository,
2114
+ target.token,
2115
+ target.claims,
2116
+ s
2117
+ )
2118
+ );
2119
+ const runLifecycle = engine.name === "buildah" ? buildAndPushWithLifecycle : buildAndPushWithLifecycleDocker;
2120
+ const result = await runLifecycle(
2121
+ params.buildpack,
2122
+ {
2123
+ workPath: sourceDir,
2124
+ image,
2125
+ imageRef: target.imageRef,
2126
+ credentials,
2127
+ buildEnv: params.buildEnv,
2128
+ nodeToolchain
2129
+ },
2130
+ buildSpan
2131
+ );
2132
+ const resolvedRef = `${VCR_REGISTRY}/${target.fullRepository}@${result.digest}`;
2133
+ buildSpan?.setAttributes({
2134
+ "image.digest": result.digest,
2135
+ "image.resolved_ref": resolvedRef
2136
+ });
2137
+ info(`Image reference ${resolvedRef}`);
2138
+ return resolvedRef;
2139
+ });
2140
+ }
2141
+ );
2142
+ } finally {
2143
+ try {
2144
+ (0, import_node_fs10.rmSync)(sourceDir, { recursive: true, force: true });
2145
+ } catch {
2146
+ debug(`Could not remove buildpack source snapshot ${sourceDir}.`);
2147
+ }
2148
+ }
2149
+ }
2150
+
2151
+ // src/image-source.ts
2152
+ var import_node_fs11 = require("fs");
2153
+ var import_node_path11 = __toESM(require("path"));
2154
+ var DETECT_SENTINEL = "<detect>";
2155
+ function resolveImageSource(options, context) {
2156
+ const { config, workPath, entrypoint } = options;
2157
+ const entrypointRef = readString(entrypoint);
2158
+ const isDetectSentinel = entrypointRef === DETECT_SENTINEL;
2159
+ const buildpack = requestedBuildpack(config);
2160
+ if (buildpack && !isDetectSentinel) {
2161
+ throw new Error(
2162
+ "Buildpack builds are selected by Vercel from the project's framework or the service's runtime; `buildpack` is not a `builds` configuration option."
2163
+ );
2164
+ }
2165
+ const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
2166
+ const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
2167
+ const dockerfilePath = import_node_path11.default.join(workPath, dockerfileRel);
2168
+ const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs11.existsSync)(dockerfilePath);
2169
+ if (hasDockerfile) {
2170
+ return { kind: "dockerfile", dockerfileRel, dockerfilePath };
2171
+ }
2172
+ const prebuiltImage = readString(config.handler) ?? (isDetectSentinel ? void 0 : entrypointRef);
2173
+ if (prebuiltImage) {
2174
+ return { kind: "prebuilt", imageRef: prebuiltImage };
2175
+ }
2176
+ if (buildpack) {
2177
+ return { kind: "buildpack", buildpack };
2178
+ }
2179
+ throw new Error(
2180
+ "Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to " + (context === "dev" ? "run with `vercel dev`." : "build.")
2181
+ );
2182
+ }
2183
+
2184
+ // src/dev.ts
2185
+ var import_node_child_process3 = require("child_process");
2186
+ var import_node_fs12 = require("fs");
2187
+ var import_node_os8 = require("os");
2188
+ var import_node_path12 = __toESM(require("path"));
2189
+ var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
2190
+ "TMPDIR",
2191
+ "TMP",
2192
+ "TEMP",
2193
+ "HOME",
2194
+ "PATH",
2195
+ "PWD",
2196
+ "OLDPWD",
2197
+ "SHELL",
2198
+ "SHLVL",
2199
+ "USER",
2200
+ "LOGNAME",
2201
+ "TERM",
2202
+ "TERM_PROGRAM",
2203
+ "TERM_PROGRAM_VERSION",
2204
+ "TERM_SESSION_ID",
2205
+ "COLORTERM",
2206
+ "LANG",
2207
+ "LC_ALL",
2208
+ "LC_CTYPE",
2209
+ "COMMAND_MODE",
2210
+ "SECURITYSESSIONID",
2211
+ "__CF_USER_TEXT_ENCODING",
2212
+ "__CFBundleIdentifier"
2213
+ ]);
2214
+ function isHostOnlyEnvVar(key) {
2215
+ return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
2216
+ }
2217
+ function writeEnvFile(env) {
2218
+ const dir = (0, import_node_fs12.mkdtempSync)(import_node_path12.default.join((0, import_node_os8.tmpdir)(), "vercel-container-dev-env-"));
2219
+ const file = import_node_path12.default.join(dir, "env");
2220
+ const lines = [];
2221
+ for (const [key, value] of Object.entries(env)) {
2222
+ if (value.includes("\n")) {
2223
+ continue;
2224
+ }
2225
+ lines.push(`${key}=${value}`);
2226
+ }
2227
+ (0, import_node_fs12.writeFileSync)(file, `${lines.join("\n")}
2228
+ `);
2229
+ return file;
2230
+ }
2231
+ function useContainerHost(endpoint) {
2232
+ try {
2233
+ const url = new URL(endpoint);
2234
+ if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]") {
2235
+ url.hostname = "host.docker.internal";
2236
+ return url.toString();
2237
+ }
2238
+ } catch {
2239
+ }
2240
+ return endpoint;
2241
+ }
2242
+ function emit(out, line) {
2243
+ if (out.onStderr) {
2244
+ out.onStderr(Buffer.from(`${line}
2245
+ `));
2246
+ } else {
2247
+ process.stderr.write(`${line}
2248
+ `);
2249
+ }
2250
+ }
2251
+ function runForwarded(cmd, args, out, opts = {}) {
2252
+ return new Promise((resolve2, reject) => {
2253
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2254
+ let stdout = "";
2255
+ let stderr = "";
2256
+ child.stdout?.on("data", (chunk) => {
2257
+ stdout += chunk.toString();
2258
+ if (!opts.quiet) {
2259
+ if (out.onStdout) {
2260
+ out.onStdout(chunk);
2261
+ } else {
2262
+ process.stderr.write(chunk.toString());
2263
+ }
2264
+ }
2265
+ });
2266
+ child.stderr?.on("data", (chunk) => {
2267
+ stderr += chunk.toString();
2268
+ if (!opts.quiet) {
2269
+ if (out.onStderr) {
2270
+ out.onStderr(chunk);
2271
+ } else {
2272
+ process.stderr.write(chunk.toString());
1169
2273
  }
1170
2274
  }
1171
2275
  });
@@ -1182,7 +2286,7 @@ function runForwarded(cmd, args, out, opts = {}) {
1182
2286
  });
1183
2287
  child.on("close", (code) => {
1184
2288
  if (code === 0) {
1185
- resolve({ stdout });
2289
+ resolve2({ stdout });
1186
2290
  } else {
1187
2291
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
1188
2292
  reject(
@@ -1195,41 +2299,30 @@ ${detail}` : "")
1195
2299
  });
1196
2300
  });
1197
2301
  }
1198
- function normalizeCommand(command) {
1199
- if (typeof command === "string") {
1200
- return [command];
1201
- }
1202
- if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
1203
- return command;
1204
- }
1205
- return void 0;
1206
- }
1207
2302
  async function resolveDevImage(options, out, span) {
1208
- const { config, workPath, entrypoint } = options;
1209
- const entrypointRef = readString(entrypoint);
1210
- const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
1211
- const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
1212
- const dockerfilePath = import_node_path4.default.join(workPath, dockerfileRel);
1213
- const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs4.existsSync)(dockerfilePath);
1214
- const prebuiltImage = readString(config.handler) ?? (hasDockerfile ? void 0 : entrypointRef);
1215
- if (!hasDockerfile) {
1216
- if (!prebuiltImage) {
1217
- throw new Error(
1218
- "Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to run with `vercel dev`."
1219
- );
1220
- }
2303
+ const source = resolveImageSource(options, "dev");
2304
+ if (source.kind === "prebuilt") {
1221
2305
  span?.setAttributes({ "container.dev_mode": "prebuilt" });
1222
- emit(out, `\u25B2 container vercel dev: using prebuilt image ${prebuiltImage}`);
1223
- return prebuiltImage;
2306
+ emit(
2307
+ out,
2308
+ `\u25B2 container vercel dev: using prebuilt image ${source.imageRef}`
2309
+ );
2310
+ return { image: source.imageRef };
2311
+ }
2312
+ if (source.kind === "buildpack") {
2313
+ throw new Error(
2314
+ `Unexpected buildpack image source in \`vercel dev\` for "${options.workPath}".`
2315
+ );
1224
2316
  }
1225
- if (!(0, import_node_fs4.existsSync)(dockerfilePath)) {
2317
+ const { dockerfilePath } = source;
2318
+ if (!(0, import_node_fs12.existsSync)(dockerfilePath)) {
1226
2319
  throw new Error(
1227
2320
  `Dockerfile not found at "${dockerfilePath}" for container service.`
1228
2321
  );
1229
2322
  }
1230
2323
  const serviceName = options.service?.name ?? "service";
1231
2324
  const tag = devImageTag(serviceName);
1232
- const contextDir = import_node_path4.default.dirname(dockerfilePath);
2325
+ const contextDir = import_node_path12.default.dirname(dockerfilePath);
1233
2326
  const buildArgFlags2 = [];
1234
2327
  const buildEnv = options.meta?.buildEnv ?? {};
1235
2328
  for (const [key, value] of Object.entries(buildEnv)) {
@@ -1245,7 +2338,7 @@ async function resolveDevImage(options, out, span) {
1245
2338
  out
1246
2339
  );
1247
2340
  emit(out, `\u25B2 container built ${tag}`);
1248
- return tag;
2341
+ return { image: tag };
1249
2342
  }
1250
2343
  async function resolveContainerPort(image, out) {
1251
2344
  try {
@@ -1355,7 +2448,7 @@ async function startContainer(options, reuseKey) {
1355
2448
  const { config, meta, onStdout, onStderr } = options;
1356
2449
  const out = { onStdout, onStderr };
1357
2450
  await assertDockerAvailable(out);
1358
- const image = await withSpan(
2451
+ const { image } = await withSpan(
1359
2452
  span,
1360
2453
  "container.dev.resolve_image",
1361
2454
  {},
@@ -1385,9 +2478,8 @@ async function startContainer(options, reuseKey) {
1385
2478
  }
1386
2479
  mergedEnv.PORT = String(containerPort);
1387
2480
  const envFilePath = writeEnvFile(mergedEnv);
1388
- const command = normalizeCommand(
1389
- config.command
1390
- );
2481
+ const rawCommand = config.command;
2482
+ const command = normalizeCommand(rawCommand);
1391
2483
  const requestedHostPort = typeof meta?.port === "number" ? meta.port : 0;
1392
2484
  const args = [
1393
2485
  "run",
@@ -1421,7 +2513,7 @@ async function startContainer(options, reuseKey) {
1421
2513
  }
1422
2514
  });
1423
2515
  const cleanupEnvFile = () => {
1424
- (0, import_node_fs4.rmSync)(import_node_path4.default.dirname(envFilePath), { recursive: true, force: true });
2516
+ (0, import_node_fs12.rmSync)(import_node_path12.default.dirname(envFilePath), { recursive: true, force: true });
1425
2517
  };
1426
2518
  const shutdown = async () => {
1427
2519
  runningContainers.delete(reuseKey);
@@ -1454,7 +2546,7 @@ async function startContainer(options, reuseKey) {
1454
2546
  break;
1455
2547
  } catch (err) {
1456
2548
  lastErr = err;
1457
- await new Promise((resolve) => setTimeout(resolve, 250));
2549
+ await new Promise((resolve2) => setTimeout(resolve2, 250));
1458
2550
  }
1459
2551
  }
1460
2552
  if (hostPort === void 0) {
@@ -1497,11 +2589,11 @@ async function startContainer(options, reuseKey) {
1497
2589
  }
1498
2590
 
1499
2591
  // src/prepare-cache.ts
1500
- var import_build_utils3 = require("@vercel/build-utils");
1501
- var import_node_fs5 = require("fs");
1502
- var import_node_path5 = require("path");
2592
+ var import_build_utils5 = require("@vercel/build-utils");
2593
+ var import_node_fs13 = require("fs");
2594
+ var import_node_path13 = require("path");
1503
2595
  var CACHE_ROOT = "/vercel";
1504
- var GRAPH_ROOT_REL = import_node_path5.posix.relative(CACHE_ROOT, BUILDAH_GRAPH_ROOT);
2596
+ var GRAPH_ROOT_REL = import_node_path13.posix.relative(CACHE_ROOT, BUILDAH_GRAPH_ROOT);
1505
2597
  async function prepareCache(_options) {
1506
2598
  if (process.env.VERCEL_VCR_DISABLE_LAYER_CACHE) {
1507
2599
  debug("layer cache disabled (VERCEL_VCR_DISABLE_LAYER_CACHE)");
@@ -1511,12 +2603,12 @@ async function prepareCache(_options) {
1511
2603
  debug("skipping container layer cache (not in build container)");
1512
2604
  return {};
1513
2605
  }
1514
- if (!(0, import_node_fs5.existsSync)(BUILDAH_GRAPH_ROOT)) {
2606
+ if (!(0, import_node_fs13.existsSync)(BUILDAH_GRAPH_ROOT)) {
1515
2607
  debug(`no buildah store to cache at ${BUILDAH_GRAPH_ROOT}`);
1516
2608
  return {};
1517
2609
  }
1518
2610
  const start = Date.now();
1519
- const files = await (0, import_build_utils3.glob)(`${GRAPH_ROOT_REL}/**`, CACHE_ROOT);
2611
+ const files = await (0, import_build_utils5.glob)(`${GRAPH_ROOT_REL}/**`, CACHE_ROOT);
1520
2612
  const count = Object.keys(files).length;
1521
2613
  info(
1522
2614
  `cached container layer store: ${count} files from ${BUILDAH_GRAPH_ROOT} in ${Date.now() - start}ms`
@@ -1534,15 +2626,6 @@ function resolveFunctionSourceFile(options) {
1534
2626
  }
1535
2627
  return entrypoint;
1536
2628
  }
1537
- function normalizeCommand2(command) {
1538
- if (typeof command === "string") {
1539
- return [command];
1540
- }
1541
- if (Array.isArray(command) && command.every((item) => typeof item === "string")) {
1542
- return command;
1543
- }
1544
- return void 0;
1545
- }
1546
2629
  function sanitizeRepository(name) {
1547
2630
  const sanitized = name.toLowerCase().replace(/[^a-z0-9-_./]/g, "-").replace(/-+/g, "-").replace(/(^[-/.]+)|([-/.]+$)/g, "");
1548
2631
  return sanitized || "service";
@@ -1558,6 +2641,33 @@ function resolveImageTag() {
1558
2641
  }
1559
2642
  return `build-${Date.now().toString(36)}`;
1560
2643
  }
2644
+ async function authenticateRegistry(engine, buildParams, span) {
2645
+ const forceLogin = readString(process.env.VERCEL_VCR_FORCE_LOGIN) === "1";
2646
+ const authFile = forceLogin ? void 0 : existingRegistryAuthFile();
2647
+ if (authFile) {
2648
+ debug(`registry auth file present: ${authFile}`);
2649
+ step(`Using registry credentials from ${authFile}`);
2650
+ span?.setAttributes({
2651
+ "container.registry": VCR_REGISTRY,
2652
+ "registry.username": buildParams.username,
2653
+ "registry.auth_file": authFile,
2654
+ "registry.login_skipped": toTag(true)
2655
+ });
2656
+ done("authenticated via provisioned credentials");
2657
+ return;
2658
+ }
2659
+ step(`Authenticating to ${VCR_REGISTRY} as ${buildParams.username}`);
2660
+ await withSpan(
2661
+ span,
2662
+ "container.registry_login",
2663
+ {
2664
+ "container.registry": VCR_REGISTRY,
2665
+ "registry.username": buildParams.username
2666
+ },
2667
+ () => engine.login(buildParams)
2668
+ );
2669
+ done("authenticated");
2670
+ }
1561
2671
  async function buildAndPushImage(params) {
1562
2672
  const { contextDir, dockerfilePath, repository, tag, buildArgs, parentSpan } = params;
1563
2673
  const engine = selectContainerEngine();
@@ -1570,25 +2680,12 @@ async function buildAndPushImage(params) {
1570
2680
  "container.repository": repository
1571
2681
  },
1572
2682
  async (buildSpan) => {
1573
- const token = await withSpan(
1574
- buildSpan,
1575
- "container.mint_oidc",
1576
- {},
1577
- (s) => resolveOidcTokenForBuild(s)
1578
- );
1579
- const claims = decodeOidcClaims(token);
1580
- debug(`registry token: ${tokenFingerprint(token)}`);
1581
- debugTokenClaims("OIDC token claims", token);
1582
- const username = claims.owner_id;
1583
- if (!username) {
1584
- throw new Error(
1585
- "VERCEL_OIDC_TOKEN is missing the `owner_id` (team id) claim required to authenticate to the container registry."
1586
- );
1587
- }
1588
- const fullRepository = [claims.owner, claims.project, repository].join(
1589
- "/"
1590
- );
1591
- const imageRef = `${VCR_REGISTRY}/${fullRepository}:${tag}`;
2683
+ const target = await resolveRegistryTarget({
2684
+ repository,
2685
+ tag,
2686
+ span: buildSpan
2687
+ });
2688
+ const { token, claims, username, fullRepository, imageRef } = target;
1592
2689
  buildSpan?.setAttributes({
1593
2690
  "container.repository": fullRepository,
1594
2691
  "image.tag": tag,
@@ -1625,31 +2722,7 @@ async function buildAndPushImage(params) {
1625
2722
  buildArgs,
1626
2723
  span: buildSpan
1627
2724
  };
1628
- const forceLogin = readString(process.env.VERCEL_VCR_FORCE_LOGIN) === "1";
1629
- const authFile = forceLogin ? void 0 : existingRegistryAuthFile();
1630
- if (authFile) {
1631
- debug(`registry auth file present: ${authFile}`);
1632
- step(`Using registry credentials from ${authFile}`);
1633
- buildSpan?.setAttributes({
1634
- "container.registry": VCR_REGISTRY,
1635
- "registry.username": username,
1636
- "registry.auth_file": authFile,
1637
- "registry.login_skipped": toTag(true)
1638
- });
1639
- done("authenticated via provisioned credentials");
1640
- } else {
1641
- step(`Authenticating to ${VCR_REGISTRY} as ${username}`);
1642
- await withSpan(
1643
- buildSpan,
1644
- "container.registry_login",
1645
- {
1646
- "container.registry": VCR_REGISTRY,
1647
- "registry.username": username
1648
- },
1649
- () => engine.login(buildParams)
1650
- );
1651
- done("authenticated");
1652
- }
2725
+ await authenticateRegistry(engine, buildParams, buildSpan);
1653
2726
  await withSpan(
1654
2727
  buildSpan,
1655
2728
  "container.ensure_repository",
@@ -1703,47 +2776,70 @@ async function buildAndPushImage(params) {
1703
2776
  }
1704
2777
  );
1705
2778
  }
1706
- async function resolveImageHandler(options, span) {
1707
- const { config, workPath, entrypoint, meta } = options;
1708
- const entrypointRef = readString(entrypoint);
1709
- const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
1710
- const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
1711
- const dockerfilePath = import_node_path6.default.join(workPath, dockerfileRel);
1712
- const hasDockerfile = dockerfileConfigured !== void 0 || (0, import_node_fs6.existsSync)(dockerfilePath);
1713
- const prebuiltImage = readString(config.handler) ?? (hasDockerfile ? void 0 : entrypointRef);
2779
+ async function resolveImageHandler(options, source, span) {
2780
+ const { config, meta } = options;
1714
2781
  span?.setAttributes({
1715
- "container.has_dockerfile": toTag(hasDockerfile),
2782
+ "container.has_dockerfile": toTag(source.kind === "dockerfile"),
1716
2783
  "container.is_dev": toTag(Boolean(meta?.isDev))
1717
2784
  });
1718
- if (!hasDockerfile) {
1719
- if (!prebuiltImage) {
1720
- throw new Error(
1721
- "Container service must specify an entrypoint: a prebuilt OCI image reference, or a Dockerfile path to build."
1722
- );
1723
- }
2785
+ if (source.kind === "prebuilt") {
1724
2786
  span?.setAttributes({ "container.mode": "prebuilt" });
1725
- info(`Using prebuilt image ${prebuiltImage}`);
1726
- return prebuiltImage;
2787
+ info(`Using prebuilt image ${source.imageRef}`);
2788
+ return source.imageRef;
2789
+ }
2790
+ if (source.kind === "buildpack") {
2791
+ const { buildpack } = source;
2792
+ if (meta?.isDev) {
2793
+ const tag3 = devImageTag(options.service?.name ?? "service");
2794
+ span?.setAttributes({
2795
+ "container.mode": "buildpack-dev",
2796
+ "buildpack.runtime": buildpack.runtime,
2797
+ "image.tag": tag3
2798
+ });
2799
+ return tag3;
2800
+ }
2801
+ const repository2 = sanitizeRepository(
2802
+ options.service?.name ?? buildpack.runtime
2803
+ );
2804
+ const tag2 = resolveImageTag();
2805
+ span?.setAttributes({
2806
+ "container.mode": "buildpack-build-and-push",
2807
+ "buildpack.runtime": buildpack.runtime,
2808
+ "container.repository": repository2,
2809
+ "image.tag": tag2
2810
+ });
2811
+ return buildAndPushBuildpack({
2812
+ buildpack,
2813
+ files: options.files,
2814
+ workPath: options.workPath,
2815
+ repository: repository2,
2816
+ tag: tag2,
2817
+ config,
2818
+ meta,
2819
+ buildEnv: buildArgsFromEnv(meta?.buildEnv),
2820
+ parentSpan: span
2821
+ });
1727
2822
  }
2823
+ const { dockerfileRel, dockerfilePath } = source;
1728
2824
  if (meta?.isDev) {
1729
2825
  const serviceName2 = options.service?.name;
1730
2826
  const tag2 = devImageTag(
1731
- serviceName2 ?? import_node_path6.default.basename(dockerfileRel).split(".")[0]
2827
+ serviceName2 ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
1732
2828
  );
1733
2829
  span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
1734
2830
  return tag2;
1735
2831
  }
1736
- if (!(0, import_node_fs6.existsSync)(dockerfilePath)) {
2832
+ if (!(0, import_node_fs14.existsSync)(dockerfilePath)) {
1737
2833
  throw new Error(
1738
2834
  `Dockerfile not found at "${dockerfilePath}" for container service.`
1739
2835
  );
1740
2836
  }
1741
2837
  const serviceName = options.service?.name;
1742
2838
  const repository = sanitizeRepository(
1743
- serviceName ?? import_node_path6.default.basename(dockerfileRel).split(".")[0]
2839
+ serviceName ?? import_node_path14.default.basename(dockerfileRel).split(".")[0]
1744
2840
  );
1745
2841
  const tag = resolveImageTag();
1746
- const contextDir = import_node_path6.default.dirname(dockerfilePath);
2842
+ const contextDir = import_node_path14.default.dirname(dockerfilePath);
1747
2843
  const buildArgs = buildArgsFromEnv(meta?.buildEnv);
1748
2844
  span?.setAttributes({
1749
2845
  "container.mode": "build_and_push",
@@ -1772,21 +2868,22 @@ function buildArgsFromEnv(env) {
1772
2868
  return Object.keys(out).length > 0 ? out : void 0;
1773
2869
  }
1774
2870
  async function build(options) {
2871
+ const source = resolveImageSource(options, "build");
1775
2872
  const image = await withSpan(
1776
2873
  options.span,
1777
2874
  "container.resolve_image",
1778
2875
  { "service.name": options.service?.name },
1779
- (span) => resolveImageHandler(options, span)
2876
+ (span) => resolveImageHandler(options, source, span)
1780
2877
  );
1781
- const lambdaOptions = await (0, import_build_utils4.getLambdaOptionsFromFunction)({
2878
+ const lambdaOptions = await (0, import_build_utils6.getLambdaOptionsFromFunction)({
1782
2879
  sourceFile: resolveFunctionSourceFile(options),
1783
2880
  config: options.config
1784
2881
  });
1785
- const command = normalizeCommand2(options.config.command);
2882
+ const command = source.kind === "buildpack" ? void 0 : normalizeCommand(options.config.command);
1786
2883
  await generateProjectManifest({
1787
2884
  workPath: options.workPath,
1788
2885
  framework: options.config.framework ?? void 0,
1789
- serviceType: options.service ? (0, import_build_utils4.getReportedServiceType)(options.service) : void 0
2886
+ serviceType: options.service ? (0, import_build_utils6.getReportedServiceType)(options.service) : void 0
1790
2887
  });
1791
2888
  const routes = [
1792
2889
  { handle: "filesystem" },