Skip to content

Commit bd8223d

Browse files
feat: add --domain flag to deploy command for custom domains (#10312)
* 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: [email protected] <[email protected]> * fix: clarify test name for domain flag behavior with routes Co-Authored-By: [email protected] <[email protected]> * refactor: simplify route handling logic for consistency Co-Authored-By: [email protected] <[email protected]> * fix: apply prettier formatting fixes Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]>
1 parent 42aafa3 commit bd8223d

File tree

5 files changed

+180
-3
lines changed

5 files changed

+180
-3
lines changed

.changeset/nine-onions-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
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.

packages/wrangler/src/__tests__/deploy.test.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,161 @@ Update them to point to this script instead?`,
16921692
'Publishing to Custom Domain "api.example.com" was skipped, fix conflict and try again'
16931693
);
16941694
});
1695+
it("should deploy domains passed via --domain flag as custom domains", async () => {
1696+
writeWranglerConfig({});
1697+
writeWorkerSource();
1698+
mockSubDomainRequest();
1699+
mockUpdateWorkerSubdomain({ enabled: false });
1700+
mockUploadWorkerRequest({ expectedType: "esm" });
1701+
mockCustomDomainsChangesetRequest({});
1702+
mockPublishCustomDomainsRequest({
1703+
publishFlags: {
1704+
override_scope: true,
1705+
override_existing_origin: false,
1706+
override_existing_dns_record: false,
1707+
},
1708+
domains: [{ hostname: "api.example.com" }],
1709+
});
1710+
1711+
await runWrangler("deploy ./index --domain api.example.com");
1712+
expect(std.out).toContain("api.example.com (custom domain)");
1713+
});
1714+
1715+
it("should deploy multiple domains passed via --domain flags", async () => {
1716+
writeWranglerConfig({});
1717+
writeWorkerSource();
1718+
mockSubDomainRequest();
1719+
mockUpdateWorkerSubdomain({ enabled: false });
1720+
mockUploadWorkerRequest({ expectedType: "esm" });
1721+
mockCustomDomainsChangesetRequest({});
1722+
mockPublishCustomDomainsRequest({
1723+
publishFlags: {
1724+
override_scope: true,
1725+
override_existing_origin: false,
1726+
override_existing_dns_record: false,
1727+
},
1728+
domains: [
1729+
{ hostname: "api.example.com" },
1730+
{ hostname: "app.example.com" },
1731+
],
1732+
});
1733+
1734+
await runWrangler(
1735+
"deploy ./index --domain api.example.com --domain app.example.com"
1736+
);
1737+
expect(std.out).toContain("api.example.com (custom domain)");
1738+
expect(std.out).toContain("app.example.com (custom domain)");
1739+
});
1740+
1741+
it("should deploy --domain flags alongside routes (from config when no CLI routes)", async () => {
1742+
writeWranglerConfig({
1743+
routes: ["example.com/api/*"],
1744+
});
1745+
writeWorkerSource();
1746+
mockSubDomainRequest();
1747+
mockUpdateWorkerSubdomain({ enabled: false });
1748+
mockUploadWorkerRequest({ expectedType: "esm" });
1749+
mockCustomDomainsChangesetRequest({});
1750+
mockPublishCustomDomainsRequest({
1751+
publishFlags: {
1752+
override_scope: true,
1753+
override_existing_origin: false,
1754+
override_existing_dns_record: false,
1755+
},
1756+
domains: [{ hostname: "api.example.com" }],
1757+
});
1758+
// Mock the regular route deployment for the configured route
1759+
msw.use(
1760+
http.put(
1761+
"*/accounts/:accountId/workers/scripts/:scriptName/routes",
1762+
() => {
1763+
return HttpResponse.json(
1764+
{
1765+
success: true,
1766+
errors: [],
1767+
messages: [],
1768+
result: ["example.com/api/*"],
1769+
},
1770+
{ status: 200 }
1771+
);
1772+
},
1773+
{ once: true }
1774+
)
1775+
);
1776+
1777+
await runWrangler("deploy ./index --domain api.example.com");
1778+
expect(std.out).toContain("example.com/api/*");
1779+
expect(std.out).toContain("api.example.com (custom domain)");
1780+
});
1781+
1782+
it("should validate domain flags and reject invalid domains with wildcards", async () => {
1783+
writeWranglerConfig({});
1784+
writeWorkerSource();
1785+
1786+
await expect(runWrangler("deploy ./index --domain *.example.com"))
1787+
.rejects.toThrowErrorMatchingInlineSnapshot(`
1788+
[Error: Invalid Routes:
1789+
*.example.com:
1790+
Wildcard operators (*) are not allowed in Custom Domains]
1791+
`);
1792+
});
1793+
1794+
it("should validate domain flags and reject invalid domains with paths", async () => {
1795+
writeWranglerConfig({});
1796+
writeWorkerSource();
1797+
1798+
await expect(
1799+
runWrangler("deploy ./index --domain api.example.com/path")
1800+
).rejects.toThrowErrorMatchingInlineSnapshot(`
1801+
[Error: Invalid Routes:
1802+
api.example.com/path:
1803+
Paths are not allowed in Custom Domains]
1804+
`);
1805+
});
1806+
1807+
it("should handle both --route and --domain flags together", async () => {
1808+
writeWranglerConfig({
1809+
routes: ["config.com/api/*"],
1810+
});
1811+
writeWorkerSource();
1812+
mockSubDomainRequest();
1813+
mockUpdateWorkerSubdomain({ enabled: false });
1814+
mockUploadWorkerRequest({ expectedType: "esm" });
1815+
mockCustomDomainsChangesetRequest({});
1816+
mockPublishCustomDomainsRequest({
1817+
publishFlags: {
1818+
override_scope: true,
1819+
override_existing_origin: false,
1820+
override_existing_dns_record: false,
1821+
},
1822+
domains: [{ hostname: "api.example.com" }],
1823+
});
1824+
// Mock the regular route deployment for the CLI route (should override config)
1825+
msw.use(
1826+
http.put(
1827+
"*/accounts/:accountId/workers/scripts/:scriptName/routes",
1828+
() => {
1829+
return HttpResponse.json(
1830+
{
1831+
success: true,
1832+
errors: [],
1833+
messages: [],
1834+
result: ["cli.com/override/*"],
1835+
},
1836+
{ status: 200 }
1837+
);
1838+
},
1839+
{ once: true }
1840+
)
1841+
);
1842+
1843+
await runWrangler(
1844+
"deploy ./index --route cli.com/override/* --domain api.example.com"
1845+
);
1846+
expect(std.out).toContain("cli.com/override/*");
1847+
expect(std.out).toContain("api.example.com (custom domain)");
1848+
expect(std.out).not.toContain("config.com/api/*");
1849+
});
16951850
});
16961851

16971852
describe("deploy asset routes", () => {

packages/wrangler/src/deploy/deploy.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type Props = {
9393
alias: Record<string, string> | undefined;
9494
triggers: string[] | undefined;
9595
routes: string[] | undefined;
96+
domains: string[] | undefined;
9697
legacyEnv: boolean | undefined;
9798
jsxFactory: string | undefined;
9899
jsxFragment: string | undefined;
@@ -415,9 +416,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
415416
);
416417
}
417418

419+
const domainRoutes = (props.domains || []).map((domain) => ({
420+
pattern: domain,
421+
custom_domain: true,
422+
}));
418423
const routes =
419424
props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
420-
validateRoutes(routes, props.assetsOptions);
425+
const allRoutes = [...routes, ...domainRoutes];
426+
validateRoutes(allRoutes, props.assetsOptions);
421427

422428
const jsxFactory = props.jsxFactory || config.jsx_factory;
423429
const jsxFragment = props.jsxFragment || config.jsx_fragment;
@@ -1049,7 +1055,10 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
10491055
}
10501056

10511057
// deploy triggers
1052-
const targets = await triggersDeploy(props);
1058+
const targets = await triggersDeploy({
1059+
...props,
1060+
routes: allRoutes,
1061+
});
10531062

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

packages/wrangler/src/deploy/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ export const deployCommand = createCommand({
144144
requiresArg: true,
145145
array: true,
146146
},
147+
domains: {
148+
describe: "Custom domains to deploy to",
149+
alias: "domain",
150+
type: "string",
151+
requiresArg: true,
152+
array: true,
153+
},
147154
"jsx-factory": {
148155
describe: "The function that is called for each JSX element",
149156
type: "string",
@@ -344,6 +351,7 @@ export const deployCommand = createCommand({
344351
jsxFragment: args.jsxFragment,
345352
tsconfig: args.tsconfig,
346353
routes: args.routes,
354+
domains: args.domains,
347355
assetsOptions,
348356
legacyAssetPaths: siteAssetPaths,
349357
legacyEnv: isLegacyEnv(config),

packages/wrangler/src/triggers/deploy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Props = {
2626
name: string | undefined;
2727
env: string | undefined;
2828
triggers: string[] | undefined;
29-
routes: string[] | undefined;
29+
routes: Route[] | undefined;
3030
legacyEnv: boolean | undefined;
3131
dryRun: boolean | undefined;
3232
assetsOptions: AssetsOptions | undefined;

0 commit comments

Comments
 (0)