openship 0.6.6 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/bare-FA66N7GC.js +29 -0
  2. package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
  3. package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
  4. package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
  5. package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
  6. package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
  7. package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
  8. package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
  9. package/dist/chunk-AHJYP7DG.js +938 -0
  10. package/dist/chunk-BO6CY4V2.js +265 -0
  11. package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
  12. package/dist/chunk-D6HU4VUZ.js +249 -0
  13. package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
  14. package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
  15. package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
  16. package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
  17. package/dist/chunk-N5YZMO33.js +111 -0
  18. package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
  19. package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
  20. package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
  21. package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
  22. package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
  23. package/dist/chunk-R4VDPFLS.js +106 -0
  24. package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
  25. package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
  26. package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
  27. package/dist/chunk-SK4FFCFR.js +70 -0
  28. package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
  29. package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
  30. package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
  31. package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
  32. package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
  33. package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
  34. package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
  35. package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
  36. package/dist/cloud-REV3DPJH.js +42 -0
  37. package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
  38. package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
  39. package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
  40. package/dist/edge-import-YJVJUGYC.js +56 -0
  41. package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
  42. package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
  43. package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
  44. package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
  45. package/dist/index.js +198 -76
  46. package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
  47. package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
  48. package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
  49. package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
  50. package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
  51. package/dist/server/index.js +40049 -30378
  52. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  53. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  54. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  55. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  56. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  57. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  58. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  59. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  60. package/dist/server/migrations/meta/_journal.json +56 -0
  61. package/dist/setup-IFIMPQZJ.js +25 -0
  62. package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
  63. package/dist/up-HQS7IR7H.js +61 -0
  64. package/package.json +1 -1
  65. package/dist/bare-OXJVW3AK.js +0 -25
  66. package/dist/chunk-LH2PLRQO.js +0 -434
  67. package/dist/chunk-SM26HDS2.js +0 -185
  68. package/dist/cloud-75VIT4KV.js +0 -37
  69. package/dist/edge-import-WJUB4M7F.js +0 -52
  70. package/dist/host-channel-preflight-KE6OONYK.js +0 -50
  71. package/dist/setup-XGLGUUFW.js +0 -22
  72. package/dist/up-K4BV7466.js +0 -57
@@ -4,17 +4,23 @@ import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  assembleGitClone,
7
+ dockerBuildContextDirectory,
8
+ generateDockerfile,
7
9
  localGitSshWriter,
8
10
  materializeGitSsh,
9
- sq
10
- } from "./chunk-LH2PLRQO.js";
11
+ normalizeDockerRelativePath,
12
+ resolveContextDockerfileCandidates,
13
+ resolveDockerRootDirectory,
14
+ resolveDockerfileCandidates,
15
+ resolveWithinDirectory
16
+ } from "./chunk-AHJYP7DG.js";
11
17
  import {
12
18
  getTarCreateEnv,
13
19
  prepareSourceTarArgs
14
- } from "./chunk-Q7WGTQQ5.js";
20
+ } from "./chunk-VAI5VOOI.js";
15
21
  import {
16
- packageManagerEnsureCommand
17
- } from "./chunk-UJ6JZM65.js";
22
+ isValidEnvKey
23
+ } from "./chunk-WEZFTL4V.js";
18
24
  import {
19
25
  __commonJS,
20
26
  __toESM
@@ -478,451 +484,242 @@ var require_ignore = __commonJS({
478
484
  }
479
485
  });
480
486
 
