Skip to content

Commit 6906bf0

Browse files
[wrangler] Warn when custom-domain Preview URLs are missing (#15760)
Co-authored-by: Patrick O'Donnell <podonnell@cloudflare.com>
1 parent 354ebdb commit 6906bf0

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

‎.changeset/tall-ducks-preview.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@cloudflare/deploy-helpers": patch
3+
"wrangler": patch
4+
---
5+
6+
Warn when `wrangler preview` returns only non-custom-domain URLs even though custom-domain Preview URLs are configured.

‎packages/deploy-helpers/src/preview/preview.ts‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,64 @@ function formatPreviewDeploymentSummary(
803803
].join("\n");
804804
}
805805

806+
function getPreviewCustomDomainHostnames(config: Config): string[] {
807+
const routes = config.routes ?? (config.route ? [config.route] : []);
808+
return routes
809+
.filter(
810+
(route): route is CustomDomainRoute =>
811+
isCustomDomainRoute(route) && route.previews_enabled === true
812+
)
813+
.map((route) => normalizeHostname(route.pattern));
814+
}
815+
816+
function normalizeHostname(hostname: string) {
817+
try {
818+
return new URL(`https://${hostname}`).hostname.replace(/\.$/, "");
819+
} catch {
820+
return hostname.toLowerCase().replace(/\.$/, "");
821+
}
822+
}
823+
824+
function hostnameMatchesCustomDomain(hostname: string, customDomain: string) {
825+
const normalizedHostname = normalizeHostname(hostname);
826+
return (
827+
normalizedHostname === customDomain ||
828+
normalizedHostname.endsWith(`.${customDomain}`)
829+
);
830+
}
831+
832+
function previewUrlMatchesCustomDomain(url: string, customDomains: string[]) {
833+
try {
834+
const { hostname } = new URL(url);
835+
return customDomains.some((domain) =>
836+
hostnameMatchesCustomDomain(hostname, domain)
837+
);
838+
} catch {
839+
return false;
840+
}
841+
}
842+
843+
function logMissingCustomDomainPreviewUrlsWarning(
844+
config: Config,
845+
previewResource: PreviewResource,
846+
deployment: DeploymentResource
847+
) {
848+
const customDomains = getPreviewCustomDomainHostnames(config);
849+
const urls = [...(previewResource.urls ?? []), ...(deployment.urls ?? [])];
850+
if (
851+
customDomains.length === 0 ||
852+
urls.length === 0 ||
853+
urls.some((url) => previewUrlMatchesCustomDomain(url, customDomains))
854+
) {
855+
return;
856+
}
857+
858+
logger.log("");
859+
logger.warn(
860+
"Custom domain Preview URLs are configured, but none are active for this Preview. If you added `previews_enabled = true` after your last deployment, run `wrangler deploy` once to publish the custom domain Preview route, then run `wrangler preview` again. If you already deployed with that setting, the custom domain may still be provisioning."
861+
);
862+
}
863+
806864
function logMissingPreviewsBindingsWarning(
807865
productionBindingsExpectedInPreview: Record<string, { type: string }>,
808866
remotePreviewDefaultBindings: Record<string, Binding> | undefined,
@@ -1082,6 +1140,11 @@ async function runPreview(
10821140
pullRequest
10831141
)
10841142
);
1143+
logMissingCustomDomainPreviewUrlsWarning(
1144+
config,
1145+
previewResource,
1146+
deployment
1147+
);
10851148
}
10861149

10871150
return { preview: previewResource, deployment, isNewPreview };

‎packages/wrangler/src/__tests__/preview.test.ts‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3608,6 +3608,132 @@ describe("wrangler preview", () => {
36083608
expect(std.out).toContain('"preview_urls": true');
36093609
});
36103610

