diff --git a/.changeset/restrict-name-ownership-guard-to-pages-delegation.md b/.changeset/restrict-name-ownership-guard-to-pages-delegation.md new file mode 100644 index 0000000000..0a7ac47be2 --- /dev/null +++ b/.changeset/restrict-name-ownership-guard-to-pages-delegation.md @@ -0,0 +1,9 @@ +--- +"wrangler": patch +--- + +Fix `wrangler deploy` aborting in CI for autoconfigured projects + +A recent change guarded non-interactive deploys against overwriting a same-named Worker whenever there was no config file naming it. This was too broad: a plain `wrangler deploy` run in CI without a config file (for example an autoconfigured project whose generated config PR has not been merged) would fail with "A Worker named ... already exists in your account", even though re-deploying to that Worker is the intended behaviour. + +The guard is now limited to the Pages-to-Workers delegation, where the target name is a Pages project name that must not clobber an unrelated Worker. Plain deploys once again deploy normally. diff --git a/packages/wrangler/src/__tests__/deploy/name-collision-guard.test.ts b/packages/wrangler/src/__tests__/deploy/name-collision-guard.test.ts index a08a9a452c..6fae7e67a4 100644 --- a/packages/wrangler/src/__tests__/deploy/name-collision-guard.test.ts +++ b/packages/wrangler/src/__tests__/deploy/name-collision-guard.test.ts @@ -7,6 +7,8 @@ import { run } from "../../experimental-flags"; import { mockAccountId, mockApiToken } from "../helpers/mock-account-id"; import { mockConsoleMethods } from "../helpers/mock-console"; import { useMockIsTTY } from "../helpers/mock-istty"; +import { mockUploadWorkerRequest } from "../helpers/mock-upload-worker"; +import { mockSubDomainRequest } from "../helpers/mock-workers-subdomain"; import { createFetchResult, msw } from "../helpers/msw"; import { writeWorkerSource } from "../helpers/write-worker-source"; @@ -122,4 +124,39 @@ describe("deploy name-collision guard (non-interactive)", () => { 'A Worker named "existing-worker" already exists in your account.' ); }); + + it("does not guard a plain non-interactive deploy (no delegation)", async ({ + expect, + }) => { + // Regression test for #14312: a plain `wrangler deploy` in CI with no + // config file and no --name (e.g. an autoconfigured project whose + // generated config PR has not been merged) is routinely re-run against + // the same, already-deployed Worker. Only the Pages-to-Workers delegation + // guards name ownership, so this must deploy normally instead of aborting. + const args = delegatedDeployArgs({}); + const config = readConfig(args, { useRedirectIfAvailable: true }); + config.name = "existing-worker"; + config.main = "index.js"; + config.compatibility_date = "2024-01-01"; + mockExistingWorker(); + mockSubDomainRequest(); + mockUploadWorkerRequest({ expectedScriptName: "existing-worker" }); + // The existing Worker means deploy checks its latest deployment before + // overwriting; an empty list means there is nothing to confirm. + msw.use( + http.get( + "*/accounts/:accountId/workers/scripts/:scriptName/deployments", + () => HttpResponse.json(createFetchResult({ deployments: [] })) + ) + ); + + await expect( + run(experimentalFlags, () => + runDeployCommandHandler(args, { + config, + // pagesToWorkersDelegation defaults to false + }) + ) + ).resolves.toBeUndefined(); + }); }); diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 0e1dcb4ee8..92d56857b4 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -125,23 +125,21 @@ export async function runDeployCommandHandler( // Capture whether this project can prove it owns the target Worker name, // BEFORE autoconfig generates or rewrites the config. Ownership is proven by // a config file that names the Worker; without one a same-named remote Worker - // is a probable collision rather than a redeploy. We only guard - // non-interactive deploys (agents, CI, the Pages-to-Workers delegation): - // there we cannot prompt to resolve the collision, and silently overwriting - // an unrelated Worker is the worst outcome. Interactive users pick the name - // at a prompt and keep the existing confirmation flow. + // could be a collision rather than a redeploy. // - // For the Pages-to-Workers delegation we guard even when a name was passed: - // the name is a Pages project name carried across, and an existing Worker of - // the same name is a different resource we must not clobber. Repeat - // delegations are unaffected because the first one writes a config file - // (so `configPath` is then set). Outside the delegation, an explicit `--name` - // is treated as deliberate ownership so plain `wrangler deploy --name foo` - // keeps working in CI. See `failIfWorkerNameTaken` in preUploadApiChecks. + // We only guard the Pages-to-Workers delegation: there the name is a Pages + // project name carried across (or auto-generated), so an existing Worker of + // the same name is a different resource we must not clobber, and being + // non-interactive there is no prompt to resolve it. Repeat delegations are + // unaffected because the first one writes a config file (so `configPath` is + // then set). + // + // Plain `wrangler deploy` is NOT guarded, even in CI with an autoconfigured + // name: autoconfigured projects are routinely redeployed in CI (e.g. when the + // auto-generated config PR has not been merged), and blocking that regressed + // those workflows. See `failIfWorkerNameTaken` in preUploadApiChecks. const nameOwnershipUnverified = - !config.configPath && - isNonInteractiveOrCI() && - (pagesToWorkersDelegation || !args.name); + !config.configPath && isNonInteractiveOrCI() && pagesToWorkersDelegation; // --- Step 0. Auto-config --- // const autoConfigResult = await maybeRunAutoConfig(args, config, {