Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nine-onions-wonder.md
Original file line number Diff line number Diff line change
@@ -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.
155 changes: 155 additions & 0 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,161 @@ 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 deploy --domain flags alongside routes (from config when no CLI routes)", 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]
`);
});

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", () => {
Expand Down
13 changes: 11 additions & 2 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type Props = {
alias: Record<string, string> | undefined;
triggers: string[] | undefined;
routes: string[] | undefined;
domains: string[] | undefined;
legacyEnv: boolean | undefined;
jsxFactory: string | undefined;
jsxFragment: string | undefined;
Expand Down Expand Up @@ -416,9 +417,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
);
}

const domainRoutes = (props.domains || []).map((domain) => ({
pattern: domain,
custom_domain: true,
}));
const routes =
props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
validateRoutes(routes, props.assetsOptions);
const allRoutes = [...routes, ...domainRoutes];
validateRoutes(allRoutes, props.assetsOptions);

const jsxFactory = props.jsxFactory || config.jsx_factory;
const jsxFragment = props.jsxFragment || config.jsx_fragment;
Expand Down Expand Up @@ -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: allRoutes,
});

logger.log("Current Version ID:", versionId);

Expand Down
8 changes: 8 additions & 0 deletions packages/wrangler/src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/src/triggers/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading