@vercel/container 22.0.2 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -38,7 +38,43 @@ __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
+ await engine.build(params);
51
+ if ((await engine.onBuildTriggers?.(params.imageRef))?.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 ${params.imageRef}`,
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
+ });
74
+ } finally {
75
+ await (0, import_promises.rm)(contextDir, { recursive: true, force: true });
76
+ }
77
+ }
42
78
 
43
79
  // src/diagnostics.ts
44
80
  var import_build_utils = require("@vercel/build-utils");
@@ -63,14 +99,15 @@ var diagnostics = (0, import_build_utils.createDiagnostics)("container");
63
99
 
64
100
  // src/index.ts
65
101
  var import_node_fs16 = require("fs");
66
- var import_node_path17 = __toESM(require("path"));
102
+ var import_node_path22 = __toESM(require("path"));
67
103
 
68
104
  // src/util.ts
69
105
  var import_build_utils2 = require("@vercel/build-utils");
70
106
  var import_node_child_process = require("child_process");
71
107
  var import_node_fs = require("fs");
72
- var import_node_os = require("os");
73
- var import_node_path = require("path");
108
+ var import_promises2 = require("fs/promises");
109
+ var import_node_os2 = require("os");
110
+ var import_node_path2 = require("path");
74
111
  var DEBUG = Boolean((0, import_build_utils2.getPlatformEnv)("BUILDER_DEBUG"));
75
112
  function normalizeCommand(command) {
76
113
  if (typeof command === "string") {
@@ -105,11 +142,23 @@ function debug(message) {
105
142
  function elapsed(since) {
106
143
  return `${((Date.now() - since) / 1e3).toFixed(1)}s`;
107
144
  }
145
+ function formatBytes(bytes) {
146
+ if (bytes < 1024)
147
+ return `${bytes} B`;
148
+ const units = ["KiB", "MiB", "GiB"];
149
+ let value = bytes / 1024;
150
+ let unit = 0;
151
+ while (value >= 1024 && unit < units.length - 1) {
152
+ value /= 1024;
153
+ unit += 1;
154
+ }
155
+ return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
156
+ }
108
157
  function shortDigest(digest) {
109
158
  return digest.startsWith("sha256:") ? `${digest.slice(0, 19)}\u2026` : digest;
110
159
  }
111
160
  function delay(ms) {
112
- return new Promise((resolve4) => setTimeout(resolve4, ms));
161
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
113
162
  }
114
163
  async function withSpan(parent, name, attrs, fn) {
115
164
  if (!parent) {
@@ -124,21 +173,21 @@ function readString(value) {
124
173
  return typeof value === "string" && value.length > 0 ? value : void 0;
125
174
  }
126
175
  function isDockerfileRef(ref) {
127
- return /(docker|container)file/i.test((0, import_node_path.basename)(ref));
176
+ return /(docker|container)file/i.test((0, import_node_path2.basename)(ref));
128
177
  }
129
178
  var DOCKERFILE_CANDIDATES = [
130
179
  "Dockerfile.vercel",
131
180
  "Containerfile.vercel"
132
181
  ];
133
182
  function findDockerfile(workPath) {
134
- return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path.join)(workPath, name)));
183
+ return DOCKERFILE_CANDIDATES.find((name) => (0, import_node_fs.existsSync)((0, import_node_path2.join)(workPath, name)));
135
184
  }
136
185
  function devImageTag(serviceName) {
137
186
  const safe = serviceName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-");
138
187
  return `vercel-dev/${safe || "service"}:dev`;
139
188
  }
140
189
  function run(cmd, args, opts = {}) {
141
- return new Promise((resolve4, reject) => {
190
+ return new Promise((resolve5, reject) => {
142
191
  const child = (0, import_node_child_process.spawn)(cmd, args, {
143
192
  cwd: opts.cwd,
144
193
  env: opts.env,
@@ -173,7 +222,7 @@ function run(cmd, args, opts = {}) {
173
222
  });
174
223
  child.on("close", (code) => {
175
224
  if (code === 0) {
176
- resolve4({ stdout, stderr });
225
+ resolve5({ stdout, stderr });
177
226
  } else {
178
227
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
179
228
  const error = new Error(
@@ -189,6 +238,23 @@ ${detail}` : "")
189
238
  }
190
239
  });
191
240
  }
