From ca2cf3f754d68032d5879aa84f1d9331eddf0022 Mon Sep 17 00:00:00 2001 From: Daniel Rivas <1887507+danielrs@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:49:36 -0500 Subject: [PATCH] feat: wrangler deploy prompts warning with deployment --- .../wrangler/src/__tests__/deploy.test.ts | 27 ++++++++ .../__tests__/deprecated-usage-model.test.ts | 11 +++- .../helpers/msw/handlers/versions.ts | 61 +++++++++++++++++++ packages/wrangler/src/deploy/deploy.ts | 14 ++++- packages/wrangler/src/versions/deploy.ts | 28 +++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index c57fb1291ae7..0ea332d34825 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -43,6 +43,10 @@ import { mswSuccessOauthHandlers, mswSuccessUserHandlers, } from "./helpers/msw"; +import { + mswListNewDeploymentsLatestFiftyFifty, + mswListNewDeploymentsLatestFull, +} from "./helpers/msw/handlers/versions"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import { writeWorkerSource } from "./helpers/write-worker-source"; @@ -78,6 +82,7 @@ describe("deploy", () => { setIsTTY(true); mockLastDeploymentRequest(); mockDeploymentsListRequest(); + msw.use(...mswListNewDeploymentsLatestFull); logger.loggerLevel = "log"; }); @@ -423,6 +428,28 @@ describe("deploy", () => { `); }); + it("should warn user when worker has deployment with multiple versions", async () => { + msw.use(...mswListNewDeploymentsLatestFiftyFifty); + writeWranglerToml(); + writeWorkerSource(); + mockSubDomainRequest(); + mockUploadWorkerRequest(); + mockConfirm({ + text: "Are you sure you want to continue?", + result: false, + }); + + await runWrangler("deploy ./index"); + + expect(std.warn).toMatchInlineSnapshot(` + "▲ [WARNING] Your latest deployment has multiple versions. + + \\"wrangler deploy\\" will upload a new version and deploy it globally immediately. + + " + `); + }); + it("should warn user when additional properties are passed to a services config", async () => { writeWranglerToml({ d1_databases: [ diff --git a/packages/wrangler/src/__tests__/deprecated-usage-model.test.ts b/packages/wrangler/src/__tests__/deprecated-usage-model.test.ts index aa25d0273057..3e5ef2472277 100644 --- a/packages/wrangler/src/__tests__/deprecated-usage-model.test.ts +++ b/packages/wrangler/src/__tests__/deprecated-usage-model.test.ts @@ -4,6 +4,7 @@ import { mockConsoleMethods } from "./helpers/mock-console"; import { mockUploadWorkerRequest } from "./helpers/mock-upload-worker"; import { mockSubDomainRequest } from "./helpers/mock-workers-subdomain"; import { msw, mswSuccessDeploymentScriptMetadata } from "./helpers/msw"; +import { mswListNewDeploymentsLatestFull } from "./helpers/msw/handlers/versions"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import { writeWorkerSource } from "./helpers/write-worker-source"; @@ -26,7 +27,10 @@ describe("deprecated-usage-model", () => { }); it("should warn user about ignored usage model if usage_model specified", async () => { - msw.use(...mswSuccessDeploymentScriptMetadata); + msw.use( + ...mswSuccessDeploymentScriptMetadata, + ...mswListNewDeploymentsLatestFull + ); writeWranglerToml({ usage_model: "bundled" }); writeWorkerSource(); mockSubDomainRequest(); @@ -41,7 +45,10 @@ describe("deprecated-usage-model", () => { `); }); it("should not warn user about ignored usage model if usage_model not specified", async () => { - msw.use(...mswSuccessDeploymentScriptMetadata); + msw.use( + ...mswSuccessDeploymentScriptMetadata, + ...mswListNewDeploymentsLatestFull + ); writeWranglerToml(); writeWorkerSource(); mockSubDomainRequest(); diff --git a/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts b/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts index ecd7bc3c1312..173d851009fe 100644 --- a/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts +++ b/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts @@ -1,5 +1,66 @@ import { http, HttpResponse } from "msw"; import { createFetchResult } from "../index"; +import type { ApiDeployment } from "../../../../versions/types"; + +export const mswListNewDeploymentsLatestFull = [ + http.get( + "*/accounts/:accountId/workers/scripts/:scriptName/deployments", + ({ params }) => { + return HttpResponse.json( + createFetchResult({ + deployments: [ + { + id: `deployment:${params["scriptName"]}`, + source: "api", + strategy: "percentage", + author_email: "author@example.com", + created_on: "2021-01-01T00:00:00.000000Z", + versions: [ + { + version_id: `version:${params["scriptName"]}:0`, + percentage: 100.0, + }, + ], + }, + ] as Array, + }) + ); + }, + { once: true } + ), +]; + +export const mswListNewDeploymentsLatestFiftyFifty = [ + http.get( + "*/accounts/:accountId/workers/scripts/:scriptName/deployments", + ({ params }) => { + return HttpResponse.json( + createFetchResult({ + deployments: [ + { + id: `deployment:${params["scriptName"]}`, + source: "api", + strategy: "percentage", + author_email: "author@example.com", + created_on: "2021-01-01T00:00:00.000000Z", + versions: [ + { + version_id: `version:${params["scriptName"]}:0`, + percentage: 50.0, + }, + { + version_id: `version:${params["scriptName"]}:1`, + percentage: 50.0, + }, + ], + }, + ] as Array, + }) + ); + }, + { once: true } + ), +]; export const mswListNewDeployments = http.get( "*/accounts/:accountId/workers/scripts/:workerName/deployments", diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index aec7003c676e..f50c149e9922 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -2,6 +2,7 @@ import assert from "node:assert"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { URLSearchParams } from "node:url"; +import { cancel } from "@cloudflare/cli"; import { fetchListResult, fetchResult } from "../cfetch"; import { printBindings } from "../config"; import { bundleWorker } from "../deployment-bundle/bundle"; @@ -45,6 +46,7 @@ import { } from "../sourcemap"; import triggersDeploy from "../triggers/deploy"; import { logVersionIdChange } from "../utils/deployment-id-version-id-change"; +import { confirmLatestDeploymentOverwrite } from "../versions/deploy"; import { getZoneForRoute } from "../zones"; import type { Config } from "../config"; import type { @@ -421,7 +423,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m const envName = props.env ?? "production"; const start = Date.now(); - const notProd = Boolean(!props.legacyEnv && props.env); + const prod = Boolean(props.legacyEnv || !props.env); + const notProd = !prod; const workerName = notProd ? `${scriptName} (${envName})` : scriptName; const workerUrl = props.dispatchNamespace ? `/accounts/${accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${scriptName}` @@ -433,6 +436,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m const { format } = props.entry; + if (!props.dispatchNamespace && prod && accountId && scriptName) { + const yes = await confirmLatestDeploymentOverwrite(accountId, scriptName); + if (!yes) { + cancel("Aborting deploy..."); + return; + } + } + if ( !props.isWorkersSite && Boolean(props.assetPaths) && @@ -460,6 +471,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml" ); } + try { if (props.noBundle) { // if we're not building, let's just copy the entry to the destination directory diff --git a/packages/wrangler/src/versions/deploy.ts b/packages/wrangler/src/versions/deploy.ts index 372c4a402c66..3593e13ee04d 100644 --- a/packages/wrangler/src/versions/deploy.ts +++ b/packages/wrangler/src/versions/deploy.ts @@ -9,14 +9,18 @@ import { spinnerWhile, } from "@cloudflare/cli/interactive"; import { findWranglerToml, readConfig } from "../config"; +import { confirm } from "../dialogs"; import { UserError } from "../errors"; +import { logger } from "../logger"; import * as metrics from "../metrics"; +import { APIError } from "../parse"; import { printWranglerBanner } from "../update-check"; import { requireAuth } from "../user"; import formatLabelledValues from "../utils/render-labelled-values"; import { createDeployment, fetchDeployableVersions, + fetchLatestDeployment, fetchLatestDeploymentVersions, fetchVersions, patchNonVersionedScriptSettings, @@ -205,6 +209,30 @@ function getConfig( return config; } +/** + * Prompts the user for confirmation when overwriting the latest deployment, given that it's split. + */ +export async function confirmLatestDeploymentOverwrite( + accountId: string, + scriptName: string +) { + try { + const latest = await fetchLatestDeployment(accountId, scriptName); + if (latest && latest.versions.length >= 2) { + logger.warn( + `Your latest deployment has multiple versions.\n"wrangler deploy" will upload a new version and deploy it globally immediately.` + ); + return await confirm("Are you sure you want to continue?"); + } + } catch (e) { + const isNotFound = e instanceof APIError && e.code == 10007; + if (!isNotFound) { + throw e; + } + } + return true; +} + export async function printLatestDeployment( accountId: string, workerName: string,