Skip to content

Commit 05440a1

Browse files
enable environment targeting in config patching with wrangler deploy --x-remote-diff-check (#11160)
* enable environment targeting in `wrangler deploy --x-remote-diff-check` * remove `isTopLevelOnly` logic
1 parent 305d7bf commit 05440a1

File tree

7 files changed

+134
-36
lines changed

7 files changed

+134
-36
lines changed

.changeset/dark-bars-turn.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Allows auto-update of the local Wrangler configuration file to match remote configuration when running `wrangler deploy --env <TARGET_ENV>`
6+
7+
When running `wrangler deploy`, with `--x-remote-diff-check` and after cancelling the deployment due to destructive changes present in the local config file, Wrangler offers to update the Wrangler configuration file to match the remote configuration. This wasn't however enabled when a target environment was specified (via the `--env|-e` flag). Now this will also apply when an environment is targeted.

packages/workers-utils/src/config/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616
* Notes:
1717
*
1818
* - Fields that are only specified in `ConfigFields` and not `Environment` can only appear
19-
* in the top level config and should not appear in any environments.
19+
* in the top level config and should not appear in any environments.
2020
* - Fields that are specified in `PagesConfigFields` are only relevant for Pages projects
2121
* - All top level fields in config and environments are optional in the Wrangler configuration file.
2222
*

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14934,7 +14934,7 @@ export default{
1493414934
script: {
1493514935
created_on: "2025-08-07T09:34:47.846308Z",
1493614936
modified_on: "2025-08-08T10:48:12.688997Z",
14937-
id: "silent-firefly-dbe3",
14937+
id: "my-worker-id",
1493814938
observability: { enabled: true, head_sampling_rate: 1 },
1493914939
compatibility_date: "2024-04-24",
1494014940
},
@@ -15000,7 +15000,7 @@ export default{
1500015000
script: {
1500115001
created_on: "2025-08-07T09:34:47.846308Z",
1500215002
modified_on: "2025-08-08T10:48:12.688997Z",
15003-
id: "silent-firefly-dbe3",
15003+
id: "my-worker-id",
1500415004
observability: { enabled: true, head_sampling_rate: 1 },
1500515005
compatibility_date: "2024-04-24",
1500615006
},
@@ -15065,7 +15065,7 @@ export default{
1506515065
script: {
1506615066
created_on: "2025-08-07T09:34:47.846308Z",
1506715067
modified_on: "2025-08-08T10:48:12.688997Z",
15068-
id: "silent-firefly-dbe3",
15068+
id: "my-worker-id",
1506915069
observability: { enabled: true, head_sampling_rate: 1 },
1507015070
compatibility_date: "2024-04-24",
1507115071
},

packages/wrangler/src/__tests__/deploy/get-config-patch.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,56 @@ describe("getConfigPatch", () => {
250250
},
251251
});
252252
});
253+
254+
test("configs get added/set to a target environment", () => {
255+
/*
256+
Note: in the remote configuration we don't know if a value is actually there because
257+
inherited from the top level or not, so to be safe we just add it to the target
258+
environment, this applies to all configurations besides the ones that are only
259+
allowed at the top level
260+
*/
261+
expect(
262+
getConfigPatch(
263+
{
264+
preview_urls: {
265+
__old: false,
266+
__new: true,
267+
},
268+
kv_namespaces: [
269+
[
270+
"-",
271+
{
272+
id: "<my-kv>",
273+
binding: "MY_KV",
274+
},
275+
],
276+
[
277+
"-",
278+
{
279+
id: "<my-kv-a>",
280+
binding: "MY_KV_A",
281+
},
282+
],
283+
],
284+
},
285+
"staging"
286+
)
287+
).toEqual({
288+
env: {
289+
staging: {
290+
kv_namespaces: [
291+
{
292+
binding: "MY_KV",
293+
id: "<my-kv>",
294+
},
295+
{
296+
binding: "MY_KV_A",
297+
id: "<my-kv-a>",
298+
},
299+
],
300+
preview_urls: false,
301+
},
302+
},
303+
});
304+
});
253305
});