241
+ async function hasCommand(name) {
242
+ try {
243
+ await run("which", [name], { quiet: true });
244
+ return true;
245
+ } catch {
246
+ return false;
247
+ }
248
+ }
249
+ async function lstatIfPresent(path4) {
250
+ try {
251
+ return await (0, import_promises2.lstat)(path4);
252
+ } catch (error) {
253
+ if (error.code === "ENOENT")
254
+ return void 0;
255
+ throw error;
256
+ }
257
+ }
192
258
  function extractField(text, label) {
193
259
  const match = text.match(new RegExp(`^\\s*${label}:\\s*(.+)$`, "m"));
194
260
  return match?.[1]?.trim();
@@ -202,44 +268,130 @@ function existingRegistryAuthFile() {
202
268
  return (0, import_node_fs.existsSync)(explicit) ? explicit : void 0;
203
269
  }
204
270
  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");
271
+ const configHome = fromXdg || (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config");
272
+ const defaultPath = (0, import_node_path2.join)(configHome, "containers", "auth.json");
207
273
  return (0, import_node_fs.existsSync)(defaultPath) ? defaultPath : void 0;
208
274
  }
209
275
 
210
276
  // src/engines/buildah.ts
277
+ var import_build_utils3 = require("@vercel/build-utils");
278
+ var import_node_crypto3 = require("crypto");
211
279
  var import_node_fs3 = require("fs");
280
+ var import_promises5 = require("fs/promises");
281
+ var import_node_os5 = require("os");
282
+ var import_node_path5 = require("path");
283
+
284
+ // src/buildah-store-cache/state.ts
285
+ var import_node_crypto = require("crypto");
286
+ var import_promises3 = require("fs/promises");
212
287
  var import_node_os3 = require("os");
213
288
  var import_node_path3 = require("path");
214
289
 
290
+ // src/buildah-store-cache/config.ts
291
+ var BUILDAH_STORE_CACHE_FLAG = "VERCEL_BUILDAH_STORE_CACHE";
292
+ function buildahStoreCacheRequested() {
293
+ return isBuildContainer() && process.env[BUILDAH_STORE_CACHE_FLAG] === "1";
294
+ }
295
+ function buildahStoreCacheEnabled() {
296
+ return buildahStoreCacheRequested() && deploymentId() !== void 0;
297
+ }
298
+ function deploymentId() {
299
+ return readString(process.env.VERCEL_DEPLOYMENT_ID);
300
+ }
301
+
302
+ // src/buildah-store-cache/state.ts
303
+ var CLEANUP_CONTAINER_RE = /^vercel-cnb-[a-zA-Z0-9_.-]+$/;
304
+ function buildahStoreStateDirectory() {
305
+ const deployment = deploymentId();
306
+ if (!deployment) {
307
+ throw new Error("VERCEL_DEPLOYMENT_ID is not set");
308
+ }
309
+ return (0, import_node_path3.join)(
310
+ (0, import_node_os3.tmpdir)(),
311
+ "vercel-buildah-store",
312
+ deployment.replace(/[^a-zA-Z0-9_-]/g, "_")
313
+ );
314
+ }
315
+ async function createBuildahStoreStateDirectory() {
316
+ const directory = buildahStoreStateDirectory();
317
+ await (0, import_promises3.mkdir)(directory, { recursive: true, mode: 448 });
318
+ return directory;
319
+ }
320
+ async function writeStateRecord(suffix, contents) {
321
+ const path4 = (0, import_node_path3.join)(
322
+ await createBuildahStoreStateDirectory(),
323
+ `${(0, import_node_crypto.randomUUID)()}${suffix}`
324
+ );
325
+ await (0, import_promises3.writeFile)(path4, contents, { mode: 384, flag: "wx" });
326
+ return path4;
327
+ }
328
+
329
+ // src/buildah-store-cache/usage.ts
330
+ var import_promises4 = require("fs/promises");
331
+ function imageIdsFromBuildOutput(output) {
332
+ const ids = /* @__PURE__ */ new Set();
333
+ for (const match of output.matchAll(
334
+ /--> (?:Using cache )?([a-f0-9]{12,64})[ \t\r]*$/gm
335
+ )) {
336
+ ids.add(match[1]);
337
+ }
338
+ return [...ids];
339
+ }
340
+ var BuildahImageUsage = class _BuildahImageUsage {
341
+ constructor(path4) {
342
+ this.path = path4;
343
+ }
344
+ static async begin() {
345
+ if (!buildahStoreCacheEnabled()) {
346
+ return void 0;
347
+ }
348
+ try {
349
+ return new _BuildahImageUsage(
350
+ await writeStateRecord(".images", "pending\n")
351
+ );
352
+ } catch (error) {
353
+ debug(
354
+ `could not record Buildah image usage: ${error.message}`
355
+ );
356
+ return void 0;
357
+ }
358
+ }
359
+ /** A failure leaves the record pending, so the save stops instead of pruning blindly. */
360
+ async record(images) {
361
+ try {
362
+ const ids = (await images()).map((id) => id.replace(/^sha256:/, ""));
363
+ if (ids.length === 0) {
364
+ throw new Error("no Buildah image IDs were recorded");
365
+ }
366
+ const temporary = `${this.path}.tmp`;
367
+ await (0, import_promises4.writeFile)(temporary, `${ids.join("\n")}
368
+ `, { mode: 384 });
369
+ await (0, import_promises4.rename)(temporary, this.path);
370
+ } catch (error) {
371
+ debug(
372
+ `could not record Buildah image usage: ${error.message}`
373
+ );
374
+ }
375
+ }
376
+ };
377
+
215
378
  // src/storage-driver.ts
216
- var import_node_child_process2 = require("child_process");
217
379
  var import_node_fs2 = require("fs");
218
- var import_node_os2 = require("os");
219
- var import_node_path2 = require("path");
380
+ var import_node_os4 = require("os");
381
+ var import_node_path4 = require("path");
220
382
  var BUILDAH_GRAPH_ROOT = "/vercel/.containers/storage";
221
383
  var BUILDAH_RUN_ROOT = "/run/containers/storage";
222
384
  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
385
  var cachedStorageDriver;
232
386
  function selectStorageDriver() {
233
387
  if (!cachedStorageDriver) {
234
388
  cachedStorageDriver = (async () => {
235
389
  const override = readString(process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER);
236
- if (override) {
390
+ if (override)
237
391
  return override;
238
- }
239
- if (isBuildContainer()) {
392
+ if (isBuildContainer())
240
393
  return void 0;
241
- }
242
- if (await hasBinary("fuse-overlayfs") && (0, import_node_fs2.existsSync)("/dev/fuse")) {
394
+ if (await hasCommand("fuse-overlayfs") && (0, import_node_fs2.existsSync)("/dev/fuse")) {
243
395
  return "fuse-overlayfs";
244
396
  }
245
397
  return "vfs";
@@ -253,8 +405,8 @@ short-name-mode = "permissive"
253
405
  var cachedRegistriesConfPath;
254
406
  function buildahRegistriesConfPath() {
255
407
  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");
408
+ const dir = (0, import_node_fs2.mkdtempSync)((0, import_node_path4.join)((0, import_node_os4.tmpdir)(), "vercel-container-registries-"));
409
+ cachedRegistriesConfPath = (0, import_node_path4.join)(dir, "registries.conf");
258
410
  (0, import_node_fs2.writeFileSync)(cachedRegistriesConfPath, BUILDAH_REGISTRIES_CONF);
259
411
  }
260
412
  return cachedRegistriesConfPath;
@@ -266,9 +418,8 @@ async function buildahStorageArgs() {
266
418
  "--registries-conf",
267
419
  buildahRegistriesConfPath()
268
420
  ];
269
- if (!driver) {
421
+ if (!driver)
270
422
  return [...rootArgs, ...registriesArgs];
271
- }
272
423
  if (driver === "fuse-overlayfs") {
273
424
  return [
274
425
  ...rootArgs,
@@ -281,121 +432,9 @@ async function buildahStorageArgs() {
281
432
  }
282
433
  return [...rootArgs, ...registriesArgs, "--storage-driver", driver];
283
434
  }
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
435
 
397
436
  // src/oidc.ts
398
- var import_node_crypto = require("crypto");
437
+ var import_node_crypto2 = require("crypto");
399
438
  function parseOidcToken(token) {
400
439
  const parts = token.split(".");
401
440
  if (parts.length !== 3) {
@@ -426,7 +465,7 @@ function decodeOidcClaims(token) {
426
465
  function tokenFingerprint(token) {
427
466
  if (!token)
428
467
  return "absent";
429
- const sha = (0, import_node_crypto.createHash)("sha256").update(token).digest("hex").slice(0, 8);
468
+ const sha = (0, import_node_crypto2.createHash)("sha256").update(token).digest("hex").slice(0, 8);
430
469
  return `present(len=${token.length}, sha256=${sha})`;
431
470
  }
432
471
  function debugTokenClaims(label, token) {
@@ -720,206 +759,259 @@ async function ensureRepository(repository, token, claims, span) {
720
759
  }
721
760
 
722
761
  // 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;
762
+ function localImageRef(params) {
763
+ const { imageRef } = params;
764
+ if (!params.useBuildahStoreCache) {
765
+ return imageRef;
728
766
  }
729
- const built = Math.max(steps - cached, 0);
730
- info(`layer cache: ${cached}/${steps} steps reused, ${built} rebuilt`);
767
+ return `${imageRef.slice(0, imageRef.lastIndexOf(":"))}:vercel-buildah-store`;
731
768
  }
732
- var buildahEngine = {
733
- name: "buildah",
734
- async ensureReady(span) {
769
+ async function deploymentSourceDateEpoch() {
770
+ if ((0, import_build_utils3.resolveBuildConcurrency)() <= 1) {
771
+ return void 0;
772
+ }
773
+ if (readString(process.env.SOURCE_DATE_EPOCH?.trim()) !== void 0) {
774
+ return void 0;
775
+ }
776
+ try {
777
+ const path4 = (0, import_node_path5.join)(
778
+ await createBuildahStoreStateDirectory(),
779
+ "source-date-epoch"
780
+ );
781
+ const candidate = `${path4}.${(0, import_node_crypto3.randomUUID)()}`;
782
+ await (0, import_promises5.writeFile)(candidate, `${Math.floor(Date.now() / 1e3)}
783
+ `, {
784
+ flag: "wx",
785
+ mode: 384
786
+ });
735
787
  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;
788
+ await (0, import_promises5.link)(candidate, path4);
789
+ } catch (error) {
790
+ if (error.code !== "EEXIST")
791
+ throw error;
792
+ } finally {
793
+ await (0, import_promises5.rm)(candidate, { force: true });
750
794
  }
751
- },
752
- async logDiagnostics(span) {
753
- 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
+ const epoch = (await (0, import_promises5.readFile)(path4, "utf8")).trim();
796
+ if (!/^[0-9]+$/.test(epoch)) {
797
+ throw new Error(`invalid value ${JSON.stringify(epoch)}`);
766
798
  }
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}`);
782
- }
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 }
799
+ return epoch;
800
+ } catch (error) {
801
+ debug(
802
+ `could not share a Buildah source date epoch: ${error.message}`
803
+ );
804
+ return void 0;
805
+ }
806
+ }
807
+ function createBuildahEngine(client) {
808
+ return {
809
+ name: "buildah",
810
+ async ensureReady(span) {
811
+ try {
812
+ const storageDriver = await selectStorageDriver();
813
+ const version2 = await client.version();
814
+ span?.setAttributes({
815
+ "buildah.version": version2.split("\n")[0],
816
+ "buildah.storage_driver": storageDriver ?? "storage.conf"
817
+ });
818
+ } catch (err) {
819
+ const message = err.message;
820
+ if (/Command not found/i.test(message)) {
821
+ throw new Error(
822
+ 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."
823
+ );
824
+ }
825
+ throw err;
826
+ }
827
+ },
828
+ async logDiagnostics(span) {
829
+ try {
830
+ const storageDriver = await selectStorageDriver();
831
+ const version2 = await client.version();
832
+ info(
833
+ `buildah: ${version2.split("\n")[0] ?? version2} (storage-driver=${storageDriver ?? "storage.conf"})`
834
+ );
835
+ span?.setAttributes({
836
+ "container.engine": "buildah",
837
+ "buildah.version": toTag(version2.split("\n")[0]),
838
+ "buildah.storage_driver": toTag(storageDriver ?? "storage.conf")
839
+ });
840
+ } catch (err) {
841
+ debug(`buildah diagnostics unavailable: ${err.message}`);
842
+ }
843
+ },
844
+ async withRuntime(_span, fn) {
845
+ return fn();
846
+ },
847
+ async build(params) {
848
+ try {
849
+ const { stdout: stdout2 } = await client.run(["images", "--quiet"], {
850
+ quiet: true
851
+ });
852
+ const imageCount = stdout2.trim() ? stdout2.trim().split("\n").length : 0;
853
+ info(
854
+ imageCount > 0 ? `Buildah store: warm (${imageCount} image(s) present before build)` : "Buildah store: cold (no images present before build)"
855
+ );
856
+ } catch (err) {
857
+ debug(`could not read store warmth: ${err.message}`);
858
+ }
859
+ const sourceDateEpoch = await deploymentSourceDateEpoch();
860
+ if (sourceDateEpoch) {
861
+ info(
862
+ `Buildah source date epoch: ${sourceDateEpoch} (shared by this deployment's concurrent service builds)`
863
+ );
864
+ }
865
+ const usage = await BuildahImageUsage.begin();
866
+ const { stdout, stderr } = await client.run([
867
+ "build",
868
+ "--platform",
869
+ TARGET_PLATFORM,
870
+ // Commit and cache a layer per Dockerfile instruction so unchanged steps
871
+ // (base image, dependency installs, etc.) can be reused on later builds
872
+ // when the image store is warm. Without this buildah squashes the build
873
+ // and no per-step caching happens.
874
+ "--layers",
875
+ // With --layers, Buildah leaves the working container of a FROM-only
876
+ // stage behind, which stops the store cache from being saved.
877
+ "--force-rm",
878
+ ...sourceDateEpoch ? ["--source-date-epoch", sourceDateEpoch] : [],
879
+ ...params.useBuildahStoreCache ? ["--pull=always"] : [],
880
+ // Use the host network namespace for RUN steps. The build runs inside a
881
+ // restricted Hive cell that cannot program iptables, so buildah's default
882
+ // rootless networking (netavark) fails with
883
+ // "netavark: iptables ... Could not fetch rule set generation id".
884
+ // Host networking skips per-container network setup and reuses the cell's
885
+ // existing egress.
886
+ "--network",
887
+ "host",
888
+ ...buildArgFlags(params),
889
+ "-t",
890
+ localImageRef(params),
891
+ "-f",
892
+ params.dockerfilePath,
893
+ params.contextDir
894
+ ]);
895
+ await usage?.record(
896
+ () => imageIdsFromBuildOutput(`${stdout}
897
+ ${stderr}`)
821
898
  );
822
- } catch (err) {
823
- const message = err.message;
824
- if (/denied|forbidden|unauthorized|401|403/i.test(message)) {
825
- throw new Error(
826
- formatVcrAuthError(
899
+ },
900
+ async login(params) {
901
+ try {
902
+ await client.run(
903
+ [
904
+ "login",
827
905
  params.registry,
906
+ "--username",
828
907
  params.username,
829
- `Underlying error: ${message}`
830
- )
908
+ "--password-stdin"
909
+ ],
910
+ { input: params.token, quiet: !DEBUG }
831
911
  );
912
+ } catch (err) {
913
+ const message = err.message;
914
+ if (/denied|forbidden|unauthorized|401|403/i.test(message)) {
915
+ throw new Error(
916
+ formatVcrAuthError(
917
+ params.registry,
918
+ params.username,
919
+ `Underlying error: ${message}`
920
+ )
921
+ );
922
+ }
923
+ throw err;
832
924
  }
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");
925
+ },
926
+ async verifyStorage(span) {
927
+ await client.verifyStorage((message) => {
928
+ info(message);
929
+ const info0 = message.split("\n")[0];
930
+ span?.setAttributes({ "buildah.storage.verify": info0 });
931
+ });
932
+ },
933
+ async reportStorage(span) {
934
+ if (!DEBUG) {
851
935
  return;
852
936
  }
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")
937
+ try {
938
+ const storeInfo = await client.info();
939
+ if (!storeInfo) {
940
+ debug("buildah info: no `store` field in output");
941
+ return;
942
+ }
943
+ info(
944
+ `buildah storage: graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} driver=${storeInfo.driver} backingFs=${storeInfo.backingFs || "?"}`
906
945
  );
946
+ span?.setAttributes({
947
+ "buildah.storage.graph_root": storeInfo.graphRoot,
948
+ "buildah.storage.run_root": storeInfo.runRoot,
949
+ "buildah.storage.driver": storeInfo.driver,
950
+ "buildah.storage.backing_fs": storeInfo.backingFs
951
+ });
952
+ } catch (err) {
953
+ debug(`buildah storage report unavailable: ${err.message}`);
954
+ }
955
+ },
956
+ async push(params) {
957
+ const digestDir = (0, import_node_fs3.mkdtempSync)((0, import_node_path5.join)((0, import_node_os5.tmpdir)(), "vercel-container-digest-"));
958
+ const digestFile = (0, import_node_path5.join)(digestDir, "digest");
959
+ const zstdEnabled = !readString(process.env.VERCEL_VCR_DISABLE_ZSTD);
960
+ const zstdArgs = zstdEnabled ? [
961
+ "--compression-format",
962
+ "zstd",
963
+ "--compression-level",
964
+ "3",
965
+ "--force-compression",
966
+ "--format",
967
+ "oci"
968
+ ] : [];
969
+ info(
970
+ `pushing ${params.imageRef} ` + (zstdEnabled ? "with zstd compression (level=3, force, oci)" : "with default compression (zstd disabled)")
971
+ );
972
+ const pushStart = Date.now();
973
+ let digest;
974
+ try {
975
+ await client.run([
976
+ "push",
977
+ ...zstdArgs,
978
+ "--digestfile",
979
+ digestFile,
980
+ localImageRef(params),
981
+ ...params.useBuildahStoreCache ? [`docker://${params.imageRef}`] : []
982
+ ]);
983
+ digest = readDigestFile(digestFile);
984
+ } catch (err) {
985
+ const message = err.message;
986
+ if (/denied|forbidden|unauthorized|not found|401|403|404/i.test(message)) {
987
+ throw new Error(
988
+ [
989
+ `Pushing ${params.imageRef} was denied.`,
990
+ "",
991
+ `The build tried to ensure the "${params.repository}" repository exists, but`,
992
+ "the push was still rejected. Verify access (or create the repository under",
993
+ "your project's Sandboxes \u2192 Container Registry tab), then re-run the build.",
994
+ "",
995
+ `Underlying error: ${message}`
996
+ ].join("\n")
997
+ );
998
+ }
999
+ throw err;
1000
+ } finally {
1001
+ (0, import_node_fs3.rmSync)(digestDir, { recursive: true, force: true });
907
1002
  }
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;
1003
+ debug(`push completed in ${Date.now() - pushStart}ms`);
1004
+ if (DIGEST_RE.test(digest)) {
1005
+ debug(`pushed digest ${digest}`);
1006
+ return digest;
1007
+ }
1008
+ debug(
1009
+ `--digestfile did not contain a digest (${JSON.stringify(digest)}) \u2014 resolving registry manifest`
1010
+ );
1011
+ return resolvePushedImageDigest(params);
916
1012
  }
917
- debug(
918
- `--digestfile did not contain a digest (${JSON.stringify(digest)}) \u2014 resolving registry manifest`
919
- );
920
- return resolvePushedImageDigest(params);
921
- }
922
- };
1013
+ };
1014
+ }
923
1015
  function readDigestFile(path4) {
924
1016
  try {
925
1017
  return (0, import_node_fs3.readFileSync)(path4, "utf8").trim();
@@ -930,19 +1022,11 @@ function readDigestFile(path4) {
930
1022
  }
931
1023
 
932
1024
  // src/engines/docker.ts
933
- var import_node_child_process3 = require("child_process");
1025
+ var import_node_child_process2 = require("child_process");
934
1026
  function runDocker(args, opts = {}) {
935
1027
  debug(`exec: docker ${args.join(" ")}`);
936
1028
  return run("docker", args, opts);
937
1029
  }
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
1030
  async function isDockerDaemonReachable() {
947
1031
  try {
948
1032
  await run("docker", ["version", "--format", "{{.Server.Version}}"], {
@@ -966,7 +1050,7 @@ async function startDockerDaemon(span) {
966
1050
  span?.setAttributes({ "docker.storage_driver": driver });
967
1051
  step(`Starting Docker daemon (storage-driver=${driver})`);
968
1052
  debug(`exec: dockerd ${args.join(" ")}`);
969
- const child = (0, import_node_child_process3.spawn)("dockerd", args, {
1053
+ const child = (0, import_node_child_process2.spawn)("dockerd", args, {
970
1054
  stdio: ["ignore", "pipe", "pipe"]
971
1055
  });
972
1056
  let log = "";
@@ -1019,7 +1103,7 @@ async function startDockerDaemon(span) {
1019
1103
  ].join("\n")
1020
1104
  );
1021
1105
  }
1022
- await new Promise((resolve4) => setTimeout(resolve4, 500));
1106
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
1023
1107
  }
1024
1108
  }
1025
1109
  async function stopDockerDaemon(daemon, span) {
@@ -1029,12 +1113,12 @@ async function stopDockerDaemon(daemon, span) {
1029
1113
  }
1030
1114
  step("Stopping Docker daemon");
1031
1115
  const stopTimeoutMs = Number(process.env.VERCEL_VCR_DOCKERD_STOP_TIMEOUT_MS) || 1e4;
1032
- await new Promise((resolve4) => {
1116
+ await new Promise((resolve5) => {
1033
1117
  let settled = false;
1034
1118
  const finish = () => {
1035
1119
  if (!settled) {
1036
1120
  settled = true;
1037
- resolve4();
1121
+ resolve5();
1038
1122
  }
1039
1123
  };
1040
1124
  child.once("exit", finish);
@@ -1062,7 +1146,7 @@ async function withManagedDaemon(span, fn) {
1062
1146
  if (await isDockerDaemonReachable()) {
1063
1147
  return fn();
1064
1148
  }
1065
- if (!await hasBinary2("dockerd")) {
1149
+ if (!await hasCommand("dockerd")) {
1066
1150
  return fn();
1067
1151
  }
1068
1152
  const daemon = await withSpan(
@@ -1162,6 +1246,16 @@ ${version2.trim()}`);
1162
1246
  params.contextDir
1163
1247
  ]);
1164
1248
  },
1249
+ async onBuildTriggers(imageRef) {
1250
+ const { stdout } = await runDocker([
1251
+ "image",
1252
+ "inspect",
1253
+ "--format",
1254
+ "{{json .Config.OnBuild}}",
1255
+ imageRef
1256
+ ]);
1257
+ return JSON.parse(stdout) ?? [];
1258
+ },
1165
1259
  async login(params) {
1166
1260
  try {
1167
1261
  await runDocker(
@@ -1223,41 +1317,163 @@ ${version2.trim()}`);
1223
1317
  };
1224
1318
 
1225
1319
  // src/engines/index.ts
1226
- function selectContainerEngine() {
1320
+ function selectContainerEngine(client) {
1227
1321
  const override = readString(
1228
1322
  process.env.VERCEL_CONTAINER_ENGINE
1229
1323
  )?.toLowerCase();
1230
1324
  if (override === "buildah")
1231
- return buildahEngine;
1325
+ return createBuildahEngine(client);
1232
1326
  if (override === "docker")
1233
1327
  return dockerEngine;
1234
- return isBuildContainer() ? buildahEngine : dockerEngine;
1328
+ return isBuildContainer() ? createBuildahEngine(client) : dockerEngine;
1235
1329
  }
1236
1330
 
1331
+ // src/clients/buildah.ts
1332
+ var BuildahClient = class {
1333
+ constructor(lock) {
1334
+ this.lock = lock;
1335
+ }
1336
+ async run(args, options = {}) {
1337
+ const fullArgs = [...await buildahStorageArgs(), ...args];
1338
+ debug(`exec: buildah ${fullArgs.join(" ")}`);
1339
+ const start = Date.now();
1340
+ try {
1341
+ const [command, ...commandArgs] = await this.lock.wrap([
1342
+ "buildah",
1343
+ ...fullArgs
1344
+ ]);
1345
+ const result = await run(command, commandArgs, options);
1346
+ debug(`buildah ${args[0]} finished in ${Date.now() - start}ms`);
1347
+ return result;
1348
+ } catch (error) {
1349
+ debug(`buildah ${args[0]} failed after ${Date.now() - start}ms`);
1350
+ throw error;
1351
+ }
1352
+ }
1353
+ /** Version checks must not open the store or acquire its lock before restore. */
1354
+ version() {
1355
+ this.cachedVersion ??= run("buildah", ["--version"], { quiet: true }).then(({ stdout }) => stdout.trim()).catch((error) => {
1356
+ this.cachedVersion = void 0;
1357
+ throw error;
1358
+ });
1359
+ return this.cachedVersion;
1360
+ }
1361
+ /** Only stable store fields are cached; image and container counts are not. */
1362
+ info() {
1363
+ this.cachedStoreInfo ??= this.readStoreInfo().then(
1364
+ (store) => {
1365
+ if (!store)
1366
+ this.cachedStoreInfo = void 0;
1367
+ return store;
1368
+ },
1369
+ (error) => {
1370
+ this.cachedStoreInfo = void 0;
1371
+ throw error;
1372
+ }
1373
+ );
1374
+ return this.cachedStoreInfo;
1375
+ }
1376
+ /** Storage mismatches are diagnostic unless strict verification is requested. */
1377
+ async verifyStorage(log = () => {
1378
+ }) {
1379
+ if (!isBuildContainer())
1380
+ return;
1381
+ if (readString(process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER))
1382
+ return;
1383
+ const strict = Boolean(readString(process.env.VERCEL_VCR_STRICT_STORAGE));
1384
+ let storeInfo;
1385
+ try {
1386
+ storeInfo = await this.info();
1387
+ } catch (error) {
1388
+ const message = `Could not verify buildah storage via \`buildah info\`: ${error.message}`;
1389
+ if (strict)
1390
+ throw new Error(message);
1391
+ log(message);
1392
+ return;
1393
+ }
1394
+ if (!storeInfo) {
1395
+ const message = "Could not verify buildah storage: `buildah info` returned no store data.";
1396
+ if (strict)
1397
+ throw new Error(message);
1398
+ log(message);
1399
+ return;
1400
+ }
1401
+ const problems = [];
1402
+ if (storeInfo.driver !== REQUIRED_BUILD_CONTAINER_DRIVER) {
1403
+ problems.push(
1404
+ `storage driver is "${storeInfo.driver}", expected "${REQUIRED_BUILD_CONTAINER_DRIVER}"`
1405
+ );
1406
+ }
1407
+ if (storeInfo.graphRoot !== BUILDAH_GRAPH_ROOT) {
1408
+ problems.push(
1409
+ `graphRoot is "${storeInfo.graphRoot}", expected the mounted volume "${BUILDAH_GRAPH_ROOT}"`
1410
+ );
1411
+ }
1412
+ if (storeInfo.backingFs && storeInfo.backingFs === "overlayfs") {
1413
+ problems.push(
1414
+ `backing filesystem is "${storeInfo.backingFs}" (the overlay rootfs), not the mounted volume`
1415
+ );
1416
+ }
1417
+ const summary = `buildah storage: driver=${storeInfo.driver} graphRoot=${storeInfo.graphRoot} runRoot=${storeInfo.runRoot} backingFs=${storeInfo.backingFs || "?"}`;
1418
+ if (problems.length === 0) {
1419
+ log(`${summary} \u2014 verified`);
1420
+ return;
1421
+ }
1422
+ const detail = `${summary}
1423
+ Problems: ${problems.join("; ")}.
1424
+ 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).`;
1425
+ if (strict) {
1426
+ throw new Error(
1427
+ `Container build storage is not configured as intended.
1428
+ ${detail}`
1429
+ );
1430
+ }
1431
+ log(
1432
+ `${detail}
1433
+ Continuing (set VERCEL_VCR_STRICT_STORAGE=1 to fail builds).`
1434
+ );
1435
+ }
1436
+ async readStoreInfo() {
1437
+ const { stdout } = await this.run(["info"], { quiet: true });
1438
+ const store = JSON.parse(stdout).store;
1439
+ if (!store)
1440
+ return void 0;
1441
+ const graphStatus = store.GraphStatus;
1442
+ return {
1443
+ graphRoot: String(store.GraphRoot ?? ""),
1444
+ runRoot: String(store.RunRoot ?? ""),
1445
+ driver: String(store.GraphDriverName ?? ""),
1446
+ backingFs: String(
1447
+ graphStatus?.["Backing Filesystem"] ?? graphStatus?.["Backing filesystem"] ?? ""
1448
+ )
1449
+ };
1450
+ }
1451
+ };
1452
+
1237
1453
  // src/buildpacks/build.ts
1238
1454
  var import_node_fs13 = require("fs");
1239
- var import_node_os7 = require("os");
1240
- var import_node_path13 = require("path");
1455
+ var import_node_os10 = require("os");
1456
+ var import_node_path17 = require("path");
1241
1457
 
1242
1458
  // src/buildpacks/cache.ts
1243
- var import_node_crypto2 = require("crypto");
1459
+ var import_node_crypto4 = require("crypto");
1244
1460
  var import_node_fs4 = require("fs");
1245
- var import_promises = require("fs/promises");
1246
- var import_node_path4 = require("path");
1461
+ var import_promises6 = require("fs/promises");
1462
+ var import_node_path6 = require("path");
1247
1463
  var BUILDPACK_CACHE_DIR = ".vercel/cache/buildpacks";
1248
1464
  var CNB_COMMITTED_DIR = "committed";
1249
1465
  var CNB_CACHE_METADATA_FILE = "io.buildpacks.lifecycle.cache.metadata";
1250
1466
  var IMAGE_ID_FILE = "image-id";
1251
1467
  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("/") || ".";
1468
+ const root = (0, import_node_path6.resolve)(options.repoRootPath || options.workPath);
1469
+ const app = (0, import_node_path6.relative)(root, (0, import_node_path6.resolve)(options.workPath)).split(import_node_path6.sep).join("/") || ".";
1254
1470
  const daemon = options.config?.daemon;
1255
1471
  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);
1472
+ const key = (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([app, owner])).digest("hex").slice(0, 32);
1473
+ return (0, import_node_path6.join)(root, BUILDPACK_CACHE_DIR, key);
1258
1474
  }
1259
1475
  async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1260
- const committed = (0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR);
1476
+ const committed = (0, import_node_path6.join)(cacheDir, CNB_COMMITTED_DIR);
1261
1477
  const [copy, key] = await Promise.allSettled([
1262
1478
  copyCommitted(dir, cacheDir),
1263
1479
  cacheKey
@@ -1267,7 +1483,7 @@ async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1267
1483
  throw copy.reason;
1268
1484
  if (key.status === "rejected")
1269
1485
  throw key.reason;
1270
- if (readRegularFile((0, import_node_path4.join)(dir, IMAGE_ID_FILE)) !== key.value) {
1486
+ if (readRegularFile((0, import_node_path6.join)(dir, IMAGE_ID_FILE)) !== key.value) {
1271
1487
  (0, import_node_fs4.rmSync)(committed, { recursive: true, force: true });
1272
1488
  return { status: "miss", reason: "base_image_changed" };
1273
1489
  }
@@ -1282,11 +1498,11 @@ async function restoreBuildpackCache(dir, cacheKey, cacheDir) {
1282
1498
  }
1283
1499
  }
1284
1500
  async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1285
- const committed = (0, import_node_path4.join)(cacheDir, CNB_COMMITTED_DIR);
1501
+ const committed = (0, import_node_path6.join)(cacheDir, CNB_COMMITTED_DIR);
1286
1502
  let metadata;
1287
1503
  try {
1288
1504
  assertDirectory(committed);
1289
- metadata = readRegularFile((0, import_node_path4.join)(committed, CNB_CACHE_METADATA_FILE));
1505
+ metadata = readRegularFile((0, import_node_path6.join)(committed, CNB_CACHE_METADATA_FILE));
1290
1506
  } catch (error) {
1291
1507
  const missing = error.code === "ENOENT";
1292
1508
  return {
@@ -1300,7 +1516,7 @@ async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1300
1516
  const bytes = sizeOf(committed);
1301
1517
  (0, import_node_fs4.rmSync)(dir, { recursive: true, force: true });
1302
1518
  await copyCommitted(cacheDir, dir);
1303
- (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(dir, IMAGE_ID_FILE), cacheKey);
1519
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dir, IMAGE_ID_FILE), cacheKey);
1304
1520
  return { status: "saved", bytes };
1305
1521
  } catch (error) {
1306
1522
  try {
@@ -1312,16 +1528,16 @@ async function saveBuildpackCache(dir, cacheKey, cacheDir) {
1312
1528
  }
1313
1529
  function isSaved(dir, cacheKey, metadata) {
1314
1530
  try {
1315
- const committed = (0, import_node_path4.join)(dir, CNB_COMMITTED_DIR);
1531
+ const committed = (0, import_node_path6.join)(dir, CNB_COMMITTED_DIR);
1316
1532
  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;
1533
+ 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
1534
  } catch {
1319
1535
  return false;
1320
1536
  }
1321
1537
  }
1322
1538
  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);
1539
+ const source = (0, import_node_path6.join)(from, CNB_COMMITTED_DIR);
1540
+ const target = (0, import_node_path6.join)(to, CNB_COMMITTED_DIR);
1325
1541
  assertDirectory(source);
1326
1542
  (0, import_node_fs4.mkdirSync)(target, { recursive: true });
1327
1543
  const entries = (0, import_node_fs4.readdirSync)(source, { withFileTypes: true });
@@ -1331,9 +1547,9 @@ async function copyCommitted(from, to) {
1331
1547
  }
1332
1548
  const copies = await Promise.allSettled(
1333
1549
  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),
1550
+ (entry) => (0, import_promises6.copyFile)(
1551
+ (0, import_node_path6.join)(source, entry.name),
1552
+ (0, import_node_path6.join)(target, entry.name),
1337
1553
  import_node_fs4.constants.COPYFILE_FICLONE
1338
1554
  )
1339
1555
  )
@@ -1344,12 +1560,12 @@ async function copyCommitted(from, to) {
1344
1560
  }
1345
1561
  function assertDirectory(path4) {
1346
1562
  if (!(0, import_node_fs4.lstatSync)(path4).isDirectory()) {
1347
- throw new Error(`${(0, import_node_path4.basename)(path4)} is not a directory`);
1563
+ throw new Error(`${(0, import_node_path6.basename)(path4)} is not a directory`);
1348
1564
  }
1349
1565
  }
1350
1566
  function readRegularFile(path4) {
1351
1567
  if (!(0, import_node_fs4.lstatSync)(path4).isFile())
1352
- throw notRegularFile((0, import_node_path4.basename)(path4));
1568
+ throw notRegularFile((0, import_node_path6.basename)(path4));
1353
1569
  return (0, import_node_fs4.readFileSync)(path4, "utf8");
1354
1570
  }
1355
1571
  function notRegularFile(name) {
@@ -1357,25 +1573,13 @@ function notRegularFile(name) {
1357
1573
  }
1358
1574
  function sizeOf(dir) {
1359
1575
  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,
1576
+ (bytes, name) => bytes + (0, import_node_fs4.lstatSync)((0, import_node_path6.join)(dir, name)).size,
1361
1577
  0
1362
1578
  );
1363
1579
  }
1364
1580
  function firstLine(error) {
1365
1581
  return error.message.split("\n")[0];
1366
1582
  }
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
1583
  function describeRestore(outcome) {
1380
1584
  return outcome.status === "hit" ? `restored buildpack cache (${formatBytes(outcome.bytes)})` : `no buildpack cache to restore (${outcome.reason})`;
1381
1585
  }
@@ -1406,11 +1610,11 @@ function recordSave(span, outcome, durationMs) {
1406
1610
  }
1407
1611
 
1408
1612
  // src/buildpacks/lifecycle/lifecycle.ts
1409
- var import_build_utils3 = require("@vercel/build-utils");
1410
- var import_node_crypto3 = require("crypto");
1613
+ var import_build_utils4 = require("@vercel/build-utils");
1614
+ var import_node_crypto5 = require("crypto");
1411
1615
  var import_node_fs5 = require("fs");
1412
- var import_node_os4 = require("os");
1413
- var import_node_path5 = require("path");
1616
+ var import_node_os6 = require("os");
1617
+ var import_node_path7 = require("path");
1414
1618
  var import_smol_toml = require("smol-toml");
1415
1619
  var import_tar = require("tar");
1416
1620
 
@@ -1561,39 +1765,39 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1561
1765
  const symlinks = /* @__PURE__ */ new Set();
1562
1766
  for (const [name, file] of Object.entries(files)) {
1563
1767
  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) {
1768
+ const normalized = import_node_path7.posix.normalize(portable).replace(/\/$/, "");
1769
+ if (!normalized || normalized === "." || import_node_path7.win32.isAbsolute(name) || import_node_path7.posix.isAbsolute(portable) || portable.split("/").includes("..") || normalized in staged) {
1566
1770
  throw new Error(`Invalid buildpack source path ${JSON.stringify(name)}.`);
1567
1771
  }
1568
1772
  staged[normalized] = file;
1569
- if (!(0, import_build_utils3.isSymbolicLink)(file.mode))
1773
+ if (!(0, import_build_utils4.isSymbolicLink)(file.mode))
1570
1774
  continue;
1571
- let target = (0, import_build_utils3.getSymlinkTarget)(file);
1775
+ let target = (0, import_build_utils4.getSymlinkTarget)(file);
1572
1776
  if (target === null && file.type === "FileRef") {
1573
- target = (await (0, import_build_utils3.streamToBuffer)(await file.toStreamAsync())).toString();
1777
+ target = (await (0, import_build_utils4.streamToBuffer)(await file.toStreamAsync())).toString();
1574
1778
  }
1575
1779
  if (!target) {
1576
1780
  throw new Error(`Cannot read buildpack source symlink ${name}.`);
1577
1781
  }
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)) {
1782
+ if ((0, import_node_path7.isAbsolute)(target)) {
1783
+ target = (0, import_node_path7.relative)((0, import_node_path7.resolve)(workPath, import_node_path7.posix.dirname(normalized)), target) || ".";
1784
+ } else if (import_node_path7.win32.isAbsolute(target)) {
1581
1785
  throw new Error(
1582
1786
  `Buildpack source symlink ${name} has an absolute target.`
1583
1787
  );
1584
1788
  }
1585
1789
  target = target.replace(/\\/g, "/");
1586
- const resolved = import_node_path5.posix.join(import_node_path5.posix.dirname(normalized), target);
1790
+ const resolved = import_node_path7.posix.join(import_node_path7.posix.dirname(normalized), target);
1587
1791
  if (resolved === ".." || resolved.startsWith("../")) {
1588
1792
  throw new Error(
1589
1793
  `Buildpack source symlink ${name} escapes the service root. Buildpack services must be self-contained.`
1590
1794
  );
1591
1795
  }
1592
- staged[normalized] = new import_build_utils3.FileBlob({ data: target, mode: file.mode });
1796
+ staged[normalized] = new import_build_utils4.FileBlob({ data: target, mode: file.mode });
1593
1797
  symlinks.add(normalized);
1594
1798
  }
1595
1799
  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)) {
1800
+ for (let parent = import_node_path7.posix.dirname(name); parent !== "."; parent = import_node_path7.posix.dirname(parent)) {
1597
1801
  if (symlinks.has(parent)) {
1598
1802
  throw new Error(
1599
1803
  `Buildpack source ${name} is inside symlink ${parent}.`
@@ -1601,17 +1805,17 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1601
1805
  }
1602
1806
  }
1603
1807
  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)) {
1808
+ if (file.type === "FileFsRef" && !(0, import_build_utils4.isSymbolicLink)(file.mode)) {
1809
+ const sourcePath = (0, import_node_path7.resolve)(file.fsPath);
1810
+ const sourceRelative = (0, import_node_path7.relative)((0, import_node_path7.resolve)(workPath), sourcePath);
1811
+ if (sourceRelative.startsWith(`..${import_node_path7.sep}`) || sourceRelative === ".." || (0, import_node_path7.isAbsolute)(sourceRelative)) {
1608
1812
  continue;
1609
1813
  }
1610
1814
  const sourceRoot = import_node_fs5.realpathSync.native(workPath);
1611
1815
  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)) {
1816
+ if (sourceTarget !== (0, import_node_path7.resolve)(sourceRoot, sourceRelative)) {
1817
+ const target = (0, import_node_path7.relative)(sourceRoot, sourceTarget).split(import_node_path7.sep).join("/");
1818
+ if (target === ".." || target.startsWith("../") || (0, import_node_path7.isAbsolute)(target) || !(target in staged)) {
1615
1819
  throw new Error(
1616
1820
  `Buildpack source ${name} follows a symlink to an excluded or outside-root file. Buildpack services must be self-contained.`
1617
1821
  );
@@ -1619,25 +1823,25 @@ async function stageWorkspace({ files, workPath }, destinationDir) {
1619
1823
  }
1620
1824
  }
1621
1825
  }
1622
- await (0, import_build_utils3.download)(staged, destinationDir);
1826
+ await (0, import_build_utils4.download)(staged, destinationDir);
1623
1827
  const root = import_node_fs5.realpathSync.native(destinationDir);
1624
1828
  for (const name of symlinks) {
1625
1829
  let target;
1626
1830
  try {
1627
- target = (0, import_node_path5.relative)(root, import_node_fs5.realpathSync.native((0, import_node_path5.join)(destinationDir, name)));
1831
+ target = (0, import_node_path7.relative)(root, import_node_fs5.realpathSync.native((0, import_node_path7.join)(destinationDir, name)));
1628
1832
  } catch {
1629
1833
  throw new Error(
1630
1834
  `Buildpack source symlink ${name} has no target in the staged service.`
1631
1835
  );
1632
1836
  }
1633
- if (target === ".." || target.startsWith(`..${import_node_path5.sep}`) || (0, import_node_path5.isAbsolute)(target)) {
1837
+ if (target === ".." || target.startsWith(`..${import_node_path7.sep}`) || (0, import_node_path7.isAbsolute)(target)) {
1634
1838
  throw new Error(
1635
1839
  `Buildpack source symlink ${name} escapes the staged service.`
1636
1840
  );
1637
1841
  }
1638
1842
  }
1639
1843
  }
1640
- function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.tmpdir)()) {
1844
+ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os6.tmpdir)()) {
1641
1845
  const environment = { ...buildEnv };
1642
1846
  delete environment.VERCEL_NODE_HOME;
1643
1847
  delete environment.VERCEL_ENTRYPOINT;
@@ -1651,14 +1855,14 @@ function writePlatformEnvDir(buildEnv, config, parentDir = (0, import_node_os4.t
1651
1855
  if (config.command) {
1652
1856
  environment.VERCEL_COMMAND = JSON.stringify(config.command);
1653
1857
  }
1654
- const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-env-"));
1858
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)(parentDir, "vercel-cnb-env-"));
1655
1859
  (0, import_node_fs5.chmodSync)(dir, 493);
1656
1860
  for (const [key, value] of Object.entries(environment)) {
1657
1861
  if (!ENV_NAME_RE.test(key)) {
1658
1862
  debug(`skipping build env var with an unsupported name: ${key}`);
1659
1863
  continue;
1660
1864
  }
1661
- const file = (0, import_node_path5.join)(dir, key);
1865
+ const file = (0, import_node_path7.join)(dir, key);
1662
1866
  (0, import_node_fs5.writeFileSync)(file, value);
1663
1867
  (0, import_node_fs5.chmodSync)(file, 420);
1664
1868
  }
@@ -1723,7 +1927,7 @@ function lifecycleFailure(error, context) {
1723
1927
  { exitCode }
1724
1928
  );
1725
1929
  }
1726
- function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1930
+ function writeOrderDir(buildpack, parentDir = (0, import_node_os6.tmpdir)()) {
1727
1931
  const order = {
1728
1932
  order: [
1729
1933
  {
@@ -1734,9 +1938,9 @@ function writeOrderDir(buildpack, parentDir = (0, import_node_os4.tmpdir)()) {
1734
1938
  }
1735
1939
  ]
1736
1940
  };
1737
- const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)(parentDir, "vercel-cnb-order-"));
1941
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)(parentDir, "vercel-cnb-order-"));
1738
1942
  (0, import_node_fs5.chmodSync)(dir, 493);
1739
- const file = (0, import_node_path5.join)(dir, "order.toml");
1943
+ const file = (0, import_node_path7.join)(dir, "order.toml");
1740
1944
  (0, import_node_fs5.writeFileSync)(file, (0, import_smol_toml.stringify)(order));
1741
1945
  (0, import_node_fs5.chmodSync)(file, 420);
1742
1946
  return dir;
@@ -1765,7 +1969,7 @@ async function downloadPinnedArchive(archive, destinationPath) {
1765
1969
  );
1766
1970
  }
1767
1971
  const contents = new Uint8Array(await response.arrayBuffer());
1768
- const checksum = (0, import_node_crypto3.createHash)("sha256").update(contents).digest("hex");
1972
+ const checksum = (0, import_node_crypto5.createHash)("sha256").update(contents).digest("hex");
1769
1973
  if (checksum !== archive.sha256) {
1770
1974
  throw new Error(
1771
1975
  `Checksum mismatch for ${archiveName(archive)}: expected ${archive.sha256}, received ${checksum}.`
@@ -1774,7 +1978,7 @@ async function downloadPinnedArchive(archive, destinationPath) {
1774
1978
  (0, import_node_fs5.writeFileSync)(destinationPath, contents);
1775
1979
  }
1776
1980
  async function fetchPinnedArchive(archive, destinationDir) {
1777
- const archivePath = (0, import_node_path5.join)(destinationDir, `${archive.sha256}.tgz`);
1981
+ const archivePath = (0, import_node_path7.join)(destinationDir, `${archive.sha256}.tgz`);
1778
1982
  try {
1779
1983
  await downloadPinnedArchive(archive, archivePath);
1780
1984
  (0, import_tar.extract)({ file: archivePath, cwd: destinationDir, sync: true });
@@ -1791,16 +1995,16 @@ function blobPath(layoutDir, digest) {
1791
1995
  if (!match) {
1792
1996
  throw new Error(`Unsupported OCI digest ${JSON.stringify(digest)}.`);
1793
1997
  }
1794
- return (0, import_node_path5.join)(layoutDir, "blobs", "sha256", match[1]);
1998
+ return (0, import_node_path7.join)(layoutDir, "blobs", "sha256", match[1]);
1795
1999
  }
1796
2000
  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");
2001
+ const layoutDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)((0, import_node_os6.tmpdir)(), "vercel-cnb-buildpackage-"));
2002
+ const archivePath = (0, import_node_path7.join)(layoutDir, "buildpackage.cnb");
1799
2003
  try {
1800
2004
  await downloadPinnedArchive(archive, archivePath);
1801
2005
  await (0, import_tar.extract)({ file: archivePath, cwd: layoutDir });
1802
2006
  const index = readOciJson(
1803
- (0, import_node_path5.join)(layoutDir, "index.json")
2007
+ (0, import_node_path7.join)(layoutDir, "index.json")
1804
2008
  );
1805
2009
  const name = archiveName(archive);
1806
2010
  const manifestDigest = index.manifests?.[0]?.digest;
@@ -1822,7 +2026,7 @@ async function fetchBuildpackage(archive, cnbDir) {
1822
2026
  );
1823
2027
  }
1824
2028
  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");
2029
+ const actual = (0, import_node_crypto5.createHash)("sha256").update(new Uint8Array((0, import_node_fs5.readFileSync)(layerPath))).digest("hex");
1826
2030
  if (`sha256:${actual}` !== layer.digest) {
1827
2031
  throw new Error(
1828
2032
  `${name} layer ${layer.digest} hashes to sha256:${actual}.`
@@ -1859,14 +2063,14 @@ async function fetchDistribution(buildpack, cnbDir) {
1859
2063
  )
1860
2064
  ]);
1861
2065
  for (const entry of buildpack.buildpacks) {
1862
- const directory = (0, import_node_path5.join)(
2066
+ const directory = (0, import_node_path7.join)(
1863
2067
  cnbDir,
1864
2068
  "buildpacks",
1865
2069
  entry.id.replace("/", "_"),
1866
2070
  entry.version
1867
2071
  );
1868
2072
  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()) {
2073
+ if (!(0, import_node_fs5.statSync)((0, import_node_path7.join)(directory, file), { throwIfNoEntry: false })?.isFile()) {
1870
2074
  throw new Error(
1871
2075
  `Buildpack ${entry.id}@${entry.version} is missing ${file}.`
1872
2076
  );
@@ -1891,20 +2095,113 @@ function creatorArgs(image, imageRef, options = {}) {
1891
2095
 
1892
2096
  // src/buildpacks/lifecycle/buildah.ts
1893
2097
  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) {
2098
+ var import_node_crypto6 = require("crypto");
2099
+ var import_node_os8 = require("os");
2100
+ var import_node_path10 = require("path");
2101
+
2102
+ // src/buildah-store-cache/cleanup.ts
2103
+ var import_node_child_process3 = require("child_process");
2104
+
2105
+ // src/buildah-store-cache/command/index.ts
2106
+ var import_node_path8 = require("path");
2107
+ var STORE_COMMAND_SCRIPT = (0, import_node_path8.join)(__dirname, "buildah-store-command.js");
2108
+ function storeCommand(request, script = STORE_COMMAND_SCRIPT) {
2109
+ return [process.execPath, script, JSON.stringify(request)];
2110
+ }
2111
+ async function runStoreCommand(request, lock) {
2112
+ const [command, ...args] = lock.exclusive(storeCommand(request));
2113
+ const { stdout } = await run(command, args, { quiet: true });
2114
+ return JSON.parse(stdout);
2115
+ }
2116
+
2117
+ // src/buildah-store-cache/lock.ts
2118
+ var import_node_os7 = require("os");
2119
+ var import_node_path9 = require("path");
2120
+ var FileLock = class {
2121
+ constructor(path4) {
2122
+ this.path = path4;
2123
+ }
2124
+ shared(command) {
2125
+ return this.wrap("--shared", command);
2126
+ }
2127
+ exclusive(command) {
2128
+ return this.wrap("--exclusive", command);
2129
+ }
2130
+ wrap(mode, command) {
2131
+ return ["flock", mode, "--close", this.path, ...command];
2132
+ }
2133
+ };
2134
+ var flockAvailable;
2135
+ async function buildahStoreLock() {
2136
+ if (!buildahStoreCacheEnabled()) {
2137
+ return void 0;
2138
+ }
2139
+ flockAvailable ??= hasCommand("flock");
2140
+ return await flockAvailable ? new FileLock((0, import_node_path9.join)((0, import_node_os7.tmpdir)(), "vercel-buildah-store.lock")) : void 0;
2141
+ }
2142
+ var sharedBuildahStoreLock = {
2143
+ async wrap(command) {
2144
+ const lock = await buildahStoreLock();
2145
+ return lock ? lock.shared(command) : command;
2146
+ }
2147
+ };
2148
+
2149
+ // src/buildah-store-cache/cleanup.ts
2150
+ async function startContainerCleanup(name) {
2151
+ const rmArgs = [...await buildahStorageArgs(), "rm", name];
2152
+ debug(`exec in the background: buildah ${rmArgs.join(" ")}`);
2153
+ const start = Date.now();
2154
+ const [command, ...commandArgs] = await cleanupCommand(name, rmArgs);
2155
+ const child = (0, import_node_child_process3.spawn)(command, commandArgs, {
2156
+ detached: true,
2157
+ stdio: "ignore"
2158
+ });
2159
+ child.on(
2160
+ "error",
2161
+ (error) => debug(`background buildah rm failed: ${error.message}`)
2162
+ );
2163
+ child.on(
2164
+ "exit",
2165
+ (code) => debug(
2166
+ `background buildah rm exited with code ${code} after ${Date.now() - start}ms`
2167
+ )
2168
+ );
2169
+ child.unref();
2170
+ }
2171
+ async function cleanupCommand(container, buildahArgs) {
2172
+ const record = await recordPendingCleanup(container);
2173
+ return sharedBuildahStoreLock.wrap(
2174
+ record ? storeCommand({ action: "cleanup", options: { record, buildahArgs } }) : ["buildah", ...buildahArgs]
2175
+ );
2176
+ }
2177
+ async function recordPendingCleanup(container) {
2178
+ if (!buildahStoreCacheEnabled()) {
2179
+ return void 0;
2180
+ }
2181
+ try {
2182
+ if (!CLEANUP_CONTAINER_RE.test(container)) {
2183
+ throw new Error("invalid Buildah cleanup container");
2184
+ }
2185
+ return await writeStateRecord(".cleanup", `${container}
2186
+ `);
2187
+ } catch (error) {
2188
+ debug(`could not record Buildah cleanup: ${error.message}`);
2189
+ return void 0;
2190
+ }
2191
+ }
2192
+
2193
+ // src/buildpacks/lifecycle/buildah.ts
2194
+ async function cleanUpBuildContainer(name) {
1898
2195
  try {
1899
- await startBuildahInBackground(["rm", name]);
2196
+ await startContainerCleanup(name);
1900
2197
  } catch (error) {
1901
2198
  debug(
1902
- `could not remove Buildah CNB container ${name}: ${error.message}`
2199
+ `could not clean up Buildah CNB container ${name}: ${error.message}`
1903
2200
  );
1904
2201
  }
1905
2202
  }
1906
- async function inspectBaseImageId(container) {
1907
- const { stdout } = await runBuildah(
2203
+ async function inspectBaseImageId(container, client) {
2204
+ const { stdout } = await client.run(
1908
2205
  ["inspect", "--type", "container", container],
1909
2206
  { quiet: true }
1910
2207
  );
@@ -1929,14 +2226,14 @@ function cacheScratchParent() {
1929
2226
  );
1930
2227
  }
1931
2228
  }
1932
- return (0, import_node_os5.tmpdir)();
2229
+ return (0, import_node_os8.tmpdir)();
1933
2230
  }
1934
2231
  function chownTree(root, uid, gid) {
1935
2232
  const stat = (0, import_node_fs6.lstatSync)(root);
1936
2233
  if (stat.isDirectory()) {
1937
2234
  (0, import_node_fs6.chmodSync)(root, 493);
1938
2235
  for (const entry of (0, import_node_fs6.readdirSync)(root)) {
1939
- chownTree((0, import_node_path6.join)(root, entry), uid, gid);
2236
+ chownTree((0, import_node_path10.join)(root, entry), uid, gid);
1940
2237
  }
1941
2238
  } else if (stat.isFile()) {
1942
2239
  (0, import_node_fs6.chmodSync)(root, 420);
@@ -1949,12 +2246,13 @@ var RegistrySetupFailure = class extends Error {
1949
2246
  this.reason = reason;
1950
2247
  }
1951
2248
  };
1952
- async function prepareBuildContainer(buildContainer, buildpack, params) {
2249
+ async function prepareBuildContainer(buildContainer, buildpack, params, client) {
1953
2250
  const start = Date.now();
1954
2251
  step(
1955
2252
  `Preparing ${buildpack.runtime} ${params.image.version} stock image and Vercel buildpacks`
1956
2253
  );
1957
- await runBuildah([
2254
+ const usage = await BuildahImageUsage.begin();
2255
+ await client.run([
1958
2256
  "from",
1959
2257
  "--pull=always",
1960
2258
  "--platform",
@@ -1963,7 +2261,10 @@ async function prepareBuildContainer(buildContainer, buildpack, params) {
1963
2261
  buildContainer,
1964
2262
  params.image.buildImage
1965
2263
  ]);
1966
- await runBuildah([
2264
+ await usage?.record(async () => [
2265
+ await inspectBaseImageId(buildContainer, client)
2266
+ ]);
2267
+ await client.run([
1967
2268
  "run",
1968
2269
  "--network",
1969
2270
  "host",
@@ -1973,7 +2274,7 @@ async function prepareBuildContainer(buildContainer, buildpack, params) {
1973
2274
  "-c",
1974
2275
  `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
2276
  ]);
1976
- await runBuildah([
2277
+ await client.run([
1977
2278
  "copy",
1978
2279
  "--chown",
1979
2280
  BUILD_USER,
@@ -1989,17 +2290,17 @@ async function fetchDistributionTimed(buildpack, cnbDir) {
1989
2290
  await fetchDistribution(buildpack, cnbDir);
1990
2291
  done(`fetched the CNB lifecycle and buildpacks in ${elapsed(start)}`);
1991
2292
  }
1992
- async function copySavedCache(dir, buildContainer, containerReady) {
2293
+ async function copySavedCache(dir, buildContainer, containerReady, client) {
1993
2294
  const start = Date.now();
1994
2295
  step("Restoring the buildpack cache");
1995
2296
  let scratch;
1996
2297
  try {
1997
- scratch = (0, import_node_fs6.mkdtempSync)((0, import_node_path6.join)(cacheScratchParent(), "vercel-cnb-cache-"));
2298
+ scratch = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)(cacheScratchParent(), "vercel-cnb-cache-"));
1998
2299
  (0, import_node_fs6.chmodSync)(scratch, 493);
1999
- const cacheDir = (0, import_node_path6.join)(scratch, "cache");
2300
+ const cacheDir = (0, import_node_path10.join)(scratch, "cache");
2000
2301
  (0, import_node_fs6.mkdirSync)(cacheDir, { mode: 493 });
2001
2302
  const imageId = containerReady.then(
2002
- () => inspectBaseImageId(buildContainer)
2303
+ () => inspectBaseImageId(buildContainer, client)
2003
2304
  );
2004
2305
  const restored = await restoreBuildpackCache(dir, imageId, cacheDir);
2005
2306
  const baseImageId = await imageId;
@@ -2025,7 +2326,7 @@ async function copySavedCache(dir, buildContainer, containerReady) {
2025
2326
  };
2026
2327
  }
2027
2328
  }
2028
- async function buildAndPushWithLifecycle(buildpack, params, span) {
2329
+ async function buildAndPushWithLifecycle(buildpack, params, client, span) {
2029
2330
  const urls = distributionUrls(buildpack);
2030
2331
  return withSpan(
2031
2332
  span,
@@ -2041,20 +2342,20 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2041
2342
  "image.ref": params.imageRef
2042
2343
  },
2043
2344
  async (lifecycleSpan) => {
2044
- const suffix = `${process.pid}-${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`;
2345
+ const suffix = `${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
2045
2346
  const buildContainer = `vercel-cnb-${buildpack.runtime}-${suffix}`;
2046
2347
  let cnbDir;
2047
2348
  let reportDir;
2048
2349
  let orderDir;
2049
2350
  let platformEnvDir;
2050
2351
  let cacheScratch;
2051
- let containerRemoval;
2352
+ let containerCleanup;
2052
2353
  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");
2354
+ reportDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)((0, import_node_os8.tmpdir)(), "vercel-cnb-report-"));
2355
+ const reportPath = (0, import_node_path10.join)(reportDir, "report.toml");
2055
2356
  (0, import_node_fs6.chownSync)(reportDir, BUILD_USER_ID, BUILD_GROUP_ID);
2056
2357
  (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-"));
2358
+ cnbDir = (0, import_node_fs6.mkdtempSync)((0, import_node_path10.join)((0, import_node_os8.tmpdir)(), "vercel-cnb-"));
2058
2359
  (0, import_node_fs6.chmodSync)(cnbDir, 493);
2059
2360
  orderDir = writeOrderDir(buildpack);
2060
2361
  platformEnvDir = writePlatformEnvDir(params.buildEnv, {
@@ -2066,13 +2367,19 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2066
2367
  const containerReady = prepareBuildContainer(
2067
2368
  buildContainer,
2068
2369
  buildpack,
2069
- params
2370
+ params,
2371
+ client
2070
2372
  );
2071
2373
  const [registry, container, distribution2, cacheCopy] = await Promise.allSettled([
2072
2374
  registrySetup,
2073
2375
  containerReady,
2074
2376
  fetchDistributionTimed(buildpack, cnbDir),
2075
- params.cache ? copySavedCache(params.cache.dir, buildContainer, containerReady) : void 0
2377
+ params.cache ? copySavedCache(
2378
+ params.cache.dir,
2379
+ buildContainer,
2380
+ containerReady,
2381
+ client
2382
+ ) : void 0
2076
2383
  ]);
2077
2384
  const copy = cacheCopy.status === "fulfilled" ? cacheCopy.value : void 0;
2078
2385
  cacheScratch = copy?.scratch;
@@ -2105,7 +2412,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2105
2412
  step(
2106
2413
  `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
2107
2414
  );
2108
- await runBuildah(
2415
+ await client.run(
2109
2416
  [
2110
2417
  "run",
2111
2418
  "--network",
@@ -2142,7 +2449,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2142
2449
  ],
2143
2450
  { env: lifecycleEnv }
2144
2451
  );
2145
- containerRemoval = removeContainerInBackground(buildContainer);
2452
+ containerCleanup = cleanUpBuildContainer(buildContainer);
2146
2453
  const digest = readReportDigest((0, import_node_fs6.readFileSync)(reportPath, "utf8"));
2147
2454
  if (!digest) {
2148
2455
  throw new Error(
@@ -2179,7 +2486,7 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2179
2486
  span: lifecycleSpan
2180
2487
  });
2181
2488
  } finally {
2182
- containerRemoval ??= removeContainerInBackground(buildContainer);
2489
+ containerCleanup ??= cleanUpBuildContainer(buildContainer);
2183
2490
  for (const dir of [
2184
2491
  cnbDir,
2185
2492
  reportDir,
@@ -2195,17 +2502,17 @@ async function buildAndPushWithLifecycle(buildpack, params, span) {
2195
2502
  }
2196
2503
  }
2197
2504
  }
2198
- await containerRemoval;
2505
+ await containerCleanup;
2199
2506
  }
2200
2507
  }
2201
2508
  );
2202
2509
  }
2203
2510
 
2204
2511
  // src/buildpacks/lifecycle/docker.ts
2205
- var import_node_crypto5 = require("crypto");
2512
+ var import_node_crypto7 = require("crypto");
2206
2513
  var import_node_fs7 = require("fs");
2207
- var import_node_os6 = require("os");
2208
- var import_node_path7 = require("path");
2514
+ var import_node_os9 = require("os");
2515
+ var import_node_path11 = require("path");
2209
2516
  function localBuildImageDockerfile(buildImage, nodeMajor) {
2210
2517
  const lines = [];
2211
2518
  if (nodeMajor !== void 0) {
@@ -2224,7 +2531,7 @@ function localBuildImageDockerfile(buildImage, nodeMajor) {
2224
2531
  `;
2225
2532
  }
2226
2533
  function localBuildImageTag(dockerfile) {
2227
- const hash = (0, import_node_crypto5.createHash)("sha256").update(dockerfile).digest("hex");
2534
+ const hash = (0, import_node_crypto7.createHash)("sha256").update(dockerfile).digest("hex");
2228
2535
  return `vercel-cnb-build:${hash.slice(0, 16)}`;
2229
2536
  }
2230
2537
  async function runDocker2(args, opts = {}) {
@@ -2250,8 +2557,8 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2250
2557
  let stageDir;
2251
2558
  const containers = [];
2252
2559
  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");
2560
+ stageDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path11.join)((0, import_node_os9.tmpdir)(), "vercel-cnb-"));
2561
+ const cnbDir = (0, import_node_path11.join)(stageDir, "cnb");
2255
2562
  (0, import_node_fs7.mkdirSync)(cnbDir, { mode: 493 });
2256
2563
  const orderDir = writeOrderDir(buildpack, stageDir);
2257
2564
  const platformEnvDir = writePlatformEnvDir(
@@ -2271,7 +2578,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2271
2578
  params.nodeToolchain?.major
2272
2579
  );
2273
2580
  const buildImageTag = localBuildImageTag(dockerfile);
2274
- const buildImageIdPath = (0, import_node_path7.join)(stageDir, "build-image.id");
2581
+ const buildImageIdPath = (0, import_node_path11.join)(stageDir, "build-image.id");
2275
2582
  step(
2276
2583
  `Preparing ${buildpack.runtime} ${params.image.version} build image` + (params.nodeToolchain ? ` with Node.js ${params.nodeToolchain.major}` : "")
2277
2584
  );
@@ -2293,7 +2600,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2293
2600
  if (!/^sha256:[a-f0-9]{64}$/.test(buildImageId)) {
2294
2601
  throw new Error("Docker build did not report a valid image ID.");
2295
2602
  }
2296
- const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto5.randomBytes)(8).toString("hex")}`;
2603
+ const containerName = `vercel-cnb-${buildpack.runtime}-${process.pid}-${(0, import_node_crypto7.randomBytes)(8).toString("hex")}`;
2297
2604
  const initContainerName = `${containerName}-init`;
2298
2605
  containers.push(initContainerName);
2299
2606
  await runDocker2([
@@ -2377,7 +2684,7 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2377
2684
  `Building and publishing ${params.imageRef} via Vercel ${buildpack.runtime} buildpacks`
2378
2685
  );
2379
2686
  await runDocker2(["start", "--attach", containerName]);
2380
- const reportPath = (0, import_node_path7.join)(stageDir, "report.toml");
2687
+ const reportPath = (0, import_node_path11.join)(stageDir, "report.toml");
2381
2688
  await runDocker2([
2382
2689
  "cp",
2383
2690
  `${containerName}:/platform-output/report.toml`,
@@ -2431,15 +2738,15 @@ var buildAndPushWithLifecycleDocker = async (buildpack, params, span) => {
2431
2738
  };
2432
2739
 
2433
2740
  // src/buildpacks/node-toolchain.ts
2434
- var import_build_utils4 = require("@vercel/build-utils");
2741
+ var import_build_utils5 = require("@vercel/build-utils");
2435
2742
  var import_node_fs8 = require("fs");
2436
- var import_node_path8 = require("path");
2743
+ var import_node_path12 = require("path");
2437
2744
  async function selectNodeMajor(workPath, config, meta) {
2438
- if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(workPath, "package.json"))) {
2745
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path12.join)(workPath, "package.json"))) {
2439
2746
  return void 0;
2440
2747
  }
2441
- const version2 = await (0, import_build_utils4.getNodeVersion)(workPath, void 0, config, meta);
2442
- if ((0, import_build_utils4.isBunVersion)(version2)) {
2748
+ const version2 = await (0, import_build_utils5.getNodeVersion)(workPath, void 0, config, meta);
2749
+ if ((0, import_build_utils5.isBunVersion)(version2)) {
2443
2750
  throw new Error(
2444
2751
  "Buildpack asset builds require Node.js. Bun is not supported yet; configure engines.node or use Dockerfile.vercel."
2445
2752
  );
@@ -2456,7 +2763,7 @@ async function resolveNodeToolchain(workPath, config, meta) {
2456
2763
  return void 0;
2457
2764
  }
2458
2765
  for (const executable of ["node", "npm"]) {
2459
- if (!(0, import_node_fs8.existsSync)((0, import_node_path8.join)(toolchain.home, "bin", executable))) {
2766
+ if (!(0, import_node_fs8.existsSync)((0, import_node_path12.join)(toolchain.home, "bin", executable))) {
2460
2767
  throw new Error(
2461
2768
  `The selected Node.js ${toolchain.version} installation is missing ${toolchain.home}/bin/${executable} in the build environment.`
2462
2769
  );
@@ -2470,11 +2777,11 @@ async function resolveLocalNodeToolchain(workPath, config, meta) {
2470
2777
 
2471
2778
  // src/buildpacks/registry.ts
2472
2779
  var import_node_fs12 = require("fs");
2473
- var import_node_path12 = require("path");
2780
+ var import_node_path16 = require("path");
2474
2781
 
2475
2782
  // src/buildpacks/go.ts
2476
2783
  var import_node_fs9 = require("fs");
2477
- var import_node_path9 = require("path");
2784
+ var import_node_path13 = require("path");
2478
2785
  var DEFAULT_GO_VERSION = "1";
2479
2786
  var GO_IMAGE_REPOSITORY = "docker.io/library/golang";
2480
2787
  var GO_BUILD_IMAGE_DISTRO = "bookworm";
@@ -2503,7 +2810,7 @@ function compareImageVersions(a, b) {
2503
2810
  return a.parts[0] - b.parts[0] || a.parts[1] - b.parts[1] || a.parts[2] - b.parts[2];
2504
2811
  }
2505
2812
  function selectGoVersion(workPath) {
2506
- const goMod = (0, import_node_path9.join)(workPath, "go.mod");
2813
+ const goMod = (0, import_node_path13.join)(workPath, "go.mod");
2507
2814
  if (!(0, import_node_fs9.existsSync)(goMod))
2508
2815
  return DEFAULT_GO_VERSION;
2509
2816
  const contents = (0, import_node_fs9.readFileSync)(goMod, "utf8");
@@ -2528,10 +2835,10 @@ var go = {
2528
2835
  },
2529
2836
  defaultEntrypoint({ workPath, zeroConfig }) {
2530
2837
  const candidate = GO_CANDIDATE_ENTRYPOINTS.find(
2531
- (name) => (0, import_node_fs9.existsSync)((0, import_node_path9.join)(workPath, name))
2838
+ (name) => (0, import_node_fs9.existsSync)((0, import_node_path13.join)(workPath, name))
2532
2839
  );
2533
2840
  if (candidate)
2534
- return import_node_path9.posix.dirname(candidate);
2841
+ return import_node_path13.posix.dirname(candidate);
2535
2842
  if (zeroConfig) {
2536
2843
  const last = GO_CANDIDATE_ENTRYPOINTS.length - 1;
2537
2844
  throw new Error(
@@ -2544,7 +2851,7 @@ var go = {
2544
2851
 
2545
2852
  // src/buildpacks/ruby.ts
2546
2853
  var import_node_fs10 = require("fs");
2547
- var import_node_path10 = require("path");
2854
+ var import_node_path14 = require("path");
2548
2855
  var import_smol_toml2 = require("smol-toml");
2549
2856
 
2550
2857
  // src/buildpacks/version.ts
@@ -2576,7 +2883,7 @@ var ruby = {
2576
2883
  return { version: version2, buildImage: image, runImage: image };
2577
2884
  },
2578
2885
  validate({ workPath, buildEnv }) {
2579
- const gemfile = (0, import_node_path10.join)(workPath, "Gemfile");
2886
+ const gemfile = (0, import_node_path14.join)(workPath, "Gemfile");
2580
2887
  if (!(0, import_node_fs10.existsSync)(gemfile))
2581
2888
  return;
2582
2889
  if (!GEMFILE_RAILS_GEM_RE.test((0, import_node_fs10.readFileSync)(gemfile, "utf8")))
@@ -2590,12 +2897,12 @@ var ruby = {
2590
2897
  };
2591
2898
  function selectRubyVersion(workPath) {
2592
2899
  for (const name of RUBY_VERSION_FILES) {
2593
- const file = (0, import_node_path10.join)(workPath, name);
2900
+ const file = (0, import_node_path14.join)(workPath, name);
2594
2901
  if ((0, import_node_fs10.existsSync)(file)) {
2595
2902
  return parseVersionFile(name, (0, import_node_fs10.readFileSync)(file, "utf8"));
2596
2903
  }
2597
2904
  }
2598
- const gemfile = (0, import_node_path10.join)(workPath, "Gemfile");
2905
+ const gemfile = (0, import_node_path14.join)(workPath, "Gemfile");
2599
2906
  return (0, import_node_fs10.existsSync)(gemfile) ? parseGemfileRubyVersion((0, import_node_fs10.readFileSync)(gemfile, "utf8")) : void 0;
2600
2907
  }
2601
2908
  function parseVersionFile(name, contents) {
@@ -2634,7 +2941,7 @@ function parseGemfileRubyVersion(contents) {
2634
2941
 
2635
2942
  // src/buildpacks/rust.ts
2636
2943
  var import_node_fs11 = require("fs");
2637
- var import_node_path11 = require("path");
2944
+ var import_node_path15 = require("path");
2638
2945
  var import_smol_toml3 = require("smol-toml");
2639
2946
  var DEFAULT_RUST_VERSION = "1";
2640
2947
  var RUST_IMAGE_REPOSITORY = "docker.io/library/rust";
@@ -2654,12 +2961,12 @@ var rust = {
2654
2961
  };
2655
2962
  },
2656
2963
  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;
2964
+ return zeroConfig && (0, import_node_fs11.existsSync)((0, import_node_path15.join)(workPath, "src/main.rs")) ? "src/main.rs" : void 0;
2658
2965
  }
2659
2966
  };
2660
2967
  function selectRustVersion(workPath) {
2661
2968
  for (const name of RUST_TOOLCHAIN_FILES) {
2662
- const file = (0, import_node_path11.join)(workPath, name);
2969
+ const file = (0, import_node_path15.join)(workPath, name);
2663
2970
  if ((0, import_node_fs11.existsSync)(file)) {
2664
2971
  const channel = parseToolchainFile(name, (0, import_node_fs11.readFileSync)(file, "utf8"));
2665
2972
  return channel !== void 0 && isValidVersion(channel) ? channel : void 0;
@@ -2705,7 +3012,7 @@ function requestedBuildpack(config) {
2705
3012
  }
2706
3013
  function hasProjectMarkers(buildpack, workPath) {
2707
3014
  return buildpack.projectMarkers.some(
2708
- (name) => (0, import_node_fs12.existsSync)((0, import_node_path12.join)(workPath, name))
3015
+ (name) => (0, import_node_fs12.existsSync)((0, import_node_path16.join)(workPath, name))
2709
3016
  );
2710
3017
  }
2711
3018
 
@@ -2719,11 +3026,12 @@ function selectEntrypoint(params, sourceDir) {
2719
3026
  return fromEnv ?? params.buildpack.defaultEntrypoint?.({ workPath: sourceDir, zeroConfig });
2720
3027
  }
2721
3028
  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-"));
3029
+ const sourceDir = (0, import_node_fs13.mkdtempSync)((0, import_node_path17.join)((0, import_node_os10.tmpdir)(), "vercel-cnb-source-"));
2723
3030
  try {
2724
3031
  step("Staging buildpack source files");
2725
3032
  await stageWorkspace(params, sourceDir);
2726
- const engine = selectContainerEngine();
3033
+ const { buildahClient } = params;
3034
+ const engine = selectContainerEngine(buildahClient);
2727
3035
  if (!hasProjectMarkers(params.buildpack, sourceDir)) {
2728
3036
  throw new Error(
2729
3037
  `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 +3148,7 @@ async function buildAndPushBuildpack(params) {
2840
3148
  result = await buildAndPushWithLifecycle(
2841
3149
  buildpack,
2842
3150
  { ...lifecycleParams, prepareRegistry },
3151
+ buildahClient,
2843
3152
  buildSpan
2844
3153
  );
2845
3154
  } else {
@@ -2871,7 +3180,7 @@ async function buildAndPushBuildpack(params) {
2871
3180
 
2872
3181
  // src/image-source.ts
2873
3182
  var import_node_fs14 = require("fs");
2874
- var import_node_path14 = __toESM(require("path"));
3183
+ var import_node_path18 = __toESM(require("path"));
2875
3184
  var DETECT_SENTINEL = "<detect>";
2876
3185
  function resolveImageSource(options, context) {
2877
3186
  const { config, workPath, entrypoint } = options;
@@ -2888,7 +3197,7 @@ function resolveImageSource(options, context) {
2888
3197
  }
2889
3198
  const dockerfileConfigured = entrypointRef && isDockerfileRef(entrypointRef) ? entrypointRef : findDockerfile(workPath);
2890
3199
  const dockerfileRel = dockerfileConfigured ?? "Dockerfile";
2891
- const dockerfilePath = import_node_path14.default.join(workPath, dockerfileRel);
3200
+ const dockerfilePath = import_node_path18.default.join(workPath, dockerfileRel);
2892
3201
  const hasDockerfile = dockerfileConfigured !== void 0 || !buildpack && (0, import_node_fs14.existsSync)(dockerfilePath);
2893
3202
  if (hasDockerfile) {
2894
3203
  return { kind: "dockerfile", dockerfileRel, dockerfilePath };
@@ -2905,11 +3214,252 @@ function resolveImageSource(options, context) {
2905
3214
  );
2906
3215
  }
2907
3216
 
3217
+ // src/buildah-store-cache/cache.ts
3218
+ var import_build_utils6 = require("@vercel/build-utils");
3219
+ var import_node_crypto8 = require("crypto");
3220
+ var import_promises7 = require("fs/promises");
3221
+ var import_node_os11 = require("os");
3222
+ var import_node_path19 = require("path");
3223
+ var BUILDAH_STORE_CACHE_DIR = ".vercel/cache/buildah-store";
3224
+ var STORE_PATHS = {
3225
+ graphRoot: BUILDAH_GRAPH_ROOT,
3226
+ // Buildah's default when running as root, as it does in the build container.
3227
+ blobInfoCache: "/var/lib/containers/cache/blob-info-cache-v1.sqlite"
3228
+ };
3229
+ var BuildahStoreCache = class _BuildahStoreCache {
3230
+ constructor(repositoryRoot, client) {
3231
+ this.repositoryRoot = repositoryRoot;
3232
+ this.client = client;
3233
+ }
3234
+ static forProject(options, client) {
3235
+ return new _BuildahStoreCache(
3236
+ (0, import_node_path19.resolve)(options.repoRootPath || options.workPath),
3237
+ client
3238
+ );
3239
+ }
3240
+ /**
3241
+ * From `build()`, before a Buildah service opens the store or reads its build
3242
+ * context. A service that builds nothing leaves the snapshot for a later
3243
+ * deployment.
3244
+ */
3245
+ async beforeBuild(options) {
3246
+ if (!isBuildContainer()) {
3247
+ return;
3248
+ }
3249
+ if (buildahStoreCacheEnabled()) {
3250
+ if (options.buildsWithBuildah) {
3251
+ await this.restore(options.span);
3252
+ }
3253
+ return;
3254
+ }
3255
+ if (buildahStoreCacheRequested()) {
3256
+ info("Buildah store cache disabled: VERCEL_DEPLOYMENT_ID is not set");
3257
+ }
3258
+ await this.removeSnapshots();
3259
+ }
3260
+ /** From `prepareCache`, which the platform runs once every service has built. */
3261
+ async afterBuilds() {
3262
+ if (!isBuildContainer()) {
3263
+ return {};
3264
+ }
3265
+ if (buildahStoreCacheEnabled()) {
3266
+ return this.save();
3267
+ }
3268
+ await this.removeSnapshots();
3269
+ return {};
3270
+ }
3271
+ async restore(span) {
3272
+ const start = Date.now();
3273
+ step("Restoring the Buildah store cache");
3274
+ let result = "miss";
3275
+ let reason;
3276
+ let bytes = 0;
3277
+ try {
3278
+ const archive = await this.archivePath({ create: false });
3279
+ const restored = await restoreStore(archive);
3280
+ result = restored.status;
3281
+ if (restored.status === "hit")
3282
+ bytes = restored.bytes;
3283
+ if (restored.status === "miss")
3284
+ reason = restored.reason;
3285
+ } catch (error) {
3286
+ result = "miss";
3287
+ reason = failureReason(error);
3288
+ }
3289
+ await this.removeSnapshots();
3290
+ const restoredBytes = result === "hit" ? bytes : 0;
3291
+ const detail = reason ?? (result === "hit" ? formatBytes(restoredBytes) : void 0);
3292
+ done(
3293
+ `Buildah store cache: ${result}${detail ? ` (${detail})` : ""} in ${elapsed(start)}`
3294
+ );
3295
+ span?.setAttributes({
3296
+ "container.buildah_store.restore": result,
3297
+ "container.buildah_store.restore_ms": String(Date.now() - start),
3298
+ "container.buildah_store.restore_bytes": String(restoredBytes),
3299
+ "container.buildah_store.miss_reason": reason
3300
+ });
3301
+ }
3302
+ async save() {
3303
+ const start = Date.now();
3304
+ step("Saving the Buildah store cache");
3305
+ try {
3306
+ const archive = await this.archivePath({ create: true });
3307
+ const saved = await saveStore(archive);
3308
+ done(await describeSave2(saved, archive, start));
3309
+ if (!await isRegularFile(archive)) {
3310
+ return {};
3311
+ }
3312
+ return await (0, import_build_utils6.glob)(
3313
+ `${BUILDAH_STORE_CACHE_DIR}/${(0, import_node_path19.basename)(archive)}`,
3314
+ this.repositoryRoot
3315
+ );
3316
+ } catch (error) {
3317
+ done(`Buildah store cache not saved (${failureReason(error)})`);
3318
+ return {};
3319
+ }
3320
+ }
3321
+ async removeSnapshots() {
3322
+ try {
3323
+ await (0, import_promises7.rm)(await this.snapshotDirectory({ create: false }), {
3324
+ recursive: true,
3325
+ force: true
3326
+ });
3327
+ } catch (error) {
3328
+ debug(
3329
+ `could not remove Buildah store snapshots: ${failureReason(error)}`
3330
+ );
3331
+ }
3332
+ }
3333
+ async archivePath(options) {
3334
+ const key = await snapshotKey(this.client);
3335
+ return (0, import_node_path19.join)(await this.snapshotDirectory(options), `store-${key}.tar.zst`);
3336
+ }
3337
+ async snapshotDirectory({
3338
+ create
3339
+ }) {
3340
+ const root = await (0, import_promises7.realpath)(this.repositoryRoot);
3341
+ let directory = root;
3342
+ for (const part of BUILDAH_STORE_CACHE_DIR.split("/")) {
3343
+ directory = (0, import_node_path19.join)(directory, part);
3344
+ if (create) {
3345
+ await (0, import_promises7.mkdir)(directory, { mode: 448 }).catch((error) => {
3346
+ if (error.code !== "EEXIST")
3347
+ throw error;
3348
+ });
3349
+ }
3350
+ const stats = await lstatIfPresent(directory);
3351
+ if (!stats) {
3352
+ return (0, import_node_path19.join)(root, BUILDAH_STORE_CACHE_DIR);
3353
+ }
3354
+ if (!stats.isDirectory()) {
3355
+ throw new Error(`${directory} is not a directory`);
3356
+ }
3357
+ }
3358
+ return directory;
3359
+ }
3360
+ };
3361
+ async function restoreStore(archive) {
3362
+ const storeLock = await requireStoreLock();
3363
+ const state = await createBuildahStoreStateDirectory();
3364
+ const gate = new FileLock((0, import_node_path19.join)(state, "restore.lock"));
3365
+ return runStoreCommand(
3366
+ {
3367
+ action: "restore",
3368
+ options: {
3369
+ ...STORE_PATHS,
3370
+ archive,
3371
+ storeLock: storeLock.path,
3372
+ marker: (0, import_node_path19.join)(state, "restored")
3373
+ }
3374
+ },
3375
+ gate
3376
+ );
3377
+ }
3378
+ async function saveStore(archive) {
3379
+ const buildahArgs = await buildahStorageArgs();
3380
+ const storeLock = await requireStoreLock();
3381
+ return runStoreCommand(
3382
+ {
3383
+ action: "save",
3384
+ options: {
3385
+ ...STORE_PATHS,
3386
+ archive,
3387
+ stateDirectory: await createBuildahStoreStateDirectory(),
3388
+ threads: (0, import_node_os11.availableParallelism)(),
3389
+ buildahArgs
3390
+ }
3391
+ },
3392
+ storeLock
3393
+ );
3394
+ }
3395
+ async function requireStoreLock() {
3396
+ const lock = await buildahStoreLock();
3397
+ if (!lock)
3398
+ throw new Error("flock unavailable");
3399
+ return lock;
3400
+ }
3401
+ async function describeSave2(result, archive, start) {
3402
+ if (result.status === "saved") {
3403
+ return `saved the Buildah store cache (${formatBytes((await (0, import_promises7.lstat)(archive)).size)}) in ${elapsed(start)}`;
3404
+ }
3405
+ if (result.status === "unused") {
3406
+ 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";
3407
+ }
3408
+ if (result.status === "skipped" && result.previous === "failed") {
3409
+ throw new Error("an earlier save for this deployment failed");
3410
+ }
3411
+ return "Buildah store cache already prepared for this deployment";
3412
+ }
3413
+ async function snapshotKey(client) {
3414
+ const configuration = await Promise.all(
3415
+ [
3416
+ "/usr/share/containers/storage.conf",
3417
+ "/etc/containers/storage.conf",
3418
+ (0, import_node_path19.join)(
3419
+ process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0, import_node_os11.homedir)(), ".config"),
3420
+ "containers/storage.conf"
3421
+ ),
3422
+ process.env.CONTAINERS_STORAGE_CONF,
3423
+ "/etc/subuid",
3424
+ "/etc/subgid"
3425
+ ].map(readOptionalFile)
3426
+ );
3427
+ return (0, import_node_crypto8.createHash)("sha256").update(
3428
+ JSON.stringify({
3429
+ version: await client.version(),
3430
+ arch: process.arch,
3431
+ uid: process.getuid?.(),
3432
+ gid: process.getgid?.(),
3433
+ driver: process.env.VERCEL_VCR_DOCKER_STORAGE_DRIVER,
3434
+ image: process.env.VERCEL_BUILD_IMAGE,
3435
+ configuration
3436
+ })
3437
+ ).digest("hex").slice(0, 32);
3438
+ }
3439
+ async function readOptionalFile(path4) {
3440
+ if (!path4) {
3441
+ return "";
3442
+ }
3443
+ try {
3444
+ return await (0, import_promises7.readFile)(path4, "utf8");
3445
+ } catch (error) {
3446
+ if (error.code === "ENOENT")
3447
+ return "";
3448
+ throw error;
3449
+ }
3450
+ }
3451
+ async function isRegularFile(path4) {
3452
+ return (await lstatIfPresent(path4))?.isFile() ?? false;
3453
+ }
3454
+ function failureReason(error) {
3455
+ return String(error?.message ?? error).trim().split("\n").at(-1) ?? "";
3456
+ }
3457
+
2908
3458
  // src/dev.ts
2909
3459
  var import_node_child_process4 = require("child_process");
2910
3460
  var import_node_fs15 = require("fs");
2911
- var import_node_os8 = require("os");
2912
- var import_node_path15 = __toESM(require("path"));
3461
+ var import_node_os12 = require("os");
3462
+ var import_node_path20 = __toESM(require("path"));
2913
3463
  var HOST_ONLY_ENV = /* @__PURE__ */ new Set([
2914
3464
  "TMPDIR",
2915
3465
  "TMP",
@@ -2939,8 +3489,8 @@ function isHostOnlyEnvVar(key) {
2939
3489
  return HOST_ONLY_ENV.has(key) || key.startsWith("__") || key.startsWith("XPC_") || key.startsWith("SSH_") || key.startsWith("Apple");
2940
3490
  }
2941
3491
  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");
3492
+ const dir = (0, import_node_fs15.mkdtempSync)(import_node_path20.default.join((0, import_node_os12.tmpdir)(), "vercel-container-dev-env-"));
3493
+ const file = import_node_path20.default.join(dir, "env");
2944
3494
  const lines = [];
2945
3495
  for (const [key, value] of Object.entries(env)) {
2946
3496
  if (value.includes("\n")) {
@@ -2973,7 +3523,7 @@ function emit(out, line) {
2973
3523
  }
2974
3524
  }
2975
3525
  function runForwarded(cmd, args, out, opts = {}) {
2976
- return new Promise((resolve4, reject) => {
3526
+ return new Promise((resolve5, reject) => {
2977
3527
  const child = (0, import_node_child_process4.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
2978
3528
  let stdout = "";
2979
3529
  let stderr = "";
@@ -3010,7 +3560,7 @@ function runForwarded(cmd, args, out, opts = {}) {
3010
3560
  });
3011
3561
  child.on("close", (code) => {
3012
3562
  if (code === 0) {
3013
- resolve4({ stdout });
3563
+ resolve5({ stdout });
3014
3564
  } else {
3015
3565
  const detail = stderr.trim().split("\n").slice(-5).join("\n");
3016
3566
  reject(
@@ -3046,7 +3596,7 @@ async function resolveDevImage(options, out, span) {
3046
3596
  }
3047
3597
  const serviceName = options.service?.name ?? "service";
3048
3598
  const tag = devImageTag(serviceName);
3049
- const contextDir = import_node_path15.default.dirname(dockerfilePath);
3599
+ const contextDir = import_node_path20.default.dirname(dockerfilePath);
3050
3600
  const buildArgFlags2 = [];
3051
3601
  const buildEnv = options.meta?.buildEnv ?? {};
3052
3602
  for (const [key, value] of Object.entries(buildEnv)) {
@@ -3251,7 +3801,7 @@ async function startContainer(options, reuseKey) {
3251
3801
  }
3252
3802
  });
3253
3803
  const cleanupEnvFile = () => {
3254
- (0, import_node_fs15.rmSync)(import_node_path15.default.dirname(envFilePath), { recursive: true, force: true });
3804
+ (0, import_node_fs15.rmSync)(import_node_path20.default.dirname(envFilePath), { recursive: true, force: true });
3255
3805
  };
3256
3806
  const shutdown = async () => {
3257
3807
  runningContainers.delete(reuseKey);
@@ -3284,7 +3834,7 @@ async function startContainer(options, reuseKey) {
3284
3834
  break;
3285
3835
  } catch (err) {
3286
3836
  lastErr = err;
3287
- await new Promise((resolve4) => setTimeout(resolve4, 250));
3837
+ await new Promise((resolve5) => setTimeout(resolve5, 250));
3288
3838
  }
3289
3839
  }
3290
3840
  if (hostPort === void 0) {
@@ -3327,19 +3877,27 @@ async function startContainer(options, reuseKey) {
3327
3877
  }
3328
3878
 
3329
3879
  // src/prepare-cache.ts
3330
- var import_build_utils5 = require("@vercel/build-utils");
3331
- var import_node_path16 = require("path");
3880
+ var import_build_utils7 = require("@vercel/build-utils");
3881
+ var import_node_path21 = require("path");
3332
3882
  async function prepareCache(options) {
3333
- const root = (0, import_node_path16.resolve)(options.repoRootPath || options.workPath);
3883
+ const root = (0, import_node_path21.resolve)(options.repoRootPath || options.workPath);
3884
+ const buildpackFiles = await collectBuildpackCache(root);
3885
+ const buildahStoreFiles = await BuildahStoreCache.forProject(
3886
+ options,
3887
+ new BuildahClient(sharedBuildahStoreLock)
3888
+ ).afterBuilds();
3889
+ return { ...buildpackFiles, ...buildahStoreFiles };
3890
+ }
3891
+ async function collectBuildpackCache(root) {
3334
3892
  const start = Date.now();
3335
- const files = await (0, import_build_utils5.glob)(`${BUILDPACK_CACHE_DIR}/**`, root);
3893
+ const files = await (0, import_build_utils7.glob)(`${BUILDPACK_CACHE_DIR}/**`, root);
3336
3894
  const count = Object.keys(files).length;
3337
3895
  if (count > 0) {
3338
3896
  info(
3339
- `cached buildpack layers: ${count} file${count === 1 ? "" : "s"} under ${BUILDPACK_CACHE_DIR} in ${Date.now() - start}ms`
3897
+ `cached ${count} file${count === 1 ? "" : "s"} under ${BUILDPACK_CACHE_DIR} in ${Date.now() - start}ms`
3340
3898
  );
3341
3899
  } else {
3342
- debug("no buildpack cache to persist");
3900
+ debug(`no cache to persist under ${BUILDPACK_CACHE_DIR}`);
3343
3901
  }
3344
3902
  return files;
3345
3903
  }
@@ -3363,9 +3921,9 @@ function resolveImageTag() {
3363
3921
  if (sha) {
3364
3922
  return sha.slice(0, 12);
3365
3923
  }
3366
- const deploymentId = readString(process.env.VERCEL_DEPLOYMENT_ID);
3367
- if (deploymentId) {
3368
- return deploymentId.replace(/[^a-z0-9-_.]/gi, "-");
3924
+ const deploymentId2 = readString(process.env.VERCEL_DEPLOYMENT_ID);
3925
+ if (deploymentId2) {
3926
+ return deploymentId2.replace(/[^a-z0-9-_.]/gi, "-");
3369
3927
  }
3370
3928
  return `build-${Date.now().toString(36)}`;
3371
3929
  }
@@ -3397,8 +3955,16 @@ async function authenticateRegistry(engine, buildParams, span) {
3397
3955
  done("authenticated");
3398
3956
  }
3399
3957
  async function buildAndPushImage(params) {
3400
- const { contextDir, dockerfilePath, repository, tag, buildArgs, parentSpan } = params;
3401
- const engine = selectContainerEngine();
3958
+ const {
3959
+ contextDir,
3960
+ dockerfilePath,
3961
+ repository,
3962
+ tag,
3963
+ buildArgs,
3964
+ buildahClient,
3965
+ parentSpan
3966
+ } = params;
3967
+ const engine = selectContainerEngine(buildahClient);
3402
3968
  return withSpan(
3403
3969
  parentSpan,
3404
3970
  "container.build_and_push",
@@ -3448,6 +4014,7 @@ async function buildAndPushImage(params) {
3448
4014
  token,
3449
4015
  repository,
3450
4016
  buildArgs,
4017
+ useBuildahStoreCache: buildahStoreCacheEnabled(),
3451
4018
  span: buildSpan
3452
4019
  };
3453
4020
  await authenticateRegistry(engine, buildParams, buildSpan);
@@ -3470,7 +4037,7 @@ async function buildAndPushImage(params) {
3470
4037
  buildSpan,
3471
4038
  "container.image_build",
3472
4039
  { "image.ref": imageRef, "image.platform": TARGET_PLATFORM },
3473
- () => engine.build(buildParams)
4040
+ () => params.schedulesCommand ? buildSchedulesCommandImage(engine, buildParams) : engine.build(buildParams)
3474
4041
  );
3475
4042
  done(`built in ${elapsed(buildStart)}`);
3476
4043
  const pushStart = Date.now();
@@ -3502,7 +4069,7 @@ async function buildAndPushImage(params) {
3502
4069
  }
3503
4070
  );
3504
4071
  }
3505
- async function resolveImageHandler(options, source, span) {
4072
+ async function resolveImageHandler(options, source, buildahClient, span, schedulesCommand) {
3506
4073
  const { config, meta } = options;
3507
4074
  const { daemon } = config;
3508
4075
  span?.setAttributes({
@@ -3525,7 +4092,7 @@ async function resolveImageHandler(options, source, span) {
3525
4092
  });
3526
4093
  return tag3;
3527
4094
  }
3528
- const repository2 = daemon ? (0, import_build_utils6.getDaemonImageRepository)(daemon.name) : sanitizeRepository(options.service?.name ?? buildpack.runtime);
4095
+ const repository2 = daemon ? (0, import_build_utils8.getDaemonImageRepository)(daemon.name) : sanitizeRepository(options.service?.name ?? buildpack.runtime);
3529
4096
  const tag2 = resolveImageTag();
3530
4097
  span?.setAttributes({
3531
4098
  "container.mode": "buildpack-build-and-push",
@@ -3545,6 +4112,7 @@ async function resolveImageHandler(options, source, span) {
3545
4112
  config,
3546
4113
  meta,
3547
4114
  buildEnv: buildArgsFromEnv(meta?.buildEnv),
4115
+ buildahClient,
3548
4116
  parentSpan: span
3549
4117
  });
3550
4118
  } catch (error) {
@@ -3560,7 +4128,7 @@ async function resolveImageHandler(options, source, span) {
3560
4128
  if (meta?.isDev) {
3561
4129
  const serviceName = options.service?.name;
3562
4130
  const tag2 = devImageTag(
3563
- serviceName ?? import_node_path17.default.basename(dockerfileRel).split(".")[0]
4131
+ serviceName ?? import_node_path22.default.basename(dockerfileRel).split(".")[0]
3564
4132
  );
3565
4133
  span?.setAttributes({ "container.mode": "dev", "image.tag": tag2 });
3566
4134
  return tag2;
@@ -3570,11 +4138,11 @@ async function resolveImageHandler(options, source, span) {
3570
4138
  `Dockerfile not found at "${dockerfilePath}" for container service.`
3571
4139
  );
3572
4140
  }
3573
- const repository = daemon ? (0, import_build_utils6.getDaemonImageRepository)(daemon.name) : sanitizeRepository(
3574
- options.service?.name ?? import_node_path17.default.basename(dockerfileRel).split(".")[0]
4141
+ const repository = daemon ? (0, import_build_utils8.getDaemonImageRepository)(daemon.name) : sanitizeRepository(
4142
+ options.service?.name ?? import_node_path22.default.basename(dockerfileRel).split(".")[0]
3575
4143
  );
3576
4144
  const tag = resolveImageTag();
3577
- const contextDir = import_node_path17.default.dirname(dockerfilePath);
4145
+ const contextDir = schedulesCommand ? import_node_path22.default.join(options.workPath, schedulesCommand.root) : import_node_path22.default.dirname(dockerfilePath);
3578
4146
  const buildArgs = buildArgsFromEnv(meta?.buildEnv);
3579
4147
  span?.setAttributes({
3580
4148
  "container.mode": "build_and_push",
@@ -3587,7 +4155,9 @@ async function resolveImageHandler(options, source, span) {
3587
4155
  repository,
3588
4156
  tag,
3589
4157
  buildArgs,
3590
- parentSpan: span
4158
+ buildahClient,
4159
+ parentSpan: span,
4160
+ schedulesCommand
3591
4161
  });
3592
4162
  }
3593
4163
  function buildArgsFromEnv(env) {
@@ -3604,12 +4174,26 @@ function buildArgsFromEnv(env) {
3604
4174
  }
3605
4175
  async function build(options) {
3606
4176
  const source = resolveImageSource(options, "build");
4177
+ const schedulesCommand = options.config.schedulesCommand;
3607
4178
  const { daemon } = options.config;
4179
+ const buildahClient = new BuildahClient(sharedBuildahStoreLock);
4180
+ if (!options.meta?.isDev && selectContainerEngine(buildahClient).name === "buildah") {
4181
+ await BuildahStoreCache.forProject(options, buildahClient).beforeBuild({
4182
+ buildsWithBuildah: source.kind !== "prebuilt",
4183
+ span: options.span
4184
+ });
4185
+ }
3608
4186
  const image = await withSpan(
3609
4187
  options.span,
3610
4188
  "container.resolve_image",
3611
4189
  { "service.name": options.service?.name },
3612
- (span) => resolveImageHandler(options, source, span)
4190
+ (span) => resolveImageHandler(
4191
+ options,
4192
+ source,
4193
+ buildahClient,
4194
+ span,
4195
+ schedulesCommand
4196
+ )
3613
4197
  );
3614
4198
  if (daemon) {
3615
4199
  return {
@@ -3640,15 +4224,16 @@ async function build(options) {
3640
4224
  }
3641
4225
  };
3642
4226
  }
3643
- const lambdaOptions = await (0, import_build_utils6.getLambdaOptionsFromFunction)({
4227
+ const lambdaOptions = await (0, import_build_utils8.getLambdaOptionsFromFunction)({
3644
4228
  sourceFile: resolveFunctionSourceFile(options),
3645
- config: options.config
4229
+ config: options.config,
4230
+ isScheduleEntrypoint: schedulesCommand !== void 0
3646
4231
  });
3647
- const command = source.kind === "buildpack" ? void 0 : normalizeCommand(options.config.command);
4232
+ const command = source.kind === "buildpack" || schedulesCommand ? void 0 : normalizeCommand(options.config.command);
3648
4233
  await generateProjectManifest({
3649
4234
  workPath: options.workPath,
3650
4235
  framework: options.diagnosticsValues?.framework,
3651
- serviceType: options.service ? (0, import_build_utils6.getReportedServiceType)(options.service) : void 0
4236
+ serviceType: options.service ? (0, import_build_utils8.getReportedServiceType)(options.service) : void 0
3652
4237
  });
3653
4238
  const routes = [
3654
4239
  { handle: "filesystem" },
@@ -3668,7 +4253,7 @@ async function build(options) {
3668
4253
  }
3669
4254
  ];
3670
4255
  return {
3671
- routes,
4256
+ routes: schedulesCommand ? [] : routes,
3672
4257
  output: {
3673
4258
  [CONTAINER_OUTPUT_PATH]: {
3674
4259
  type: "Lambda",
@@ -3678,7 +4263,9 @@ async function build(options) {
3678
4263
  // (vercel/api#76729).
3679
4264
  handler: image,
3680
4265
  runtime: "container",
3681
- environment: {},
4266
+ environment: schedulesCommand ? {
4267
+ [SCHEDULES_COMMAND_ENV]: JSON.stringify(schedulesCommand.command)
4268
+ } : {},
3682
4269
  ...command ? { command } : {},
3683
4270
  ...lambdaOptions
3684
4271
  }