@vercel/container 22.0.2 → 23.0.1-snapshot-13615777d933

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 CHANGED
@@ -38,7 +38,44 @@ __export(src_exports, {
38
38
  version: () => version
39
39
  });
40
40
  module.exports = __toCommonJS(src_exports);
41
- var import_build_utils6 = require("@vercel/build-utils");
41
+ var import_build_utils8 = require("@vercel/build-utils");
42
+
43
+ // src/schedules-command.ts
44
+ var import_promises = require("fs/promises");
45
+ var import_node_os = require("os");
46
+ var import_node_path = require("path");
47
+ var SCHEDULES_COMMAND_ENV = "VERCEL_SCHEDULE_COMMAND";
48
+ var DISPATCHER = "schedules-command-dispatcher";
49
+ async function buildSchedulesCommandImage(engine, params, dispatcher = (0, import_node_path.join)(__dirname, DISPATCHER)) {
50
+ const baseImage = await engine.build(params);
51
+ if ((await engine.onBuildTriggers?.(baseImage))?.length) {
52
+ throw new Error(
53
+ `"${params.dockerfilePath}" declares ONBUILD instructions, which only run when another image is built from it. Remove them from the schedule's Dockerfile.`
54
+ );
55
+ }
56
+ const contextDir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "vercel-schedules-image-"));
57
+ try {
58
+ await (0, import_promises.copyFile)(dispatcher, (0, import_node_path.join)(contextDir, DISPATCHER));
59
+ const dockerfilePath = (0, import_node_path.join)(contextDir, "Dockerfile");
60
+ await (0, import_promises.writeFile)(
61
+ dockerfilePath,
62
+ [
63
+ `FROM ${baseImage}`,
64
+ `COPY --chmod=0555 ${DISPATCHER} /__vercel_runtime/${DISPATCHER}`,
65
+ `ENTRYPOINT ["/__vercel_runtime/${DISPATCHER}"]`
66
+ ].join("\n")
67
+ );
68
+ await engine.build({
69
+ ...params,
70
+ contextDir,
71
+ dockerfilePath,
72
+ buildArgs: void 0,
73
+ baseImageIsLocal: true
74
+ });
75
+ } finally {
76
+ await (0, import_promises.rm)(contextDir, { recursive: true, force: true });
77
+ }
78
+ }
42
79
 
43
80
  // src/diagnostics.ts
44
81
  var import_build_utils = require("@vercel/build-utils");
@@ -63,14 +100,15 @@ var diagnostics = (0, import_build_utils.createDiagnostics)("container");
63
100
 
64
101
  // src/index.ts
65
102
  var import_node_fs16 = require("fs");
66
- var import_node_path17 = __toESM(require("path"));
103
+ var import_node_path22 = __toESM(require("path"));
67
104
 
68
105
  // src/util.ts
69
106
  var import_build_utils2 = require("@vercel/build-utils");
70
107
  var import_node_child_process = require("child_process");
71
108
  var import_node_fs = require("fs");
72
- var import_node_os = require("os");
73
- var import_node_path = require("path");
109
+ var import_promises2 = require("fs/promises");
110
+ var import_node_os2 = require("os");
111
+ var import_node_path2 = require("path");
74
112
  var DEBUG = Boolean((0, import_build_utils2.getPlatformEnv)("BUILDER_DEBUG"));
75
113
  function normalizeCommand(command) {
76
114
  if (typeof command === "string") {
@@ -105,11 +143,23 @@ function debug(message) {
105
143
  function elapsed(since) {
106
144
  return `${((Date.now() - since) / 1e3).toFixed(1)}s`;
107
145
  }
146
+ function formatBytes(bytes) {
147
+ if (bytes < 1024)
148
+ return `${bytes} B`;
149
+ const units = ["KiB", "MiB", "GiB"];
150
+ let value = bytes / 1024;
151
+ let unit = 0;
152
+ while (value >= 1024 && unit < units.length - 1) {
153
+ value /= 1024;
154
+ unit += 1;
155
+ }
156
+ return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
157
+ }
108
158
  function shortDigest(digest) {
109
159
  return digest.startsWith("sha256:") ? `${digest.slice(0, 19)}\u2026` : digest;
110
160
  }
111
161
  function delay(ms) {
112
- return new Promise((resolve4) => setTimeout(resolve4, ms));
162
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
113
163
  }
114
164
  async function withSpan(parent, name, attrs, fn) {
115
165
  if (!parent) {
@@ -124,21 +174,21 @@ function readString(value) {
124
174
  return typeof value === "string" && value.length > 0 ? value : void 0;
125
175
  }
126
176
  function isDockerfileRef(ref) {
127
- return /(docker|container)file/i.test((0, import_node_path.basename)(ref));
177
+ return /(docker|container)file/i.test((0, import_node_path2.basename)(ref));
128
178
  }
129
179
  var DOCKERFILE_CANDIDATES = [
130
180
  "Dockerfile.vercel",
131
181
  "Containerfile.vercel"
132
182
  ];
133
183
  function findDockerfile(workPath) {
134
- return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path.join)(workPath, name)));
184
+ return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path2.join)(workPath, name)));
135
185
  }
136
186
  function devImageTag(serviceName) {
137
187
  const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
138
188
  return `vercel-dev/${safe || "service"}:dev`;
139
189
  }
