Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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/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.
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 (due to strict mode, to prevent this failure either 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 (due to strict mode, to prevent this failure either 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 (due to strict mode, to prevent this failure either 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