packages/wrangler/src/__tests__/deploy/get-remote-config-diff.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe("getRemoteConfigsDiff", () => {
1919
it("should handle a very simple diffing scenario (no diffs, random order)", () => {
2020
const { diff, nonDestructive } = getRemoteConfigDiff(
2121
{
22-
name: "silent-firefly-dbe3",
22+
name: "my-worker-id",
2323
main: "/tmp/src/index.js",
2424
workers_dev: true,
2525
preview_urls: true,
@@ -31,7 +31,7 @@ describe("getRemoteConfigsDiff", () => {
3131
observability: { enabled: true, head_sampling_rate: 1 },
3232
},
3333
{
34-
name: "silent-firefly-dbe3",
34+
name: "my-worker-id",
3535
workers_dev: undefined,
3636
placement: undefined,
3737
compatibility_date: "2025-07-08",
@@ -50,14 +50,14 @@ describe("getRemoteConfigsDiff", () => {
5050
it("should handle a very simple diffing scenario where there is only an addition to an array (specifically in `kv_namespaces`)", () => {
5151
const { diff, nonDestructive } = getRemoteConfigDiff(
5252
{
53-
name: "silent-firefly-dbe3",
53+
name: "my-worker-id",
5454
main: "/tmp/src/index.js",
5555
workers_dev: true,
5656
kv_namespaces: [{ binding: "MY_KV", id: "<kv-id>" }],
5757
preview_urls: true,
5858
},
5959
{
60-
name: "silent-firefly-dbe3",
60+
name: "my-worker-id",
6161
main: "/tmp/src/index.js",
6262
workers_dev: true,
6363
preview_urls: true,
@@ -83,7 +83,7 @@ describe("getRemoteConfigsDiff", () => {
8383
it("should handle a very simple diffing scenario (some diffs, random order)", () => {
8484
const { diff, nonDestructive } = getRemoteConfigDiff(
8585
{
86-
name: "silent-firefly-dbe3",
86+
name: "my-worker-id",
8787
main: "/tmp/src/index.js",
8888
workers_dev: true,
8989
preview_urls: true,
@@ -98,7 +98,7 @@ describe("getRemoteConfigsDiff", () => {
9898
compatibility_date: "2025-07-09",
9999
main: "/tmp/src/index.js",
100100
compatibility_flags: [],
101-
name: "silent-firefly-dbe3",
101+
name: "my-worker-id",
102102
workers_dev: true,
103103
limits: undefined,
104104
placement: undefined,
@@ -121,7 +121,7 @@ describe("getRemoteConfigsDiff", () => {
121121
it("should handle a diffing scenario with only additions", () => {
122122
const { diff, nonDestructive } = getRemoteConfigDiff(
123123
{
124-
name: "silent-firefly-dbe3",
124+
name: "my-worker-id",
125125
main: "/tmp/src/index.js",
126126
workers_dev: true,
127127
preview_urls: true,
@@ -134,7 +134,7 @@ describe("getRemoteConfigsDiff", () => {
134134
kv_namespaces: [{ binding: "MY_KV", id: "my-kv-123" }],
135135
},
136136
{
137-
name: "silent-firefly-dbe3",
137+
name: "my-worker-id",
138138
main: "src/index.js",
139139
compatibility_date: "2025-07-08",
140140
account_id: "account-id-123",
@@ -163,7 +163,7 @@ describe("getRemoteConfigsDiff", () => {
163163
it("should handle a diffing scenario with only deletions", () => {
164164
const { diff, nonDestructive } = getRemoteConfigDiff(
165165
{
166-
name: "silent-firefly-dbe3",
166+
name: "my-worker-id",
167167
main: "/tmp/src/index.js",
168168
workers_dev: true,
169169
preview_urls: false,
@@ -176,7 +176,7 @@ describe("getRemoteConfigsDiff", () => {
176176
kv_namespaces: [{ binding: "MY_KV", id: "my-kv-123" }],
177177
},
178178
{
179-
name: "silent-firefly-dbe3",
179+
name: "my-worker-id",
180180
main: "/tmp/src/index.js",
181181
workers_dev: true,
182182
preview_urls: false,
@@ -206,7 +206,7 @@ describe("getRemoteConfigsDiff", () => {
206206
it("should handle a diffing scenario with modifications and removals", () => {
207207
const { diff, nonDestructive } = getRemoteConfigDiff(
208208
{
209-
name: "silent-firefly-dbe3",
209+
name: "my-worker-id",
210210
main: "/tmp/src/index.js",
211211
workers_dev: true,
212212
preview_urls: true,
@@ -219,7 +219,7 @@ describe("getRemoteConfigsDiff", () => {
219219
kv_namespaces: [{ binding: "MY_KV", id: "my-kv-123" }],
220220
},
221221
{
222-
name: "silent-firefly-dbe3",
222+
name: "my-worker-id",
223223
main: "src/index.js",
224224
compatibility_date: "2025-07-09",
225225
observability: {

packages/wrangler/src/deploy/config-diffs.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,23 @@ function orderObjectFields<T extends Record<string, unknown>>(
290290
* Given a config diff generates a patch object that can be passed to `experimental_patchConfig` to revert the
291291
* changes in the config object that are described by the config diff.
292292
*
293+
* If the config is for a specific target environment, only the environment config object will be targeted for the patch.
294+
*
293295
* @param configDiff The target config diff
296+
* @param targetEnvironment the target environment if any
294297
* @returns The patch object to pass to `experimental_patchConfig` to revert the changes
295298
*/
296-
export function getConfigPatch(configDiff: ConfigDiff["diff"]): RawConfig {
299+
export function getConfigPatch(
300+
configDiff: ConfigDiff["diff"],
301+
targetEnvironment?: string | undefined
302+
): RawConfig {
297303
const patchObj: RawConfig = {};
298304

299-
populateConfigPatch(configDiff, patchObj as Record<string, JsonLike>);
305+
populateConfigPatch(
306+
configDiff,
307+
patchObj as Record<string, JsonLike>,
308+
targetEnvironment
309+
);
300310

301311
return patchObj;
302312
}
@@ -306,10 +316,12 @@ export function getConfigPatch(configDiff: ConfigDiff["diff"]): RawConfig {
306316
*
307317
* @param diff The current section of the config diff that is being analyzed
308318
* @param patchObj The current section of the patch object that is being populated
319+
* @param targetEnvironment the target environment if any
309320
*/
310321
function populateConfigPatch(
311322
diff: JsonLike,
312-
patchObj: Record<string, JsonLike> | JsonLike[]
323+
patchObj: Record<string, JsonLike> | JsonLike[],
324+
targetEnvironment?: string
313325
): void {
314326
if (!diff || typeof diff !== "object") {
315327
return;
@@ -324,7 +336,7 @@ function populateConfigPatch(
324336

325337
// We know that patchObj is not an array here
326338
assert(!Array.isArray(patchObj));
327-
return populateConfigPatchObject(diff, patchObj);
339+
return populateConfigPatchObject(diff, patchObj, targetEnvironment);
328340
}
329341

330342
/**
@@ -371,34 +383,64 @@ function populateConfigPatchArray(diff: JsonLike[], patchArray: JsonLike[]) {
371383
*
372384
* @param diff The current section of the config diff that is being analyzed
373385
* @param patchObj The current section of the patch object that is being populated
386+
* @param targetEnvironment the target environment if any
374387
*/
375388
function populateConfigPatchObject(
376389
diff: { [id: string]: JsonLike },
377-
patchObj: Record<string, JsonLike>
390+
patchObj: Record<string, JsonLike>,
391+
targetEnvironment?: string
378392
) {
393+
const getEnvObj = (targetEnv: string) => {
394+
patchObj.env ??= {};
395+
const patchObjEnv = patchObj.env as Record<string, Record<string, unknown>>;
396+
patchObjEnv[targetEnv] ??= {};
397+
return patchObjEnv[targetEnv];
398+
};
379399
Object.keys(diff)
380400
.filter((key) => diff[key] && typeof diff[key] === "object")
381401
.forEach((key) => {
382402
if (isModifiedDiffValue(diff[key])) {
383-
patchObj[key] = diff[key].__old;
403+
if (targetEnvironment) {
404+
getEnvObj(targetEnvironment)[key] = diff[key].__old;
405+
} else {
406+
patchObj[key] = diff[key].__old;
407+
}
384408
return;
385409
}
386410

387-
patchObj[key] = Array.isArray(diff[key]) ? [] : {};
411+
if (targetEnvironment) {
412+
getEnvObj(targetEnvironment)[key] ??= Array.isArray(diff[key])
413+
? []
414+
: {};
415+
} else {
416+
patchObj[key] ??= Array.isArray(diff[key]) ? [] : {};
417+
}
388418

389419
Object.entries(diff[key] as Record<string, JsonLike>).forEach(
390420
([entryKey, entryValue]) => {
391421
if (entryKey.endsWith("__deleted")) {
392-
(patchObj[key] as Record<string, unknown>)[
393-
entryKey.replace("__deleted", "")
394-
] = entryValue;
422+
let patchObjectToUpdate = patchObj[key] as Record<string, unknown>;
423+
if (targetEnvironment) {
424+
const envObj = getEnvObj(targetEnvironment);
425+
envObj[key] ??= {};
426+
patchObjectToUpdate = envObj[key] as Record<string, unknown>;
427+
}
428+
patchObjectToUpdate[entryKey.replace("__deleted", "")] = entryValue;
395429
return;
396430
}
397431
}
398432
);
399433

400434
if (diff[key] && typeof diff[key] === "object") {
401-
populateConfigPatch(diff[key], patchObj[key]);
435+
populateConfigPatch(
436+
diff[key],
437+
(targetEnvironment
438+
? getEnvObj(targetEnvironment)[key]
439+
: patchObj[key]) as Record<string, JsonLike> | JsonLike[]
440+
// Note: we are not passing the target environment since in the recursive calls
441+
// we are already one level deep and dealing with the environment specific
442+
// patch object
443+
);
402444
return;
403445
}
404446
});

packages/wrangler/src/deploy/deploy.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -432,22 +432,19 @@ export default async function deploy(props: Props): Promise<{
432432
config.userConfigPath &&
433433
/\.jsonc?$/.test(config.userConfigPath)
434434
) {
435-
const targetingEnvironment = !!props.env;
436-
437435
if (
438-
// TODO: Currently if the user is targeting an environment we don't offer them to update
439-
// their config file since that is fairly nuanced, we should also support environments
440-
// (the best we can) here
441-
!targetingEnvironment &&
442-
(await confirm(
436+
await confirm(
443437
"Would you like to update the local config file with the remote values?",
444438
{
445439
defaultValue: true,
446440
fallbackValue: true,
447441
}
448-
))
442+
)
449443
) {
450-
const patchObj: RawConfig = getConfigPatch(configDiff.diff);
444+
const patchObj: RawConfig = getConfigPatch(
445+
configDiff.diff,
446+
props.env
447+
);
451448

452449
experimental_patchConfig(
453450
config.userConfigPath,

0 commit comments

Comments
 (0)