Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .changeset/tidy-carpets-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"wrangler": patch
"@cloudflare/deploy-helpers": patch
---

Preserve user config binding fields when provisioning resources through a redirected config

Wrangler now writes only newly provisioned resource identifiers into the original config, preventing generated relative paths such as D1 `migrations_dir` from replacing user-authored paths during Vite deployments.
63 changes: 50 additions & 13 deletions packages/deploy-helpers/src/deploy/helpers/provision-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -903,22 +903,54 @@ const HANDLERS = {
},
};

type RawConfigBinding = { binding: string } & Record<string, unknown>;

function getRawConfigBindings(
config: RawConfig,
resourceType: keyof typeof HANDLERS
): Array<{ binding: string }> {
): RawConfigBinding[] {
if (resourceType === "queue") {
return config.queues?.producers ?? [];
return (config.queues?.producers ?? []) as RawConfigBinding[];
}

const configField = HANDLERS[resourceType].configField;
return (config[configField] ?? []) as Array<{ binding: string }>;
return (config[configField] ?? []) as RawConfigBinding[];
}

function addProvisionedIdentifier(
originalBinding: RawConfigBinding,
binding: ProvisionableBinding
): RawConfigBinding {
switch (binding.type) {
case "kv_namespace":
return { ...originalBinding, id: binding.id };
case "d1":
return { ...originalBinding, database_id: binding.database_id };
case "r2_bucket":
return { ...originalBinding, bucket_name: binding.bucket_name };
case "ai_search_namespace":
return { ...originalBinding, namespace: binding.namespace };
case "agent_memory":
return { ...originalBinding, namespace: binding.namespace };
case "queue":
return {
...originalBinding,
queue:
typeof binding.queue_name === "string"
? binding.queue_name
: undefined,
};
case "dispatch_namespace":
return { ...originalBinding, namespace: binding.namespace };
case "flagship":
return { ...originalBinding, app_id: binding.app_id };
}
}

function addBindingToPatch(
patch: RawConfig,
resourceType: ProvisionableBinding["type"],
binding: ReturnType<typeof toConfigBinding>
binding: ReturnType<typeof toConfigBinding> | RawConfigBinding
): void {
const serialisableBinding = Object.fromEntries(
Object.entries(binding).filter(
Expand Down Expand Up @@ -1274,7 +1306,10 @@ export async function provisionBindings(

const patch: RawConfig = {};

const existingBindingNames = new Map<keyof typeof HANDLERS, Set<string>>();
const originalBindings = new Map<
keyof typeof HANDLERS,
Map<string, RawConfigBinding>
>();

const isUsingRedirectedConfig =
config.userConfigPath && config.userConfigPath !== config.configPath;
Expand All @@ -1291,14 +1326,14 @@ export async function provisionBindings(
for (const resourceType of Object.keys(
HANDLERS
) as (keyof typeof HANDLERS)[]) {
const bindingNames = new Set<string>();
const bindingsByName = new Map<string, RawConfigBinding>();
for (const binding of getRawConfigBindings(
unredirectedConfig,
resourceType
)) {
bindingNames.add(binding.binding);
bindingsByName.set(binding.binding, binding);
}
existingBindingNames.set(resourceType, bindingNames);
originalBindings.set(resourceType, bindingsByName);
}
}

Expand All @@ -1308,13 +1343,15 @@ export async function provisionBindings(
}

// See above for why we skip writing back some bindings to the config file.
if (
isUsingRedirectedConfig &&
!existingBindingNames.get(binding.type)?.has(bindingName)
) {
const originalBinding = originalBindings
.get(binding.type)
?.get(bindingName);
if (isUsingRedirectedConfig && !originalBinding) {
continue;
}
const bindingToWrite = toConfigBinding(bindingName, binding);
const bindingToWrite = originalBinding
? addProvisionedIdentifier(originalBinding, binding)
: toConfigBinding(bindingName, binding);
addBindingToPatch(patch, binding.type, bindingToWrite);
}

Expand Down
11 changes: 9 additions & 2 deletions packages/wrangler/src/__tests__/provision.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,15 +1076,21 @@ describe("resource provisioning", () => {
`);
});

it("can provision KV, R2 and D1 bindings with new resources w/ redirected config", async ({
it("preserves D1 migrations_dir when provisioning with a redirected config", async ({
expect,
}) => {
writeWranglerConfig({
main: "index.js",
kv_namespaces: [{ binding: "KV" }],
r2_buckets: [{ binding: "R2" }],
d1_databases: [{ binding: "D1", migrations_dir: "migrations" }],
});
writeRedirectedWranglerConfig({
main: "../index.js",
compatibility_flags: ["nodejs_compat"],
kv_namespaces: [{ binding: "KV" }],
r2_buckets: [{ binding: "R2" }],
d1_databases: [{ binding: "D1" }],
d1_databases: [{ binding: "D1", migrations_dir: "../../migrations" }],
});
mockGetSettings();
mockListKVNamespacesRequest(expect, {
Expand Down Expand Up @@ -1234,6 +1240,7 @@ describe("resource provisioning", () => {

[[d1_databases]]
binding = "D1"
migrations_dir = "migrations"
database_id = "new-d1-id"
"
`);
Expand Down
Loading