feat: add artifact publish preflight

This commit is contained in:
Codex
2026-05-20 21:49:50 +00:00
parent 68ae2722ab
commit 021a9eef01
10 changed files with 636 additions and 45 deletions
+60 -6
View File
@@ -5,10 +5,10 @@ import { runCommand, type CommandResult } from "./command";
import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "./config";
import { startJob } from "./jobs";
type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
type ArtifactDeployEnvironment = "prod" | "dev";
interface ArtifactRegistryOptions {
export interface ArtifactRegistryOptions {
environment: ArtifactDeployEnvironment | null;
providerId: string;
host: string;
@@ -49,6 +49,15 @@ interface RenderedBundle {
};
}
export interface ArtifactRegistryReadonlyProbe {
action: "status" | "health";
providerId: string;
script: string;
timeoutMs: number;
healthMode: boolean;
options: ArtifactRegistryOptions;
}
const defaultOptions: ArtifactRegistryOptions = {
environment: null,
providerId: "D601",
@@ -758,7 +767,7 @@ function environmentValue(value: string, option: string): ArtifactDeployEnvironm
throw new Error(`${option} must be one of: prod, dev`);
}
function parseOptions(args: string[]): ArtifactRegistryOptions {
export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOptions {
const options = { ...defaultOptions };
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
@@ -1103,7 +1112,7 @@ function registryHealthDecision(checks: Record<string, boolean>, commandOk: bool
};
}
function commandTail(result: CommandResult): Record<string, unknown> {
export function artifactRegistryCommandTail(result: CommandResult): Record<string, unknown> {
return {
command: result.command.length > 7 ? [...result.command.slice(0, 7), "<readonly-script>"] : result.command,
exitCode: result.exitCode,
@@ -1114,6 +1123,10 @@ function commandTail(result: CommandResult): Record<string, unknown> {
};
}
function commandTail(result: CommandResult): Record<string, unknown> {
return artifactRegistryCommandTail(result);
}
function artifactConsumerSpec(serviceId: string, environment: ArtifactDeployEnvironment | null): ArtifactConsumerSpec | null {
const key = environment === null || environment === "prod" ? serviceId : `${environment}:${serviceId}`;
const explicit = artifactConsumerSpecs[key];
@@ -1329,12 +1342,53 @@ function runReadonlyStatus(options: ArtifactRegistryOptions, healthMode: boolean
image: options.image,
paths: bundle.paths,
},
command: commandTail(result),
command: artifactRegistryCommandTail(result),
};
}
return statusFromValues(options, parseKeyValueOutput(result.stdout), result, healthMode);
}
export function buildArtifactRegistryReadonlyProbe(action: "status" | "health", options: ArtifactRegistryOptions): ArtifactRegistryReadonlyProbe {
const bundle = renderBundle(options);
const healthMode = action === "health";
return {
action,
providerId: options.providerId,
script: statusScript(options, bundle),
timeoutMs: options.timeoutMs,
healthMode,
options,
};
}
export function artifactRegistryReadonlyResultFromCommand(
probe: ArtifactRegistryReadonlyProbe,
command: CommandResult,
): Record<string, unknown> {
if (command.exitCode !== 0 || command.timedOut) {
const bundle = renderBundle(probe.options);
return {
ok: false,
readonly: true,
installed: false,
healthy: false,
decision: "retryable-transient",
retryable: true,
healthyScopes: [],
failedScopes: ["provider-ssh-command"],
runtimeApiHealthy: false,
checks: {},
expected: {
endpoint: `http://${probe.options.host}:${probe.options.port}`,
image: probe.options.image,
paths: bundle.paths,
},
command: artifactRegistryCommandTail(command),
};
}
return statusFromValues(probe.options, parseKeyValueOutput(command.stdout), command, probe.healthMode);
}
function remoteWriteFileCommand(item: RenderedFile): string {
const encoded = Buffer.from(item.content, "utf8").toString("base64");
const rootOwned = item.path.startsWith("/etc/");
@@ -2540,7 +2594,7 @@ export async function runArtifactRegistryCommand(args: string[]): Promise<unknow
throw new Error("artifact-registry usage: plan|render|status|health|install|deploy-backend-core|deploy-service");
}
const typedAction = action as ArtifactRegistryAction;
const options = parseOptions(args.slice(1));
const options = parseArtifactRegistryOptions(args.slice(1));
if (action === "plan") return plan(options);
if (action === "render") return { ok: true, providerId: options.providerId, render: renderBundle(options) };
if (action === "status") return runReadonlyStatus(options, false);