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
7 changes: 7 additions & 0 deletions .changeset/calm-camels-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": minor
---

Add strict mode for the `wrangler deploy` command

Add a new flag: `--strict` that makes the `wrangler deploy` command be more strict and not deploy workers when the deployment could 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.
93 changes: 93 additions & 0 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14021,6 +14021,99 @@ export default{
return normalizedLog;
}
});

describe("with strict mode enabled", () => {
it("should error if there are remote config difference (with --x-remote-diff-check) in non-interactive mode", async () => {
setIsTTY(false);

writeWorkerSource();
mockGetServiceByName("test-name", "production", "dash");
writeWranglerConfig(
{
compatibility_date: "2024-04-24",
main: "./index.js",
},
"./wrangler.json"
);
mockSubDomainRequest();
mockUploadWorkerRequest();
mockGetServiceBindings("test-name", []);
mockGetServiceRoutes("test-name", []);
mockGetServiceCustomDomainRecords([]);
mockGetServiceSubDomainData("test-name", {
enabled: true,
previews_enabled: false,
});
mockGetServiceSchedules("test-name", { schedules: [] });
mockGetServiceMetadata("test-name", {
created_on: "2025-08-07T09:34:47.846308Z",
modified_on: "2025-08-08T10:48:12.688997Z",
script: {
created_on: "2025-08-07T09:34:47.846308Z",
modified_on: "2025-08-08T10:48:12.688997Z",
id: "silent-firefly-dbe3",
observability: { enabled: true, head_sampling_rate: 1 },
compatibility_date: "2024-04-24",
},
} as unknown as ServiceMetadataRes["default_environment"]);

await runWrangler("deploy --x-remote-diff-check --strict");

expect(std.warn).toMatchInlineSnapshot(`
"▲ [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:

\\"bindings\\": []
},
\\"observability\\": {
- \\"enabled\\": true,
+ \\"enabled\\": false,
\\"head_sampling_rate\\": 1,
\\"logs\\": {
\\"enabled\\": false,

Deploying the Worker will override the remote configuration with your local one.

"
`);

expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Aborting the deployment operation because of conflicts. To override and deploy anyway remove the \`--strict\` flag

"
`);
// note: the test and the wrangler run share the same process, and we expect the deploy command (which fails)
// to set a non-zero exit code
expect(process.exitCode).not.toBe(0);
});

it("should error when worker was last deployed from api", async () => {
setIsTTY(false);

msw.use(...mswSuccessDeploymentScriptAPI);
writeWranglerConfig();
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest();

await runWrangler("deploy ./index --strict");

expect(std.warn).toMatchInlineSnapshot(`
"▲ [WARNING] You are about to publish a Workers Service that was last updated via the script API.

Edits that have been made via the script API will be overridden by your local code and config.

"
`);
expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Aborting the deployment operation because of conflicts. To override and deploy anyway remove the \`--strict\` flag

"
`);
// note: the test and the wrangler run share the same process, and we expect the deploy command (which fails)
// to set a non-zero exit code
expect(process.exitCode).not.toBe(0);
});
});
});

/** Write mock assets to the file system so they can be uploaded. */
Expand Down
28 changes: 25 additions & 3 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from "../environments";
import { UserError } from "../errors";
import { getFlag } from "../experimental-flags";
import { isNonInteractiveOrCI } from "../is-interactive";
import { logger } from "../logger";
import { getMetricsUsageHeaders } from "../metrics";
import { isNavigatorDefined } from "../navigator-user-agent";
Expand Down Expand Up @@ -120,6 +121,7 @@ type Props = {
experimentalAutoCreate: boolean;
metafile: string | boolean | undefined;
containersRollout: "immediate" | "gradual" | undefined;
strict: boolean | undefined;
};

export type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
Expand Down Expand Up @@ -357,6 +359,8 @@ export default async function deploy(props: Props): Promise<{
workerTag: string | null;
targets?: string[];
}> {
const deployConfirm = getDeployConfirmFunction(props.strict);

// TODO: warn if git/hg has uncommitted changes
const { config, accountId, name, entry } = props;
let workerTag: string | null = null;
Expand Down Expand Up @@ -417,23 +421,23 @@ export default async function deploy(props: Props): Promise<{
`\n${configDiff.diff}\n\n` +
"Deploying the Worker will override the remote configuration with your local one."
);
if (!(await confirm("Would you like to continue?"))) {
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
}
}
} else {
logger.warn(
`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.`
);
if (!(await confirm("Would you like to continue?"))) {
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
}
}
} else if (script.last_deployed_from === "api") {
logger.warn(
`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.`
);
if (!(await confirm("Would you like to continue?"))) {
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
}
}
Expand Down Expand Up @@ -1430,3 +1434,21 @@ export async function updateQueueConsumers(

return updateConsumers;
}

function getDeployConfirmFunction(
strictMode = false
): (text: string) => Promise<boolean> {
const nonInteractive = isNonInteractiveOrCI();

if (nonInteractive && strictMode) {
return async () => {
logger.error(
"Aborting the deployment operation because of conflicts. To override and deploy anyway remove the `--strict` flag"
);
process.exitCode = 1;
return false;
};
}

return confirm;
}
11 changes: 9 additions & 2 deletions packages/wrangler/src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,17 @@ export const deployCommand = createCommand({
choices: ["immediate", "gradual"] as const,
},
"experimental-deploy-remote-diff-check": {
describe: `Experimental: Enable The Deployment Remote Diff check`,
describe: "Experimental: Enable The Deployment Remote Diff check",
type: "boolean",
hidden: true,
alias: ["x-remote-diff-check"],
},
strict: {
describe:
"Enables strict mode for the deploy command, this prevents deployments to occur when there are even small potential risks.",
type: "boolean",
default: false,
},
},
behaviour: {
useConfigRedirectIfAvailable: true,
Expand All @@ -251,7 +257,7 @@ export const deployCommand = createCommand({
validateArgs(args) {
if (args.nodeCompat) {
throw new UserError(
`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.`,
"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.",
{ telemetryMessage: true }
);
}
Expand Down Expand Up @@ -382,6 +388,7 @@ export const deployCommand = createCommand({
dispatchNamespace: args.dispatchNamespace,
experimentalAutoCreate: args.experimentalAutoCreate,
containersRollout: args.containersRollout,
strict: args.strict,
});

writeOutput({
Expand Down
Loading