Skip to content

Commit 835d6f7

Browse files
authored
wrangler: feat: validate Worker subdomain mixed state (#10770)
Enabling or disabling `workers_dev` is often an indication that the user is also trying to enable or disable `preview_urls`. Warn the user when these enter mixed state.
1 parent d3aee31 commit 835d6f7

File tree

7 files changed

+272
-8
lines changed

7 files changed

+272
-8
lines changed

.changeset/tiny-yaks-obey.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Enabling or disabling `workers_dev` is often an indication that
6+
the user is also trying to enable or disable `preview_urls`. Warn the
7+
user when these enter mixed state.

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

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6968,6 +6968,154 @@ addEventListener('fetch', event => {});`
69686968
});
69696969
});
69706970

6971+
describe("workers_dev mixed state warnings", () => {
6972+
beforeEach(() => {
6973+
vi.stubEnv("WRANGLER_DISABLE_SUBDOMAIN_MIXED_STATE_CHECK", "false");
6974+
});
6975+
6976+
afterEach(() => {
6977+
vi.unstubAllEnvs();
6978+
});
6979+
6980+
it("should not warn when config is the same as remote", async () => {
6981+
writeWranglerConfig({
6982+
workers_dev: false,
6983+
preview_urls: true,
6984+
});
6985+
writeWorkerSource();
6986+
mockSubDomainRequest("test-sub-domain", true, false);
6987+
mockUploadWorkerRequest();
6988+
mockGetWorkerSubdomain({ enabled: false, previews_enabled: true });
6989+
await runWrangler("deploy ./index");
6990+
6991+
expect(std.out).toMatchInlineSnapshot(`
6992+
"Total Upload: xx KiB / gzip: xx KiB
6993+
Worker Startup Time: 100 ms
6994+
Uploaded test-name (TIMINGS)
6995+
No deploy targets for test-name (TIMINGS)
6996+
Current Version ID: Galaxy-Class"
6997+
`);
6998+
expect(std.err).toMatchInlineSnapshot(`""`);
6999+
expect(std.warn).toMatchInlineSnapshot(`""`);
7000+
});
7001+
7002+
it("should not warn when workers_dev=false,preview_urls=false", async () => {
7003+
writeWranglerConfig({
7004+
workers_dev: false,
7005+
preview_urls: false,
7006+
});
7007+
writeWorkerSource();
7008+
mockSubDomainRequest("test-sub-domain", true, false);
7009+
mockUploadWorkerRequest();
7010+
mockGetWorkerSubdomain({ enabled: true, previews_enabled: true });
7011+
mockUpdateWorkerSubdomain({ enabled: false, previews_enabled: false });
7012+
await runWrangler("deploy ./index");
7013+
7014+
expect(std.out).toMatchInlineSnapshot(`
7015+
"Total Upload: xx KiB / gzip: xx KiB
7016+
Worker Startup Time: 100 ms
7017+
Uploaded test-name (TIMINGS)
7018+
No deploy targets for test-name (TIMINGS)
7019+
Current Version ID: Galaxy-Class"
7020+
`);
7021+
expect(std.err).toMatchInlineSnapshot(`""`);
7022+
expect(std.warn).toMatchInlineSnapshot(`""`);
7023+
});
7024+
7025+
it("should not warn when workers_dev=true,preview_urls=true", async () => {
7026+
writeWranglerConfig({
7027+
workers_dev: true,
7028+
preview_urls: true,
7029+
});
7030+
writeWorkerSource();
7031+
mockSubDomainRequest("test-sub-domain", true, false);
7032+
mockUploadWorkerRequest();
7033+
mockGetWorkerSubdomain({ enabled: false, previews_enabled: false });
7034+
mockUpdateWorkerSubdomain({ enabled: true, previews_enabled: true });
7035+
await runWrangler("deploy ./index");
7036+
7037+
expect(std.out).toMatchInlineSnapshot(`
7038+
"Total Upload: xx KiB / gzip: xx KiB
7039+
Worker Startup Time: 100 ms
7040+
Uploaded test-name (TIMINGS)
7041+
Deployed test-name triggers (TIMINGS)
7042+
https://test-name.test-sub-domain.workers.dev
7043+
Current Version ID: Galaxy-Class"
7044+
`);
7045+
expect(std.err).toMatchInlineSnapshot(`""`);
7046+
expect(std.warn).toMatchInlineSnapshot(`""`);
7047+
});
7048+
7049+
it("should warn when workers_dev=false,preview_urls=true", async () => {
7050+
writeWranglerConfig({
7051+
workers_dev: false,
7052+
preview_urls: true,
7053+
});
7054+
writeWorkerSource();
7055+
mockSubDomainRequest("test-sub-domain", true, false);
7056+
mockUploadWorkerRequest();
7057+
mockGetWorkerSubdomain({ enabled: true, previews_enabled: true });
7058+
mockUpdateWorkerSubdomain({ enabled: false, previews_enabled: true });
7059+
await runWrangler("deploy ./index");
7060+
7061+
expect(std.out).toMatchInlineSnapshot(`
7062+
"Total Upload: xx KiB / gzip: xx KiB
7063+
Worker Startup Time: 100 ms
7064+
Uploaded test-name (TIMINGS)
7065+
No deploy targets for test-name (TIMINGS)
7066+
Current Version ID: Galaxy-Class"
7067+
`);
7068+
expect(std.err).toMatchInlineSnapshot(`""`);
7069+
expect(std.warn).toMatchInlineSnapshot(`
7070+
"▲ [WARNING] You are disabling the 'workers.dev' subdomain for this Worker, but Preview URLs are still enabled.
7071+
7072+
Preview URLs will automatically generate a unique, shareable link for each new version which will
7073+
be accessible at:
7074+
https://<VERSION_PREFIX>-test-name.test-sub-domain.workers.dev
7075+
7076+
To prevent this Worker from being unintentionally public, you may want to disable the Preview URLs
7077+
as well by setting \`preview_urls = false\` in your Wrangler config file.
7078+
7079+
"
7080+
`);
7081+
});
7082+
7083+
it("should warn when workers_dev=true,preview_urls=false", async () => {
7084+
writeWranglerConfig({
7085+
workers_dev: true,
7086+
preview_urls: false,
7087+
});
7088+
writeWorkerSource();
7089+
mockSubDomainRequest("test-sub-domain", true, false);
7090+
mockUploadWorkerRequest();
7091+
mockGetWorkerSubdomain({ enabled: false, previews_enabled: false });
7092+
mockUpdateWorkerSubdomain({ enabled: true, previews_enabled: false });
7093+
await runWrangler("deploy ./index");
7094+
7095+
expect(std.out).toMatchInlineSnapshot(`
7096+
"Total Upload: xx KiB / gzip: xx KiB
7097+
Worker Startup Time: 100 ms
7098+
Uploaded test-name (TIMINGS)
7099+
Deployed test-name triggers (TIMINGS)
7100+
https://test-name.test-sub-domain.workers.dev
7101+
Current Version ID: Galaxy-Class"
7102+
`);
7103+
expect(std.err).toMatchInlineSnapshot(`""`);
7104+
expect(std.warn).toMatchInlineSnapshot(`
7105+
"▲ [WARNING] You are enabling the 'workers.dev' subdomain for this Worker, but Preview URLs are still disabled.
7106+
7107+
Preview URLs will automatically generate a unique, shareable link for each new version which will
7108+
be accessible at:
7109+
https://<VERSION_PREFIX>-test-name.test-sub-domain.workers.dev
7110+
7111+
You may want to enable the Preview URLs as well by setting \`preview_urls = true\` in your Wrangler
7112+
config file.
7113+
7114+
"
7115+
`);
7116+
});
7117+
});
7118+
69717119
describe("[define]", () => {
69727120
it("should be able to define values that will be substituted into top-level identifiers", async () => {
69737121
writeWranglerConfig({

packages/wrangler/src/__tests__/helpers/mock-workers-subdomain.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { createFetchResult, msw } from "./msw";
44
/** Create a mock handler for the request to get the account's subdomain. */
55
export function mockSubDomainRequest(
66
subdomain = "test-sub-domain",
7-
registeredWorkersDev = true
7+
registeredWorkersDev = true,
8+
once = true
89
) {
910
if (registeredWorkersDev) {
1011
msw.use(
@@ -13,7 +14,7 @@ export function mockSubDomainRequest(
1314
() => {
1415
return HttpResponse.json(createFetchResult({ subdomain }));
1516
},
16-
{ once: true }
17+
{ once }
1718
)
1819
);
1920
} else {
@@ -27,7 +28,7 @@ export function mockSubDomainRequest(
2728
])
2829
);
2930
},
30-
{ once: true }
31+
{ once }
3132
)
3233
);
3334
}

packages/wrangler/src/__tests__/vitest.setup.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,8 @@ vi.mock("../../package.json", () => {
222222
version: "x.x.x",
223223
};
224224
});
225+
226+
// Disable subdomain mixed state check for tests (specific test will enable it).
227+
beforeEach(() => {
228+
vi.stubEnv("WRANGLER_DISABLE_SUBDOMAIN_MIXED_STATE_CHECK", "true");
229+
});

packages/wrangler/src/environment-variables/factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type VariableNames =
3838
| "CLOUDFLARE_INCLUDE_PROCESS_ENV"
3939
/** Include a trace header in all API requests that Wrangler makes (for internal use only) */
4040
| "WRANGLER_TRACE_ID"
41+
/** Disable the check for mixed state of subdomain flags (`workers_dev`, `preview_urls`, etc.) (default: false). */
42+
| "WRANGLER_DISABLE_SUBDOMAIN_MIXED_STATE_CHECK"
4143

4244
// ## Logging & Output
4345

packages/wrangler/src/environment-variables/misc-variables.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ export const getDockerPath = getEnvironmentVariableFactory({
272272
},
273273
});
274274

