From c7f34c7e964354895d69940f67077fd92175874f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:20:06 +0000 Subject: [PATCH 1/4] feat: add --domain flag to deploy command for custom domains - Add --domain flag (alias: --domains) to wrangler deploy command - Convert domain strings to custom domain route format automatically - Add comprehensive tests for domain flag functionality - Validate domains using existing custom domain validation rules Fixes #10215 Co-Authored-By: pbacondarwin@cloudflare.com --- .changeset/nine-onions-wonder.md | 5 + .../wrangler/src/__tests__/deploy.test.ts | 111 ++++++++++++++++++ packages/wrangler/src/deploy/deploy.ts | 13 +- packages/wrangler/src/deploy/index.ts | 8 ++ packages/wrangler/src/triggers/deploy.ts | 2 +- 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 .changeset/nine-onions-wonder.md diff --git a/.changeset/nine-onions-wonder.md b/.changeset/nine-onions-wonder.md new file mode 100644 index 000000000000..84c0608ea99c --- /dev/null +++ b/.changeset/nine-onions-wonder.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Added `--domain` flag to `wrangler deploy` command for deploying to custom domains. Use `--domain example.com` to deploy directly to a custom domain without manually configuring routes. diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index a12c7e1fb408..c4b04a9476c3 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -1696,6 +1696,117 @@ Update them to point to this script instead?`, 'Publishing to Custom Domain "api.example.com" was skipped, fix conflict and try again' ); }); + it("should deploy domains passed via --domain flag as custom domains", async () => { + writeWranglerConfig({}); + writeWorkerSource(); + mockSubDomainRequest(); + mockUpdateWorkerSubdomain({ enabled: false }); + mockUploadWorkerRequest({ expectedType: "esm" }); + mockCustomDomainsChangesetRequest({}); + mockPublishCustomDomainsRequest({ + publishFlags: { + override_scope: true, + override_existing_origin: false, + override_existing_dns_record: false, + }, + domains: [{ hostname: "api.example.com" }], + }); + + await runWrangler("deploy ./index --domain api.example.com"); + expect(std.out).toContain("api.example.com (custom domain)"); + }); + + it("should deploy multiple domains passed via --domain flags", async () => { + writeWranglerConfig({}); + writeWorkerSource(); + mockSubDomainRequest(); + mockUpdateWorkerSubdomain({ enabled: false }); + mockUploadWorkerRequest({ expectedType: "esm" }); + mockCustomDomainsChangesetRequest({}); + mockPublishCustomDomainsRequest({ + publishFlags: { + override_scope: true, + override_existing_origin: false, + override_existing_dns_record: false, + }, + domains: [ + { hostname: "api.example.com" }, + { hostname: "app.example.com" }, + ], + }); + + await runWrangler( + "deploy ./index --domain api.example.com --domain app.example.com" + ); + expect(std.out).toContain("api.example.com (custom domain)"); + expect(std.out).toContain("app.example.com (custom domain)"); + }); + + it("should combine --domain flags with existing routes configuration", async () => { + writeWranglerConfig({ + routes: ["example.com/api/*"], + }); + writeWorkerSource(); + mockSubDomainRequest(); + mockUpdateWorkerSubdomain({ enabled: false }); + mockUploadWorkerRequest({ expectedType: "esm" }); + mockCustomDomainsChangesetRequest({}); + mockPublishCustomDomainsRequest({ + publishFlags: { + override_scope: true, + override_existing_origin: false, + override_existing_dns_record: false, + }, + domains: [{ hostname: "api.example.com" }], + }); + // Mock the regular route deployment for the configured route + msw.use( + http.put( + "*/accounts/:accountId/workers/scripts/:scriptName/routes", + () => { + return HttpResponse.json( + { + success: true, + errors: [], + messages: [], + result: ["example.com/api/*"], + }, + { status: 200 } + ); + }, + { once: true } + ) + ); + + await runWrangler("deploy ./index --domain api.example.com"); + expect(std.out).toContain("example.com/api/*"); + expect(std.out).toContain("api.example.com (custom domain)"); + }); + + it("should validate domain flags and reject invalid domains with wildcards", async () => { + writeWranglerConfig({}); + writeWorkerSource(); + + await expect(runWrangler("deploy ./index --domain *.example.com")) + .rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid Routes: + *.example.com: + Wildcard operators (*) are not allowed in Custom Domains] + `); + }); + + it("should validate domain flags and reject invalid domains with paths", async () => { + writeWranglerConfig({}); + writeWorkerSource(); + + await expect( + runWrangler("deploy ./index --domain api.example.com/path") + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid Routes: + api.example.com/path: + Paths are not allowed in Custom Domains] + `); + }); }); describe("deploy asset routes", () => { diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index 6f594d8ba0d4..b04ef0e02a48 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -94,6 +94,7 @@ type Props = { alias: Record | undefined; triggers: string[] | undefined; routes: string[] | undefined; + domains: string[] | undefined; legacyEnv: boolean | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; @@ -416,8 +417,13 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m ); } - const routes = + const configRoutes = props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? []; + const domainRoutes = (props.domains || []).map((domain) => ({ + pattern: domain, + custom_domain: true, + })); + const routes = [...configRoutes, ...domainRoutes]; validateRoutes(routes, props.assetsOptions); const jsxFactory = props.jsxFactory || config.jsx_factory; @@ -1050,7 +1056,10 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m } // deploy triggers - const targets = await triggersDeploy(props); + const targets = await triggersDeploy({ + ...props, + routes: routes, + }); logger.log("Current Version ID:", versionId); diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 678a16fd1b43..f993161fd4a3 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -144,6 +144,13 @@ export const deployCommand = createCommand({ requiresArg: true, array: true, }, + domains: { + describe: "Custom domains to deploy to", + alias: "domain", + type: "string", + requiresArg: true, + array: true, + }, "jsx-factory": { describe: "The function that is called for each JSX element", type: "string", @@ -344,6 +351,7 @@ export const deployCommand = createCommand({ jsxFragment: args.jsxFragment, tsconfig: args.tsconfig, routes: args.routes, + domains: args.domains, assetsOptions, legacyAssetPaths: siteAssetPaths, legacyEnv: isLegacyEnv(config), diff --git a/packages/wrangler/src/triggers/deploy.ts b/packages/wrangler/src/triggers/deploy.ts index edcc88694fa5..a3bf94ce7788 100644 --- a/packages/wrangler/src/triggers/deploy.ts +++ b/packages/wrangler/src/triggers/deploy.ts @@ -27,7 +27,7 @@ type Props = { name: string | undefined; env: string | undefined; triggers: string[] | undefined; - routes: string[] | undefined; + routes: Route[] | undefined; legacyEnv: boolean | undefined; dryRun: boolean | undefined; assetsOptions: AssetsOptions | undefined; From e6d4824e2cd2fcdeb8a9daefc3d725aa0058a730 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:14:06 +0000 Subject: [PATCH 2/4] fix: clarify test name for domain flag behavior with routes Co-Authored-By: pbacondarwin@cloudflare.com --- .../wrangler/src/__tests__/deploy.test.ts | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index c4b04a9476c3..1ad3f9c4cc15 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -1742,7 +1742,7 @@ Update them to point to this script instead?`, expect(std.out).toContain("app.example.com (custom domain)"); }); - it("should combine --domain flags with existing routes configuration", async () => { + it("should deploy --domain flags alongside routes (from config when no CLI routes)", async () => { writeWranglerConfig({ routes: ["example.com/api/*"], }); @@ -1807,6 +1807,48 @@ Update them to point to this script instead?`, Paths are not allowed in Custom Domains] `); }); + + it("should handle both --route and --domain flags together", async () => { + writeWranglerConfig({ + routes: ["config.com/api/*"], + }); + writeWorkerSource(); + mockSubDomainRequest(); + mockUpdateWorkerSubdomain({ enabled: false }); + mockUploadWorkerRequest({ expectedType: "esm" }); + mockCustomDomainsChangesetRequest({}); + mockPublishCustomDomainsRequest({ + publishFlags: { + override_scope: true, + override_existing_origin: false, + override_existing_dns_record: false, + }, + domains: [{ hostname: "api.example.com" }], + }); + // Mock the regular route deployment for the CLI route (should override config) + msw.use( + http.put( + "*/accounts/:accountId/workers/scripts/:scriptName/routes", + () => { + return HttpResponse.json( + { + success: true, + errors: [], + messages: [], + result: ["cli.com/override/*"], + }, + { status: 200 } + ); + }, + { once: true } + ) + ); + + await runWrangler("deploy ./index --route cli.com/override/* --domain api.example.com"); + expect(std.out).toContain("cli.com/override/*"); + expect(std.out).toContain("api.example.com (custom domain)"); + expect(std.out).not.toContain("config.com/api/*"); + }); }); describe("deploy asset routes", () => { From 027161999c3c27c8f59df5e41493aa972bdca836 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:14:59 +0000 Subject: [PATCH 3/4] refactor: simplify route handling logic for consistency Co-Authored-By: pbacondarwin@cloudflare.com --- packages/wrangler/src/deploy/deploy.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index b04ef0e02a48..ad21e9e8744f 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -417,14 +417,13 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m ); } - const configRoutes = - props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? []; const domainRoutes = (props.domains || []).map((domain) => ({ pattern: domain, custom_domain: true, })); - const routes = [...configRoutes, ...domainRoutes]; - validateRoutes(routes, props.assetsOptions); + const routes = props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? []; + const allRoutes = [...routes, ...domainRoutes]; + validateRoutes(allRoutes, props.assetsOptions); const jsxFactory = props.jsxFactory || config.jsx_factory; const jsxFragment = props.jsxFragment || config.jsx_fragment; @@ -1058,7 +1057,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m // deploy triggers const targets = await triggersDeploy({ ...props, - routes: routes, + routes: allRoutes, }); logger.log("Current Version ID:", versionId); From c75252b9ae375e3453c523c430cec8e230f5e375 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:49:36 +0000 Subject: [PATCH 4/4] fix: apply prettier formatting fixes Co-Authored-By: pbacondarwin@cloudflare.com --- packages/wrangler/src/__tests__/deploy.test.ts | 4 +++- packages/wrangler/src/deploy/deploy.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 1ad3f9c4cc15..571ef01dce5b 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -1844,7 +1844,9 @@ Update them to point to this script instead?`, ) ); - await runWrangler("deploy ./index --route cli.com/override/* --domain api.example.com"); + await runWrangler( + "deploy ./index --route cli.com/override/* --domain api.example.com" + ); expect(std.out).toContain("cli.com/override/*"); expect(std.out).toContain("api.example.com (custom domain)"); expect(std.out).not.toContain("config.com/api/*"); diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index ad21e9e8744f..f16c399d37e9 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -421,7 +421,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m pattern: domain, custom_domain: true, })); - const routes = props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? []; + const routes = + props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? []; const allRoutes = [...routes, ...domainRoutes]; validateRoutes(allRoutes, props.assetsOptions);