diff --git a/.changeset/tidy-carpets-smile.md b/.changeset/tidy-carpets-smile.md new file mode 100644 index 00000000000..0f185763f50 --- /dev/null +++ b/.changeset/tidy-carpets-smile.md @@ -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. diff --git a/packages/deploy-helpers/src/deploy/helpers/provision-bindings.ts b/packages/deploy-helpers/src/deploy/helpers/provision-bindings.ts index bb9ed81564a..2723f5b0796 100644 --- a/packages/deploy-helpers/src/deploy/helpers/provision-bindings.ts +++ b/packages/deploy-helpers/src/deploy/helpers/provision-bindings.ts @@ -903,22 +903,54 @@ const HANDLERS = { }, }; +type RawConfigBinding = { binding: string } & Record; + 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 + binding: ReturnType | RawConfigBinding ): void { const serialisableBinding = Object.fromEntries( Object.entries(binding).filter( @@ -1274,7 +1306,10 @@ export async function provisionBindings( const patch: RawConfig = {}; - const existingBindingNames = new Map>(); + const originalBindings = new Map< + keyof typeof HANDLERS, + Map + >(); const isUsingRedirectedConfig = config.userConfigPath && config.userConfigPath !== config.configPath; @@ -1291,14 +1326,14 @@ export async function provisionBindings( for (const resourceType of Object.keys( HANDLERS ) as (keyof typeof HANDLERS)[]) { - const bindingNames = new Set(); + const bindingsByName = new Map(); for (const binding of getRawConfigBindings( unredirectedConfig, resourceType )) { - bindingNames.add(binding.binding); + bindingsByName.set(binding.binding, binding); } - existingBindingNames.set(resourceType, bindingNames); + originalBindings.set(resourceType, bindingsByName); } } @@ -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); } diff --git a/packages/wrangler/src/__tests__/provision.test.ts b/packages/wrangler/src/__tests__/provision.test.ts index d8b3ee5a7ba..52712ce8338 100644 --- a/packages/wrangler/src/__tests__/provision.test.ts +++ b/packages/wrangler/src/__tests__/provision.test.ts @@ -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, { @@ -1234,6 +1240,7 @@ describe("resource provisioning", () => { [[d1_databases]] binding = "D1" + migrations_dir = "migrations" database_id = "new-d1-id" " `);