Skip to content

Commit 7562f67

Browse files
Add strict mode for the wrangler deploy command
1 parent b421bf6 commit 7562f67

File tree

4 files changed

+134
-5
lines changed

4 files changed

+134
-5
lines changed

.changeset/calm-camels-return.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+
Add strict mode for the `wrangler deploy` command
6+
7+
Add a new flag: `--strict` that makes the `wrangler deploy` command be more strict/prudent and not deploy workers when such deployments can be potentially problematic. This "strict mode" currently only affects non-interactive sessions where conflicts with the remote settings for the worker (for example when the worker has been re-deployed via the dashboard) will cause the deployment to fail instead of automatically overriding the remote settings.

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

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14021,6 +14021,99 @@ export default{
1402114021
return normalizedLog;
1402214022
}
1402314023
});
14024+
14025+
describe("with strict mode enabled", () => {
14026+
it("should error if there are remote config difference (with --x-remote-diff-check) in non-interactive mode", async () => {
14027+
setIsTTY(false);
14028+
14029+
writeWorkerSource();
14030+
mockGetServiceByName("test-name", "production", "dash");
14031+
writeWranglerConfig(
14032+
{
14033+
compatibility_date: "2024-04-24",
14034+
main: "./index.js",
14035+
},
14036+
"./wrangler.json"
14037+
);
14038+
mockSubDomainRequest();
14039+
mockUploadWorkerRequest();
14040+
mockGetServiceBindings("test-name", []);
14041+
mockGetServiceRoutes("test-name", []);
14042+
mockGetServiceCustomDomainRecords([]);
14043+
mockGetServiceSubDomainData("test-name", {
14044+
enabled: true,
14045+
previews_enabled: false,
14046+
});
14047+
mockGetServiceSchedules("test-name", { schedules: [] });
14048+
mockGetServiceMetadata("test-name", {
14049+
created_on: "2025-08-07T09:34:47.846308Z",
14050+
modified_on: "2025-08-08T10:48:12.688997Z",
14051+
script: {
14052+
created_on: "2025-08-07T09:34:47.846308Z",
14053+
modified_on: "2025-08-08T10:48:12.688997Z",
14054+
id: "silent-firefly-dbe3",
14055+
observability: { enabled: true, head_sampling_rate: 1 },
14056+
compatibility_date: "2024-04-24",
14057+
},
14058+
} as unknown as ServiceMetadataRes["default_environment"]);
14059+
14060+
await runWrangler("deploy --x-remote-diff-check --strict");
14061+
14062+
expect(std.warn).toMatchInlineSnapshot(`
14063+
"▲ [WARNING] The local configuration being used (generated from your local configuration file) differs from the remote configuration of your Worker set via the Cloudflare Dashboard:
14064+
14065+
\\"bindings\\": []
14066+
},
14067+
\\"observability\\": {
14068+
- \\"enabled\\": true,
14069+
+ \\"enabled\\": false,
14070+
\\"head_sampling_rate\\": 1,
14071+
\\"logs\\": {
14072+
\\"enabled\\": false,
14073+
14074+
Deploying the Worker will override the remote configuration with your local one.
14075+
14076+
"
14077+
`);
14078+
14079+
expect(std.err).toMatchInlineSnapshot(`
14080+
"X [ERROR] Aborting the deployment operation (due to strict mode, to prevent this failure either remove the \`--strict\` flag)
14081+
14082+
"
14083+
`);
14084+
// note: the test and the wrangler run share the same process, and we expect the deploy command (which fails)
14085+
// to set a non-zero exit code
14086+
expect(process.exitCode).not.toBe(0);
14087+
});
14088+
14089+
it("should error when worker was last deployed from api", async () => {
14090+
setIsTTY(false);
14091+
14092+
msw.use(...mswSuccessDeploymentScriptAPI);
14093+
writeWranglerConfig();
14094+
writeWorkerSource();
14095+
mockSubDomainRequest();
14096+
mockUploadWorkerRequest();
14097+
14098+
await runWrangler("deploy ./index --strict");
14099+
14100+
expect(std.warn).toMatchInlineSnapshot(`
14101+
"▲ [WARNING] You are about to publish a Workers Service that was last updated via the script API.
14102+
14103+
Edits that have been made via the script API will be overridden by your local code and config.
14104+
14105+
"
14106+
`);
14107+
expect(std.err).toMatchInlineSnapshot(`
14108+
"X [ERROR] Aborting the deployment operation (due to strict mode, to prevent this failure either remove the \`--strict\` flag)
14109+
14110+
"
14111+
`);
14112+
// note: the test and the wrangler run share the same process, and we expect the deploy command (which fails)
14113+
// to set a non-zero exit code
14114+
expect(process.exitCode).not.toBe(0);
14115+
});
14116+
});
1402414117
});
1402514118

1402614119
/** Write mock assets to the file system so they can be uploaded. */

