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.
- package/dist/bare-FA66N7GC.js +29 -0
- package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
- package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
- package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
- package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
- package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
- package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
- package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
- package/dist/chunk-AHJYP7DG.js +938 -0
- package/dist/chunk-BO6CY4V2.js +265 -0
- package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
- package/dist/chunk-D6HU4VUZ.js +249 -0
- package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
- package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
- package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
- package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
- package/dist/chunk-N5YZMO33.js +111 -0
- package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
- package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
- package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
- package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
- package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
- package/dist/chunk-R4VDPFLS.js +106 -0
- package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
- package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
- package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
- package/dist/chunk-SK4FFCFR.js +70 -0
- package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
- package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
- package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
- package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
- package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
- package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
- package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
- package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
- package/dist/cloud-REV3DPJH.js +42 -0
- package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
- package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
- package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
- package/dist/edge-import-YJVJUGYC.js +56 -0
- package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
- package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
- package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
- package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
- package/dist/index.js +198 -76
- package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
- package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
- package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
- package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
- package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
- package/dist/server/index.js +40049 -30378
- package/dist/server/migrations/0109_service_build_args.sql +9 -0
- package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
- package/dist/server/migrations/0111_host_port_claim.sql +381 -0
- package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
- package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
- package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
- package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
- package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
- package/dist/server/migrations/meta/_journal.json +56 -0
- package/dist/setup-IFIMPQZJ.js +25 -0
- package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
- package/dist/up-HQS7IR7H.js +61 -0
- package/package.json +1 -1
- package/dist/bare-OXJVW3AK.js +0 -25
- package/dist/chunk-LH2PLRQO.js +0 -434
- package/dist/chunk-SM26HDS2.js +0 -185
- package/dist/cloud-75VIT4KV.js +0 -37
- package/dist/edge-import-WJUB4M7F.js +0 -52
- package/dist/host-channel-preflight-KE6OONYK.js +0 -50
- package/dist/setup-XGLGUUFW.js +0 -22
- 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
|
-
|
|
10
|
-
|
|
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-
|
|
20
|
+
} from "./chunk-VAI5VOOI.js";
|
|
15
21
|
import {
|
|
16
|
-
|
|
17
|
-
} from "./chunk-
|
|
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-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
539
|
-
|
|
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
|
-
|
|
542
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
596
|
-
}
|
|
597
|
-
function
|
|
598
|
-
return
|
|
563
|
+
};
|
|
564
|
+
var decoder = new TextDecoder("utf-8", { fatal: false });
|
|
565
|
+
function text(bytes) {
|
|
566
|
+
return decoder.decode(bytes);
|
|
599
567
|
}
|
|
600
|
-
function
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
|
607
|
+
return vertex;
|
|
612
608
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
lines
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
|
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
|
|
716
|
+
import { access, mkdtemp, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
|
|
920
717
|
import { tmpdir } from "os";
|
|
921
|
-
import { join
|
|
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((
|
|
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
|
|
965
|
-
stderr +=
|
|
966
|
-
for (const ln of
|
|
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)
|
|
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
|
|
982
|
-
return value.split(
|
|
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(
|
|
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
|
|
843
|
+
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
1018
844
|
await Promise.all(
|
|
1019
845
|
entries.map(async (entry) => {
|
|
1020
|
-
const absolutePath =
|
|
1021
|
-
const relativePath =
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
1061
|
-
const candidates = resolveDockerfileCandidates(rootDirectory, explicitDockerfilePath);
|
|
888
|
+
async function firstExistingCandidate(dir, candidates) {
|
|
1062
889
|
for (const candidate of candidates) {
|
|
1063
|
-
const candidatePath =
|
|
1064
|
-
const exists = await
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
resolveDockerfileCandidates,
|
|
1196
|
-
staticBuilderOutputPath,
|
|
1197
|
-
generateDockerfile,
|
|
1058
|
+
resolveDockerBuildArgs,
|
|
1059
|
+
BuildKitTraceDecoder,
|
|
1198
1060
|
prepareSourceTree,
|
|
1061
|
+
missingContextDockerfileMessage,
|
|
1199
1062
|
resolveServiceDockerfile,
|
|
1200
1063
|
createDockerBuildContext
|
|
1201
1064
|
};
|