1import { Docker } from "@/Docker/Docker.ts";
2import { Stage } from "@/Stage.ts";
3import * as Effect from "effect/Effect";
4import * as Schema from "effect/Schema";
5
6export const withBuilder =
7 (prefix: string, options?: { attestations?: boolean }) =>
8 <A, E, R>(effect: Effect.Effect<A, E, R>) =>
9 Effect.gen(function* () {
10 const name = `${prefix}-${yield* Stage}`;
11 const docker = yield* Docker;
12 return yield* Effect.acquireUseRelease(
13 // A killed run cannot release its builder. Remove any leftover first,
14 // so every invocation starts from an empty cache under the same name.
15 docker
16 .run(["buildx", "rm", "--force", name])
17 .pipe(
18 Effect.ignore,
19 Effect.andThen(
20 docker.run([
21 "buildx",
22 "create",
23 "--name",
24 name,
25 "--driver",
26 "docker-container",
27 ]),
28 ),
29 ),
30 () =>
31 Effect.acquireUseRelease(
32 Effect.sync(() => {
33 const previous = {
34 builder: process.env.BUILDX_BUILDER,
35 attestations: process.env.BUILDX_NO_DEFAULT_ATTESTATIONS,
36 };
37 process.env.BUILDX_BUILDER = name;
38 process.env.BUILDX_NO_DEFAULT_ATTESTATIONS = String(
39 options?.attestations === false,
40 );
41 return previous;
42 }),
43 () =>
44 docker
45 .run(["buildx", "inspect", "--bootstrap"])
46 .pipe(Effect.andThen(effect)),
47 (previous) =>
48 Effect.sync(() => {
49 if (previous.builder === undefined)
50 delete process.env.BUILDX_BUILDER;
51 else process.env.BUILDX_BUILDER = previous.builder;
52 if (previous.attestations === undefined)
53 delete process.env.BUILDX_NO_DEFAULT_ATTESTATIONS;
54 else
55 process.env.BUILDX_NO_DEFAULT_ATTESTATIONS =
56 previous.attestations;
57 }),
58 ),
59 () => docker.run(["buildx", "rm", "--force", name]).pipe(Effect.orDie),
60 );
61 });
62
63/**
64 * Whether the installed Buildx can export straight to a registry
65 * (`buildx build --push` authenticating via DOCKER_AUTH_CONFIG, 0.26+).
66 * Older plugins make the provider `--load` the image and `docker push` it.
67 */
68export const supportsRegistryExport = Effect.gen(function* () {
69 const docker = yield* Docker;
70 const version = yield* docker.run(["buildx", "version"]);
71 const match = /buildx v(\d+)\.(\d+)\./.exec(version.stdout);
72 if (!match) return false;
73 return Number(match[1]) >= 1 || Number(match[2]) >= 26;
74});
75
76const decodeBuild = Schema.Struct({
77 ref: Schema.String,
78 status: Schema.String,
79}).pipe(Schema.fromJsonString, Schema.decodeEffect);
80
81export const buildHistory = Effect.gen(function* () {
82 const docker = yield* Docker;
83 const history = yield* docker.run([
84 "buildx",
85 "history",
86 "ls",
87 "--format",
88 "json",
89 ]);
90 return yield* Effect.forEach(
91 history.stdout.split("\n").filter(Boolean),
92 (line) => decodeBuild(line),
93 );
94});