481
- // ../../packages/adapters/src/runtime/docker-paths.ts
482
- import { access, readdir } from "fs/promises";
483
- import { isAbsolute, join, relative, resolve, sep } from "path";
484
- function toPosixPath(value) {
485
- return value.split(sep).filter(Boolean).join("/");
486
- }
487
- function toSafeSegments(value, label) {
488
- const segments = value.split(/[\\/]/).filter((segment) => segment && segment !== ".");
489
- if (segments.includes("..")) {
490
- throw new Error(
491
- `Invalid ${label} ${JSON.stringify(value)}: path segments must not contain "..".`
492
- );
487
+ // ../../packages/adapters/src/runtime/docker-build-args.ts
488
+ function resolveDockerBuildArgs(config) {
489
+ const invalidExplicit = Object.keys(config.buildArgs ?? {}).filter((key) => !isValidEnvKey(key));
490
+ if (invalidExplicit.length > 0) {
491
+ throw new Error(`Invalid Docker build argument name(s): ${invalidExplicit.join(", ")}`);
493
492
  }
494
- return segments;
495
- }
496
- function normalizeDockerRelativePath(value) {
497
- const normalized = value?.trim().replace(/^\.\//, "").replace(/^\/+|\/+$/g, "");
498
- if (!normalized || normalized === ".") {
499
- return "";
500
- }
501
- return toSafeSegments(normalized, "path").join("/");
493
+ const inherited = Object.fromEntries(
494
+ Object.entries({
495
+ ...config.envVars,
496
+ NODE_ENV: "production"
497
+ }).filter(([key]) => isValidEnvKey(key))
498
+ );
499
+ return { ...inherited, ...config.buildArgs };
502
500
  }
503
- function resolveWithinDirectory(baseDir, relativePath) {
504
- const base = resolve(baseDir);
505
- const target = resolve(base, ...toSafeSegments(relativePath?.trim() ?? "", "path"));
506
- const escape = relative(base, target);
507
- if (escape === ".." || escape.startsWith(`..${sep}`) || isAbsolute(escape)) {
508
- throw new Error(`Resolved path ${JSON.stringify(target)} escapes ${JSON.stringify(base)}.`);
501
+
502
+ // ../../packages/adapters/src/runtime/docker-buildkit-trace.ts
503
+ var WIRE_VARINT = 0;
504
+ var WIRE_64BIT = 1;
505
+ var WIRE_LENGTH = 2;
506
+ var WIRE_32BIT = 5;
507
+ var WireReader = class {
508
+ constructor(buf) {
509
+ this.buf = buf;
509
510
  }
510
- return target;
511
- }
512
- function normalizeDockerRootDirectory(rootDirectory, localPath) {
513
- let normalized = rootDirectory?.trim() ?? "";
514
- if (!normalized) {
515
- return "";
511
+ pos = 0;
512
+ get done() {
513
+ return this.pos >= this.buf.length;
516
514
  }
517
- if (localPath && isAbsolute(normalized)) {
518
- const relativePath = relative(localPath, normalized);
519
- if (!relativePath || relativePath === ".") {
520
- return "";
515
+ /** Varint as a Number. Bails past 10 bytes (the protobuf maximum) so a corrupt
516
+ * buffer cannot spin. Values we actually read (`stream`, `cached`) are tiny. */
517
+ varint() {
518
+ let result = 0;
519
+ let shift = 0;
520
+ for (let i = 0; i < 10; i += 1) {
521
+ if (this.done) return null;
522
+ const byte = this.buf[this.pos];
523
+ this.pos += 1;
524
+ result += (byte & 127) * 2 ** shift;
525
+ if ((byte & 128) === 0) return result;
526
+ shift += 7;
521
527
  }
522
- if (!relativePath.startsWith("..") && !isAbsolute(relativePath)) {
523
- normalized = relativePath;
524
- }
525
- }
526
- normalized = normalized.replace(/^\.\//, "").replace(/^\/+|\/+$/g, "");
527
- if (!normalized || normalized === ".") {
528
- return "";
529
- }
530
- return toSafeSegments(normalized, "rootDirectory").join("/");
531
- }
532
- function resolveExplicitDockerfileCandidate(rootDirectory, dockerfilePath) {
533
- const normalizedRootDirectory = normalizeDockerRelativePath(rootDirectory);
534
- const normalizedDockerfilePath = normalizeDockerRelativePath(dockerfilePath);
535
- if (!normalizedDockerfilePath) {
536
- return "";
528
+ return null;
537
529
  }
538
- if (!normalizedRootDirectory) {
539
- return normalizedDockerfilePath;
530
+ /** Field header → { field, wire }. */
531
+ key() {
532
+ const tag = this.varint();
533
+ if (tag === null) return null;
534
+ const field = Math.floor(tag / 8);
535
+ if (field <= 0) return null;
536
+ return { field, wire: tag % 8 };
540
537
  }
541
- if (normalizedDockerfilePath.startsWith(`${normalizedRootDirectory}/`)) {
542
- return normalizedDockerfilePath;
538
+ bytes() {
539
+ const length = this.varint();
540
+ if (length === null || length < 0 || this.pos + length > this.buf.length) return null;
541
+ const slice = this.buf.subarray(this.pos, this.pos + length);
542
+ this.pos += length;
543
+ return slice;
543
544
  }
544
- return `${normalizedRootDirectory}/${normalizedDockerfilePath}`;
545
- }
546
- function resolveDockerfileCandidates(rootDirectory, explicitDockerfilePath) {
547
- const normalizedRootDirectory = normalizeDockerRelativePath(rootDirectory);
548
- return [
549
- resolveExplicitDockerfileCandidate(rootDirectory, explicitDockerfilePath),
550
- normalizedRootDirectory ? `${normalizedRootDirectory}/Dockerfile` : "Dockerfile",
551
- "Dockerfile"
552
- ].filter(
553
- (candidate, index, values) => Boolean(candidate) && values.indexOf(candidate) === index
554
- );
555
- }
556
- var ROOT_MANIFESTS = [
557
- "package.json",
558
- "pyproject.toml",
559
- "requirements.txt",
560
- "Pipfile",
561
- "go.mod",
562
- "Cargo.toml",
563
- "Gemfile",
564
- "composer.json",
565
- "pom.xml",
566
- "build.gradle",
567
- "build.gradle.kts",
568
- "mix.exs",
569
- "Dockerfile"
570
- ];
571
- var IGNORED_DIRS = /* @__PURE__ */ new Set([
572
- "node_modules",
573
- ".git",
574
- ".next",
575
- ".nuxt",
576
- ".output",
577
- ".turbo",
578
- ".venv",
579
- "__pycache__",
580
- "build",
581
- "dist",
582
- "target",
583
- "vendor"
584
- ]);
585
- async function pathExists(path) {
586
- return access(path).then(() => true).catch(() => false);
587
- }
588
- async function manifestCount(dir) {
589
- let count = 0;
590
- for (const file of ROOT_MANIFESTS) {
591
- if (await pathExists(join(dir, file))) {
592
- count += 1;
545
+ /** Advance past a field we don't read. Returns false when the buffer is
546
+ * malformed, which ends the scan. */
547
+ skip(wire) {
548
+ switch (wire) {
549
+ case WIRE_VARINT:
550
+ return this.varint() !== null;
551
+ case WIRE_64BIT:
552
+ this.pos += 8;
553
+ return this.pos <= this.buf.length;
554
+ case WIRE_LENGTH:
555
+ return this.bytes() !== null;
556
+ case WIRE_32BIT:
557
+ this.pos += 4;
558
+ return this.pos <= this.buf.length;
559
+ default:
560
+ return false;
593
561
  }
594
562
  }
595
- return count;
596
- }
597
- function pathSegments(path) {
598
- return path.toLowerCase().split("/").filter(Boolean);
563
+ };
564
+ var decoder = new TextDecoder("utf-8", { fatal: false });
565
+ function text(bytes) {
566
+ return decoder.decode(bytes);
599
567
  }
600
- function scorePathHints(relativePath) {
601
- const segments = pathSegments(relativePath);
602
- let score = 0;
603
- for (const segment of segments) {
604
- if (["app", "apps", "site", "sites", "web", "www", "service", "services"].includes(segment)) {
605
- score += 12;
606
- }
607
- if (["docs", "example", "examples", "test", "tests", "storybook"].includes(segment)) {
608
- score -= 18;
568
+ function decodeVertex(bytes) {
569
+ const reader = new WireReader(bytes);
570
+ const vertex = {
571
+ digest: "",
572
+ name: "",
573
+ cached: false,
574
+ started: false,
575
+ completed: false,
576
+ error: ""
577
+ };
578
+ while (!reader.done) {
579
+ const key = reader.key();
580
+ if (!key) return null;
581
+ if (key.field === 1 && key.wire === WIRE_LENGTH) {
582
+ const value = reader.bytes();
583
+ if (!value) return null;
584
+ vertex.digest = text(value);
585
+ } else if (key.field === 3 && key.wire === WIRE_LENGTH) {
586
+ const value = reader.bytes();
587
+ if (!value) return null;
588
+ vertex.name = text(value);
589
+ } else if (key.field === 4 && key.wire === WIRE_VARINT) {
590
+ const value = reader.varint();
591
+ if (value === null) return null;
592
+ vertex.cached = value !== 0;
593
+ } else if (key.field === 5 && key.wire === WIRE_LENGTH) {
594
+ if (!reader.bytes()) return null;
595
+ vertex.started = true;
596
+ } else if (key.field === 6 && key.wire === WIRE_LENGTH) {
597
+ if (!reader.bytes()) return null;
598
+ vertex.completed = true;
599
+ } else if (key.field === 7 && key.wire === WIRE_LENGTH) {
600
+ const value = reader.bytes();
601
+ if (!value) return null;
602
+ vertex.error = text(value);
603
+ } else if (!reader.skip(key.wire)) {
604
+ return null;
609
605
  }
610
606
  }
611
- return score;
607
+ return vertex;
612
608
  }
613
- async function scoreCandidate(dir, relativePath) {
614
- let score = scorePathHints(relativePath);
615
- score += await manifestCount(dir) * 20;
616
- if (relativePath) {
617
- score += 6;
618
- }
619
- return score;
620
- }
621
- async function collectCandidates(rootDir, currentDir, depth, candidates) {
622
- const relativePath = toPosixPath(relative(rootDir, currentDir));
623
- if (await manifestCount(currentDir) > 0) {
624
- candidates.push({
625
- path: relativePath,
626
- score: await scoreCandidate(currentDir, relativePath)
627
- });
628
- }
629
- if (depth >= 6) {
630
- return;
631
- }
632
- const entries = await readdir(currentDir, { withFileTypes: true }).catch(() => []);
633
- for (const entry of entries) {
634
- if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name)) {
635
- continue;
609
+ function decodeLog(bytes) {
610
+ const reader = new WireReader(bytes);
611
+ let vertex = "";
612
+ let msg = null;
613
+ while (!reader.done) {
614
+ const key = reader.key();
615
+ if (!key) return null;
616
+ if (key.field === 1 && key.wire === WIRE_LENGTH) {
617
+ const value = reader.bytes();
618
+ if (!value) return null;
619
+ vertex = text(value);
620
+ } else if (key.field === 4 && key.wire === WIRE_LENGTH) {
621
+ const value = reader.bytes();
622
+ if (!value) return null;
623
+ msg = value;
624
+ } else if (!reader.skip(key.wire)) {
625
+ return null;
636
626
  }
637
- await collectCandidates(rootDir, join(currentDir, entry.name), depth + 1, candidates);
638
- }
639
- }
640
- async function resolveDockerRootDirectory(contextDir, rootDirectory, localPath) {
641
- const hasExplicitRootDirectory = typeof rootDirectory === "string";
642
- const normalized = normalizeDockerRootDirectory(rootDirectory, localPath);
643
- if (hasExplicitRootDirectory) {
644
- return normalized;
645
- }
646
- if (normalized) {
647
- return normalized;
648
- }
649
- const candidates = [];
650
- await collectCandidates(contextDir, contextDir, 0, candidates);
651
- if (candidates.length === 0) {
652
- return "";
653
- }
654
- candidates.sort((left, right) => right.score - left.score);
655
- const bestNonRoot = candidates.find((candidate) => candidate.path);
656
- if (bestNonRoot && bestNonRoot.score > 0) {
657
- return bestNonRoot.path;
658
- }
659
- return candidates[0]?.path ?? "";
660
- }
661
-
662
- // ../../packages/adapters/src/runtime/docker-build-plan.ts
663
- var DOCKER_BUILD_EVENT_PREFIX = "[openship-build]";
664
- var INLINE_BUILD_ENV_EXCLUDES = /* @__PURE__ */ new Set(["FORCE_COLOR", "TERM"]);
665
- function formatDockerBuildEvent(step, status) {
666
- return `${DOCKER_BUILD_EVENT_PREFIX} step=${step} status=${status}`;
667
- }
668
- function normalizeRelativePath(value) {
669
- const normalized = value?.trim().replace(/^\.\//, "").replace(/^\/+|\/+$/g, "");
670
- if (!normalized || normalized === ".") {
671
- return "";
672
- }
673
- return normalized;
674
- }
675
- function builderSourceDir(rootDirectory) {
676
- const normalized = normalizeDockerRootDirectory(rootDirectory);
677
- return normalized ? `/workspace/${normalized}` : "/workspace";
678
- }
679
- function buildEnvPrefix(envVars) {
680
- const assignments = Object.entries(envVars).filter(([key]) => !INLINE_BUILD_ENV_EXCLUDES.has(key)).filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)).map(([key, value]) => `${key}=${sq(value)}`);
681
- if (!envVars.NO_COLOR) {
682
- assignments.push("NO_COLOR='1'");
683
- }
684
- if (assignments.length === 0) {
685
- return "";
686
- }
687
- return `export ${assignments.join(" ")} && `;
688
- }
689
- function buildRunCommand(command, envPrefix) {
690
- return `${envPrefix}${command}`;
691
- }
692
- function runtimeCopyDirectives(config, sourceDir) {
693
- if (config.productionPaths && config.productionPaths.length > 0) {
694
- return config.productionPaths.map((path) => {
695
- const normalized = normalizeRelativePath(path);
696
- const source = normalized ? `${sourceDir}/${normalized}` : sourceDir;
697
- const target = normalized ? `/app/${normalized}` : "/app";
698
- return `COPY --from=builder ${source} ${target}`;
699
- });
700
- }
701
- return [`COPY --from=builder ${sourceDir} /app`];
702
- }
703
- function needsMultiStage(config) {
704
- return config.buildImage !== config.runtimeImage;
705
- }
706
- function workspacePrepareRunLine(config, envPrefix, workspacePrepare) {
707
- const pmEnsure = packageManagerEnsureCommand(config.packageManager);
708
- const body = pmEnsure ? `${pmEnsure} && ${workspacePrepare}` : workspacePrepare;
709
- return `RUN ${envPrefix}${body}`;
710
- }
711
- function stepsRunLine(config, envPrefix, opts) {
712
- const install = opts.install ? config.installCommand : "";
713
- const build = opts.build ? config.buildCommand : "";
714
- const steps = [];
715
- const pmEnsure = packageManagerEnsureCommand(opts.packageManager ?? config.packageManager);
716
- if (pmEnsure && (install || build)) {
717
- steps.push(pmEnsure);
718
- }
719
- if (install) {
720
- steps.push(`printf '${formatDockerBuildEvent("install", "running")}\\n'`);
721
- steps.push(buildRunCommand(install, envPrefix));
722
- steps.push(`printf '${formatDockerBuildEvent("install", "completed")}\\n'`);
723
- }
724
- if (build) {
725
- steps.push(`printf '${formatDockerBuildEvent("build", "running")}\\n'`);
726
- steps.push(buildRunCommand(build, envPrefix));
727
- steps.push(`printf '${formatDockerBuildEvent("build", "completed")}\\n'`);
728
- }
729
- return steps.length > 0 ? `RUN ${steps.join(" && ")}` : null;
730
- }
731
- function installBuildRunLine(config, envPrefix) {
732
- return stepsRunLine(config, envPrefix, { install: true, build: true });
733
- }
734
- function installRunLine(config, envPrefix) {
735
- return stepsRunLine(config, envPrefix, { install: true });
736
- }
737
- var JS_PACKAGE_MANAGERS = ["pnpm", "yarn", "bun", "npm"];
738
- function buildOnlyRunLine(config, envPrefix) {
739
- const command = config.buildCommand?.trim() ?? "";
740
- const packageManager = JS_PACKAGE_MANAGERS.find(
741
- (pm) => new RegExp(`(^|\\s|&&\\s*)${pm}\\s`).test(command)
742
- );
743
- return stepsRunLine(config, envPrefix, { build: true, packageManager });
744
- }
745
- function isPhpRuntime(config) {
746
- return /^php:/.test(config.runtimeImage) || /frankenphp/i.test(config.runtimeImage);
747
- }
748
- var PHP_EXTENSIONS = [
749
- "pdo_mysql",
750
- "pdo_pgsql",
751
- "redis",
752
- "pcntl",
753
- "bcmath",
754
- "intl",
755
- "zip",
756
- "gd",
757
- "opcache"
758
- ];
759
- var PHP_EXT_INSTALLER_IMAGE = "mlocati/php-extension-installer:2";
760
- var PHP_ASSET_BUILD_IMAGE = "node:22-bookworm-slim";
761
- var PHP_EXT_INSTALL_LINE = `RUN install-php-extensions ${PHP_EXTENSIONS.join(" ")} > /dev/null`;
762
- function generatePhpDockerfile(config) {
763
- const sourceDir = builderSourceDir(
764
- normalizeDockerRootDirectory(config.rootDirectory, config.localPath)
765
- );
766
- const envPrefix = buildEnvPrefix(config.envVars);
767
- const installLine = installRunLine(config, envPrefix);
768
- const assetBuildLine = buildOnlyRunLine(config, envPrefix);
769
- const lines = [
770
- `FROM ${config.buildImage} AS builder`,
771
- // php:*-cli ships no Composer — pull the binary from the official image.
772
- `COPY --from=composer:2 /usr/bin/composer /usr/bin/composer`,
773
- `COPY --from=${PHP_EXT_INSTALLER_IMAGE} /usr/bin/install-php-extensions /usr/bin/install-php-extensions`,
774
- PHP_EXT_INSTALL_LINE,
775
- `WORKDIR /workspace`,
776
- `COPY . /workspace`,
777
- `WORKDIR ${sourceDir}`
778
- ];
779
- if (installLine) lines.push(installLine);
780
- if (assetBuildLine) {
781
- lines.push(
782
- `FROM ${PHP_ASSET_BUILD_IMAGE} AS assets`,
783
- `WORKDIR /workspace`,
784
- `COPY . /workspace`,
785
- `WORKDIR ${sourceDir}`,
786
- assetBuildLine
787
- );
788
- }
789
- const docroot = normalizeRelativePath(config.outputDirectory) || "public";
790
- lines.push(
791
- `FROM ${config.runtimeImage} AS runtime`,
792
- PHP_EXT_INSTALL_LINE,
793
- // --chown on the COPY instead of a trailing `chown -R`: the recursive form
794
- // rewrites every file into a second layer, doubling the app's size on disk.
795
- // Ownership has to be settled HERE, before anything mounts: Docker seeds a
796
- // fresh named volume from the image directory it covers, ownership included,
797
- // so `/app/storage` is only writable by the runtime user if it was already
798
- // owned by them in the image (see @repo/core volumes.ts).
799
- `COPY --from=builder --chown=www-data:www-data ${sourceDir} /app`
800
- );
801
- if (assetBuildLine) {
802
- lines.push(
803
- `COPY --from=assets --chown=www-data:www-data ${sourceDir}/${docroot} /app/${docroot}`
804
- );
805
- }
806
- lines.push(
807
- `WORKDIR /app`,
808
- // FrankenPHP's bundled Caddyfile reads the docroot from SERVER_ROOT (default
809
- // `public/`, resolved against WORKDIR). Setting it keeps the project's
810
- // outputDirectory authoritative for BOTH what gets served and where the asset
811
- // stage's output lands — otherwise a project that renamed its docroot builds
812
- // assets into one directory and serves another.
813
- `ENV SERVER_ROOT=${docroot}/`,
814
- // Caddy's config + data dirs are root-owned in the base image, and it writes
815
- // to both on startup — without this the non-root switch below fails at boot.
816
- `RUN mkdir -p /data/caddy /config/caddy && chown -R www-data:www-data /data/caddy /config/caddy`,
817
- // The frankenphp binary carries cap_net_bind_service, so dropping root here
818
- // still leaves any port bindable.
819
- `USER www-data`,
820
- `EXPOSE ${config.port}`
821
- );
822
- if (config.startCommand) {
823
- lines.push(`CMD ["sh", "-c", ${JSON.stringify(config.startCommand)}]`);
824
- }
825
- return lines.join("\n");
826
- }
827
- function staticNginxTemplateLines(port) {
828
- return [
829
- "server {",
830
- ` listen ${port} default_server;`,
831
- " root /usr/share/nginx/html;",
832
- " index index.html;",
833
- " location / { try_files $uri $uri/ /index.html; }",
834
- "}"
835
- ];
836
- }
837
- function staticBuilderOutputPath(config) {
838
- const sourceDir = builderSourceDir(
839
- normalizeDockerRootDirectory(config.rootDirectory, config.localPath)
840
- );
841
- const output = normalizeRelativePath(config.outputDirectory);
842
- return output ? `${sourceDir}/${output}` : sourceDir;
843
- }
844
- function generateStaticDockerfile(config) {
845
- const sourceDir = builderSourceDir(
846
- normalizeDockerRootDirectory(config.rootDirectory, config.localPath)
847
- );
848
- const envPrefix = buildEnvPrefix(config.envVars);
849
- const workspacePrepare = config.workspacePrepareCommand?.trim();
850
- const outputPath = staticBuilderOutputPath(config);
851
- const nginxTemplate = staticNginxTemplateLines(config.port).map((line) => `'${line}'`).join(" ");
852
- const lines = [
853
- `FROM ${config.buildImage} AS builder`,
854
- `WORKDIR /workspace`,
855
- `COPY . /workspace`
856
- ];
857
- if (workspacePrepare) {
858
- lines.push(workspacePrepareRunLine(config, envPrefix, workspacePrepare));
859
627
  }
860
- lines.push(`WORKDIR ${sourceDir}`);
861
- const stepsLine = installBuildRunLine(config, envPrefix);
862
- if (stepsLine) lines.push(stepsLine);
863
- if (config.staticExtractOnly) return lines.join("\n");
864
- lines.push(
865
- `FROM nginx:alpine AS runtime`,
866
- `RUN rm -f /etc/nginx/conf.d/default.conf`,
867
- `COPY --from=builder ${outputPath} /usr/share/nginx/html`,
868
- `RUN printf '%s\\n' ${nginxTemplate} > /etc/nginx/conf.d/app.conf`,
869
- `EXPOSE ${config.port}`,
870
- `CMD ["nginx", "-g", "daemon off;"]`
871
- );
872
- return lines.join("\n");
628
+ if (!vertex.startsWith("sha256:") || !msg?.length) return null;
629
+ return { vertex, msg: text(msg) };
873
630
  }
874
- function generateDockerfile(config) {
875
- if (config.isStatic) {
876
- return generateStaticDockerfile(config);
877
- }
878
- if (isPhpRuntime(config) && needsMultiStage(config)) {
879
- return generatePhpDockerfile(config);
631
+ var BuildKitTraceDecoder = class {
632
+ numbers = /* @__PURE__ */ new Map();
633
+ announced = /* @__PURE__ */ new Set();
634
+ settled = /* @__PURE__ */ new Set();
635
+ /** Decode one `aux` payload into the lines it should add to the build log. */
636
+ push(auxBase64) {
637
+ let payload;
638
+ try {
639
+ payload = Uint8Array.from(Buffer.from(auxBase64, "base64"));
640
+ } catch {
641
+ return [];
642
+ }
643
+ if (payload.length === 0) return [];
644
+ const lines = [];
645
+ const reader = new WireReader(payload);
646
+ while (!reader.done) {
647
+ const key = reader.key();
648
+ if (!key) break;
649
+ if (key.field === 1 && key.wire === WIRE_LENGTH) {
650
+ const bytes = reader.bytes();
651
+ if (!bytes) break;
652
+ const vertex = decodeVertex(bytes);
653
+ if (vertex) lines.push(...this.renderVertex(vertex));
654
+ continue;
655
+ }
656
+ if (key.field === 3 && key.wire === WIRE_LENGTH) {
657
+ const bytes = reader.bytes();
658
+ if (!bytes) break;
659
+ const entry = decodeLog(bytes);
660
+ if (entry) lines.push(...this.renderLog(entry));
661
+ continue;
662
+ }
663
+ if (!reader.skip(key.wire)) break;
664
+ }
665
+ return lines;
880
666
  }
881
- const sourceDir = builderSourceDir(
882
- normalizeDockerRootDirectory(config.rootDirectory, config.localPath)
883
- );
884
- const multiStage = needsMultiStage(config);
885
- const envPrefix = buildEnvPrefix(config.envVars);
886
- const workspacePrepare = config.workspacePrepareCommand?.trim();
887
- const lines = multiStage ? [
888
- `FROM ${config.buildImage} AS builder`,
889
- `WORKDIR /workspace`,
890
- `COPY . /workspace`
891
- ] : [
892
- `FROM ${config.runtimeImage}`,
893
- `WORKDIR /workspace`,
894
- `COPY . /workspace`
895
- ];
896
- if (workspacePrepare) {
897
- lines.push(workspacePrepareRunLine(config, envPrefix, workspacePrepare));
667
+ numberFor(digest) {
668
+ const existing = this.numbers.get(digest);
669
+ if (existing !== void 0) return existing;
670
+ const next = this.numbers.size + 1;
671
+ this.numbers.set(digest, next);
672
+ return next;
898
673
  }
899
- lines.push(`WORKDIR ${sourceDir}`);
900
- const stepsLine = installBuildRunLine(config, envPrefix);
901
- if (stepsLine) {
902
- lines.push(stepsLine);
674
+ renderVertex(vertex) {
675
+ if (!vertex.digest) return [];
676
+ const number = this.numberFor(vertex.digest);
677
+ const lines = [];
678
+ if (vertex.name && !this.announced.has(vertex.digest)) {
679
+ this.announced.add(vertex.digest);
680
+ lines.push({ message: `#${number} ${vertex.name}`, level: "info" });
681
+ }
682
+ if ((vertex.completed || vertex.error) && !this.settled.has(vertex.digest)) {
683
+ this.settled.add(vertex.digest);
684
+ if (vertex.error) {
685
+ lines.push({ message: `#${number} ERROR: ${vertex.error}`, level: "error" });
686
+ } else if (vertex.cached) {
687
+ lines.push({ message: `#${number} CACHED`, level: "info" });
688
+ } else {
689
+ lines.push({ message: `#${number} DONE`, level: "info" });
690
+ }
691
+ }
692
+ return lines;
903
693
  }
904
- if (multiStage) {
905
- lines.push(`FROM ${config.runtimeImage} AS runtime`);
906
- lines.push(...runtimeCopyDirectives(config, sourceDir));
907
- lines.push(`WORKDIR /app`);
694
+ renderLog(entry) {
695
+ const number = this.numberFor(entry.vertex);
696
+ return entry.msg.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => ({ message: `#${number} ${line}`, level: "info" }));
908
697
  }
909
- lines.push(`EXPOSE ${config.port}`);
910
- if (config.startCommand) {
911
- lines.push(`CMD ["sh", "-c", ${JSON.stringify(config.startCommand)}]`);
698
+ };
699
+ function dockerfileNeedsBuildKit(contents) {
700
+ if (/^\s*#\s*syntax\s*=/im.test(contents.slice(0, 4096))) return true;
701
+ for (const line of contents.split(/\r?\n/)) {
702
+ if (/^\s*#/.test(line)) continue;
703
+ const match = /^\s*(RUN|COPY|ADD)((?:\s+--[^\s]+)*)/i.exec(line);
704
+ if (!match) continue;
705
+ const flags = match[2] ?? "";
706
+ if (/--(mount|network|security)\b/i.test(flags)) return true;
707
+ if (/--(link|checksum|keep-git-dir)\b/i.test(flags)) return true;
708
+ if (/(?:^|\s)<<-?\s*['"]?[A-Za-z_][A-Za-z0-9_]*['"]?(?:\s|$)/.test(line)) return true;
912
709
  }
913
- return lines.join("\n");
710
+ return false;
914
711
  }
915
712
 
916
713
  // ../../packages/adapters/src/runtime/docker-build-context.ts
917
714
  var import_ignore = __toESM(require_ignore(), 1);
918
715
  import { spawn } from "child_process";
919
- import { access as access2, mkdtemp, readdir as readdir2, readFile, rm, writeFile } from "fs/promises";
716
+ import { access, mkdtemp, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
920
717
  import { tmpdir } from "os";
921
- import { join as join2, relative as relative2, sep as sep2 } from "path";
718
+ import { isAbsolute, join, relative, sep } from "path";
922
719
  var GIT_CLONE_IDLE_TIMEOUT_MS = 5 * 6e4;
923
720
  var GIT_CHECKOUT_IDLE_TIMEOUT_MS = 6e4;
924
721
  function spawnGit(args, opts) {
925
- return new Promise((resolve2, reject) => {
722
+ return new Promise((resolve, reject) => {
926
723
  const child = spawn("git", args, {
927
724
  stdio: ["ignore", "pipe", "pipe"],
928
725
  env: {
@@ -961,9 +758,9 @@ function spawnGit(args, opts) {
961
758
  });
962
759
  child.stderr?.on("data", (buf) => {
963
760
  armIdle();
964
- const text = buf.toString();
965
- stderr += text;
966
- for (const ln of text.split(/\r?\n/)) flushLine(ln);
761
+ const text2 = buf.toString();
762
+ stderr += text2;
763
+ for (const ln of text2.split(/\r?\n/)) flushLine(ln);
967
764
  });
968
765
  armIdle();
969
766
  child.on("error", (err) => {
@@ -972,14 +769,14 @@ function spawnGit(args, opts) {
972
769
  });
973
770
  child.on("close", (code) => {
974
771
  clearTimeout(idleTimer);
975
- if (code === 0) resolve2();
772
+ if (code === 0) resolve();
976
773
  else reject(new Error(`git exited with code ${code}: ${stderr.trim().slice(-500) || "no stderr"}`));
977
774
  });
978
775
  });
979
776
  }
980
777
  var GENERATED_DOCKERFILE_NAME = "Dockerfile.openship";
981
- function toPosixPath2(value) {
982
- return value.split(sep2).filter(Boolean).join("/");
778
+ function toPosixPath(value) {
779
+ return value.split(sep).filter(Boolean).join("/");
983
780
  }
984
781
  function anchorDockerignorePattern(line) {
985
782
  const trimmed = line.trim();
@@ -992,7 +789,7 @@ function anchorDockerignorePattern(line) {
992
789
  }
993
790
  async function loadDockerignoreMatcher(rootPath) {
994
791
  try {
995
- const contents = await readFile(join2(rootPath, ".dockerignore"), "utf-8");
792
+ const contents = await readFile(join(rootPath, ".dockerignore"), "utf-8");
996
793
  return (0, import_ignore.default)().add(contents.split(/\r?\n/).map(anchorDockerignorePattern));
997
794
  } catch {
998
795
  return void 0;
@@ -1009,16 +806,45 @@ function buildFilePaths(config) {
1009
806
  }
1010
807
  return paths;
1011
808
  }
809
+ function contextKeepPaths(dockerfileName) {
810
+ const paths = /* @__PURE__ */ new Set([".dockerignore"]);
811
+ const segments = dockerfileName.split("/").filter(Boolean);
812
+ for (let depth = 1; depth <= segments.length; depth += 1) {
813
+ paths.add(segments.slice(0, depth).join("/"));
814
+ }
815
+ return paths;
816
+ }
817
+ async function loadContextIgnore(buildContextDir, keep) {
818
+ const matcher = await loadDockerignoreMatcher(buildContextDir);
819
+ if (!matcher) return void 0;
820
+ return (relativePosixPath) => !keep.has(relativePosixPath) && matcher.ignores(relativePosixPath);
821
+ }
822
+ async function resolveBuildContextDir(treeDir, contextSubdir) {
823
+ const target = resolveWithinDirectory(treeDir, contextSubdir);
824
+ if (!contextSubdir) return target;
825
+ const stats = await stat(target).catch(() => null);
826
+ if (!stats?.isDirectory()) {
827
+ throw new Error(
828
+ `Build context "${contextSubdir}" is not a directory in this source. Check the service's build path.`
829
+ );
830
+ }
831
+ const [realTree, realTarget] = await Promise.all([realpath(treeDir), realpath(target)]);
832
+ const escape = relative(realTree, realTarget);
833
+ if (escape === ".." || escape.startsWith(`..${sep}`) || isAbsolute(escape)) {
834
+ throw new Error(`Build context "${contextSubdir}" resolves outside the source tree.`);
835
+ }
836
+ return target;
837
+ }
1012
838
  async function applyDockerignore(contextDir, config) {
1013
839
  const matcher = await loadDockerignoreMatcher(contextDir);
1014
840
  if (!matcher) return;
1015
841
  const buildFiles = buildFilePaths(config);
1016
842
  const prune = async (currentPath) => {
1017
- const entries = await readdir2(currentPath, { withFileTypes: true });
843
+ const entries = await readdir(currentPath, { withFileTypes: true });
1018
844
  await Promise.all(
1019
845
  entries.map(async (entry) => {
1020
- const absolutePath = join2(currentPath, entry.name);
1021
- const relativePath = toPosixPath2(relative2(contextDir, absolutePath));
846
+ const absolutePath = join(currentPath, entry.name);
847
+ const relativePath = toPosixPath(relative(contextDir, absolutePath));
1022
848
  if (!buildFiles.has(relativePath) && matcher.ignores(relativePath)) {
1023
849
  await rm(absolutePath, { recursive: true, force: true });
1024
850
  return;
@@ -1029,10 +855,12 @@ async function applyDockerignore(contextDir, config) {
1029
855
  };
1030
856
  await prune(contextDir);
1031
857
  }
1032
- async function materializeLocalSource(sourcePath, targetPath) {
1033
- const { args, cleanup } = await prepareSourceTarArgs(sourcePath);
858
+ async function materializeLocalSource(sourcePath, targetPath, outputDirectory) {
859
+ const outDir = outputDirectory?.trim().replace(/^\.\//, "").replace(/^\/+|\/+$/g, "");
860
+ const alsoInclude = outDir && outDir !== "." ? [outDir] : void 0;
861
+ const { args, cleanup } = await prepareSourceTarArgs(sourcePath, { alsoInclude });
1034
862
  try {
1035
- await new Promise((resolve2, reject) => {
863
+ await new Promise((resolve, reject) => {
1036
864
  const create = spawn("tar", args, { env: getTarCreateEnv() });
1037
865
  const extract = spawn("tar", ["-xzf", "-", "-C", targetPath]);
1038
866
  let err = "";
@@ -1045,7 +873,7 @@ async function materializeLocalSource(sourcePath, targetPath) {
1045
873
  create.on("close", (code) => createCode = code);
1046
874
  create.stdout.pipe(extract.stdin);
1047
875
  extract.on("close", (code) => {
1048
- if (code === 0 && (createCode ?? 0) === 0) return resolve2();
876
+ if (code === 0 && (createCode ?? 0) === 0) return resolve();
1049
877
  const parts = [
1050
878
  createCode ? `pack exited ${createCode}: ${createErr.trim().slice(-500)}` : "",
1051
879
  code ? `extract exited ${code}: ${err.trim().slice(-500)}` : ""
@@ -1057,23 +885,28 @@ async function materializeLocalSource(sourcePath, targetPath) {
1057
885
  await cleanup();
1058
886
  }
1059
887
  }
1060
- async function resolveDockerfileName(contextDir, rootDirectory, explicitDockerfilePath) {
1061
- const candidates = resolveDockerfileCandidates(rootDirectory, explicitDockerfilePath);
888
+ async function firstExistingCandidate(dir, candidates) {
1062
889
  for (const candidate of candidates) {
1063
- const candidatePath = join2(contextDir, ...candidate.split("/"));
1064
- const exists = await access2(candidatePath).then(() => true).catch(() => false);
890
+ const candidatePath = join(dir, ...candidate.split("/"));
891
+ const exists = await access(candidatePath).then(() => true).catch(() => false);
1065
892
  if (exists) {
1066
893
  return candidate;
1067
894
  }
1068
895
  }
1069
896
  return null;
1070
897
  }
898
+ async function resolveDockerfileName(contextDir, rootDirectory, explicitDockerfilePath) {
899
+ return firstExistingCandidate(
900
+ contextDir,
901
+ resolveDockerfileCandidates(rootDirectory, explicitDockerfilePath)
902
+ );
903
+ }
1071
904
  async function cloneGitSource(config, targetPath, onLog) {
1072
905
  let sshMaterial;
1073
906
  if (config.gitSsh) {
1074
907
  sshMaterial = await materializeGitSsh(
1075
908
  localGitSshWriter(),
1076
- await mkdtemp(join2(tmpdir(), "opsh-ghkey-")),
909
+ await mkdtemp(join(tmpdir(), "opsh-ghkey-")),
1077
910
  config.gitSsh
1078
911
  );
1079
912
  }
@@ -1104,22 +937,24 @@ async function cloneGitSource(config, targetPath, onLog) {
1104
937
  env: gitEnv
1105
938
  });
1106
939
  }
1107
- await rm(join2(targetPath, ".git"), { recursive: true, force: true });
940
+ await rm(join(targetPath, ".git"), { recursive: true, force: true });
1108
941
  } finally {
1109
942
  await sshMaterial?.cleanup();
1110
943
  }
1111
944
  }
1112
945
  async function prepareSourceTree(config, opts) {
1113
- const contextDir = await mkdtemp(join2(tmpdir(), "openship-docker-context-"));
946
+ const contextDir = await mkdtemp(join(tmpdir(), "openship-docker-context-"));
1114
947
  try {
948
+ const pruneDockerignore = !opts?.perServiceBuildContexts && !dockerBuildContextDirectory(config);
1115
949
  if (config.localPath) {
1116
- await materializeLocalSource(config.localPath, contextDir);
950
+ await materializeLocalSource(config.localPath, contextDir, config.outputDirectory);
1117
951
  } else {
1118
952
  await cloneGitSource(config, contextDir, opts?.onLog);
1119
953
  }
1120
- await applyDockerignore(contextDir, config);
954
+ if (pruneDockerignore) await applyDockerignore(contextDir, config);
1121
955
  return {
1122
956
  contextDir,
957
+ dockerignorePruned: pruneDockerignore,
1123
958
  cleanup: async () => {
1124
959
  await rm(contextDir, { recursive: true, force: true }).catch(() => {
1125
960
  });
@@ -1131,20 +966,42 @@ async function prepareSourceTree(config, opts) {
1131
966
  throw error;
1132
967
  }
1133
968
  }
969
+ function missingContextDockerfileMessage(contextSubdir, dockerfilePath, opts) {
970
+ const expected = normalizeDockerRelativePath(dockerfilePath) || "Dockerfile";
971
+ const at = `${contextSubdir}/${expected.startsWith(`${contextSubdir}/`) ? expected.slice(contextSubdir.length + 1) : expected}`;
972
+ return `No Dockerfile found in the build context "${contextSubdir}". Expected ${at}.` + // Names the file the build used to fall back to. Before #634 a build dir with no
973
+ // Dockerfile silently built the REPO ROOT's one under this service's tag, so an
974
+ // upgrade turns those deploys from "green with the wrong image" to red — and
975
+ // without this sentence the red gives no clue why.
976
+ (opts?.rootDockerfileExists ? " The source root has a Dockerfile, but it is outside this service's build context, so it is not used \u2014 set the service's build path to the directory that holds the Dockerfile you want, or add one there." : "") + (opts?.generatedRecipeRefused ? " A declared build context builds from its own Dockerfile \u2014 Openship will not generate one for it, because a generated recipe copies from the source root." : "");
977
+ }
1134
978
  async function resolveServiceDockerfile(contextDir, config, opts) {
1135
979
  const requireRepositoryDockerfile = opts?.requireRepositoryDockerfile ?? false;
1136
980
  const generatedName = opts?.generatedName ?? GENERATED_DOCKERFILE_NAME;
1137
- const resolvedRootDirectory = await resolveDockerRootDirectory(
1138
- contextDir,
1139
- config.rootDirectory,
1140
- config.localPath
1141
- );
1142
- const repositoryDockerfileName = await resolveDockerfileName(
1143
- contextDir,
1144
- resolvedRootDirectory,
1145
- config.dockerfilePath
1146
- );
981
+ const contextSubdir = dockerBuildContextDirectory(config);
982
+ const buildContextDir = await resolveBuildContextDir(contextDir, contextSubdir);
983
+ const resolvedRootDirectory = contextSubdir ? contextSubdir : await resolveDockerRootDirectory(contextDir, config.rootDirectory, config.localPath);
984
+ const repositoryDockerfileName = contextSubdir ? await firstExistingCandidate(
985
+ buildContextDir,
986
+ resolveContextDockerfileCandidates(contextSubdir, config.dockerfilePath)
987
+ ) : await resolveDockerfileName(contextDir, resolvedRootDirectory, config.dockerfilePath);
1147
988
  const hasRepositoryDockerfile = repositoryDockerfileName !== null;
989
+ if (!hasRepositoryDockerfile && contextSubdir) {
990
+ throw new Error(
991
+ missingContextDockerfileMessage(contextSubdir, config.dockerfilePath, {
992
+ generatedRecipeRefused: !requireRepositoryDockerfile,
993
+ rootDockerfileExists: await access(join(contextDir, "Dockerfile")).then(() => true).catch(() => false)
994
+ })
995
+ );
996
+ }
997
+ const explicitDockerfile = normalizeDockerRelativePath(config.dockerfilePath);
998
+ if (contextSubdir && explicitDockerfile && repositoryDockerfileName === "Dockerfile" && explicitDockerfile !== "Dockerfile" && explicitDockerfile !== `${contextSubdir}/Dockerfile`) {
999
+ opts?.onLog?.({
1000
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1001
+ message: `Configured Dockerfile "${config.dockerfilePath}" was not found in build context "${contextSubdir}" \u2014 using ${contextSubdir}/Dockerfile instead.`,
1002
+ level: "warn"
1003
+ });
1004
+ }
1148
1005
  if (!hasRepositoryDockerfile && requireRepositoryDockerfile) {
1149
1006
  const expectedDockerfile = config.dockerfilePath?.trim() || "Dockerfile";
1150
1007
  throw new Error(
@@ -1153,7 +1010,7 @@ async function resolveServiceDockerfile(contextDir, config, opts) {
1153
1010
  }
1154
1011
  if (!hasRepositoryDockerfile) {
1155
1012
  await writeFile(
1156
- join2(contextDir, generatedName),
1013
+ join(contextDir, generatedName),
1157
1014
  generateDockerfile({
1158
1015
  ...config,
1159
1016
  rootDirectory: resolvedRootDirectory
@@ -1161,26 +1018,34 @@ async function resolveServiceDockerfile(contextDir, config, opts) {
1161
1018
  "utf-8"
1162
1019
  );
1163
1020
  }
1164
- const contextEntries = await readdir2(contextDir);
1021
+ const dockerfileName = repositoryDockerfileName ?? generatedName;
1022
+ const requiresBuildKit = hasRepositoryDockerfile ? await readFile(join(buildContextDir, ...dockerfileName.split("/")), "utf-8").then(dockerfileNeedsBuildKit).catch(() => false) : false;
1023
+ const ignoreContextPath = opts?.dockerignorePruned === false ? await loadContextIgnore(buildContextDir, contextKeepPaths(dockerfileName)) : void 0;
1024
+ const contextEntries = (await readdir(buildContextDir)).filter(
1025
+ (entry) => !ignoreContextPath?.(entry)
1026
+ );
1165
1027
  return {
1028
+ buildContextDir,
1029
+ contextSubdir,
1166
1030
  contextEntries,
1167
- dockerfileName: repositoryDockerfileName ?? generatedName,
1031
+ ignoreContextPath,
1032
+ dockerfileName,
1168
1033
  rootDirectory: resolvedRootDirectory,
1169
- usesRepositoryDockerfile: hasRepositoryDockerfile
1034
+ usesRepositoryDockerfile: hasRepositoryDockerfile,
1035
+ requiresBuildKit
1170
1036
  };
1171
1037
  }
1172
1038
  async function createDockerBuildContext(config, opts) {
1173
1039
  const tree = await prepareSourceTree(config, { onLog: opts?.onLog });
1174
1040
  try {
1175
1041
  const resolved = await resolveServiceDockerfile(tree.contextDir, config, {
1176
- requireRepositoryDockerfile: opts?.requireRepositoryDockerfile
1042
+ requireRepositoryDockerfile: opts?.requireRepositoryDockerfile,
1043
+ dockerignorePruned: tree.dockerignorePruned,
1044
+ onLog: opts?.onLog
1177
1045
  });
1178
1046
  return {
1047
+ ...resolved,
1179
1048
  contextDir: tree.contextDir,
1180
- contextEntries: resolved.contextEntries,
1181
- dockerfileName: resolved.dockerfileName,
1182
- rootDirectory: resolved.rootDirectory,
1183
- usesRepositoryDockerfile: resolved.usesRepositoryDockerfile,
1184
1049
  cleanup: tree.cleanup
1185
1050
  };
1186
1051
  } catch (error) {
@@ -1190,12 +1055,10 @@ async function createDockerBuildContext(config, opts) {
1190
1055
  }
1191
1056
 
1192
1057
  export {
1193
- normalizeDockerRelativePath,
1194
- resolveWithinDirectory,
1195
- resolveDockerfileCandidates,
1196
- staticBuilderOutputPath,
1197
- generateDockerfile,
1058
+ resolveDockerBuildArgs,
1059
+ BuildKitTraceDecoder,
1198
1060
  prepareSourceTree,
1061
+ missingContextDockerfileMessage,
1199
1062
  resolveServiceDockerfile,
1200
1063
  createDockerBuildContext
1201
1064
  };