3611+
function mockPreviewUrls(previewUrls: string[], deploymentUrls: string[]) {
3612+
msw.use(
3613+
http.get(
3614+
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId`,
3615+
() =>
3616+
HttpResponse.json(
3617+
{
3618+
success: false,
3619+
result: null,
3620+
errors: [{ code: 10025, message: "Preview not found" }],
3621+
},
3622+
{ status: 404 }
3623+
)
3624+
),
3625+
http.post(
3626+
`*/accounts/:accountId/workers/workers/:workerId/previews`,
3627+
() =>
3628+
HttpResponse.json(
3629+
{
3630+
success: true,
3631+
result: {
3632+
id: "preview-id-custom-domain",
3633+
name: "custom-domain-preview",
3634+
slug: "custom-domain-preview",
3635+
urls: previewUrls,
3636+
worker_name: "test-worker",
3637+
created_on: new Date().toISOString(),
3638+
},
3639+
},
3640+
{ status: 201 }
3641+
)
3642+
),
3643+
http.post(
3644+
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId/deployments`,
3645+
() =>
3646+
HttpResponse.json(
3647+
{
3648+
success: true,
3649+
result: {
3650+
id: "deployment-id-custom-domain",
3651+
preview_id: "preview-id-custom-domain",
3652+
preview_name: "custom-domain-preview",
3653+
urls: deploymentUrls,
3654+
compatibility_date: "2025-01-01",
3655+
env: {},
3656+
created_on: new Date().toISOString(),
3657+
},
3658+
},
3659+
{ status: 201 }
3660+
)
3661+
)
3662+
);
3663+
}
3664+
3665+
test.for<{
3666+
name: string;
3667+
routePattern?: string;
3668+
previewUrls: string[];
3669+
deploymentUrls: string[];
3670+
shouldWarn: boolean;
3671+
}>([
3672+
{
3673+
name: "workers.dev URLs are active but custom-domain URLs are missing",
3674+
previewUrls: ["https://custom-domain-preview.test-worker.workers.dev"],
3675+
deploymentUrls: [
3676+
"https://deployment-id-custom-domain.test-worker.workers.dev",
3677+
],
3678+
shouldWarn: true,
3679+
},
3680+
{
3681+
name: "the Preview URL array has a custom-domain URL",
3682+
previewUrls: ["https://custom-domain-preview.app.example.com"],
3683+
deploymentUrls: [
3684+
"https://deployment-id-custom-domain.test-worker.workers.dev",
3685+
],
3686+
shouldWarn: false,
3687+
},
3688+
{
3689+
name: "the deployment URL array has a custom-domain URL",
3690+
previewUrls: ["https://custom-domain-preview.test-worker.workers.dev"],
3691+
deploymentUrls: ["https://deployment-id-custom-domain.app.example.com"],
3692+
shouldWarn: false,
3693+
},
3694+
{
3695+
name: "a configured IDN custom domain matches a punycoded Preview URL",
3696+
routePattern: "bücher.example.com",
3697+
previewUrls: [
3698+
"https://custom-domain-preview.xn--bcher-kva.example.com",
3699+
],
3700+
deploymentUrls: [],
3701+
shouldWarn: false,
3702+
},
3703+
])(
3704+
"handles custom-domain Preview URL guidance when $name",
3705+
async (testCase, { expect }) => {
3706+
writeWranglerConfig(
3707+
{
3708+
main: "src/index.ts",
3709+
previews: {},
3710+
routes: [
3711+
{
3712+
pattern: testCase.routePattern ?? "app.example.com",
3713+
custom_domain: true,
3714+
previews_enabled: true,
3715+
},
3716+
],
3717+
},
3718+
"wrangler.json"
3719+
);
3720+
mockPreviewUrls(testCase.previewUrls, testCase.deploymentUrls);
3721+
3722+
await runWrangler("preview --name custom-domain-preview");
3723+
3724+
expect(std.out).not.toContain(NO_ACTIVE_PREVIEW_URLS_MESSAGE);
3725+
if (testCase.shouldWarn) {
3726+
expect(std.warn).toContain(
3727+
"Custom domain Preview URLs are configured, but none are active for this Preview. If you added `previews_enabled = true` after your last deployment, run `wrangler deploy` once to publish the custom domain Preview route, then run `wrangler preview` again. If you already deployed with that setting, the custom domain may still be provisioning."
3728+
);
3729+
} else {
3730+
expect(std.warn).not.toContain(
3731+
"Custom domain Preview URLs are configured"
3732+
);
3733+
}
3734+
}
3735+
);
3736+
36113737
test("should use the URL-encoded preview name as the Preview identifier in path params", async ({
36123738
expect,
36133739
}) => {

0 commit comments

Comments
 (0)