140
190
  function run(cmd, args, opts = {}) {
141
- return new Promise((resolve4, reject) => {
191
+ return new Promise((resolve5, reject) => {
142
192
  const child = (0, import_node_child_process.spawn)(cmd, args, {
143
193
  cwd: opts.cwd,
144
194
  env: opts.env,
@@ -173,7 +223,7 @@ function run(cmd, args, opts = {}) {
173
223
  });
174
224
  child.on("close", (code) => {
175
225
  if (code === 0) {
176
- resolve4({ stdout, stderr });
226
+ resolve5({ stdout, stderr });
177
227
  } else {
178
228
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
179
229
  const error = new Error(
@@ -189,6 +239,23 @@ ${detail}` : "")
189
239
  }
190
240
  });
191
241
  }
242
+ async function hasCommand(name) {
243
+ try {
244
+ await run("which", [name], { quiet: true });
245
+ return true;
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
250
+ async function lstatIfPresent(path4) {
251
+ try {
252
+ return await (0, import_promises2.lstat)(path4);
253
+ } catch (error) {
254
+ if (error.code === "ENOENT")
255
+ return void 0;
256
+ throw error;
257
+ }
258
+ }
192
259
  function extractField(text, label) {
193
260
  const match = text.match(new RegExp(`^\\s*${label}:\\s*(.+)$`, "m"));
194
261
  return match?.[1]?.trim();
@@ -202,44 +269,130 @@ function existingRegistryAuthFile() {
202
269
  return (0, import_node_fs.existsSync)(explicit) ? explicit : void 0;
203
270
  }
204
271
  const fromXdg = readString(process.env.XDG_CONFIG_HOME);
205
- const configHome = fromXdg || (0, import_node_path.join)((0, import_node_os.homedir)(), ".config");
206
- const defaultPath = (0, import_node_path.join)(configHome, "containers", "auth.json");
272
+ const configHome = fromXdg || (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config");
273
+ const defaultPath = (0, import_node_path2.join)(configHome, "containers", "auth.json");
207
274
  return (0, import_node_fs.existsSync)(defaultPath) ? defaultPath : void 0;
208
275
  }
209
276
 
210
277
  // src/engines/buildah.ts
278
+ var import_build_utils3 = require("@vercel/build-utils");
279
+ var import_node_crypto3 = require("crypto");
211
280
  var import_node_fs3 = require("fs");
281
+ var import_promises5 = require("fs/promises");
282
+ var import_node_os5 = require("os");
283
+ var import_node_path5 = require("path");
284
+
285
+ // src/buildah-store-cache/state.ts
286
+ var import_node_crypto = require("crypto");
287
+ var import_promises3 = require("fs/promises");
212
288
  var import_node_os3 = require("os");
213
289
  var import_node_path3 = require("path");
214
290
 
291
+ // src/buildah-store-cache/config.ts
292
+ var BUILDAH_STORE_CACHE_FLAG = "VERCEL_BUILDAH_STORE_CACHE";
293
+ function buildahStoreCacheRequested() {
294
+ return isBuildContainer() && process.env[BUILDAH_STORE_CACHE_FLAG] === "1";
295
+ }
296
+ function buildahStoreCacheEnabled() {
297
+ return buildahStoreCacheRequested() && deploymentId() !== void 0;
298
+ }
299
+ function deploymentId() {
300
+ return readString(process.env.VERCEL_DEPLOYMENT_ID);
301
+ }
302
+
303
+ // src/buildah-store-cache/state.ts
304
+ var CLEANUP_CONTAINER_RE = /^vercel-cnb-[a-zA-Z0-9_.-]+$/;
305
+ function buildahStoreStateDirectory() {
306
+ const deployment = deploymentId();
307
+ if (!deployment) {
308
+ throw new Error("VERCEL_DEPLOYMENT_ID is not set");
309
+ }
310
+ return (0, import_node_path3.join)(
311
+ (0, import_node_os3.tmpdir)(),
312
+ "vercel-buildah-store",
313
+ deployment.replace(/[^a-zA-Z0-9_-]/g, "_")
314
+ );
315
+ }
316
+ async function createBuildahStoreStateDirectory() {
317
+ const directory = buildahStoreStateDirectory();
318
+ await (0, import_promises3.mkdir)(directory, { recursive: true, mode: 448 });
319
+ return directory;
320
+ }
321
+ async function writeStateRecord(suffix, contents) {
322
+ const path4 = (0, import_node_path3.join)(
323
+ await createBuildahStoreStateDirectory(),
324
+ `${(0, import_node_crypto.randomUUID)()}${suffix}`
325
+ );
326
+ await (0, import_promises3.writeFile)(path4, contents, { mode: 384, flag: "wx" });
327
+ return path4;
328
+ }
329
+
330
+ // src/buildah-store-cache/usage.ts
331
+ var import_promises4 = require("fs/promises");
332
+ function imageIdsFromBuildOutput(output) {
333
+ const ids = /* @__PURE__ */ new Set();
334
+ for (const match of output.matchAll(
335
+ /--> (?:Using cache )?([a-f0-9]{12,64})[ \t\r]*$/gm
336
+ )) {
337
+ ids.add(match[1]);
338
+ }
339
+ return [...ids];
340
+ }
341
+ var BuildahImageUsage = class _BuildahImageUsage {
342
+ constructor(path4) {
343
+ this.path = path4;
344
+ }
345
+ static async begin() {
346
+ if (!buildahStoreCacheEnabled()) {
347
+ return void 0;
348
+ }
349
+ try {
350
+ return new _BuildahImageUsage(
351
+ await writeStateRecord(".images", "pending\n")
352
+ );
353
+ } catch (error) {
354
+ debug(
355
+ `could not record Buildah image usage: ${error.message}`
356
+ );
357
+ return void 0;
358
+ }
359
+ }
360
+ /** A failure leaves the record pending, so the save stops instead of pruning blindly. */
361
+ async record(images) {
362
+ try {
363
+ const ids = (await images()).map((id) => id.replace(/^sha256:/, ""));
364
+ if (ids.length === 0) {
365
+ throw new Error("no Buildah image IDs were recorded");
366
+ }
367
+ const temporary = `${this.path}.tmp`;
368
+ await (0, import_promises4.writeFile)(temporary, `${ids.join("\n")}
369
+ `, { mode: 384 });
370
+ await (0, import_promises4.rename)(temporary, this.path);
371
+ } catch (error) {
372
+ debug(
373
+ `could not record Buildah image usage: ${error.message}`
374
+ );
375
+ }
376
+ }
377
+ };
378
+
215
379
  // src/storage-driver.ts
216
- var import_node_child_process2 = require("child_process");
217
380
  var import_node_fs2 = require("fs");
218
- var import_node_os2 = require("os");
219
- var import_node_path2 = require("path");
381
+ var import_node_os4 = require("os");
382
+ var import_node_path4 = require("path");
220
383
  var BUILDAH_GRAPH_ROOT = "/vercel/.containers/storage";
221
384
  var BUILDAH_RUN_ROOT = "/run/containers/storage";
222
385
  var REQUIRED_BUILD_CONTAINER_DRIVER = "overlay";
223
- async function hasBinary(name) {
224
- try {
225
- await run("which", [name], { quiet: true });
226
- return true;
227
- } catch {
228
- return false;
229
- }
230
- }
231
386
  var cachedStorageDriver;
232
387
  function selectStorageDriver() {
233
388
  if (!cachedStorageDriver) {
234
389
  cachedStorageDriver = (async () => {
235
390
  const override = readString(process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER);
236
- if (override) {
391
+ if (override)
237
392
  return override;
238
- }
239
- if (isBuildContainer()) {
393
+ if (isBuildContainer())
240
394
  return void 0;
241
- }
242
- if (await hasBinary("fuse-overlayfs") && (0, import_node_fs2.existsSync)("/dev/fuse")) {
395
+ if (await hasCommand("fuse-overlayfs") && (0, import_node_fs2.existsSync)("/dev/fuse")) {
243
396
  return "fuse-overlayfs";
244
397
  }
245
398
  return "vfs";
@@ -253,8 +406,8 @@ short-name-mode = "permissive"
253
406
  var cachedRegistriesConfPath;
254
407
  function buildahRegistriesConfPath() {
255
408
  if (!cachedRegistriesConfPath) {
256
- const dir = (0, import_node_fs2.mkdtempSync)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "vercel-container-registries-"));
257
- cachedRegistriesConfPath = (0, import_node_path2.join)(dir, "registries.conf");
409
+ const dir = (0, import_node_fs2.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "vercel-container-registries-"));
410
+ cachedRegistriesConfPath = (0, import_node_path4.join)(dir, "registries.conf");
258
411
  (0, import_node_fs2.writeFileSync)(cachedRegistriesConfPath, BUILDAH_REGISTRIES_CONF);
259
412
  }
260
413
  return cachedRegistriesConfPath;
@@ -266,9 +419,8 @@ async function buildahStorageArgs() {
266
419
  "--registries-conf",
267
420
  buildahRegistriesConfPath()
268
421
  ];
269
- if (!driver) {
422
+ if (!driver)
270
423
  return [...rootArgs, ...registriesArgs];
271
- }
272
424
  if (driver === "fuse-overlayfs") {
273
425
  return [
274
426
  ...rootArgs,
@@ -281,121 +433,9 @@ async function buildahStorageArgs() {
281
433
  }
282
434
  return [...rootArgs, ...registriesArgs, "--storage-driver", driver];
283
435
  }
284
- async function runBuildah(args, opts = {}) {
285
- const fullArgs = [...await buildahStorageArgs(), ...args];
286
- debug(`exec: buildah ${fullArgs.join(" ")}`);
287
- const start = Date.now();
288
- try {
289
- const result = await run("buildah", fullArgs, opts);
290
- debug(`buildah ${args[0]} finished in ${Date.now() - start}ms`);
291
- return result;
292
- } catch (error) {
293
- debug(`buildah ${args[0]} failed after ${Date.now() - start}ms`);
294
- throw error;
295
- }
296
- }
297
- async function startBuildahInBackground(args) {
298
- const fullArgs = [...await buildahStorageArgs(), ...args];
299
- debug(`exec in the background: buildah ${fullArgs.join(" ")}`);
300
- const start = Date.now();
301
- const child = (0, import_node_child_process2.spawn)("buildah", fullArgs, {
302
- detached: true,
303
- stdio: "ignore"
304
- });
305
- child.on(
306
- "error",
307
- (error) => debug(`background buildah ${args[0]} failed: ${error.message}`)
308
- );
309
- child.on(
310
- "exit",
311
- (code) => debug(
312
- `background buildah ${args[0]} exited with code ${code} after ${Date.now() - start}ms`
313
- )
314
- );
315
- child.unref();
316
- }
317
- async function readBuildahStoreInfo() {
318
- const { stdout } = await runBuildah(["info"], { quiet: true });
319
- const store = JSON.parse(stdout).store;
320
- if (!store) {
321
- return void 0;
322
- }
323
- const graphStatus = store.GraphStatus;
324
- return {
325
- graphRoot: String(store.GraphRoot ?? ""),
326
- runRoot: String(store.RunRoot ?? ""),
327
- driver: String(store.GraphDriverName ?? ""),
328
- backingFs: String(
329
- graphStatus?.["Backing Filesystem"] ?? graphStatus?.["Backing filesystem"] ?? ""
330
- )
331
- };
332
- }
333
- async function assertBuildContainerStorage(log = () => {
334
- }) {
335
- if (!isBuildContainer()) {
336
- return;
337
- }
338
- if (readString(process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER)) {
339
- return;
340
- }
341
- const strict = Boolean(readString(process.env.VERCEL_VCR_STRICT_STORAGE));
342
- let storeInfo;
343
- try {
344
- storeInfo = await readBuildahStoreInfo();
345
- } catch (err) {
346
- const message = `Could not verify buildah storage via \`buildah info\`: ${err.message}`;
347
- if (strict) {
348
- throw new Error(message);
349
- }
350
- log(message);
351
- return;
352
- }
353
- if (!storeInfo) {
354
- const message = "Could not verify buildah storage: `buildah info` returned no store data.";
355
- if (strict) {
356
- throw new Error(message);
357
- }
358
- log(message);
359
- return;
360
- }
361
- const problems = [];
362
- if (storeInfo.driver !== REQUIRED_BUILD_CONTAINER_DRIVER) {
363
- problems.push(
364
- `storage driver is "${storeInfo.driver}", expected "${REQUIRED_BUILD_CONTAINER_DRIVER}"`
365
- );
366
- }
367
- if (storeInfo.graphRoot !== BUILDAH_GRAPH_ROOT) {
368
- problems.push(
369
- `graphRoot is "${storeInfo.graphRoot}", expected the mounted volume "${BUILDAH_GRAPH_ROOT}"`
370
- );
371
- }
372
- if (storeInfo.backingFs && storeInfo.backingFs === "overlayfs") {
373
- problems.push(
374
- `backing filesystem is "${storeInfo.backingFs}" (the overlay rootfs), not the mounted volume`
375
- );
376
- }
377
- const summary = `buildah storage: driver=${storeInfo.driver} graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} backingFs=${storeInfo.backingFs || "?"}`;
378
- if (problems.length === 0) {
379
- log(`${summary} \u2014 verified`);
380
- return;
381
- }
382
- const detail = `${summary}
383
- Problems: ${problems.join("; ")}.
384
- Expected the native overlay driver with the graphroot under the XFS \`/vercel\` cell volume (requires vercel/hive#2310 capabilities + the storage.conf from vercel/api#76567).`;
385
- if (strict) {
386
- throw new Error(
387
- `Container build storage is not configured as intended.
388
- ${detail}`
389
- );
390
- }
391
- log(
392
- `${detail}
393
- Continuing (set VERCEL_VCR_STRICT_STORAGE=1 to fail builds).`
394
- );
395
- }
396
436
 
397
437
  // src/oidc.ts
398
- var import_node_crypto = require("crypto");
438
+ var import_node_crypto2 = require("crypto");
399
439
  function parseOidcToken(token) {
400
440
  const parts = token.split(".");
401
441
  if (parts.length !== 3) {
@@ -426,7 +466,7 @@ function decodeOidcClaims(token) {
426
466
  function tokenFingerprint(token) {
427
467
  if (!token)
428
468
  return "absent";
429
- const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
469
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(token).digest("hex").slice(0, 8);
430
470
  return `present(len=${token.length}, sha256=${sha})`;
431
471
  }
432
472
  function debugTokenClaims(label, token) {
@@ -720,206 +760,266 @@ async function ensureRepository(repository, token, claims, span) {
720
760
  }
721
761
 
722
762
  // src/engines/buildah.ts
723
- function logLayerCacheSummary(output) {
724
- const steps = (output.match(/^STEP\s+\d+\/\d+:/gm) || []).length;
725
- const cached = (output.match(/-->\s+Using cache\b/gi) || []).length;
726
- if (steps === 0) {
727
- return;
763
+ function localImageRef(params) {
764
+ const { imageRef } = params;
765
+ if (!params.useBuildahStoreCache) {
766
+ return imageRef;
728
767
  }
729
- const built = Math.max(steps - cached, 0);
730
- info(`layer cache: ${cached}/${steps} steps reused, ${built} rebuilt`);
768
+ return `${imageRef.slice(0, imageRef.lastIndexOf(":"))}:vercel-buildah-store`;
731
769
  }
732
- var buildahEngine = {
733
- name: "buildah",
734
- async ensureReady(span) {
735
- try {
736
- const storageDriver = await selectStorageDriver();
737
- const { stdout } = await runBuildah(["--version"], { quiet: true });
738
- span?.setAttributes({
739
- "buildah.version": stdout.trim().split("\n")[0],
740
- "buildah.storage_driver": storageDriver ?? "storage.conf"
741
- });
742
- } catch (err) {
743
- const message = err.message;
744
- if (/Command not found/i.test(message)) {
745
- throw new Error(
746
- isBuildContainer() ? "The `buildah` CLI is not available in this build container. Install buildah (via SPAL) in the build image." : "Buildah was not found on your PATH. Install buildah or run the build on Vercel where the build container provides it."
747
- );
748
- }
749
- throw err;
750
- }
751
- },
752
- async logDiagnostics(span) {
770
+ function pullFlags(params) {
771
+ if (params.baseImageIsLocal) {
772
+ return ["--pull=never"];
773
+ }
774
+ return params.useBuildahStoreCache ? ["--pull=always"] : [];
775
+ }
776
+ async function deploymentSourceDateEpoch() {
777
+ if ((0, import_build_utils3.resolveBuildConcurrency)() <= 1) {
778
+ return void 0;
779
+ }
780
+ if (readString(process.env.SOURCE_DATE_EPOCH?.trim()) !== void 0) {
781
+ return void 0;
782
+ }
783
+ try {
784
+ const path4 = (0, import_node_path5.join)(
785
+ await createBuildahStoreStateDirectory(),
786
+ "source-date-epoch"
787
+ );
788
+ const candidate = `${path4}.${(0, import_node_crypto3.randomUUID)()}`;
789
+ await (0, import_promises5.writeFile)(candidate, `${Math.floor(Date.now() / 1e3)}
790
+ `, {
791
+ flag: "wx",
792
+ mode: 384
793
+ });
753
794
  try {
754
- const storageDriver = await selectStorageDriver();
755
- const version2 = (await runBuildah(["--version"], { quiet: true })).stdout.trim();
756
- info(
757
- `buildah: ${version2.split("\n")[0] ?? version2} (storage-driver=${storageDriver ?? "storage.conf"})`
758
- );
759
- span?.setAttributes({
760
- "container.engine": "buildah",
761
- "buildah.version": toTag(version2.split("\n")[0]),
762
- "buildah.storage_driver": toTag(storageDriver ?? "storage.conf")
763
- });
764
- } catch (err) {
765
- debug(`buildah diagnostics unavailable: ${err.message}`);
795
+ await (0, import_promises5.link)(candidate, path4);
796
+ } catch (error) {
797
+ if (error.code !== "EEXIST")
798
+ throw error;
799
+ } finally {
800
+ await (0, import_promises5.rm)(candidate, { force: true });
766
801
  }
767
- },
768
- async withRuntime(_span, fn) {
769
- return fn();
770
- },
771
- async build(params) {
772
- try {
773
- const { stdout: stdout2 } = await runBuildah(["images", "--quiet"], {
774
- quiet: true
775
- });
776
- const imageCount = stdout2.trim() ? stdout2.trim().split("\n").length : 0;
777
- info(
778
- imageCount > 0 ? `layer store: warm (${imageCount} image(s) present before build)` : "layer store: cold (no images present before build)"
779
- );
780
- } catch (err) {
781
- debug(`could not read store warmth: ${err.message}`);
802
+ const epoch = (await (0, import_promises5.readFile)(path4, "utf8")).trim();
803
+ if (!/^[0-9]+$/.test(epoch)) {
804
+ throw new Error(`invalid value ${JSON.stringify(epoch)}`);
782
805
  }
783
- const { stdout, stderr } = await runBuildah([
784
- "build",
785
- "--platform",
786
- TARGET_PLATFORM,
787
- // Commit and cache a layer per Dockerfile instruction so unchanged steps
788
- // (base image, dependency installs, etc.) can be reused on later builds
789
- // when the image store is warm. Without this buildah squashes the build
790
- // and no per-step caching happens.
791
- "--layers",
792
- // Use the host network namespace for RUN steps. The build runs inside a
793
- // restricted Hive cell that cannot program iptables, so buildah's default
794
- // rootless networking (netavark) fails with
795
- // "netavark: iptables ... Could not fetch rule set generation id".
796
- // Host networking skips per-container network setup and reuses the cell's
797
- // existing egress.
798
- "--network",
799
- "host",
800
- ...buildArgFlags(params),
801
- "-t",
802
- params.imageRef,
803
- "-f",
804
- params.dockerfilePath,
805
- params.contextDir
806
- ]);
807
- logLayerCacheSummary(`${stdout}
808
- ${stderr}`);
809
- },
810
- async login(params) {
811
- try {
812
- await runBuildah(
813
- [
814
- "login",
815
- params.registry,
816
- "--username",
817
- params.username,
818
- "--password-stdin"
819
- ],
820
- { input: params.token, quiet: !DEBUG }
806
+ return epoch;
807
+ } catch (error) {
808
+ debug(
809
+ `could not share a Buildah source date epoch: ${error.message}`
810
+ );
811
+ return void 0;
812
+ }
813
+ }
814
+ function createBuildahEngine(client) {
815
+ return {
816
+ name: "buildah",
817
+ async ensureReady(span) {
818
+ try {
819
+ const storageDriver = await selectStorageDriver();
820
+ const version2 = await client.version();
821
+ span?.setAttributes({
822
+ "buildah.version": version2.split("\n")[0],
823
+ "buildah.storage_driver": storageDriver ?? "storage.conf"
824
+ });
825
+ } catch (err) {
826
+ const message = err.message;
827
+ if (/Command not found/i.test(message)) {
828
+ throw new Error(
829
+ isBuildContainer() ? "The `buildah` CLI is not available in this build container. Install buildah (via SPAL) in the build image." : "Buildah was not found on your PATH. Install buildah or run the build on Vercel where the build container provides it."
830
+ );
831
+ }
832
+ throw err;
833
+ }
834
+ },
835
+ async logDiagnostics(span) {
836
+ try {
837
+ const storageDriver = await selectStorageDriver();
838
+ const version2 = await client.version();
839
+ info(
840
+ `buildah: ${version2.split("\n")[0] ?? version2} (storage-driver=${storageDriver ?? "storage.conf"})`
841
+ );
842
+ span?.setAttributes({
843
+ "container.engine": "buildah",
844
+ "buildah.version": toTag(version2.split("\n")[0]),
845
+ "buildah.storage_driver": toTag(storageDriver ?? "storage.conf")
846
+ });
847
+ } catch (err) {
848
+ debug(`buildah diagnostics unavailable: ${err.message}`);
849
+ }
850
+ },
851
+ async withRuntime(_span, fn) {
852
+ return fn();
853
+ },
854
+ async build(params) {
855
+ try {
856
+ const { stdout: stdout2 } = await client.run(["images", "--quiet"], {
857
+ quiet: true
858
+ });
859
+ const imageCount = stdout2.trim() ? stdout2.trim().split("\n").length : 0;
860
+ info(
861
+ imageCount > 0 ? `Buildah store: warm (${imageCount} image(s) present before build)` : "Buildah store: cold (no images present before build)"
862
+ );
863
+ } catch (err) {
864
+ debug(`could not read store warmth: ${err.message}`);
865
+ }
866
+ const sourceDateEpoch = await deploymentSourceDateEpoch();
867
+ if (sourceDateEpoch) {
868
+ info(
869
+ `Buildah source date epoch: ${sourceDateEpoch} (shared by this deployment's concurrent service builds)`
870
+ );
871
+ }
872
+ const usage = await BuildahImageUsage.begin();
873
+ const { stdout, stderr } = await client.run([
874
+ "build",
875
+ "--platform",
876
+ TARGET_PLATFORM,
877
+ // Commit and cache a layer per Dockerfile instruction so unchanged steps
878
+ // (base image, dependency installs, etc.) can be reused on later builds
879
+ // when the image store is warm. Without this buildah squashes the build
880
+ // and no per-step caching happens.
881
+ "--layers",
882
+ // With --layers, Buildah leaves the working container of a FROM-only
883
+ // stage behind, which stops the store cache from being saved.
884
+ "--force-rm",
885
+ ...sourceDateEpoch ? ["--source-date-epoch", sourceDateEpoch] : [],
886
+ ...pullFlags(params),
887
+ // Use the host network namespace for RUN steps. The build runs inside a
888
+ // restricted Hive cell that cannot program iptables, so buildah's default
889
+ // rootless networking (netavark) fails with
890
+ // "netavark: iptables ... Could not fetch rule set generation id".
891
+ // Host networking skips per-container network setup and reuses the cell's
892
+ // existing egress.
893
+ "--network",
894
+ "host",
895
+ ...buildArgFlags(params),
896
+ "-t",
897
+ localImageRef(params),
898
+ "-f",
899
+ params.dockerfilePath,
900
+ params.contextDir
901
+ ]);
902
+ await usage?.record(
903
+ () => imageIdsFromBuildOutput(`${stdout}
904
+ ${stderr}`)
821
905
  );
822
- } catch (err) {
823
- const message = err.message;
824
- if (/denied|forbidden|unauthorized|401|403/i.test(message)) {
825
- throw new Error(
826
- formatVcrAuthError(
906
+ return localImageRef(params);
907
+ },
908
+ async login(params) {
909
+ try {
910
+ await client.run(
911
+ [
912
+ "login",
827
913
  params.registry,
914
+ "--username",
828
915
  params.username,
829
- `Underlying error: ${message}`
830
- )
916
+ "--password-stdin"
917
+ ],
918
+ { input: params.token, quiet: !DEBUG }
831
919
  );
920
+ } catch (err) {
921
+ const message = err.message;
922
+ if (/denied|forbidden|unauthorized|401|403/i.test(message)) {
923
+ throw new Error(
924
+ formatVcrAuthError(
925
+ params.registry,
926
+ params.username,
927
+ `Underlying error: ${message}`
928
+ )
929
+ );
930
+ }
931
+ throw err;
832
932
  }
833
- throw err;
834
- }
835
- },
836
- async verifyStorage(span) {
837
- await assertBuildContainerStorage((message) => {
838
- info(message);
839
- const info0 = message.split("\n")[0];
840
- span?.setAttributes({ "buildah.storage.verify": info0 });
841
- });
842
- },
843
- async reportStorage(span) {
844
- if (!DEBUG) {
845
- return;
846
- }
847
- try {
848
- const storeInfo = await readBuildahStoreInfo();
849
- if (!storeInfo) {
850
- debug("buildah info: no `store` field in output");
933
+ },
934
+ async verifyStorage(span) {
935
+ await client.verifyStorage((message) => {
936
+ info(message);
937
+ const info0 = message.split("\n")[0];
938
+ span?.setAttributes({ "buildah.storage.verify": info0 });
939
+ });
940
+ },
941
+ async reportStorage(span) {
942
+ if (!DEBUG) {
851
943
  return;
852
944
  }
853
- info(
854
- `buildah storage: graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} driver=${storeInfo.driver} backingFs=${storeInfo.backingFs || "?"}`
855
- );
856
- span?.setAttributes({
857
- "buildah.storage.graph_root": storeInfo.graphRoot,
858
- "buildah.storage.run_root": storeInfo.runRoot,
859
- "buildah.storage.driver": storeInfo.driver,
860
- "buildah.storage.backing_fs": storeInfo.backingFs
861
- });
862
- } catch (err) {
863
- debug(`buildah storage report unavailable: ${err.message}`);
864
- }
865
- },
866
- async push(params) {
867
- const digestDir = (0, import_node_fs3.mkdtempSync)((0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "vercel-container-digest-"));
868
- const digestFile = (0, import_node_path3.join)(digestDir, "digest");
869
- const zstdEnabled = !readString(process.env.VERCEL_VCR_DISABLE_ZSTD);
870
- const zstdArgs = zstdEnabled ? [
871
- "--compression-format",
872
- "zstd",
873
- "--compression-level",
874
- "3",
875
- "--force-compression",
876
- "--format",
877
- "oci"
878
- ] : [];
879
- info(
880
- `pushing ${params.imageRef} ` + (zstdEnabled ? "with zstd compression (level=3, force, oci)" : "with default compression (zstd disabled)")
881
- );
882
- const pushStart = Date.now();
883
- let digest;
884
- try {
885
- await runBuildah([
886
- "push",
887
- ...zstdArgs,
888
- "--digestfile",
889
- digestFile,
890
- params.imageRef
891
- ]);
892
- digest = readDigestFile(digestFile);
893
- } catch (err) {
894
- const message = err.message;
895
- if (/denied|forbidden|unauthorized|not found|401|403|404/i.test(message)) {
896
- throw new Error(
897
- [
898
- `Pushing ${params.imageRef} was denied.`,
899
- "",
900
- `The build tried to ensure the "${params.repository}" repository exists, but`,
901
- "the push was still rejected. Verify access (or create the repository under",
902
- "your project's Sandboxes \u2192 Container Registry tab), then re-run the build.",
903
- "",
904
- `Underlying error: ${message}`
905
- ].join("\n")
945
+ try {
946
+ const storeInfo = await client.info();
947
+ if (!storeInfo) {
948
+ debug("buildah info: no `store` field in output");
949
+ return;
950
+ }
951
+ info(
952
+ `buildah storage: graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} driver=${storeInfo.driver} backingFs=${storeInfo.backingFs || "?"}`
906
953
  );
954
+ span?.setAttributes({
955
+ "buildah.storage.graph_root": storeInfo.graphRoot,
956
+ "buildah.storage.run_root": storeInfo.runRoot,
957
+ "buildah.storage.driver": storeInfo.driver,
958
+ "buildah.storage.backing_fs": storeInfo.backingFs
959
+ });
960
+ } catch (err) {
961
+ debug(`buildah storage report unavailable: ${err.message}`);
962
+ }
963
+ },
964
+ async push(params) {
965
+ const digestDir = (0, import_node_fs3.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-container-digest-"));
966
+ const digestFile = (0, import_node_path5.join)(digestDir, "digest");
967
+ const zstdEnabled = !readString(process.env.VERCEL_VCR_DISABLE_ZSTD);
968
+ const zstdArgs = zstdEnabled ? [
969
+ "--compression-format",
970
+ "zstd",
971
+ "--compression-level",
972
+ "3",
973
+ "--force-compression",
974
+ "--format",
975
+ "oci"
976
+ ] : [];
977
+ info(
978
+ `pushing ${params.imageRef} ` + (zstdEnabled ? "with zstd compression (level=3, force, oci)" : "with default compression (zstd disabled)")
979
+ );
980
+ const pushStart = Date.now();
981
+ let digest;
982
+ try {
983
+ await client.run([
984
+ "push",
985
+ ...zstdArgs,
986
+ "--digestfile",
987
+ digestFile,
988
+ localImageRef(params),
989
+ ...params.useBuildahStoreCache ? [`docker://${params.imageRef}`] : []
990
+ ]);
991
+ digest = readDigestFile(digestFile);
992
+ } catch (err) {
993
+ const message = err.message;
994
+ if (/denied|forbidden|unauthorized|not found|401|403|404/i.test(message)) {
995
+ throw new Error(
996
+ [
997
+ `Pushing ${params.imageRef} was denied.`,
998
+ "",
999
+ `The build tried to ensure the "${params.repository}" repository exists, but`,
1000
+ "the push was still rejected. Verify access (or create the repository under",
1001
+ "your project's Sandboxes \u2192 Container Registry tab), then re-run the build.",
1002
+ "",
1003
+ `Underlying error: ${message}`
1004
+ ].join("\n")
1005
+ );
1006
+ }
1007
+ throw err;
1008
+ } finally {
1009
+ (0, import_node_fs3.rmSync)(digestDir, { recursive: true, force: true });
907
1010
  }
908
- throw err;
909
- } finally {
910
- (0, import_node_fs3.rmSync)(digestDir, { recursive: true, force: true });
911
- }
912
- debug(`push completed in ${Date.now() - pushStart}ms`);
913
- if (DIGEST_RE.test(digest)) {
914
- debug(`pushed digest ${digest}`);
915
- return digest;
1011
+ debug(`push completed in ${Date.now() - pushStart}ms`);
1012
+ if (DIGEST_RE.test(digest)) {
1013
+ debug(`pushed digest ${digest}`);
1014
+ return digest;
1015
+ }
1016
+ debug(
1017
+ `--digestfile did not contain a digest (${JSON.stringify(digest)}) \u2014 resolving registry manifest`
1018
+ );
1019
+ return resolvePushedImageDigest(params);
916
1020
  }
917
- debug(
918
- `--digestfile did not contain a digest (${JSON.stringify(digest)}) \u2014 resolving registry manifest`
919
- );
920
- return resolvePushedImageDigest(params);
921
- }
922
- };
1021
+ };
1022
+ }
923
1023
  function readDigestFile(path4) {
924
1024
  try {
925
1025
  return (0, import_node_fs3.readFileSync)(path4, "utf8").trim();
@@ -930,19 +1030,11 @@ function readDigestFile(path4) {
930
1030
  }
931
1031
 
932
1032
  // src/engines/docker.ts
933
- var import_node_child_process3 = require("child_process");
1033
+ var import_node_child_process2 = require("child_process");
934
1034
  function runDocker(args, opts = {}) {
935
1035
  debug(`exec: docker ${args.join(" ")}`);
936
1036
  return run("docker", args, opts);
937
1037
  }
938
- async function hasBinary2(name) {
939
- try {
940
- await run("which", [name], { quiet: true });
941
- return true;
942
- } catch {
943
- return false;
944
- }
945
- }
946
1038
  async function isDockerDaemonReachable() {
947
1039
  try {
948
1040
  await run("docker", ["version", "--format", "{{.Server.Version}}"], {
@@ -966,7 +1058,7 @@ async function startDockerDaemon(span) {
966
1058
  span?.setAttributes({ "docker.storage_driver": driver });
967
1059
  step(`Starting Docker daemon (storage-driver=${driver})`);
968
1060
  debug(`exec: dockerd ${args.join(" ")}`);
969
- const child = (0, import_node_child_process3.spawn)("dockerd", args, {
1061
+ const child = (0, import_node_child_process2.spawn)("dockerd", args, {
970
1062
  stdio: ["ignore", "pipe", "pipe"]
971
1063
  });
972
1064
  let log = "";
@@ -1019,7 +1111,7 @@ async function startDockerDaemon(span) {
1019
1111
  ].join("\n")
1020
1112
  );
1021
1113
  }
1022
- await new Promise((resolve4) => setTimeout(resolve4, 500));
1114
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
1023
1115
  }
1024
1116
  }
1025
1117
  async function stopDockerDaemon(daemon, span) {
@@ -1029,12 +1121,12 @@ async function stopDockerDaemon(daemon, span) {
1029
1121
  }
1030
1122
  step("Stopping Docker daemon");
1031
1123
  const stopTimeoutMs = Number(process.env.VERCEL_VCR_DOCKERD_STOP_TIMEOUT_MS) || 1e4;
1032
- await new Promise((resolve4) => {
1124
+ await new Promise((resolve5) => {
1033
1125
  let settled = false;
1034
1126
  const finish = () => {
1035
1127
  if (!settled) {
1036
1128
  settled = true;
1037
- resolve4();
1129
+ resolve5();
1038
1130
  }
1039
1131
  };
1040
1132
  child.once("exit", finish);
@@ -1062,7 +1154,7 @@ async function withManagedDaemon(span, fn) {
1062
1154
  if (await isDockerDaemonReachable()) {
1063
1155
  return fn();
1064
1156
  }
1065
- if (!await hasBinary2("dockerd")) {
1157
+ if (!await hasCommand("dockerd")) {
1066
1158
  return fn();
1067
1159
  }
1068
1160
  const daemon = await withSpan(
@@ -1161,6 +1253,17 @@ ${version2.trim()}`);
1161
1253
  params.dockerfilePath,
1162
1254
  params.contextDir
1163
1255
  ]);
1256
+ return params.imageRef;
1257
+ },
1258
+ async onBuildTriggers(imageRef) {
1259
+ const { stdout } = await runDocker([
1260
+ "image",
1261
+ "inspect",
1262
+ "--format",
1263
+ "{{json .Config.OnBuild}}",
1264
+ imageRef
1265
+ ]);
1266
+ return JSON.parse(stdout) ?? [];
1164
1267
  },
1165
1268
  async login(params) {
1166
1269
  try {
@@ -1223,41 +1326,163 @@ ${version2.trim()}`);
1223
1326
  };
1224
1327
 
1225
1328
  // src/engines/index.ts
1226
- function selectContainerEngine() {
1329
+ function selectContainerEngine(client) {
1227
1330
  const override = readString(
1228
1331
  process.env.VERCEL_CONTAINER_ENGINE
1229
1332
  )?.toLowerCase();
1230
1333
  if (override === "buildah")
1231
- return buildahEngine;
1334
+ return createBuildahEngine(client);
1232
1335
  if (override === "docker")
1233
1336
  return dockerEngine;
1234
- return isBuildContainer() ? buildahEngine : dockerEngine;
1337
+ return isBuildContainer() ? createBuildahEngine(client) : dockerEngine;
1235
1338
  }
1236
1339
 
1340
+ // src/clients/buildah.ts
1341
+ var BuildahClient = class {
1342
+ constructor(lock) {
1343
+ this.lock = lock;
1344
+ }
1345
+ async run(args, options = {}) {
1346
+ const fullArgs = [...await buildahStorageArgs(), ...args];
1347
+ debug(`exec: buildah ${fullArgs.join(" ")}`);
1348
+ const start = Date.now();
1349
+ try {
1350
+ const [command, ...commandArgs] = await this.lock.wrap([
1351
+ "buildah",
1352
+ ...fullArgs
1353
+ ]);
1354
+ const result = await run(command, commandArgs, options);
1355
+ debug(`buildah ${args[0]} finished in ${Date.now() - start}ms`);
1356
+ return result;
1357
+ } catch (error) {
1358
+ debug(`buildah ${args[0]} failed after ${Date.now() - start}ms`);
1359
+ throw error;
1360
+ }
1361
+ }
1362
+ /** Version checks must not open the store or acquire its lock before restore. */
1363
+ version() {
1364
+ this.cachedVersion ??= run("buildah", ["--version"], { quiet: true }).then(({ stdout }) => stdout.trim()).catch((error) => {
1365
+ this.cachedVersion = void 0;
1366
+ throw error;
1367
+ });
1368
+ return this.cachedVersion;
1369
+ }
1370
+ /** Only stable store fields are cached; image and container counts are not. */
1371
+ info() {
1372
+ this.cachedStoreInfo ??= this.readStoreInfo().then(
1373
+ (store) => {
1374
+ if (!store)
1375
+ this.cachedStoreInfo = void 0;
1376
+ return store;
1377
+ },
1378
+ (error) => {
1379
+ this.cachedStoreInfo = void 0;
1380
+ throw error;
1381
+ }
1382
+ );
1383
+ return this.cachedStoreInfo;
1384
+ }
1385
+ /** Storage mismatches are diagnostic unless strict verification is requested. */
1386
+ async verifyStorage(log = () => {
1387
+ }) {
1388
+ if (!isBuildContainer())
1389
+ return;
1390
+ if (readString(process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER))
1391
+ return;
1392
+ const strict = Boolean(readString(process.env.VERCEL_VCR_STRICT_STORAGE));
1393
+ let storeInfo;
1394
+ try {
1395
+ storeInfo = await this.info();
1396
+ } catch (error) {
1397
+ const message = `Could not verify buildah storage via \`buildah info\`: ${error.message}`;
1398
+ if (strict)
1399
+ throw new Error(message);
1400
+ log(message);
1401
+ return;
1402
+ }
1403
+ if (!storeInfo) {
1404
+ const message = "Could not verify buildah storage: `buildah info` returned no store data.";
1405
+ if (strict)
1406
+ throw new Error(message);
1407
+ log(message);
1408
+ return;
1409
+ }
1410
+ const problems = [];
1411
+ if (storeInfo.driver !== REQUIRED_BUILD_CONTAINER_DRIVER) {
1412
+ problems.push(
1413
+ `storage driver is "${storeInfo.driver}", expected "${REQUIRED_BUILD_CONTAINER_DRIVER}"`
1414
+ );
1415
+ }
1416
+ if (storeInfo.graphRoot !== BUILDAH_GRAPH_ROOT) {
1417
+ problems.push(
1418
+ `graphRoot is "${storeInfo.graphRoot}", expected the mounted volume "${BUILDAH_GRAPH_ROOT}"`
1419
+ );
1420
+ }
1421
+ if (storeInfo.backingFs && storeInfo.backingFs === "overlayfs") {
1422
+ problems.push(
1423
+ `backing filesystem is "${storeInfo.backingFs}" (the overlay rootfs), not the mounted volume`
1424
+ );
1425
+ }
1426
+ const summary = `buildah storage: driver=${storeInfo.driver} graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} backingFs=${storeInfo.backingFs || "?"}`;
1427
+ if (problems.length === 0) {
1428
+ log(`${summary} \u2014 verified`);
1429
+ return;
1430
+ }
1431
+ const detail = `${summary}
1432
+ Problems: ${problems.join("; ")}.
1433
+ Expected the native overlay driver with the graphroot under the XFS \`/vercel\` cell volume (requires vercel/hive#2310 capabilities + the storage.conf from vercel/api#76567).`;
1434
+ if (strict) {
1435
+ throw new Error(
1436
+ `Container build storage is not configured as intended.
1437
+ ${detail}`
1438
+ );
1439
+ }
1440
+ log(
1441
+ `${detail}
1442
+ Continuing (set VERCEL_VCR_STRICT_STORAGE=1 to fail builds).`
1443
+ );
1444
+ }
1445
+ async readStoreInfo() {
1446
+ const { stdout } = await this.run(["info"], { quiet: true });
1447
+ const store = JSON.parse(stdout).store;
1448
+ if (!store)
1449
+ return void 0;
1450
+ const graphStatus = store.GraphStatus;
1451
+ return {
1452
+ graphRoot: String(store.GraphRoot ?? ""),
1453
+ runRoot: String(store.RunRoot ?? ""),
1454
+ driver: String(store.GraphDriverName ?? ""),
1455
+ backingFs: String(
1456
+ graphStatus?.["Backing Filesystem"] ?? graphStatus?.["Backing filesystem"] ?? ""
1457
+ )
1458
+ };
1459
+ }
1460
+ };
1461
+
1237
1462
  // src/buildpacks/build.ts
1238
1463
  var import_node_fs13 = require("fs");
1239
- var import_node_os7 = require("os");
1240
- var import_node_path13 = require("path");
1464
+ var import_node_os10 = require("os");
1465
+ var import_node_path17 = require("path");
1241
1466
 
1242
1467
  // src/buildpacks/cache.ts
1243
- var import_node_crypto2 = require("crypto");
1468
+ var import_node_crypto4 = require("crypto");
1244
1469
  var import_node_fs4 = require("fs");
1245
- var import_promises = require("fs/promises");
1246
- var import_node_path4 = require("path");
1470
+ var import_promises6 = require("fs/promises");
1471
+ var import_node_path6 = require("path");
1247
1472
  var BUILDPACK_CACHE_DIR = ".vercel/cache/buildpacks";
1248
1473
  var CNB_COMMITTED_DIR = "committed";
1249
1474
  var CNB_CACHE_METADATA_FILE = "io.buildpacks.lifecycle.cache.metadata";
1250
1475
  var IMAGE_ID_FILE = "image-id";
1251
1476
  function buildpackCacheDir(options) {
1252
- const root = (0, import_node_path4.resolve)(options.repoRootPath || options.workPath);
1253
- const app = (0, import_node_path4.relative)(root, (0, import_node_path4.resolve)(options.workPath)).split(import_node_path4.sep).join("/") || ".";
1477
+ const root = (0, import_node_path6.resolve)(options.repoRootPath || options.workPath);
1478
+ const app = (0, import_node_path6.relative)(root, (0, import_node_path6.resolve)(options.workPath)).split(import_node_path6.sep).join("/") || ".";
1254
1479
  const daemon = options.config?.daemon;
1255
1480
  const owner = daemon ? { daemon: daemon.name } : options.service?.name ?? null;
1256
- const key = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify([app, owner])).digest("hex").slice(0, 32);
1257
- return (0, import_node_path4.join)(root, BUILDPACK_CACHE_DIR, key);
1481
+ const key = (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([app, owner])).digest("hex").slice(0, 32);
1482
+ return (0, import_node_path6.join)(root, BUILDPACK_CACHE_DIR, key);
1258
1483
  }
1259
1484
  async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1260
- const committed = (0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR);
1485
+ const committed = (0, import_node_path6.join)(cacheDir, CNB_COMMITTED_DIR);
1261
1486
  const [copy, key] = await Promise.allSettled([
1262
1487
  copyCommitted(dir, cacheDir),
1263
1488
  cacheKey
@@ -1267,7 +1492,7 @@ async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1267
1492
  throw copy.reason;
1268
1493
  if (key.status === "rejected")
1269
1494
  throw key.reason;
1270
- if (readRegularFile((0, import_node_path4.join)(dir, IMAGE_ID_FILE)) !== key.value) {
1495
+ if (readRegularFile((0, import_node_path6.join)(dir, IMAGE_ID_FILE)) !== key.value) {
1271
1496
  (0, import_node_fs4.rmSync)(committed, { recursive: true, force: true });
1272
1497
  return { status: "miss", reason: "base_image_changed" };
1273
1498
  }
@@ -1282,11 +1507,11 @@ async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1282
1507
  }
1283
1508
  }
1284
1509
  async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1285
- const committed = (0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR);
1510
+ const committed = (0, import_node_path6.join)(cacheDir, CNB_COMMITTED_DIR);
1286
1511
  let metadata;
1287
1512
  try {
1288
1513
  assertDirectory(committed);
1289
- metadata = readRegularFile((0, import_node_path4.join)(committed, CNB_CACHE_METADATA_FILE));
1514
+ metadata = readRegularFile((0, import_node_path6.join)(committed, CNB_CACHE_METADATA_FILE));
1290
1515
  } catch (error) {
1291
1516
  const missing = error.code === "ENOENT";
1292
1517
  return {
@@ -1300,7 +1525,7 @@ async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1300
1525
  const bytes = sizeOf(committed);
1301
1526
  (0, import_node_fs4.rmSync)(dir, { recursive: true, force: true });
1302
1527
  await copyCommitted(cacheDir, dir);
1303
- (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(dir, IMAGE_ID_FILE), cacheKey);
1528
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dir, IMAGE_ID_FILE), cacheKey);
1304
1529
  return { status: "saved", bytes };
1305
1530
  } catch (error) {
1306
1531
  try {
@@ -1312,16 +1537,16 @@ async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1312
1537
  }
1313
1538
  function isSaved(dir, cacheKey, metadata) {
1314
1539
  try {
1315
- const committed = (0, import_node_path4.join)(dir, CNB_COMMITTED_DIR);
1540
+ const committed = (0, import_node_path6.join)(dir, CNB_COMMITTED_DIR);
1316
1541
  assertDirectory(committed);
1317
- return readRegularFile((0, import_node_path4.join)(dir, IMAGE_ID_FILE)) === cacheKey && readRegularFile((0, import_node_path4.join)(committed, CNB_CACHE_METADATA_FILE)) === metadata;
1542
+ return readRegularFile((0, import_node_path6.join)(dir, IMAGE_ID_FILE)) === cacheKey && readRegularFile((0, import_node_path6.join)(committed, CNB_CACHE_METADATA_FILE)) === metadata;
1318
1543
  } catch {
1319
1544
  return false;
1320
1545
  }
1321
1546
  }
1322
1547
  async function copyCommitted(from, to) {
1323
- const source = (0, import_node_path4.join)(from, CNB_COMMITTED_DIR);
1324
- const target = (0, import_node_path4.join)(to, CNB_COMMITTED_DIR);
1548
+ const source = (0, import_node_path6.join)(from, CNB_COMMITTED_DIR);
1549
+ const target = (0, import_node_path6.join)(to, CNB_COMMITTED_DIR);
1325
1550
  assertDirectory(source);
1326
1551
  (0, import_node_fs4.mkdirSync)(target, { recursive: true });
1327
1552
  const entries = (0, import_node_fs4.readdirSync)(source, { withFileTypes: true });
@@ -1331,9 +1556,9 @@ async function copyCommitted(from, to) {
1331
1556
  }
1332
1557
  const copies = await Promise.allSettled(
1333
1558
  entries.map(
1334
- (entry) => (0, import_promises.copyFile)(
1335
- (0, import_node_path4.join)(source, entry.name),
1336
- (0, import_node_path4.join)(target, entry.name),
1559
+ (entry) => (0, import_promises6.copyFile)(
1560
+ (0, import_node_path6.join)(source, entry.name),
1561
+ (0, import_node_path6.join)(target, entry.name),
1337
1562
  import_node_fs4.constants.COPYFILE_FICLONE
1338
1563
  )
1339
1564
  )
@@ -1344,12 +1569,12 @@ async function copyCommitted(from, to) {
1344
1569
  }
1345
1570
  function assertDirectory(path4) {
1346
1571
  if (!(0, import_node_fs4.lstatSync)(path4).isDirectory()) {
1347
- throw new Error(`${(0, import_node_path4.basename)(path4)} is not a directory`);
1572
+ throw new Error(`${(0, import_node_path6.basename)(path4)} is not a directory`);
1348
1573
  }
1349
1574
  }
1350
1575
  function readRegularFile(path4) {
1351
1576
  if (!(0, import_node_fs4.lstatSync)(path4).isFile())
1352
- throw notRegularFile((0, import_node_path4.basename)(path4));
1577
+ throw notRegularFile((0, import_node_path6.basename)(path4));
1353
1578
  return (0, import_node_fs4.readFileSync)(path4, "utf8");
1354
1579
  }
1355
1580
  function notRegularFile(name) {
@@ -1357,25 +1582,13 @@ function notRegularFile(name) {
1357
1582
  }
1358
1583
  function sizeOf(dir) {
1359
1584
  return (0, import_node_fs4.readdirSync)(dir).reduce(
1360
- (bytes, name) => bytes + (0, import_node_fs4.lstatSync)((0, import_node_path4.join)(dir, name)).size,
1585
+ (bytes, name) => bytes + (0, import_node_fs4.lstatSync)((0, import_node_path6.join)(dir, name)).size,
1361
1586
  0
1362
1587
  );
1363
1588
  }
1364
1589
  function firstLine(error) {
1365
1590
  return error.message.split("\n")[0];
1366
1591
  }
1367
- function formatBytes(bytes) {
1368
- if (bytes < 1024)
1369
- return `${bytes} B`;
1370
- const units = ["KiB", "MiB", "GiB"];
1371
- let value = bytes / 1024;
1372
- let unit = 0;
1373
- while (value >= 1024 && unit < units.length - 1) {
1374
- value /= 1024;
1375
- unit += 1;
1376
- }
1377
- return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
1378
- }
1379
1592
  function describeRestore(outcome) {
1380
1593
  return outcome.status === "hit" ? `restored buildpack cache (${formatBytes(outcome.bytes)})` : `no buildpack cache to restore (${outcome.reason})`;
1381
1594
  }
@@ -1406,11 +1619,11 @@ function recordSave(span, outcome, durationMs) {
1406
1619
  }
1407
1620
 
1408
1621
  // src/buildpacks/lifecycle/lifecycle.ts
1409
- var import_build_utils3 = require("@vercel/build-utils");
1410
- var import_node_crypto3 = require("crypto");
1622
+ var import_build_utils4 = require("@vercel/build-utils");
1623
+ var import_node_crypto5 = require("crypto");
1411
1624
  var import_node_fs5 = require("fs");
1412
- var import_node_os4 = require("os");
1413
- var import_node_path5 = require("path");
1625
+ var import_node_os6 = require("os");
1626
+ var import_node_path7 = require("path");
1414
1627
  var import_smol_toml = require("smol-toml");
1415
1628
  var import_tar = require("tar");
1416
1629
 
@@ -1561,39 +1774,39 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1561
1774
  const symlinks = /* @__PURE__ */ new Set();
1562
1775
  for (const [name, file] of Object.entries(files)) {
1563
1776
  const portable = name.replace(/\\/g, "/");
1564
- const normalized = import_node_path5.posix.normalize(portable).replace(/\/$/, "");
1565
- if (!normalized || normalized === "." || import_node_path5.win32.isAbsolute(name) || import_node_path5.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1777
+ const normalized = import_node_path7.posix.normalize(portable).replace(/\/$/, "");
1778
+ if (!normalized || normalized === "." || import_node_path7.win32.isAbsolute(name) || import_node_path7.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1566
1779
  throw new Error(`Invalid buildpack source path ${JSON.stringify(name)}.`);
1567
1780
  }
1568
1781
  staged[normalized] = file;
1569
- if (!(0, import_build_utils3.isSymbolicLink)(file.mode))
1782
+ if (!(0, import_build_utils4.isSymbolicLink)(file.mode))
1570
1783
  continue;
1571
- let target = (0, import_build_utils3.getSymlinkTarget)(file);
1784
+ let target = (0, import_build_utils4.getSymlinkTarget)(file);
1572
1785
  if (target === null && file.type === "FileRef") {
1573
- target = (await (0, import_build_utils3.streamToBuffer)(await file.toStreamAsync())).toString();
1786
+ target = (await (0, import_build_utils4.streamToBuffer)(await file.toStreamAsync())).toString();
1574
1787
  }
1575
1788
  if (!target) {
1576
1789
  throw new Error(`Cannot read buildpack source symlink ${name}.`);
1577
1790
  }
1578
- if ((0, import_node_path5.isAbsolute)(target)) {
1579
- target = (0, import_node_path5.relative)((0, import_node_path5.resolve)(workPath, import_node_path5.posix.dirname(normalized)), target) || ".";
1580
- } else if (import_node_path5.win32.isAbsolute(target)) {
1791
+ if ((0, import_node_path7.isAbsolute)(target)) {
1792
+ target = (0, import_node_path7.relative)((0, import_node_path7.resolve)(workPath, import_node_path7.posix.dirname(normalized)), target) || ".";
1793
+ } else if (import_node_path7.win32.isAbsolute(target)) {
1581
1794
  throw new Error(
1582
1795
  `Buildpack source symlink ${name} has an absolute target.`
1583
1796
  );
1584
1797
  }
1585
1798
  target = target.replace(/\\/g, "/");
1586
- const resolved = import_node_path5.posix.join(import_node_path5.posix.dirname(normalized), target);
1799
+ const resolved = import_node_path7.posix.join(import_node_path7.posix.dirname(normalized), target);
1587
1800
  if (resolved === ".." || resolved.startsWith("../")) {
1588
1801
  throw new Error(
1589
1802
  `Buildpack source symlink ${name} escapes the service root. Buildpack services must be self-contained.`
1590
1803
  );
1591
1804
  }
1592
- staged[normalized] = new import_build_utils3.FileBlob({ data: target, mode: file.mode });
1805
+ staged[normalized] = new import_build_utils4.FileBlob({ data: target, mode: file.mode });
1593
1806
  symlinks.add(normalized);
1594
1807
  }
1595
1808
  for (const name of Object.keys(staged)) {
1596
- for (let parent = import_node_path5.posix.dirname(name); parent !== "."; parent = import_node_path5.posix.dirname(parent)) {
1809
+ for (let parent = import_node_path7.posix.dirname(name); parent !== "."; parent = import_node_path7.posix.dirname(parent)) {
1597
1810
  if (symlinks.has(parent)) {
1598
1811
  throw new Error(
1599
1812
  `Buildpack source ${name} is inside symlink ${parent}.`
@@ -1601,17 +1814,17 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1601
1814
  }
1602
1815
  }
1603
1816
  const file = staged[name];
1604
- if (file.type === "FileFsRef" && !(0, import_build_utils3.isSymbolicLink)(file.mode)) {
1605
- const sourcePath = (0, import_node_path5.resolve)(file.fsPath);
1606
- const sourceRelative = (0, import_node_path5.relative)((0, import_node_path5.resolve)(workPath), sourcePath);
1607
- if (sourceRelative.startsWith(`..${import_node_path5.sep}`) || sourceRelative === ".." || (0, import_node_path5.isAbsolute)(sourceRelative)) {
1817
+ if (file.type === "FileFsRef" && !(0, import_build_utils4.isSymbolicLink)(file.mode)) {
1818
+ const sourcePath = (0, import_node_path7.resolve)(file.fsPath);
1819
+ const sourceRelative = (0, import_node_path7.relative)((0, import_node_path7.resolve)(workPath), sourcePath);
1820
+ if (sourceRelative.startsWith(`..${import_node_path7.sep}`) || sourceRelative === ".." || (0, import_node_path7.isAbsolute)(sourceRelative)) {
1608
1821
  continue;
1609
1822
  }
1610
1823
  const sourceRoot = import_node_fs5.realpathSync.native(workPath);
1611
1824
  const sourceTarget = import_node_fs5.realpathSync.native(sourcePath);
1612
- if (sourceTarget !== (0, import_node_path5.resolve)(sourceRoot, sourceRelative)) {
1613
- const target = (0, import_node_path5.relative)(sourceRoot, sourceTarget).split(import_node_path5.sep).join("/");
1614
- if (target === ".." || target.startsWith("../") || (0, import_node_path5.isAbsolute)(target) || !(target in staged)) {
1825
+ if (sourceTarget !== (0, import_node_path7.resolve)(sourceRoot, sourceRelative)) {
1826
+ const target = (0, import_node_path7.relative)(sourceRoot, sourceTarget).split(import_node_path7.sep).join("/");
1827
+ if (target === ".." || target.startsWith("../") || (0, import_node_path7.isAbsolute)(target) || !(target in staged)) {
1615
1828
  throw new Error(
1616
1829
  `Buildpack source ${name} follows a symlink to an excluded or outside-root file. Buildpack services must be self-contained.`
1617
1830
  );
@@ -1619,25 +1832,25 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1619
1832
  }
1620
1833
  }
1621
1834
  }
1622
- await (0, import_build_utils3.download)(staged, destinationDir);
1835
+ await (0, import_build_utils4.download)(staged, destinationDir);
1623
1836
  const root = import_node_fs5.realpathSync.native(destinationDir);
1624
1837
  for (const name of symlinks) {
1625
1838
  let target;
1626
1839
  try {
1627
- target = (0, import_node_path5.relative)(root, import_node_fs5.realpathSync.native((0, import_node_path5.join)(destinationDir, name)));
1840
+ target = (0, import_node_path7.relative)(root, import_node_fs5.realpathSync.native((0, import_node_path7.join)(destinationDir, name)));
1628
1841
  } catch {
1629
1842
  throw new Error(
1630
1843
  `Buildpack source symlink ${name} has no target in the staged service.`
1631
1844
  );
1632
1845
  }
1633
- if (target === ".." || target.startsWith(`..${import_node_path5.sep}`) || (0, import_node_path5.isAbsolute)(target)) {
1846
+ if (target === ".." || target.startsWith(`..${import_node_path7.sep}`) || (0, import_node_path7.isAbsolute)(target)) {
1634
1847
  throw new Error(
1635
1848
  `Buildpack source symlink ${name} escapes the staged service.`
1636
1849
  );
1637
1850
  }
1638
1851
  }
1639
1852
  }
1640
- function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.tmpdir)()) {
1853
+ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os6.tmpdir)()) {
1641
1854
  const environment = { ...buildEnv };
1642
1855
  delete environment.VERCEL_NODE_HOME;
1643
1856
  delete environment.VERCEL_ENTRYPOINT;
@@ -1651,14 +1864,14 @@ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.t
1651
1864
  if (config.command) {
1652
1865
  environment.VERCEL_COMMAND = JSON.stringify(config.command);
1653
1866
  }
1654
- const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-env-"));
1867
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)(parentDir, "vercel-cnb-env-"));
1655
1868
  (0, import_node_fs5.chmodSync)(dir, 493);
1656
1869
  for (const [key, value] of Object.entries(environment)) {
1657
1870
  if (!ENV_NAME_RE.test(key)) {
1658
1871
  debug(`skipping build env var with an unsupported name: ${key}`);
1659
1872
  continue;
1660
1873
  }
1661
- const file = (0, import_node_path5.join)(dir, key);
1874
+ const file = (0, import_node_path7.join)(dir, key);
1662
1875
  (0, import_node_fs5.writeFileSync)(file, value);
1663
1876
  (0, import_node_fs5.chmodSync)(file, 420);
1664
1877
  }
@@ -1723,7 +1936,7 @@ function lifecycleFailure(error, context) {
1723
1936
  { exitCode }
1724
1937
  );
1725
1938
  }
1726
- function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1939
+ function writeOrderDir(buildpack, parentDir = (0, import_node_os6.tmpdir)()) {
1727
1940
  const order = {
1728
1941
  order: [
1729
1942
  {
@@ -1734,9 +1947,9 @@ function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1734
1947
  }
1735
1948
  ]
1736
1949
  };
1737
- const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-order-"));
1950
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)(parentDir, "vercel-cnb-order-"));
1738
1951
  (0, import_node_fs5.chmodSync)(dir, 493);
1739
- const file = (0, import_node_path5.join)(dir, "order.toml");
1952
+ const file = (0, import_node_path7.join)(dir, "order.toml");
1740
1953
  (0, import_node_fs5.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
1741
1954
  (0, import_node_fs5.chmodSync)(file, 420);
1742
1955
  return dir;
@@ -1765,7 +1978,7 @@ async function downloadPinnedArchive(archive, destinationPath) {
1765
1978
  );
1766
1979
  }
1767
1980
  const contents = new Uint8Array(await response.arrayBuffer());
1768
- const checksum = (0, import_node_crypto3.createHash)("sha256").update(contents).digest("hex");
1981
+ const checksum = (0, import_node_crypto5.createHash)("sha256").update(contents).digest("hex");
1769
1982
  if (checksum !== archive.sha256) {
1770
1983
  throw new Error(
1771
1984
  `Checksum mismatch for ${archiveName(archive)}: expected ${archive.sha256}, received ${checksum}.`
@@ -1774,7 +1987,7 @@ async function downloadPinnedArchive(archive, destinationPath) {
1774
1987
  (0, import_node_fs5.writeFileSync)(destinationPath, contents);
1775
1988
  }
1776
1989
  async function fetchPinnedArchive(archive, destinationDir) {
1777
- const archivePath = (0, import_node_path5.join)(destinationDir, `${archive.sha256}.tgz`);
1990
+ const archivePath = (0, import_node_path7.join)(destinationDir, `${archive.sha256}.tgz`);
1778
1991
  try {
1779
1992
  await downloadPinnedArchive(archive, archivePath);
1780
1993
  (0, import_tar.extract)({ file: archivePath, cwd: destinationDir, sync: true });
@@ -1791,16 +2004,16 @@ function blobPath(layoutDir, digest) {
1791
2004
  if (!match) {
1792
2005
  throw new Error(`Unsupported OCI digest ${JSON.stringify(digest)}.`);
1793
2006
  }
1794
- return (0, import_node_path5.join)(layoutDir, "blobs", "sha256", match[1]);
2007
+ return (0, import_node_path7.join)(layoutDir, "blobs", "sha256", match[1]);
1795
2008
  }
1796
2009
  async function fetchBuildpackage(archive, cnbDir) {
1797
- const layoutDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os4.tmpdir)(), "vercel-cnb-buildpackage-"));
1798
- const archivePath = (0, import_node_path5.join)(layoutDir, "buildpackage.cnb");
2010
+ const layoutDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-buildpackage-"));
2011
+ const archivePath = (0, import_node_path7.join)(layoutDir, "buildpackage.cnb");
1799
2012
  try {
1800
2013
  await downloadPinnedArchive(archive, archivePath);
1801
2014
  await (0, import_tar.extract)({ file: archivePath, cwd: layoutDir });
1802
2015
  const index = readOciJson(
1803
- (0, import_node_path5.join)(layoutDir, "index.json")
2016
+ (0, import_node_path7.join)(layoutDir, "index.json")
1804
2017
  );
1805
2018
  const name = archiveName(archive);
1806
2019
  const manifestDigest = index.manifests?.[0]?.digest;
@@ -1822,7 +2035,7 @@ async function fetchBuildpackage(archive, cnbDir) {
1822
2035
  );
1823
2036
  }
1824
2037
  const layerPath = blobPath(layoutDir, layer.digest);
1825
- const actual = (0, import_node_crypto3.createHash)("sha256").update(new Uint8Array((0, import_node_fs5.readFileSync)(layerPath))).digest("hex");
2038
+ const actual = (0, import_node_crypto5.createHash)("sha256").update(new Uint8Array((0, import_node_fs5.readFileSync)(layerPath))).digest("hex");
1826
2039
  if (`sha256:${actual}` !== layer.digest) {
1827
2040
  throw new Error(
1828
2041
  `${name} layer ${layer.digest} hashes to sha256:${actual}.`
@@ -1859,14 +2072,14 @@ async function fetchDistribution(buildpack, cnbDir) {
1859
2072
  )
1860
2073
  ]);
1861
2074
  for (const entry of buildpack.buildpacks) {
1862
- const directory = (0, import_node_path5.join)(
2075
+ const directory = (0, import_node_path7.join)(
1863
2076
  cnbDir,
1864
2077
  "buildpacks",
1865
2078
  entry.id.replace("/", "_"),
1866
2079
  entry.version
1867
2080
  );
1868
2081
  for (const file of ["buildpack.toml", "bin/detect", "bin/build"]) {
1869
- if (!(0, import_node_fs5.statSync)((0, import_node_path5.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
2082
+ if (!(0, import_node_fs5.statSync)((0, import_node_path7.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
1870
2083
  throw new Error(
1871
2084
  `Buildpack ${entry.id}@${entry.version} is missing ${file}.`
1872
2085
  );
@@ -1891,20 +2104,113 @@ function creatorArgs(image, imageRef, options = {}) {
1891
2104
 
1892
2105
  // src/buildpacks/lifecycle/buildah.ts
1893
2106
  var import_node_fs6 = require("fs");
1894
- var import_node_crypto4 = require("crypto");
1895
- var import_node_os5 = require("os");
1896
- var import_node_path6 = require("path");
1897
- async function removeContainerInBackground(name) {
2107
+ var import_node_crypto6 = require("crypto");
2108
+ var import_node_os8 = require("os");
2109
+ var import_node_path10 = require("path");
2110
+
2111
+ // src/buildah-store-cache/cleanup.ts
2112
+ var import_node_child_process3 = require("child_process");
2113
+
2114
+ // src/buildah-store-cache/command/index.ts
2115
+ var import_node_path8 = require("path");
2116
+ var STORE_COMMAND_SCRIPT = (0, import_node_path8.join)(__dirname, "buildah-store-command.js");
2117
+ function storeCommand(request, script = STORE_COMMAND_SCRIPT) {
2118
+ return [process.execPath, script, JSON.stringify(request)];
2119
+ }
2120
+ async function runStoreCommand(request, lock) {
2121
+ const [command, ...args] = lock.exclusive(storeCommand(request));
2122
+ const { stdout } = await run(command, args, { quiet: true });
2123
+ return JSON.parse(stdout);
2124
+ }
2125
+
2126
+ // src/buildah-store-cache/lock.ts
2127
+ var import_node_os7 = require("os");
2128
+ var import_node_path9 = require("path");
2129
+ var FileLock = class {
2130
+ constructor(path4) {
2131
+ this.path = path4;
2132
+ }
2133
+ shared(command) {
2134
+ return this.wrap("--shared", command);
2135
+ }
2136
+ exclusive(command) {
2137
+ return this.wrap("--exclusive", command);
2138
+ }
2139
+ wrap(mode, command) {
2140
+ return ["flock", mode, "--close", this.path, ...command];
2141
+ }
2142
+ };
2143
+ var flockAvailable;
2144
+ async function buildahStoreLock() {
2145
+ if (!buildahStoreCacheEnabled()) {
2146
+ return void 0;
2147
+ }
2148
+ flockAvailable ??= hasCommand("flock");
2149
+ return await flockAvailable ? new FileLock((0, import_node_path9.join)((0, import_node_os7.tmpdir)(), "vercel-buildah-store.lock")) : void 0;
2150
+ }
2151
+ var sharedBuildahStoreLock = {
2152
+ async wrap(command) {
2153
+ const lock = await buildahStoreLock();
2154
+ return lock ? lock.shared(command) : command;
2155
+ }
2156
+ };
2157
+
2158
+ // src/buildah-store-cache/cleanup.ts
2159
+ async function startContainerCleanup(name) {
2160
+ const rmArgs = [...await buildahStorageArgs(), "rm", name];
2161
+ debug(`exec in the background: buildah ${rmArgs.join(" ")}`);
2162
+ const start = Date.now();
2163
+ const [command, ...commandArgs] = await cleanupCommand(name, rmArgs);
2164
+ const child = (0, import_node_child_process3.spawn)(command, commandArgs, {
2165
+ detached: true,
2166
+ stdio: "ignore"
2167
+ });
2168
+ child.on(
2169
+ "error",
2170
+ (error) => debug(`background buildah rm failed: ${error.message}`)
2171
+ );
2172
+ child.on(
2173
+ "exit",
2174
+ (code) => debug(
2175
+ `background buildah rm exited with code ${code} after ${Date.now() - start}ms`
2176
+ )
2177
+ );
2178
+ child.unref();
2179
+ }
2180
+ async function cleanupCommand(container, buildahArgs) {
2181
+ const record = await recordPendingCleanup(container);
2182
+ return sharedBuildahStoreLock.wrap(
2183
+ record ? storeCommand({ action: "cleanup", options: { record, buildahArgs } }) : ["buildah", ...buildahArgs]
2184
+ );
2185
+ }
2186
+ async function recordPendingCleanup(container) {
2187
+ if (!buildahStoreCacheEnabled()) {
2188
+ return void 0;
2189
+ }
2190
+ try {
2191
+ if (!CLEANUP_CONTAINER_RE.test(container)) {
2192
+ throw new Error("invalid Buildah cleanup container");
2193
+ }
2194
+ return await writeStateRecord(".cleanup", `${container}
2195
+ `);
2196
+ } catch (error) {
2197
+ debug(`could not record Buildah cleanup: ${error.message}`);
2198
+ return void 0;
2199
+ }
2200
+ }
2201
+
2202
+ // src/buildpacks/lifecycle/buildah.ts
2203
+ async function cleanUpBuildContainer(name) {
1898
2204
  try {
1899
- await startBuildahInBackground(["rm", name]);
2205
+ await startContainerCleanup(name);
1900
2206
  } catch (error) {
1901
2207
  debug(
1902
- `could not remove Buildah CNB container ${name}: ${error.message}`
2208
+ `could not clean up Buildah CNB container ${name}: ${error.message}`
1903
2209
  );
1904
2210
  }
1905
2211
  }
1906
- async function inspectBaseImageId(container) {
1907
- const { stdout } = await runBuildah(
2212
+ async function inspectBaseImageId(container, client) {
2213
+ const { stdout } = await client.run(
1908
2214
  ["inspect", "--type", "container", container],
1909
2215
  { quiet: true }
1910
2216
  );
@@ -1929,14 +2235,14 @@ function cacheScratchParent() {
1929
2235
  );
1930
2236
  }
1931
2237
  }
1932
- return (0, import_node_os5.tmpdir)();
2238
+ return (0, import_node_os8.tmpdir)();
1933
2239
  }
1934
2240
  function chownTree(root, uid, gid) {
1935
2241
  const stat = (0, import_node_fs6.lstatSync)(root);
1936
2242
  if (stat.isDirectory()) {
1937
2243
  (0, import_node_fs6.chmodSync)(root, 493);
1938
2244
  for (const entry of (0, import_node_fs6.readdirSync)(root)) {
1939
- chownTree((0, import_node_path6.join)(root, entry), uid, gid);
2245
+ chownTree((0, import_node_path10.join)(root, entry), uid, gid);
1940
2246
  }
1941
2247
  } else if (stat.isFile()) {
1942
2248
  (0, import_node_fs6.chmodSync)(root, 420);
@@ -1949,12 +2255,13 @@ var RegistrySetupFailure = class extends Error {
1949
2255
  this.reason = reason;
1950
2256
  }
1951
2257
  };
1952
- async function prepareBuildContainer(buildContainer, buildpack, params) {
2258
+ async function prepareBuildContainer(buildContainer, buildpack, params, client) {
1953
2259
  const start = Date.now();
1954
2260
  step(
1955
2261
  `Preparing ${buildpack.runtime} ${params.image.version} stock image and Vercel buildpacks`
1956
2262
  );
1957
- await runBuildah([
2263
+ const usage = await BuildahImageUsage.begin();
2264
+ await client.run([
1958
2265
  "from",
1959
2266
  "--pull=always",
1960
2267
  "--platform",
@@ -1963,7 +2270,10 @@ async function prepareBuildContainer(buildContainer, buildpack, params) {
1963
2270
  buildContainer,
1964
2271
  params.image.buildImage
1965
2272
  ]);
1966
- await runBuildah([
2273
+ await usage?.record(async () => [
2274
+ await inspectBaseImageId(buildContainer, client)
2275
+ ]);
2276
+ await client.run([
1967
2277
  "run",
1968
2278
  "--network",
1969
2279
  "host",
@@ -1973,7 +2283,7 @@ async function prepareBuildContainer(buildContainer, buildpack, params) {
1973
2283
  "-c",
1974
2284
  `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`
1975
2285
  ]);
1976
- await runBuildah([
2286
+ await client.run([
1977
2287
  "copy",
1978
2288
  "--chown",
1979
2289
  BUILD_USER,
@@ -1989,17 +2299,17 @@ async function fetchDistributionTimed(buildpack, cnbDir) {
1989
2299
  await fetchDistribution(buildpack, cnbDir);
1990
2300
  done(`fetched the CNB lifecycle and buildpacks in ${elapsed(start)}`);
1991
2301
  }
1992
- async function copySavedCache(dir, buildContainer, containerReady) {
2302
+ async function copySavedCache(dir, buildContainer, containerReady, client) {
1993
2303
  const start = Date.now();
1994
2304
  step("Restoring the buildpack cache");
1995
2305
  let scratch;
1996
2306
  try {
1997
- scratch = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)(cacheScratchParent(), "vercel-cnb-cache-"));
2307
+ scratch = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)(cacheScratchParent(), "vercel-cnb-cache-"));
1998
2308
  (0, import_node_fs6.chmodSync)(scratch, 493);
1999
- const cacheDir = (0, import_node_path6.join)(scratch, "cache");
2309
+ const cacheDir = (0, import_node_path10.join)(scratch, "cache");
2000
2310
  (0, import_node_fs6.mkdirSync)(cacheDir, { mode: 493 });
2001
2311
  const imageId = containerReady.then(
2002
- () => inspectBaseImageId(buildContainer)
2312
+ () => inspectBaseImageId(buildContainer, client)
2003
2313
  );
2004
2314
  const restored = await restoreBuildpackCache(dir, imageId, cacheDir);
2005
2315
  const baseImageId = await imageId;
@@ -2025,7 +2335,7 @@ async function copySavedCache(dir, buildContainer, containerReady) {
2025
2335
  };
2026
2336
  }
2027
2337
  }
2028
- async function buildAndPushWithLifecycle(buildpack, params, span) {
2338
+ async function buildAndPushWithLifecycle(buildpack, params, client, span) {
2029
2339
  const urls = distributionUrls(buildpack);
2030
2340
  return withSpan(
2031
2341
  span,
@@ -2041,20 +2351,20 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2041
2351
  "image.ref": params.imageRef
2042
2352
  },
2043
2353
  async (lifecycleSpan) => {
2044
- const suffix = `${process.pid}-${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`;
2354
+ const suffix = `${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
2045
2355
  const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
2046
2356
  let cnbDir;
2047
2357
  let reportDir;
2048
2358
  let orderDir;
2049
2359
  let platformEnvDir;
2050
2360
  let cacheScratch;
2051
- let containerRemoval;
2361
+ let containerCleanup;
2052
2362
  try {
2053
- reportDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-report-"));
2054
- const reportPath = (0, import_node_path6.join)(reportDir, "report.toml");
2363
+ reportDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)((0, import_node_os8.tmpdir)(), "vercel-cnb-report-"));
2364
+ const reportPath = (0, import_node_path10.join)(reportDir, "report.toml");
2055
2365
  (0, import_node_fs6.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
2056
2366
  (0, import_node_fs6.chmodSync)(reportDir, 493);
2057
- cnbDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)((0, import_node_os5.tmpdir)(), "vercel-cnb-"));
2367
+ cnbDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)((0, import_node_os8.tmpdir)(), "vercel-cnb-"));
2058
2368
  (0, import_node_fs6.chmodSync)(cnbDir, 493);
2059
2369
  orderDir = writeOrderDir(buildpack);
2060
2370
  platformEnvDir = writePlatformEnvDir(params.buildEnv, {
@@ -2066,13 +2376,19 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2066
2376
  const containerReady = prepareBuildContainer(
2067
2377
  buildContainer,
2068
2378
  buildpack,
2069
- params
2379
+ params,
2380
+ client
2070
2381
  );
2071
2382
  const [registry, container, distribution2, cacheCopy] = await Promise.allSettled([
2072
2383
  registrySetup,
2073
2384
  containerReady,
2074
2385
  fetchDistributionTimed(buildpack, cnbDir),
2075
- params.cache ? copySavedCache(params.cache.dir, buildContainer, containerReady) : void 0
2386
+ params.cache ? copySavedCache(
2387
+ params.cache.dir,
2388
+ buildContainer,
2389
+ containerReady,
2390
+ client
2391
+ ) : void 0
2076
2392
  ]);
2077
2393
  const copy = cacheCopy.status === "fulfilled" ? cacheCopy.value : void 0;
2078
2394
  cacheScratch = copy?.scratch;
@@ -2105,7 +2421,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2105
2421
  step(
2106
2422
  `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
2107
2423
  );
2108
- await runBuildah(
2424
+ await client.run(
2109
2425
  [
2110
2426
  "run",
2111
2427
  "--network",
@@ -2142,7 +2458,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2142
2458
  ],
2143
2459
  { env: lifecycleEnv }
2144
2460
  );
2145
- containerRemoval = removeContainerInBackground(buildContainer);
2461
+ containerCleanup = cleanUpBuildContainer(buildContainer);
2146
2462
  const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
2147
2463
  if (!digest) {
2148
2464
  throw new Error(
@@ -2179,7 +2495,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2179
2495
  span: lifecycleSpan
2180
2496
  });
2181
2497
  } finally {
2182
- containerRemoval ??= removeContainerInBackground(buildContainer);
2498
+ containerCleanup ??= cleanUpBuildContainer(buildContainer);
2183
2499
  for (const dir of [
2184
2500
  cnbDir,
2185
2501
  reportDir,
@@ -2195,17 +2511,17 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2195
2511
  }
2196
2512
  }
2197
2513
  }
2198
- await containerRemoval;
2514
+ await containerCleanup;
2199
2515
  }
2200
2516
  }
2201
2517
  );
2202
2518
  }
2203
2519
 
2204
2520
  // src/buildpacks/lifecycle/docker.ts
2205
- var import_node_crypto5 = require("crypto");
2521
+ var import_node_crypto7 = require("crypto");
2206
2522
  var import_node_fs7 = require("fs");
2207
- var import_node_os6 = require("os");
2208
- var import_node_path7 = require("path");
2523
+ var import_node_os9 = require("os");
2524
+ var import_node_path11 = require("path");
2209
2525
  function localBuildImageDockerfile(buildImage, nodeMajor) {
2210
2526
  const lines = [];
2211
2527
  if (nodeMajor !== void 0) {
@@ -2224,7 +2540,7 @@ function localBuildImageDockerfile(buildImage, nodeMajor) {
2224
2540
  `;
2225
2541
  }
2226
2542
  function localBuildImageTag(dockerfile) {
2227
- const hash = (0, import_node_crypto5.createHash)("sha256").update(dockerfile).digest("hex");
2543
+ const hash = (0, import_node_crypto7.createHash)("sha256").update(dockerfile).digest("hex");
2228
2544
  return `vercel-cnb-build:${hash.slice(0, 16)}`;
2229
2545
  }
2230
2546
  async function runDocker2(args, opts = {}) {
@@ -2250,8 +2566,8 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2250
2566
  let stageDir;
2251
2567
  const containers = [];
2252
2568
  try {
2253
- stageDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path7.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-"));
2254
- const cnbDir = (0, import_node_path7.join)(stageDir, "cnb");
2569
+ stageDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path11.join)((0, import_node_os9.tmpdir)(), "vercel-cnb-"));
2570
+ const cnbDir = (0, import_node_path11.join)(stageDir, "cnb");
2255
2571
  (0, import_node_fs7.mkdirSync)(cnbDir, { mode: 493 });
2256
2572
  const orderDir = writeOrderDir(buildpack, stageDir);
2257
2573
  const platformEnvDir = writePlatformEnvDir(
@@ -2271,7 +2587,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2271
2587
  params.nodeToolchain?.major
2272
2588
  );
2273
2589
  const buildImageTag = localBuildImageTag(dockerfile);
2274
- const buildImageIdPath = (0, import_node_path7.join)(stageDir, "build-image.id");
2590
+ const buildImageIdPath = (0, import_node_path11.join)(stageDir, "build-image.id");
2275
2591
  step(
2276
2592
  `Preparing ${buildpack.runtime} ${params.image.version} build image` + (params.nodeToolchain ? ` with Node.js ${params.nodeToolchain.major}` : "")
2277
2593
  );
@@ -2293,7 +2609,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2293
2609
  if (!/^sha256:[a-f0-9]{64}$/.test(buildImageId)) {
2294
2610
  throw new Error("Docker build did not report a valid image ID.");
2295
2611
  }
2296
- const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto5.randomBytes)(8).toString("hex")}`;
2612
+ const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto7.randomBytes)(8).toString("hex")}`;
2297
2613
  const initContainerName = `${containerName}-init`;
2298
2614
  containers.push(initContainerName);
2299
2615
  await runDocker2([
@@ -2377,7 +2693,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2377
2693
  `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
2378
2694
  );
2379
2695
  await runDocker2(["start", "--attach", containerName]);
2380
- const reportPath = (0, import_node_path7.join)(stageDir, "report.toml");
2696
+ const reportPath = (0, import_node_path11.join)(stageDir, "report.toml");
2381
2697
  await runDocker2([
2382
2698
  "cp",
2383
2699
  `${containerName}:/platform-output/report.toml`,
@@ -2431,15 +2747,15 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2431
2747
  };
2432
2748
 
2433
2749
  // src/buildpacks/node-toolchain.ts
2434
- var import_build_utils4 = require("@vercel/build-utils");
2750
+ var import_build_utils5 = require("@vercel/build-utils");
2435
2751
  var import_node_fs8 = require("fs");
2436
- var import_node_path8 = require("path");
2752
+ var import_node_path12 = require("path");
2437
2753
  async function selectNodeMajor(workPath, config, meta) {
2438
- if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(workPath, "package.json"))) {
2754
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path12.join)(workPath, "package.json"))) {
2439
2755
  return void 0;
2440
2756
  }
2441
- const version2 = await (0, import_build_utils4.getNodeVersion)(workPath, void 0, config, meta);
2442
- if ((0, import_build_utils4.isBunVersion)(version2)) {
2757
+ const version2 = await (0, import_build_utils5.getNodeVersion)(workPath, void 0, config, meta);
2758
+ if ((0, import_build_utils5.isBunVersion)(version2)) {
2443
2759
  throw new Error(
2444
2760
  "Buildpack asset builds require Node.js. Bun is not supported yet; configure engines.node or use Dockerfile.vercel."
2445
2761
  );
@@ -2456,7 +2772,7 @@ async function resolveNodeToolchain(workPath, config, meta) {
2456
2772
  return void 0;
2457
2773
  }
2458
2774
  for (const executable of ["node", "npm"]) {
2459
- if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(toolchain.home, "bin", executable))) {
2775
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path12.join)(toolchain.home, "bin", executable))) {
2460
2776
  throw new Error(
2461
2777
  `The selected Node.js ${toolchain.version} installation is missing ${toolchain.home}/bin/${executable} in the build environment.`
2462
2778
  );
@@ -2470,11 +2786,11 @@ async function resolveLocalNodeToolchain(workPath, config, meta) {
2470
2786
 
2471
2787
  // src/buildpacks/registry.ts
2472
2788
  var import_node_fs12 = require("fs");
2473
- var import_node_path12 = require("path");
2789
+ var import_node_path16 = require("path");
2474
2790
 
2475
2791
  // src/buildpacks/go.ts
2476
2792
  var import_node_fs9 = require("fs");
2477
- var import_node_path9 = require("path");
2793
+ var import_node_path13 = require("path");
2478
2794
  var DEFAULT_GO_VERSION = "1";
2479
2795
  var GO_IMAGE_REPOSITORY = "docker.io/library/golang";
2480
2796
  var GO_BUILD_IMAGE_DISTRO = "bookworm";
@@ -2503,7 +2819,7 @@ function compareImageVersions(a, b) {
2503
2819
  return a.parts[0] - b.parts[0] || a.parts[1] - b.parts[1] || a.parts[2] - b.parts[2];
2504
2820
  }
2505
2821
  function selectGoVersion(workPath) {
2506
- const goMod = (0, import_node_path9.join)(workPath, "go.mod");
2822
+ const goMod = (0, import_node_path13.join)(workPath, "go.mod");
2507
2823
  if (!(0, import_node_fs9.existsSync)(goMod))
2508
2824
  return DEFAULT_GO_VERSION;
2509
2825
  const contents = (0, import_node_fs9.readFileSync)(goMod, "utf8");
@@ -2528,10 +2844,10 @@ var go = {
2528
2844
  },
2529
2845
  defaultEntrypoint({ workPath, zeroConfig }) {
2530
2846
  const candidate = GO_CANDIDATE_ENTRYPOINTS.find(
2531
- (name) => (0, import_node_fs9.existsSync)((0, import_node_path9.join)(workPath, name))
2847
+ (name) => (0, import_node_fs9.existsSync)((0, import_node_path13.join)(workPath, name))
2532
2848
  );
2533
2849
  if (candidate)
2534
- return import_node_path9.posix.dirname(candidate);
2850
+ return import_node_path13.posix.dirname(candidate);
2535
2851
  if (zeroConfig) {
2536
2852
  const last = GO_CANDIDATE_ENTRYPOINTS.length - 1;
2537
2853
  throw new Error(
@@ -2544,7 +2860,7 @@ var go = {
2544
2860
 
2545
2861
  // src/buildpacks/ruby.ts
2546
2862
  var import_node_fs10 = require("fs");
2547
- var import_node_path10 = require("path");
2863
+ var import_node_path14 = require("path");
2548
2864
  var import_smol_toml2 = require("smol-toml");
2549
2865
 
2550
2866
  // src/buildpacks/version.ts
@@ -2576,7 +2892,7 @@ var ruby = {
2576
2892
  return { version: version2, buildImage: image, runImage: image };
2577
2893
  },
2578
2894
  validate({ workPath, buildEnv }) {
2579
- const gemfile = (0, import_node_path10.join)(workPath, "Gemfile");
2895
+ const gemfile = (0, import_node_path14.join)(workPath, "Gemfile");
2580
2896
  if (!(0, import_node_fs10.existsSync)(gemfile))
2581
2897
  return;
2582
2898
  if (!GEMFILE_RAILS_GEM_RE.test((0, import_node_fs10.readFileSync)(gemfile, "utf8")))
@@ -2590,12 +2906,12 @@ var ruby = {
2590
2906
  };
2591
2907
  function selectRubyVersion(workPath) {
2592
2908
  for (const name of RUBY_VERSION_FILES) {
2593
- const file = (0, import_node_path10.join)(workPath, name);
2909
+ const file = (0, import_node_path14.join)(workPath, name);
2594
2910
  if ((0, import_node_fs10.existsSync)(file)) {
2595
2911
  return parseVersionFile(name, (0, import_node_fs10.readFileSync)(file, "utf8"));
2596
2912
  }
2597
2913
  }
2598
- const gemfile = (0, import_node_path10.join)(workPath, "Gemfile");
2914
+ const gemfile = (0, import_node_path14.join)(workPath, "Gemfile");
2599
2915
  return (0, import_node_fs10.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs10.readFileSync)(gemfile, "utf8")) : void 0;
2600
2916
  }
2601
2917
  function parseVersionFile(name, contents) {
@@ -2634,7 +2950,7 @@ function parseGemfileRubyVersion(contents) {
2634
2950
 
2635
2951
  // src/buildpacks/rust.ts
2636
2952
  var import_node_fs11 = require("fs");
2637
- var import_node_path11 = require("path");
2953
+ var import_node_path15 = require("path");
2638
2954
  var import_smol_toml3 = require("smol-toml");
2639
2955
  var DEFAULT_RUST_VERSION = "1";
2640
2956
  var RUST_IMAGE_REPOSITORY = "docker.io/library/rust";
@@ -2654,12 +2970,12 @@ var rust = {
2654
2970
  };
2655
2971
  },
2656
2972
  defaultEntrypoint({ workPath, zeroConfig }) {
2657
- return zeroConfig && (0, import_node_fs11.existsSync)((0, import_node_path11.join)(workPath, "src/main.rs")) ? "src/main.rs" : void 0;
2973
+ return zeroConfig && (0, import_node_fs11.existsSync)((0, import_node_path15.join)(workPath, "src/main.rs")) ? "src/main.rs" : void 0;
2658
2974
  }
2659
2975
  };
2660
2976
  function selectRustVersion(workPath) {
2661
2977
  for (const name of RUST_TOOLCHAIN_FILES) {
2662
- const file = (0, import_node_path11.join)(workPath, name);
2978
+ const file = (0, import_node_path15.join)(workPath, name);
2663
2979
  if ((0, import_node_fs11.existsSync)(file)) {
2664
2980
  const channel = parseToolchainFile(name, (0, import_node_fs11.readFileSync)(file, "utf8"));
2665
2981
  return channel !== void 0 && isValidVersion(channel) ? channel : void 0;
@@ -2705,7 +3021,7 @@ function requestedBuildpack(config) {
2705
3021
  }
2706
3022
  function hasProjectMarkers(buildpack, workPath) {
2707
3023
  return buildpack.projectMarkers.some(
2708
- (name) => (0, import_node_fs12.existsSync)((0, import_node_path12.join)(workPath, name))
3024
+ (name) => (0, import_node_fs12.existsSync)((0, import_node_path16.join)(workPath, name))
2709
3025
  );
2710
3026
  }
2711
3027
 
@@ -2719,11 +3035,12 @@ function selectEntrypoint(params, sourceDir) {
2719
3035
  return fromEnv ?? params.buildpack.defaultEntrypoint?.({ workPath: sourceDir, zeroConfig });
2720
3036
  }
2721
3037
  async function buildAndPushBuildpack(params) {
2722
- const sourceDir = (0, import_node_fs13.mkdtempSync)((0, import_node_path13.join)((0, import_node_os7.tmpdir)(), "vercel-cnb-source-"));
3038
+ const sourceDir = (0, import_node_fs13.mkdtempSync)((0, import_node_path17.join)((0, import_node_os10.tmpdir)(), "vercel-cnb-source-"));
2723
3039
  try {
2724
3040
  step("Staging buildpack source files");
2725
3041
  await stageWorkspace(params, sourceDir);
2726
- const engine = selectContainerEngine();
3042
+ const { buildahClient } = params;
3043
+ const engine = selectContainerEngine(buildahClient);
2727
3044
  if (!hasProjectMarkers(params.buildpack, sourceDir)) {
2728
3045
  throw new Error(
2729
3046
  `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.`
@@ -2840,6 +3157,7 @@ async function buildAndPushBuildpack(params) {
2840
3157
  result = await buildAndPushWithLifecycle(
2841
3158
  buildpack,
2842
3159
  { ...lifecycleParams, prepareRegistry },
3160
+ buildahClient,
2843
3161
  buildSpan
2844
3162
  );
2845
3163
  } else {
@@ -2871,7 +3189,7 @@ async function buildAndPushBuildpack(params) {
2871
3189
 
2872
3190
  // src/image-source.ts
2873
3191
  var import_node_fs14 = require("fs");
2874
- var import_node_path14 = __toESM(require("path"));
3192
+ var import_node_path18 = __toESM(require("path"));
2875
3193
  var DETECT_SENTINEL = "<detect>";
2876
3194
  function resolveImageSource(options, context) {
2877
3195
  const { config, workPath, entrypoint } = options;
@@ -2888,7 +3206,7 @@ function resolveImageSource(options, context) {
2888
3206
  }
2889
3207
  const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
2890
3208
  const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
2891
- const dockerfilePath = import_node_path14.default.join(workPath, dockerfileRel);
3209
+ const dockerfilePath = import_node_path18.default.join(workPath, dockerfileRel);
2892
3210
  const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs14.existsSync)(dockerfilePath);
2893
3211
  if (hasDockerfile) {
2894
3212
  return { kind: "dockerfile", dockerfileRel, dockerfilePath };
@@ -2905,11 +3223,252 @@ function resolveImageSource(options, context) {
2905
3223
  );
2906
3224
  }
2907
3225
 
3226
+ // src/buildah-store-cache/cache.ts
3227
+ var import_build_utils6 = require("@vercel/build-utils");
3228
+ var import_node_crypto8 = require("crypto");
3229
+ var import_promises7 = require("fs/promises");
3230
+ var import_node_os11 = require("os");
3231
+ var import_node_path19 = require("path");
3232
+ var BUILDAH_STORE_CACHE_DIR = ".vercel/cache/buildah-store";
3233
+ var STORE_PATHS = {
3234
+ graphRoot: BUILDAH_GRAPH_ROOT,
3235
+ // Buildah's default when running as root, as it does in the build container.
3236
+ blobInfoCache: "/var/lib/containers/cache/blob-info-cache-v1.sqlite"
3237
+ };
3238
+ var BuildahStoreCache = class _BuildahStoreCache {
3239
+ constructor(repositoryRoot, client) {
3240
+ this.repositoryRoot = repositoryRoot;
3241
+ this.client = client;
3242
+ }
3243
+ static forProject(options, client) {
3244
+ return new _BuildahStoreCache(
3245
+ (0, import_node_path19.resolve)(options.repoRootPath || options.workPath),
3246
+ client
3247
+ );
3248
+ }
3249
+ /**
3250
+ * From `build()`, before a Buildah service opens the store or reads its build
3251
+ * context. A service that builds nothing leaves the snapshot for a later
3252
+ * deployment.
3253
+ */
3254
+ async beforeBuild(options) {
3255
+ if (!isBuildContainer()) {
3256
+ return;
3257
+ }
3258
+ if (buildahStoreCacheEnabled()) {
3259
+ if (options.buildsWithBuildah) {
3260
+ await this.restore(options.span);
3261
+ }
3262
+ return;
3263
+ }
3264
+ if (buildahStoreCacheRequested()) {
3265
+ info("Buildah store cache disabled: VERCEL_DEPLOYMENT_ID is not set");
3266
+ }
3267
+ await this.removeSnapshots();
3268
+ }
3269
+ /** From `prepareCache`, which the platform runs once every service has built. */
3270
+ async afterBuilds() {
3271
+ if (!isBuildContainer()) {
3272
+ return {};
3273
+ }
3274
+ if (buildahStoreCacheEnabled()) {
3275
+ return this.save();
3276
+ }
3277
+ await this.removeSnapshots();
3278
+ return {};
3279
+ }
3280
+ async restore(span) {
3281
+ const start = Date.now();
3282
+ step("Restoring the Buildah store cache");
3283
+ let result = "miss";
3284
+ let reason;
3285
+ let bytes = 0;
3286
+ try {
3287
+ const archive = await this.archivePath({ create: false });
3288
+ const restored = await restoreStore(archive);
3289
+ result = restored.status;
3290
+ if (restored.status === "hit")
3291
+ bytes = restored.bytes;
3292
+ if (restored.status === "miss")
3293
+ reason = restored.reason;
3294
+ } catch (error) {
3295
+ result = "miss";
3296
+ reason = failureReason(error);
3297
+ }
3298
+ await this.removeSnapshots();
3299
+ const restoredBytes = result === "hit" ? bytes : 0;
3300
+ const detail = reason ?? (result === "hit" ? formatBytes(restoredBytes) : void 0);
3301
+ done(
3302
+ `Buildah store cache: ${result}${detail ? ` (${detail})` : ""} in ${elapsed(start)}`
3303
+ );
3304
+ span?.setAttributes({
3305
+ "container.buildah_store.restore": result,
3306
+ "container.buildah_store.restore_ms": String(Date.now() - start),
3307
+ "container.buildah_store.restore_bytes": String(restoredBytes),
3308
+ "container.buildah_store.miss_reason": reason
3309
+ });
3310
+ }
3311
+ async save() {
3312
+ const start = Date.now();
3313
+ step("Saving the Buildah store cache");
3314
+ try {
3315
+ const archive = await this.archivePath({ create: true });
3316
+ const saved = await saveStore(archive);
3317
+ done(await describeSave2(saved, archive, start));
3318
+ if (!await isRegularFile(archive)) {
3319
+ return {};
3320
+ }
3321
+ return await (0, import_build_utils6.glob)(
3322
+ `${BUILDAH_STORE_CACHE_DIR}/${(0, import_node_path19.basename)(archive)}`,
3323
+ this.repositoryRoot
3324
+ );
3325
+ } catch (error) {
3326
+ done(`Buildah store cache not saved (${failureReason(error)})`);
3327
+ return {};
3328
+ }
3329
+ }
3330
+ async removeSnapshots() {
3331
+ try {
3332
+ await (0, import_promises7.rm)(await this.snapshotDirectory({ create: false }), {
3333
+ recursive: true,
3334
+ force: true
3335
+ });
3336
+ } catch (error) {
3337
+ debug(
3338
+ `could not remove Buildah store snapshots: ${failureReason(error)}`
3339
+ );
3340
+ }
3341
+ }
3342
+ async archivePath(options) {
3343
+ const key = await snapshotKey(this.client);
3344
+ return (0, import_node_path19.join)(await this.snapshotDirectory(options), `store-${key}.tar.zst`);
3345
+ }
3346
+ async snapshotDirectory({
3347
+ create
3348
+ }) {
3349
+ const root = await (0, import_promises7.realpath)(this.repositoryRoot);
3350
+ let directory = root;
3351
+ for (const part of BUILDAH_STORE_CACHE_DIR.split("/")) {
3352
+ directory = (0, import_node_path19.join)(directory, part);
3353
+ if (create) {
3354
+ await (0, import_promises7.mkdir)(directory, { mode: 448 }).catch((error) => {
3355
+ if (error.code !== "EEXIST")
3356
+ throw error;
3357
+ });
3358
+ }
3359
+ const stats = await lstatIfPresent(directory);
3360
+ if (!stats) {
3361
+ return (0, import_node_path19.join)(root, BUILDAH_STORE_CACHE_DIR);
3362
+ }
3363
+ if (!stats.isDirectory()) {
3364
+ throw new Error(`${directory} is not a directory`);
3365
+ }
3366
+ }
3367
+ return directory;
3368
+ }
3369
+ };
3370
+ async function restoreStore(archive) {
3371
+ const storeLock = await requireStoreLock();
3372
+ const state = await createBuildahStoreStateDirectory();
3373
+ const gate = new FileLock((0, import_node_path19.join)(state, "restore.lock"));
3374
+ return runStoreCommand(
3375
+ {
3376
+ action: "restore",
3377
+ options: {
3378
+ ...STORE_PATHS,
3379
+ archive,
3380
+ storeLock: storeLock.path,
3381
+ marker: (0, import_node_path19.join)(state, "restored")
3382
+ }
3383
+ },
3384
+ gate
3385
+ );
3386
+ }
3387
+ async function saveStore(archive) {
3388
+ const buildahArgs = await buildahStorageArgs();
3389
+ const storeLock = await requireStoreLock();
3390
+ return runStoreCommand(
3391
+ {
3392
+ action: "save",
3393
+ options: {
3394
+ ...STORE_PATHS,
3395
+ archive,
3396
+ stateDirectory: await createBuildahStoreStateDirectory(),
3397
+ threads: (0, import_node_os11.availableParallelism)(),
3398
+ buildahArgs
3399
+ }
3400
+ },
3401
+ storeLock
3402
+ );
3403
+ }
3404
+ async function requireStoreLock() {
3405
+ const lock = await buildahStoreLock();
3406
+ if (!lock)
3407
+ throw new Error("flock unavailable");
3408
+ return lock;
3409
+ }
3410
+ async function describeSave2(result, archive, start) {
3411
+ if (result.status === "saved") {
3412
+ return `saved the Buildah store cache (${formatBytes((await (0, import_promises7.lstat)(archive)).size)}) in ${elapsed(start)}`;
3413
+ }
3414
+ if (result.status === "unused") {
3415
+ return await isRegularFile(archive) ? "kept the previous Buildah store cache: no service built with Buildah" : "no Buildah store cache to keep: no service recorded Buildah image usage";
3416
+ }
3417
+ if (result.status === "skipped" && result.previous === "failed") {
3418
+ throw new Error("an earlier save for this deployment failed");
3419
+ }
3420
+ return "Buildah store cache already prepared for this deployment";
3421
+ }
3422
+ async function snapshotKey(client) {
3423
+ const configuration = await Promise.all(
3424
+ [
3425
+ "/usr/share/containers/storage.conf",
3426
+ "/etc/containers/storage.conf",
3427
+ (0, import_node_path19.join)(
3428
+ process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0, import_node_os11.homedir)(), ".config"),
3429
+ "containers/storage.conf"
3430
+ ),
3431
+ process.env.CONTAINERS_STORAGE_CONF,
3432
+ "/etc/subuid",
3433
+ "/etc/subgid"
3434
+ ].map(readOptionalFile)
3435
+ );
3436
+ return (0, import_node_crypto8.createHash)("sha256").update(
3437
+ JSON.stringify({
3438
+ version: await client.version(),
3439
+ arch: process.arch,
3440
+ uid: process.getuid?.(),
3441
+ gid: process.getgid?.(),
3442
+ driver: process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER,
3443
+ image: process.env.VERCEL_BUILD_IMAGE,
3444
+ configuration
3445
+ })
3446
+ ).digest("hex").slice(0, 32);
3447
+ }
3448
+ async function readOptionalFile(path4) {
3449
+ if (!path4) {
3450
+ return "";
3451
+ }
3452
+ try {
3453
+ return await (0, import_promises7.readFile)(path4, "utf8");
3454
+ } catch (error) {
3455
+ if (error.code === "ENOENT")
3456
+ return "";
3457
+ throw error;
3458
+ }
3459
+ }
3460
+ async function isRegularFile(path4) {
3461
+ return (await lstatIfPresent(path4))?.isFile() ?? false;
3462
+ }
3463
+ function failureReason(error) {
3464
+ return String(error?.message ?? error).trim().split("\n").at(-1) ?? "";
3465
+ }
3466
+
2908
3467
  // src/dev.ts
2909
3468
  var import_node_child_process4 = require("child_process");
2910
3469
  var import_node_fs15 = require("fs");
2911
- var import_node_os8 = require("os");
2912
- var import_node_path15 = __toESM(require("path"));
3470
+ var import_node_os12 = require("os");
3471
+ var import_node_path20 = __toESM(require("path"));
2913
3472
  var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
2914
3473
  "TMPDIR",
2915
3474
  "TMP",
@@ -2939,8 +3498,8 @@ function isHostOnlyEnvVar(key) {
2939
3498
  return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
2940
3499
  }
2941
3500
  function writeEnvFile(env) {
2942
- const dir = (0, import_node_fs15.mkdtempSync)(import_node_path15.default.join((0, import_node_os8.tmpdir)(), "vercel-container-dev-env-"));
2943
- const file = import_node_path15.default.join(dir, "env");
3501
+ const dir = (0, import_node_fs15.mkdtempSync)(import_node_path20.default.join((0, import_node_os12.tmpdir)(), "vercel-container-dev-env-"));
3502
+ const file = import_node_path20.default.join(dir, "env");
2944
3503
  const lines = [];
2945
3504
  for (const [key, value] of Object.entries(env)) {
2946
3505
  if (value.includes("\n")) {
@@ -2973,7 +3532,7 @@ function emit(out, line) {
2973
3532
  }
2974
3533
  }
2975
3534
  function runForwarded(cmd, args, out, opts = {}) {
2976
- return new Promise((resolve4, reject) => {
3535
+ return new Promise((resolve5, reject) => {
2977
3536
  const child = (0, import_node_child_process4.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2978
3537
  let stdout = "";
2979
3538
  let stderr = "";
@@ -3010,7 +3569,7 @@ function runForwarded(cmd, args, out, opts = {}) {
3010
3569
  });
3011
3570
  child.on("close", (code) => {
3012
3571
  if (code === 0) {
3013
- resolve4({ stdout });
3572
+ resolve5({ stdout });
3014
3573
  } else {
3015
3574
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
3016
3575
  reject(
@@ -3046,7 +3605,7 @@ async function resolveDevImage(options, out, span) {
3046
3605
  }
3047
3606
  const serviceName = options.service?.name ?? "service";
3048
3607
  const tag = devImageTag(serviceName);
3049
- const contextDir = import_node_path15.default.dirname(dockerfilePath);
3608
+ const contextDir = import_node_path20.default.dirname(dockerfilePath);
3050
3609
  const buildArgFlags2 = [];
3051
3610
  const buildEnv = options.meta?.buildEnv ?? {};
3052
3611
  for (const [key, value] of Object.entries(buildEnv)) {
@@ -3251,7 +3810,7 @@ async function startContainer(options, reuseKey) {
3251
3810
  }
3252
3811
  });
3253
3812
  const cleanupEnvFile = () => {
3254
- (0, import_node_fs15.rmSync)(import_node_path15.default.dirname(envFilePath), { recursive: true, force: true });
3813
+ (0, import_node_fs15.rmSync)(import_node_path20.default.dirname(envFilePath), { recursive: true, force: true });
3255
3814
  };
3256
3815
  const shutdown = async () => {
3257
3816
  runningContainers.delete(reuseKey);
@@ -3284,7 +3843,7 @@ async function startContainer(options, reuseKey) {
3284
3843
  break;
3285
3844
  } catch (err) {
3286
3845
  lastErr = err;
3287
- await new Promise((resolve4) => setTimeout(resolve4, 250));
3846
+ await new Promise((resolve5) => setTimeout(resolve5, 250));
3288
3847
  }
3289
3848
  }
3290
3849
  if (hostPort === void 0) {
@@ -3327,19 +3886,27 @@ async function startContainer(options, reuseKey) {
3327
3886
  }
3328
3887
 
3329
3888
  // src/prepare-cache.ts
3330
- var import_build_utils5 = require("@vercel/build-utils");
3331
- var import_node_path16 = require("path");
3889
+ var import_build_utils7 = require("@vercel/build-utils");
3890
+ var import_node_path21 = require("path");
3332
3891
  async function prepareCache(options) {
3333
- const root = (0, import_node_path16.resolve)(options.repoRootPath || options.workPath);
3892
+ const root = (0, import_node_path21.resolve)(options.repoRootPath || options.workPath);
3893
+ const buildpackFiles = await collectBuildpackCache(root);
3894
+ const buildahStoreFiles = await BuildahStoreCache.forProject(
3895
+ options,
3896
+ new BuildahClient(sharedBuildahStoreLock)
3897
+ ).afterBuilds();
3898
+ return { ...buildpackFiles, ...buildahStoreFiles };
3899
+ }
3900
+ async function collectBuildpackCache(root) {
3334
3901
  const start = Date.now();
3335
- const files = await (0, import_build_utils5.glob)(`${BUILDPACK_CACHE_DIR}/**`, root);
3902
+ const files = await (0, import_build_utils7.glob)(`${BUILDPACK_CACHE_DIR}/**`, root);
3336
3903
  const count = Object.keys(files).length;
3337
3904
  if (count > 0) {
3338
3905
  info(
3339
- `cached buildpack layers: ${count} file${count === 1 ? "" : "s"} under ${BUILDPACK_CACHE_DIR} in ${Date.now() - start}ms`
3906
+ `cached ${count} file${count === 1 ? "" : "s"} under ${BUILDPACK_CACHE_DIR} in ${Date.now() - start}ms`
3340
3907
  );
3341
3908
  } else {
3342
- debug("no buildpack cache to persist");
3909
+ debug(`no cache to persist under ${BUILDPACK_CACHE_DIR}`);
3343
3910
  }
3344
3911
  return files;
3345
3912
  }
@@ -3363,9 +3930,9 @@ function resolveImageTag() {
3363
3930
  if (sha) {
3364
3931
  return sha.slice(0, 12);
3365
3932
  }
3366
- const deploymentId = readString(process.env.VERCEL_DEPLOYMENT_ID);
3367
- if (deploymentId) {
3368
- return deploymentId.replace(/[^a-z0-9-_.]/gi, "-");
3933
+ const deploymentId2 = readString(process.env.VERCEL_DEPLOYMENT_ID);
3934
+ if (deploymentId2) {
3935
+ return deploymentId2.replace(/[^a-z0-9-_.]/gi, "-");
3369
3936
  }
3370
3937
  return `build-${Date.now().toString(36)}`;
3371
3938
  }
@@ -3397,8 +3964,16 @@ async function authenticateRegistry(engine, buildParams, span) {
3397
3964
  done("authenticated");
3398
3965
  }
3399
3966
  async function buildAndPushImage(params) {
3400
- const { contextDir, dockerfilePath, repository, tag, buildArgs, parentSpan } = params;
3401
- const engine = selectContainerEngine();
3967
+ const {
3968
+ contextDir,
3969
+ dockerfilePath,
3970
+ repository,
3971
+ tag,
3972
+ buildArgs,
3973
+ buildahClient,
3974
+ parentSpan
3975
+ } = params;
3976
+ const engine = selectContainerEngine(buildahClient);
3402
3977
  return withSpan(
3403
3978
  parentSpan,
3404
3979
  "container.build_and_push",
@@ -3448,6 +4023,7 @@ async function buildAndPushImage(params) {
3448
4023
  token,
3449
4024
  repository,
3450
4025
  buildArgs,
4026
+ useBuildahStoreCache: buildahStoreCacheEnabled(),
3451
4027
  span: buildSpan
3452
4028
  };
3453
4029
  await authenticateRegistry(engine, buildParams, buildSpan);
@@ -3470,7 +4046,13 @@ async function buildAndPushImage(params) {
3470
4046
  buildSpan,
3471
4047
  "container.image_build",
3472
4048
  { "image.ref": imageRef, "image.platform": TARGET_PLATFORM },
3473
- () => engine.build(buildParams)
4049
+ async () => {
4050
+ if (params.schedulesCommand) {
4051
+ await buildSchedulesCommandImage(engine, buildParams);
4052
+ } else {
4053
+ await engine.build(buildParams);
4054
+ }
4055
+ }
3474
4056
  );
3475
4057
  done(`built in ${elapsed(buildStart)}`);
3476
4058
  const pushStart = Date.now();
@@ -3502,7 +4084,7 @@ async function buildAndPushImage(params) {
3502
4084
  }
3503
4085
  );
3504
4086
  }
3505
- async function resolveImageHandler(options, source, span) {
4087
+ async function resolveImageHandler(options, source, buildahClient, span, schedulesCommand) {
3506
4088
  const { config, meta } = options;
3507
4089
  const { daemon } = config;
3508
4090
  span?.setAttributes({
@@ -3525,7 +4107,7 @@ async function resolveImageHandler(options, source, span) {
3525
4107
  });
3526
4108
  return tag3;
3527
4109
  }
3528
- const repository2 = daemon ? (0, import_build_utils6.getDaemonImageRepository)(daemon.name) : sanitizeRepository(options.service?.name ?? buildpack.runtime);
4110
+ const repository2 = daemon ? (0, import_build_utils8.getDaemonImageRepository)(daemon.name) : sanitizeRepository(options.service?.name ?? buildpack.runtime);
3529
4111
  const tag2 = resolveImageTag();
3530
4112
  span?.setAttributes({
3531
4113
  "container.mode": "buildpack-build-and-push",
@@ -3545,6 +4127,7 @@ async function resolveImageHandler(options, source, span) {
3545
4127
  config,
3546
4128
  meta,
3547
4129
  buildEnv: buildArgsFromEnv(meta?.buildEnv),
4130
+ buildahClient,
3548
4131
  parentSpan: span
3549
4132
  });
3550
4133
  } catch (error) {
@@ -3560,7 +4143,7 @@ async function resolveImageHandler(options, source, span) {
3560
4143
  if (meta?.isDev) {
3561
4144
  const serviceName = options.service?.name;
3562
4145
  const tag2 = devImageTag(
3563
- serviceName ?? import_node_path17.default.basename(dockerfileRel).split(".")[0]
4146
+ serviceName ?? import_node_path22.default.basename(dockerfileRel).split(".")[0]
3564
4147
  );
3565
4148
  span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
3566
4149
  return tag2;
@@ -3570,15 +4153,17 @@ async function resolveImageHandler(options, source, span) {
3570
4153
  `Dockerfile not found at "${dockerfilePath}" for container service.`
3571
4154
  );
3572
4155
  }
3573
- const repository = daemon ? (0, import_build_utils6.getDaemonImageRepository)(daemon.name) : sanitizeRepository(
3574
- options.service?.name ?? import_node_path17.default.basename(dockerfileRel).split(".")[0]
4156
+ const repository = daemon ? (0, import_build_utils8.getDaemonImageRepository)(daemon.name) : sanitizeRepository(
4157
+ options.service?.name ?? import_node_path22.default.basename(dockerfileRel).split(".")[0]
3575
4158
  );
3576
4159
  const tag = resolveImageTag();
3577
- const contextDir = import_node_path17.default.dirname(dockerfilePath);
4160
+ const contextDir = schedulesCommand ? import_node_path22.default.join(options.workPath, schedulesCommand.root) : import_node_path22.default.dirname(dockerfilePath);
4161
+ const contextIsRoot = Boolean(schedulesCommand) || import_node_path22.default.dirname(import_node_path22.default.normalize(daemon ? daemon.entrypoint : dockerfileRel)) === ".";
3578
4162
  const buildArgs = buildArgsFromEnv(meta?.buildEnv);
3579
4163
  span?.setAttributes({
3580
4164
  "container.mode": "build_and_push",
3581
4165
  "container.repository": repository,
4166
+ "container.context_is_root": toTag(contextIsRoot),
3582
4167
  "image.tag": tag
3583
4168
  });
3584
4169
  return buildAndPushImage({
@@ -3587,7 +4172,9 @@ async function resolveImageHandler(options, source, span) {
3587
4172
  repository,
3588
4173
  tag,
3589
4174
  buildArgs,
3590
- parentSpan: span
4175
+ buildahClient,
4176
+ parentSpan: span,
4177
+ schedulesCommand
3591
4178
  });
3592
4179
  }
3593
4180
  function buildArgsFromEnv(env) {
@@ -3604,12 +4191,26 @@ function buildArgsFromEnv(env) {
3604
4191
  }
3605
4192
  async function build(options) {
3606
4193
  const source = resolveImageSource(options, "build");
4194
+ const schedulesCommand = options.config.schedulesCommand;
3607
4195
  const { daemon } = options.config;
4196
+ const buildahClient = new BuildahClient(sharedBuildahStoreLock);
4197
+ if (!options.meta?.isDev && selectContainerEngine(buildahClient).name === "buildah") {
4198
+ await BuildahStoreCache.forProject(options, buildahClient).beforeBuild({
4199
+ buildsWithBuildah: source.kind !== "prebuilt",
4200
+ span: options.span
4201
+ });
4202
+ }
3608
4203
  const image = await withSpan(
3609
4204
  options.span,
3610
4205
  "container.resolve_image",
3611
4206
  { "service.name": options.service?.name },
3612
- (span) => resolveImageHandler(options, source, span)
4207
+ (span) => resolveImageHandler(
4208
+ options,
4209
+ source,
4210
+ buildahClient,
4211
+ span,
4212
+ schedulesCommand
4213
+ )
3613
4214
  );
3614
4215
  if (daemon) {
3615
4216
  return {
@@ -3640,15 +4241,16 @@ async function build(options) {
3640
4241
  }
3641
4242
  };
3642
4243
  }
3643
- const lambdaOptions = await (0, import_build_utils6.getLambdaOptionsFromFunction)({
4244
+ const lambdaOptions = await (0, import_build_utils8.getLambdaOptionsFromFunction)({
3644
4245
  sourceFile: resolveFunctionSourceFile(options),
3645
- config: options.config
4246
+ config: options.config,
4247
+ isScheduleEntrypoint: schedulesCommand !== void 0
3646
4248
  });
3647
- const command = source.kind === "buildpack" ? void 0 : normalizeCommand(options.config.command);
4249
+ const command = source.kind === "buildpack" || schedulesCommand ? void 0 : normalizeCommand(options.config.command);
3648
4250
  await generateProjectManifest({
3649
4251
  workPath: options.workPath,
3650
4252
  framework: options.diagnosticsValues?.framework,
3651
- serviceType: options.service ? (0, import_build_utils6.getReportedServiceType)(options.service) : void 0
4253
+ serviceType: options.service ? (0, import_build_utils8.getReportedServiceType)(options.service) : void 0
3652
4254
  });
3653
4255
  const routes = [
3654
4256
  { handle: "filesystem" },
@@ -3668,7 +4270,7 @@ async function build(options) {
3668
4270
  }
3669
4271
  ];
3670
4272
  return {
3671
- routes,
4273
+ routes: schedulesCommand ? [] : routes,
3672
4274
  output: {
3673
4275
  [CONTAINER_OUTPUT_PATH]: {
3674
4276
  type: "Lambda",
@@ -3678,7 +4280,9 @@ async function build(options) {
3678
4280
  // (vercel/api#76729).
3679
4281
  handler: image,
3680
4282
  runtime: "container",
3681
- environment: {},
4283
+ environment: schedulesCommand ? {
4284
+ [SCHEDULES_COMMAND_ENV]: JSON.stringify(schedulesCommand.command)
4285
+ } : {},
3682
4286
  ...command ? { command } : {},
3683
4287
  ...lambdaOptions
3684
4288
  }