diff --git a/.changeset/dry-spies-lay.md b/.changeset/dry-spies-lay.md new file mode 100644 index 000000000000..61db473269d5 --- /dev/null +++ b/.changeset/dry-spies-lay.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Added `pages deployment delete ` command diff --git a/.gitignore b/.gitignore index d9ee7c5c8d9c..d33c63904968 100644 --- a/.gitignore +++ b/.gitignore @@ -208,6 +208,7 @@ tools/deployment-status.json # IntelliJ .idea/ +*.iml # VSCode Theme *.vsix diff --git a/packages/wrangler/src/__tests__/pages/pages-deployement-delete.test.ts b/packages/wrangler/src/__tests__/pages/pages-deployement-delete.test.ts new file mode 100644 index 000000000000..51451c70fe1a --- /dev/null +++ b/packages/wrangler/src/__tests__/pages/pages-deployement-delete.test.ts @@ -0,0 +1,80 @@ +import { http, HttpResponse } from "msw"; +import { endEventLoop } from "../helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "../helpers/mock-account-id"; +import { mockConsoleMethods } from "../helpers/mock-console"; +import { msw } from "../helpers/msw"; +import { runInTempDir } from "../helpers/run-in-tmp"; +import { runWrangler } from "../helpers/run-wrangler"; + +const PROJECT_NAME = "images"; +const DEPLOYMENT = "deployment"; + +describe("pages deployment delete", () => { + runInTempDir(); + mockAccountId(); + mockApiToken(); + mockConsoleMethods(); + + afterEach(async () => { + await endEventLoop(); + msw.resetHandlers(); + msw.restoreHandlers(); + }); + + it("should make request to delete deployment", async () => { + const requests = mockDeploymentDeleteRequest(DEPLOYMENT); + await runWrangler( + `pages deployment delete ${DEPLOYMENT} --project-name=${PROJECT_NAME}` + ); + expect(requests.count).toBe(1); + }); + + it("should throw an error if deployment ID is missing", async () => { + await expect( + runWrangler(`pages deployment delete --project-name=${PROJECT_NAME}`) + ).rejects.toThrow("Must specify a project name and deployment."); + }); + + it("should throw an error if project name is missing in non-interactive mode", async () => { + await expect( + runWrangler(`pages deployment delete ${DEPLOYMENT}`) + ).rejects.toThrow("Must specify a project name in non-interactive mode."); + }); +}); + +/* -------------------------------------------------- */ +/* Helper Functions */ +/* -------------------------------------------------- */ + +type RequestLogger = { + count: number; + queryParams: [string, string][][]; +}; + +function mockDeploymentDeleteRequest(deployment: string): RequestLogger { + const requests: RequestLogger = { count: 0, queryParams: [] }; + msw.use( + http.delete( + "*/accounts/:accountId/pages/projects/:project/deployments/:deployment", + ({ request, params }) => { + requests.count++; + const url = new URL(request.url); + requests.queryParams.push(Array.from(url.searchParams.entries())); + expect(params.project).toEqual(PROJECT_NAME); + expect(params.accountId).toEqual("some-account-id"); + + return HttpResponse.json( + { + success: true, + errors: [], + messages: [], + result: deployment, + }, + { status: 200 } + ); + }, + { once: true } + ) + ); + return requests; +} diff --git a/packages/wrangler/src/__tests__/pages/pages.test.ts b/packages/wrangler/src/__tests__/pages/pages.test.ts index 9737c46e59c1..1615f16cba14 100644 --- a/packages/wrangler/src/__tests__/pages/pages.test.ts +++ b/packages/wrangler/src/__tests__/pages/pages.test.ts @@ -119,11 +119,12 @@ describe("pages", () => { Interact with the deployments of a project COMMANDS - wrangler pages deployment list List deployments in your Cloudflare Pages project - wrangler pages deployment create [directory] Deploy a directory of static assets as a Pages deployment + wrangler pages deployment list List deployments in your Cloudflare Pages project + wrangler pages deployment create [directory] Deploy a directory of static assets as a Pages deployment - Alias for \\"wrangler pages deploy\\". - wrangler pages deployment tail [deployment] Start a tailing session for a project's deployment and livestream logs from your Functions + Alias for \\"wrangler pages deploy\\". + wrangler pages deployment delete [deployment] Delete a Pages deployment + wrangler pages deployment tail [deployment] Start a tailing session for a project's deployment and livestream logs from your Functions GLOBAL FLAGS --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 32fa0f5f1f9a..7ce3efb15b66 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -130,6 +130,7 @@ import { } from "./pages"; import { pagesFunctionsBuildCommand } from "./pages/build"; import { pagesFunctionsBuildEnvCommand } from "./pages/build-env"; +import { pagesDeploymentDeleteCommand } from "./pages/delete-deployment"; import { pagesDeployCommand, pagesDeploymentCreateCommand, @@ -1153,6 +1154,10 @@ export function createCLIParser(argv: string[]) { command: "wrangler pages deployment create", definition: pagesDeploymentCreateCommand, }, + { + command: "wrangler pages deployment delete", + definition: pagesDeploymentDeleteCommand, + }, { command: "wrangler pages deployment tail", definition: pagesDeploymentTailCommand, diff --git a/packages/wrangler/src/pages/delete-deployment.ts b/packages/wrangler/src/pages/delete-deployment.ts new file mode 100644 index 000000000000..2f53cf7c3c02 --- /dev/null +++ b/packages/wrangler/src/pages/delete-deployment.ts @@ -0,0 +1,81 @@ +import { fetchResult } from "../cfetch"; +import { getConfigCache } from "../config-cache"; +import { createCommand } from "../core/create-command"; +import { COMPLIANCE_REGION_CONFIG_PUBLIC } from "../environment-variables/misc-variables"; +import { FatalError } from "../errors"; +import isInteractive from "../is-interactive"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { PAGES_CONFIG_CACHE_FILENAME } from "./constants"; +import { promptSelectProject } from "./prompt-select-project"; +import type { PagesConfigCache } from "./types"; + +export const pagesDeploymentDeleteCommand = createCommand({ + metadata: { + description: "Delete a Pages deployment", + status: "stable", + owner: "Workers: Authoring and Testing", + hideGlobalFlags: ["config", "env"], + }, + behaviour: { + provideConfig: false, + }, + args: { + deployment: { + type: "string", + description: "The ID of the deployment you wish to delete", + }, + "project-name": { + type: "string", + description: + "The name of the project you would like to delete the deployment from", + }, + }, + positionalArgs: ["deployment"], + async handler(args) { + if (args.config) { + throw new FatalError( + "Pages does not support custom paths for the Wrangler configuration file", + 1 + ); + } + + if (args.env) { + throw new FatalError( + "Pages does not support targeting an environment with the --env flag. Use the --branch flag to target your production or preview branch", + 1 + ); + } + + const configCache = getConfigCache( + PAGES_CONFIG_CACHE_FILENAME + ); + + const accountId = await requireAuth(configCache); + + let projectName = args.projectName; + + if (!projectName) { + if (isInteractive()) { + projectName = await promptSelectProject({ accountId }); + } else { + throw new FatalError( + "Must specify a project name in non-interactive mode.", + 1 + ); + } + } + + if (!args.deployment || !projectName) { + throw new FatalError("Must specify a project name and deployment.", 1); + } + + await fetchResult( + COMPLIANCE_REGION_CONFIG_PUBLIC, + `/accounts/${accountId}/pages/projects/${projectName}/deployments/${args.deployment}`, + { method: "DELETE" } + ); + + logger.log(`Deployment ${args.deployment} was successfully deleted.`); + }, +});