275+
export const getSubdomainMixedStateCheckDisabled =
276+
getBooleanEnvironmentVariableFactory({
277+
variableName: "WRANGLER_DISABLE_SUBDOMAIN_MIXED_STATE_CHECK",
278+
defaultValue: false,
279+
});
280+
275281
/**
276282
/**
277283
* `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` specifies whether to load vars for local dev from `.env` files.

packages/wrangler/src/triggers/deploy.ts

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
updateQueueConsumers,
1010
validateRoutes,
1111
} from "../deploy/deploy";
12+
import { getSubdomainMixedStateCheckDisabled } from "../environment-variables/misc-variables";
1213
import { UserError } from "../errors";
14+
import { isNonInteractiveOrCI } from "../is-interactive";
1315
import { logger } from "../logger";
1416
import { ensureQueuesExistByConfig } from "../queues/client";
1517
import { getWorkersDevSubdomain } from "../routes";
@@ -310,6 +312,85 @@ export function getSubdomainValues(
310312
};
311313
}
312314

315+
async function validateSubdomainMixedState(
316+
props: Props,
317+
accountId: string,
318+
scriptName: string,
319+
configFlags: { workers_dev?: boolean; preview_urls?: boolean },
320+
desired: { workers_dev: boolean; preview_urls: boolean },
321+
remote: { workers_dev: boolean; preview_urls: boolean },
322+
firstDeploy: boolean
323+
): Promise<{
324+
workers_dev: boolean;
325+
preview_urls: boolean;
326+
}> {
327+
const { config } = props;
328+
329+
const changed =
330+
desired.workers_dev !== remote.workers_dev ||
331+
desired.preview_urls !== remote.preview_urls;
332+
333+
// Early return if config values are the same as remote values (so we only warn on change)
334+
if (!changed) {
335+
return desired;
336+
}
337+
338+
// Early return if check disabled through environment variable.
339+
if (getSubdomainMixedStateCheckDisabled()) {
340+
return desired;
341+
}
342+
343+
// Early return if non-interactive or CI
344+
if (isNonInteractiveOrCI()) {
345+
return desired;
346+
}
347+
348+
// Early return if this is the first deploy
349+
if (firstDeploy) {
350+
return desired;
351+
}
352+
353+
// Early return if config values are the same (e.g. both true or both false, not in mixed state)
354+
if (desired.workers_dev === desired.preview_urls) {
355+
return desired;
356+
}
357+
358+
const userSubdomain = await getWorkersDevSubdomain(
359+
config,
360+
accountId,
361+
config.configPath
362+
);
363+
const previewUrl = `https://<VERSION_PREFIX>-${scriptName}.${userSubdomain}`;
364+
365+
// Scenario 1: User disables workers.dev while having preview URLs enabled
366+
if (!desired.workers_dev && desired.preview_urls) {
367+
logger.warn(
368+
[
369+
"You are disabling the 'workers.dev' subdomain for this Worker, but Preview URLs are still enabled.",
370+
"Preview URLs will automatically generate a unique, shareable link for each new version which will be accessible at:",
371+
` ${previewUrl}`,
372+
"",
373+
"To prevent this Worker from being unintentionally public, you may want to disable the Preview URLs as well by setting `preview_urls = false` in your Wrangler config file.",
374+
].join("\n")
375+
);
376+
}
377+
378+
// Scenario 2: User enables workers.dev when Preview URLs are off
379+
if (desired.workers_dev && !desired.preview_urls) {
380+
logger.warn(
381+
[
382+
"You are enabling the 'workers.dev' subdomain for this Worker, but Preview URLs are still disabled.",
383+
"Preview URLs will automatically generate a unique, shareable link for each new version which will be accessible at:",
384+
` ${previewUrl}`,
385+
"",
386+
"You may want to enable the Preview URLs as well by setting `preview_urls = true` in your Wrangler config file.",
387+
].join("\n")
388+
);
389+
}
390+
391+
return desired;
392+
}
393+
313394
async function subdomainDeploy(
314395
props: Props,
315396
accountId: string,
@@ -322,11 +403,6 @@ async function subdomainDeploy(
322403
) {
323404
const { config } = props;
324405

325-
// Get desired subdomain enablement status.
326-
327-
const { workers_dev: wantWorkersDev, preview_urls: wantPreviews } =
328-
getSubdomainValues(config.workers_dev, config.preview_urls, routes);
329-
330406
// Get current subdomain enablement status.
331407

332408
const { enabled: currWorkersDev, previews_enabled: currPreviews } =
@@ -335,6 +411,25 @@ async function subdomainDeploy(
335411
previews_enabled: boolean;
336412
}>(config, `${workerUrl}/subdomain`);
337413

414+
// Get desired subdomain enablement status.
415+
416+
const desiredSubdomain = await getSubdomainValues(
417+
config.workers_dev,
418+
config.preview_urls,
419+
routes
420+
);
421+
422+
const { workers_dev: wantWorkersDev, preview_urls: wantPreviews } =
423+
await validateSubdomainMixedState(
424+
props,
425+
accountId,
426+
scriptName,
427+
config,
428+
desiredSubdomain,
429+
{ workers_dev: currWorkersDev, preview_urls: currPreviews },
430+
firstDeploy
431+
);
432+
338433
const workersDevInSync = wantWorkersDev === currWorkersDev;
339434
const previewsInSync = wantPreviews === currPreviews;
340435
const allInSync = [workersDevInSync, previewsInSync].every((v) => v);

0 commit comments

Comments
 (0)