feat: improve API keys and build workflows

This commit is contained in:
2026-10-04 20:05:13 +00:00 Unverified
parent 62f2362a2e
commit e4623efe86
27 changed files with 2080 additions and 235 deletions
+89 -20
View File
@@ -1,5 +1,5 @@
import { defineCommand } from "citty";
import { Listr, type ListrTaskWrapper } from "listr2";
import { Listr, ListrErrorTypes, type ListrTaskWrapper } from "listr2";
import { randomUUID } from "node:crypto";
import { isDeepStrictEqual } from "node:util";
import type { ComposeSpecification } from "../schema/docker.d";
@@ -765,34 +765,103 @@ export async function runUp(
},
{
title: "Build images",
rendererOptions: { outputBar: 10, persistentOutput: true },
rendererOptions: { bottomBar: Infinity, persistentOutput: true },
enabled: async () =>
build &&
Object.values((await compose()).services ?? {}).some(
(service) => service.build,
),
task: async (taskCtx, task) => {
const result = await buildServices(
project,
taskCtx.compose!,
cwd,
{
progress: (message) => {
task.output = message;
},
stream: task.stdout(),
},
{
...config,
request,
snapshot: taskCtx.snapshot,
workspaceRoot: taskCtx.workspaceRoot,
signal: task.signal,
},
const services = Object.entries(taskCtx.compose!.services ?? {})
.filter(([, service]) => service.build)
.map(([name]) => name);
const children = new Map<
string,
ListrTaskWrapper<UpContext, any, any>
>();
const pending = new Map<string, (error?: unknown) => void>();
const completed = new Map<string, Promise<unknown>>(
services.map((name) => [
name,
new Promise((resolve) => pending.set(name, resolve)),
]),
);
let result: Awaited<ReturnType<typeof buildServices>> | undefined;
let operationFailure: Error | undefined;
let operation: Promise<void> | undefined;
const start = () => {
operation ??= buildServices(
project,
taskCtx.compose!,
cwd,
{
progress: (message) => {
task.output = message;
},
service: (name) => {
let stream: ReturnType<ListrTaskWrapper<UpContext, any, any>["stdout"]> | undefined;
return {
progress: (message) => {
const child = children.get(name);
if (child && child.output !== message) child.output = message;
},
get stream() {
const child = children.get(name);
if (child) stream ??= child.stdout();
return stream;
},
};
},
settled: (name, error) => {
pending.get(name)?.(error);
pending.delete(name);
},
},
{
...config,
request,
snapshot: taskCtx.snapshot,
workspaceRoot: taskCtx.workspaceRoot,
signal: task.signal,
},
).then(
(value) => {
result = value;
},
(error: unknown) => {
const failure =
error instanceof Error ? error : new Error(String(error));
operationFailure = failure;
task.output = failure.stack ?? failure.message;
task.report(failure, ListrErrorTypes.HAS_FAILED);
for (const resolve of pending.values()) resolve(failure);
pending.clear();
},
);
return operation;
};
await task
.newListr<UpContext>(
services.map((name) => ({
title: `Build ${name}`,
rendererOptions: { outputBar: 10, persistentOutput: true },
task: async (_ctx, child) => {
children.set(name, child);
child.output = "Build queued";
if (children.size === services.length) start();
const error = await completed.get(name);
if (error) throw error;
child.output = "done";
},
})),
{ concurrent: true, exitOnError: false },
)
.run();
await operation;
if (!result) throw operationFailure ?? new Error("Build images failed");
taskCtx.buildImages = result.images;
await config.postBuild?.(result, await getHookContext());
task.output = `Built ${result.built.length} image${result.built.length === 1 ? "" : "s"}`;
task.title = `Built ${result.built.length} image${result.built.length === 1 ? "" : "s"}`;
},
},
{