diff --git a/packages/wrangler/src/pages/constants.ts b/packages/wrangler/src/pages/constants.ts index 550650aa9687..3e6c586bbe25 100644 --- a/packages/wrangler/src/pages/constants.ts +++ b/packages/wrangler/src/pages/constants.ts @@ -8,6 +8,7 @@ export const MAX_BUCKET_FILE_COUNT = 5000; export const BULK_UPLOAD_CONCURRENCY = 3; export const MAX_UPLOAD_ATTEMPTS = 5; export const MAX_DEPLOYMENT_ATTEMPTS = 3; +export const MAX_DEPLOYMENT_STATUS_ATTEMPTS = 5; export const MAX_CHECK_MISSING_ATTEMPTS = 5; export const SECONDS_TO_WAIT_FOR_PROXY = 5; export const isInPagesCI = !!process.env.CF_PAGES; diff --git a/packages/wrangler/src/pages/deploy.tsx b/packages/wrangler/src/pages/deploy.tsx index 06344835ef62..b07de5976fc4 100644 --- a/packages/wrangler/src/pages/deploy.tsx +++ b/packages/wrangler/src/pages/deploy.tsx @@ -11,7 +11,10 @@ import { FatalError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; import { requireAuth } from "../user"; -import { PAGES_CONFIG_CACHE_FILENAME } from "./constants"; +import { + MAX_DEPLOYMENT_STATUS_ATTEMPTS, + PAGES_CONFIG_CACHE_FILENAME, +} from "./constants"; import { EXIT_CODE_INVALID_PAGES_CONFIG } from "./errors"; import { listProjects } from "./projects"; import { promptSelectProject } from "./prompt-select-project"; @@ -20,8 +23,13 @@ import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; -import type { Deployment, DeploymentStage, PagesConfigCache } from "./types"; -import type { Project, UnifiedDeploymentLogMessages } from "@cloudflare/types"; +import type { PagesConfigCache } from "./types"; +import type { + Deployment, + DeploymentStage, + Project, + UnifiedDeploymentLogMessages, +} from "@cloudflare/types"; type PagesDeployArgs = StrictYargsOptionsToInterface; @@ -338,30 +346,45 @@ export const Handler = async (args: PagesDeployArgs) => { }); let latestDeploymentStage: DeploymentStage | undefined; + let attempts = 0; - // can this ever become an infinite loop? while ( + attempts < MAX_DEPLOYMENT_STATUS_ATTEMPTS && latestDeploymentStage?.name !== "deploy" && latestDeploymentStage?.status !== "success" && latestDeploymentStage?.status !== "failure" ) { try { + /* + * Exponential backoff + * On every retry, exponentially increase the wait time: 1 second, then + * 2s, then 4s, then 8s, etc. + */ + await new Promise((resolvePromise) => + setTimeout(resolvePromise, Math.pow(2, attempts++) * 1000) + ); + + logger.debug( + `attempt #${attempts}: Attempting to fetch status for deployment with id "${deploymentResponse.id}" ...` + ); + const deployment = await fetchResult( `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentResponse.id}` ); latestDeploymentStage = deployment.latest_stage; } catch (err) { + // don't retry if API call retruned an error logger.debug( `Attempt to get deployment status for deployment with id "${deploymentResponse.id}" failed: ${err}` ); } } - if (latestDeploymentStage.status === "success") { + if (latestDeploymentStage?.status === "success") { logger.log( `✨ Deployment complete! Take a peek over at ${deploymentResponse.url}` ); - } else { + } else if (latestDeploymentStage?.status === "failure") { // get persistent logs so we can show users the failure message const logs = await fetchResult( `/accounts/${accountId}/pages/projects/${projectName}/deployments/${deploymentResponse.id}/history/logs?size=10000000` @@ -371,6 +394,12 @@ export const Handler = async (args: PagesDeployArgs) => { logger.error(failureMessage.replace("Error:", "").trim()); logger.log("❌ Deployment failed!"); + } else { + logger.log( + `✨ Deployment complete! However, we couldn't ascertain the final status of your deployment.\n\n` + + `⚡️ Visit your deployment at ${deploymentResponse.url}\n` + + `⚡️ Check the deployment logs on the Cloudflare dashboard: https://dash.cloudflare.com/${accountId}/pages/view/${projectName}/${deploymentResponse.id}` + ); } await metrics.sendMetricsEvent("create pages deployment"); diff --git a/packages/wrangler/src/pages/deployment-tails.ts b/packages/wrangler/src/pages/deployment-tails.ts index 5cde6317dc49..71240a8370f9 100644 --- a/packages/wrangler/src/pages/deployment-tails.ts +++ b/packages/wrangler/src/pages/deployment-tails.ts @@ -1,4 +1,5 @@ import { setTimeout } from "node:timers/promises"; +import { Deployment } from "@cloudflare/types"; import onExit from "signal-exit"; import { printWranglerBanner } from ".."; import { fetchResult } from "../cfetch"; @@ -22,7 +23,7 @@ import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; -import type { Deployment, PagesConfigCache } from "./types"; +import type { PagesConfigCache } from "./types"; const statusChoices = ["ok", "error", "canceled"] as const; type StatusChoice = typeof statusChoices[number]; diff --git a/packages/wrangler/src/pages/deployments.tsx b/packages/wrangler/src/pages/deployments.tsx index 3878aa502a71..d45e55fd3993 100644 --- a/packages/wrangler/src/pages/deployments.tsx +++ b/packages/wrangler/src/pages/deployments.tsx @@ -1,3 +1,4 @@ +import { Deployment } from "@cloudflare/types"; import Table from "ink-table"; import React from "react"; import { format as timeagoFormat } from "timeago.js"; @@ -14,7 +15,7 @@ import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; -import type { Deployment, PagesConfigCache } from "./types"; +import type { PagesConfigCache } from "./types"; type ListArgs = StrictYargsOptionsToInterface; @@ -54,7 +55,10 @@ export async function ListHandler({ projectName }: ListArgs) { const getStatus = (deployment: Deployment) => { // Return a pretty time since timestamp if successful otherwise the status - if (deployment.latest_stage.status === `success`) { + if ( + deployment.latest_stage.status === `success` && + deployment.latest_stage.ended_on + ) { return timeagoFormat(deployment.latest_stage.ended_on); } return titleCase(deployment.latest_stage.status); diff --git a/packages/wrangler/src/pages/types.ts b/packages/wrangler/src/pages/types.ts index 4c733d72da84..bda0d21c3a7f 100644 --- a/packages/wrangler/src/pages/types.ts +++ b/packages/wrangler/src/pages/types.ts @@ -25,21 +25,6 @@ export type Project = { }; }; -export type Deployment = { - id: string; - created_on: string; - environment: string; - deployment_trigger: { - metadata: { - commit_hash: string; - branch: string; - }; - }; - url: string; - latest_stage: DeploymentStage; - project_name: string; -}; - export type UploadPayloadFile = { key: string; value: string;