Skip to content
Open
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/tidy-kv-placeholder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Replace existing bindings when adding newly created resources to Wrangler configuration

When config updates are authorized interactively or through `--update-config` or `--binding`, Wrangler now replaces an existing resource binding with the selected name instead of adding a duplicate entry. This allows template bindings with placeholder resource IDs to be updated in both interactive and non-interactive workflows.
90 changes: 90 additions & 0 deletions packages/wrangler/src/__tests__/update-config-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,96 @@ describe("createdResourceConfig()", () => {
);
});

it("interactive: replaces an existing binding with the same name", async ({
expect,
}) => {
writeWranglerConfig(
{
name: "worker",
kv_namespaces: [
{ binding: "OTHER_KV", id: "other-id" },
{
binding: "KV",
id: "<your-kv-namespace-id>",
preview_id: "preview-id",
remote: true,
},
],
},
"wrangler.json"
);

setIsTTY(true);
mockConfirm({
text: "Would you like Wrangler to add it on your behalf?",
result: true,
});
mockPrompt({
text: "What binding name would you like to use?",
result: "KV",
});
mockConfirm({
text: "For local dev, do you want to connect to the remote resource instead of a local resource?",
result: false,
});

await createdResourceConfig(
"kv_namespaces",
(name) => ({ binding: name ?? "KV", id: "random-id" }),
"wrangler.json",
undefined
);

expect(await readFile("wrangler.json", "utf8")).toMatchInlineSnapshot(`
"{
"compatibility_date": "2022-01-12",
"name": "worker",
"kv_namespaces": [
{
"binding": "OTHER_KV",
"id": "other-id"
},
{
"binding": "KV",
"id": "random-id",
"preview_id": "preview-id"
}
]
}"
`);
});

it("non interactive: replaces an existing binding with the same name", async ({
expect,
}) => {
writeWranglerConfig(
{
name: "worker",
kv_namespaces: [
{
binding: "KV",
id: "<your-kv-namespace-id>",
remote: true,
},
],
},
"wrangler.json"
);

await createdResourceConfig(
"kv_namespaces",
(name) => ({ binding: name ?? "KV", id: "random-id" }),
"wrangler.json",
undefined,
{ binding: "KV", updateConfig: true }
);

expect(await readFile("wrangler.json", "utf8")).toContain(
'"id": "random-id"'
);
expect(await readFile("wrangler.json", "utf8")).not.toContain('"remote"');
});

it("interactive: file update in env after answering yes", async ({
expect,
}) => {
Expand Down
34 changes: 29 additions & 5 deletions packages/wrangler/src/utils/add-created-resource-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
configFormat,
experimental_readRawConfig,
experimental_patchConfig,
formatConfigSnippet,
friendlyBindingNames,
Expand Down Expand Up @@ -129,16 +130,39 @@ export async function createdResourceConfig<K extends ValidKeys>(
{ defaultValue: false }
));

const configFilePatch = {
[resource]: [
{ ...snippet(bindingName), ...(useRemote ? { remote: true } : {}) },
],
const { rawConfig } = experimental_readRawConfig({ config: configPath });
const environment = env ? rawConfig.env?.[env] : rawConfig;

const binding = {
...snippet(bindingName),
remote: useRemote ? true : undefined,
};

const existingBindings = environment?.[resource];
const matchingBindingIndex = existingBindings?.findIndex(
(existingBinding) => existingBinding.binding === binding.binding
);

const shouldReplaceExistingBinding =
existingBindings !== undefined &&
matchingBindingIndex !== undefined &&
matchingBindingIndex !== -1;

let bindings: Partial<NonNullable<RawConfig[K]>[number]>[] = [binding];
if (shouldReplaceExistingBinding) {
bindings = existingBindings.map((existingBinding, index) => {
if (index !== matchingBindingIndex) {
return existingBinding;
}
return { ...existingBinding, ...binding };
});
}

const configFilePatch = { [resource]: bindings };
experimental_patchConfig(
configPath,
env ? { env: { [env]: configFilePatch } } : configFilePatch,
true
!shouldReplaceExistingBinding
);
}
}
Expand Down
Loading