import { log } from "@clack/prompts" import { defineCommand } from "../lib/command" import { globalOptions, globalOptionsSchema } from "../lib/global-options" import { readFile } from "node:fs/promises" import { z } from "zod" import { apiClient } from "../lib/api-client" import { deploymentSessionIntro } from "../lib/deployment-lifecycle" import { accent, dimmed } from "../lib/io" import { clearSpinner, startSpinner } from "../lib/spinner" import { output, PROJECT_OPT, projectOptsSchema, useProject, PROJECT_OPTION_DESCRIPTIONS, } from "../utils" const DEPLOYMENT_BINDINGS_SCHEMA = z .object({ accountId: z.string().min(1), binding: z.string().min(1), serviceId: z.string().min(1), }) .array() .superRefine((bindings, context) => { const keys = new Set() for (const [index, binding] of bindings.entries()) { const key = JSON.stringify([binding.serviceId, binding.binding]) if (keys.has(key)) { context.addIssue({ code: "custom", message: "Deployment bindings must be unique.", path: [index], }) } keys.add(key) } }) const REDEPLOYMENT_OPTS_SCHEMA = projectOptsSchema.extend({ bindings: z.string().min(1), }) export const deploymentRedeployCommand = defineCommand({ name: "redeploy", description: "Redeploy the active project version", options: { ...globalOptions, ...PROJECT_OPT, bindings: { type: "string", }, }, optionDescriptions: { ...PROJECT_OPTION_DESCRIPTIONS, bindings: "Path to a JSON array of integration account bindings", }, requiredOptions: ["bindings"], schema: globalOptionsSchema.extend(REDEPLOYMENT_OPTS_SCHEMA.shape), run: redeployProject, }) /** * Redeploys a project's active artifact with explicit account bindings. * * @param opts - Project selector and binding-file path. */ export async function redeployProject( opts: z.infer, ) { await deploymentSessionIntro() const project = await useProject(opts, { mode: "select" }) // Validate the local file before presenting the remote-operation spinner. const bindings = await readDeploymentBindings(opts.bindings) startSpinner("Starting redeployment") const deployment = await apiClient.deployment.get({ deploymentId: ( await apiClient.deployment.redeploy({ bindings, projectId: project.id, }) ).id, }) clearSpinner() output .normal(() => { log.success( `Started deployment ${accent(deployment.id)} ${dimmed(project.name)}`, ) if (deployment.authorization) { log.info( `Authorization required: ${deployment.authorization.url}\nResume with ${accent(`automate deployment authorize ${deployment.id}`)}`, ) } }) .json(deployment) } /** * Reads non-secret account binding identifiers from a JSON file. * * @param filePath - Binding file path, relative to the current directory. */ export async function readDeploymentBindings(filePath: string) { return DEPLOYMENT_BINDINGS_SCHEMA.parse( JSON.parse(await readFile(filePath, "utf8")), ) }