packages/wrangler/src/deploy/deploy.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
} from "../environments";
3434
import { UserError } from "../errors";
3535
import { getFlag } from "../experimental-flags";
36+
import { isNonInteractiveOrCI } from "../is-interactive";
3637
import { logger } from "../logger";
3738
import { getMetricsUsageHeaders } from "../metrics";
3839
import { isNavigatorDefined } from "../navigator-user-agent";
@@ -120,6 +121,7 @@ type Props = {
120121
experimentalAutoCreate: boolean;
121122
metafile: string | boolean | undefined;
122123
containersRollout: "immediate" | "gradual" | undefined;
124+
strict: boolean | undefined;
123125
};
124126

125127
export type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
@@ -357,6 +359,8 @@ export default async function deploy(props: Props): Promise<{
357359
workerTag: string | null;
358360
targets?: string[];
359361
}> {
362+
const deployConfirm = getDeployConfirmFunction(props.strict);
363+
360364
// TODO: warn if git/hg has uncommitted changes
361365
const { config, accountId, name, entry } = props;
362366
let workerTag: string | null = null;
@@ -417,23 +421,23 @@ export default async function deploy(props: Props): Promise<{
417421
`\n${configDiff.diff}\n\n` +
418422
"Deploying the Worker will override the remote configuration with your local one."
419423
);
420-
if (!(await confirm("Would you like to continue?"))) {
424+
if (!(await deployConfirm("Would you like to continue?"))) {
421425
return { versionId, workerTag };
422426
}
423427
}
424428
} else {
425429
logger.warn(
426430
`You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
427431
);
428-
if (!(await confirm("Would you like to continue?"))) {
432+
if (!(await deployConfirm("Would you like to continue?"))) {
429433
return { versionId, workerTag };
430434
}
431435
}
432436
} else if (script.last_deployed_from === "api") {
433437
logger.warn(
434438
`You are about to publish a Workers Service that was last updated via the script API.\nEdits that have been made via the script API will be overridden by your local code and config.`
435439
);
436-
if (!(await confirm("Would you like to continue?"))) {
440+
if (!(await deployConfirm("Would you like to continue?"))) {
437441
return { versionId, workerTag };
438442
}
439443
}
@@ -1430,3 +1434,21 @@ export async function updateQueueConsumers(
14301434

14311435
return updateConsumers;
14321436
}
1437+
1438+
function getDeployConfirmFunction(
1439+
strictMode = false
1440+
): (text: string) => Promise<boolean> {
1441+
const nonInteractive = isNonInteractiveOrCI();
1442+
1443+
if (nonInteractive && strictMode) {
1444+
return () => {
1445+
logger.error(
1446+
"Aborting the deployment operation (due to strict mode, to prevent this failure either remove the `--strict` flag)"
1447+
);
1448+
process.exitCode = 1;
1449+
return Promise.resolve(false);
1450+
};
1451+
}
1452+
1453+
return confirm;
1454+
}

packages/wrangler/src/deploy/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,17 @@ export const deployCommand = createCommand({
232232
choices: ["immediate", "gradual"] as const,
233233
},
234234
"experimental-deploy-remote-diff-check": {
235-
describe: `Experimental: Enable The Deployment Remote Diff check`,
235+
describe: "Experimental: Enable The Deployment Remote Diff check",
236236
type: "boolean",
237237
hidden: true,
238238
alias: ["x-remote-diff-check"],
239239
},
240+
strict: {
241+
describe:
242+
"Enables strict mode for the deploy command, this prevents deployments to occur when there are even small potential risks.",
243+
type: "boolean",
244+
default: false,
245+
},
240246
},
241247
behaviour: {
242248
useConfigRedirectIfAvailable: true,
@@ -251,7 +257,7 @@ export const deployCommand = createCommand({
251257
validateArgs(args) {
252258
if (args.nodeCompat) {
253259
throw new UserError(
254-
`The --node-compat flag is no longer supported as of Wrangler v4. Instead, use the \`nodejs_compat\` compatibility flag. This includes the functionality from legacy \`node_compat\` polyfills and natively implemented Node.js APIs. See https://developers.cloudflare.com/workers/runtime-apis/nodejs for more information.`,
260+
"The --node-compat flag is no longer supported as of Wrangler v4. Instead, use the `nodejs_compat` compatibility flag. This includes the functionality from legacy `node_compat` polyfills and natively implemented Node.js APIs. See https://developers.cloudflare.com/workers/runtime-apis/nodejs for more information.",
255261
{ telemetryMessage: true }
256262
);
257263
}
@@ -382,6 +388,7 @@ export const deployCommand = createCommand({
382388
dispatchNamespace: args.dispatchNamespace,
383389
experimentalAutoCreate: args.experimentalAutoCreate,
384390
containersRollout: args.containersRollout,
391+
strict: args.strict,
385392
});
386393

387394
writeOutput({

0 commit comments

Comments
 (0)