import { log } from "@clack/prompts" import { defineCommand } from "../lib/command" import { globalOptions, globalOptionsSchema } from "../lib/global-options" import { z } from "zod" import { apiClient } from "../lib/api-client" import { deploymentSessionIntro } from "../lib/deployment-lifecycle" import { renderDetailBlock } from "../lib/detail-block" import { accent, dimmed } from "../lib/io" import { clearSpinner, startSpinner } from "../lib/spinner" import { ORG_OPT, output, PAGINATION_OPTS, paginationOptsSchema, pluralize, PROJECT_OPT, useProjectScope, ORG_OPTION_DESCRIPTIONS, PROJECT_OPTION_DESCRIPTIONS, PAGINATION_OPTION_DESCRIPTIONS, } from "../utils" const DEPLOYMENT_LIST_OPTS_SCHEMA = globalOptionsSchema .extend({ ...paginationOptsSchema.shape, org: z.string().optional(), project: z.string().optional(), since: z.coerce.date().optional(), status: z .enum([ "planning", "awaiting_authorization", "configuring", "publishing", "succeeded", "failed", "cancelled", ]) .optional(), until: z.coerce.date().optional(), }) .refine( ({ since, until }) => since === undefined || until === undefined || since <= until, { message: "--since must be before or equal to --until", path: ["since"] }, ) export const deploymentListCommand = defineCommand({ name: "list", description: "List deployments", options: { ...globalOptions, ...ORG_OPT, ...PAGINATION_OPTS, ...PROJECT_OPT, since: { type: "string", }, status: { type: "string", }, until: { type: "string", }, }, optionDescriptions: { ...ORG_OPTION_DESCRIPTIONS, ...PAGINATION_OPTION_DESCRIPTIONS, ...PROJECT_OPTION_DESCRIPTIONS, org: "Limit project selection to an organization name or ID", since: "Include deployments created at or after this timestamp", status: "Filter by deployment status", until: "Include deployments created at or before this timestamp", }, schema: DEPLOYMENT_LIST_OPTS_SCHEMA, run: listDeployments, }) /** * Lists deployments through the public management contract. * * @param opts - Resource, status, time, and page filters. */ export async function listDeployments( opts: z.infer, ) { await deploymentSessionIntro() const { organization, project } = await useProjectScope(opts) startSpinner("Fetching deployments") const page = await apiClient.deployment.list({ cursor: opts.cursor, limit: opts.limit, organizationId: organization.id, projectId: project.id, since: opts.since, status: opts.status, until: opts.until, }) clearSpinner() output .normal(() => { if (page.items.length === 0) { log.warn("No deployments found.") return } log.message( `${accent(String(page.items.length))} ${pluralize(page.items.length, "deployment")}`, ) for (const deployment of page.items) { log.info( renderDetailBlock( { [dimmed`Deployment`]: dimmed(deployment.id), [dimmed`Project`]: `${deployment.project.organization.name} › ${deployment.project.name}`, [dimmed`Status`]: deployment.status, [dimmed`Created`]: new Date( deployment.createdAt, ).toLocaleString(), [dimmed`Automations`]: deployment.automations.length, ...(deployment.authorization ? { [dimmed`Authorization`]: `${deployment.authorization.requirements.filter(({ satisfied }) => !satisfied).length} required`, } : {}), ...(deployment.failure ? { [dimmed`Failure`]: deployment.failure.message } : {}), }, { keyAlignment: "right" }, ), ) } if (page.pageInfo.nextCursor) { log.info(`More results: --cursor ${page.pageInfo.nextCursor}`) } log.message() }) .json(page) }