11import assert from "node:assert" ;
22import { fetchResult } from "../cfetch" ;
3+ import { experimental_patchConfig } from "../cli" ;
4+ import { PatchConfigError } from "../config/patch-config" ;
35import { createD1Database } from "../d1/create" ;
46import { listDatabases } from "../d1/list" ;
57import { getDatabaseInfoFromIdOrName } from "../d1/utils" ;
68import { prompt , select } from "../dialogs" ;
79import { UserError } from "../errors" ;
10+ import { isNonInteractiveOrCI } from "../is-interactive" ;
811import { createKVNamespace , listKVNamespaces } from "../kv/helpers" ;
912import { logger } from "../logger" ;
1013import * as metrics from "../metrics" ;
1114import { APIError } from "../parse" ;
1215import { createR2Bucket , getR2Bucket , listR2Buckets } from "../r2/helpers" ;
1316import { isLegacyEnv } from "../utils/isLegacyEnv" ;
1417import { printBindings } from "../utils/print-bindings" ;
15- import type { Config } from "../config" ;
18+ import type { Config , RawConfig } from "../config" ;
1619import type { ComplianceConfig } from "../environment-variables/misc-variables" ;
1720import type { WorkerMetadataBinding } from "./create-worker-upload-form" ;
1821import type {
@@ -161,6 +164,13 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
161164 get name ( ) : string | undefined {
162165 return this . binding . bucket_name as string ;
163166 }
167+
168+ override inherit ( ) : void {
169+ if ( ! this . binding . bucket_name ) {
170+ this . binding . bucket_name = INHERIT_SYMBOL ;
171+ }
172+ }
173+
164174 async create ( name : string ) {
165175 await createR2Bucket (
166176 this . complianceConfig ,
@@ -183,7 +193,10 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
183193 ( existing ) =>
184194 existing . type === this . type &&
185195 existing . name === this . binding . binding &&
186- existing . jurisdiction === this . binding . jurisdiction
196+ existing . jurisdiction === this . binding . jurisdiction &&
197+ ( this . binding . bucket_name
198+ ? this . binding . bucket_name === existing . bucket_name
199+ : true )
187200 ) ;
188201 }
189202 async isConnectedToExistingResource ( ) : Promise < boolean > {
@@ -423,6 +436,7 @@ async function collectPendingResources(
423436 ( a , b ) => HANDLERS [ a . resourceType ] . sort - HANDLERS [ b . resourceType ] . sort
424437 ) ;
425438}
439+
426440export async function provisionBindings (
427441 bindings : CfWorkerInit [ "bindings" ] ,
428442 accountId : string ,
@@ -438,6 +452,11 @@ export async function provisionBindings(
438452 ) ;
439453
440454 if ( pendingResources . length > 0 ) {
455+ if ( ! config . configPath ) {
456+ throw new UserError (
457+ "Provisioning resources is not supported without a config file"
458+ ) ;
459+ }
441460 if ( ! isLegacyEnv ( config ) ) {
442461 throw new UserError (
443462 "Provisioning resources is not supported with a service environment"
@@ -468,6 +487,45 @@ export async function provisionBindings(
468487 ) ;
469488 }
470489
490+ const patch : RawConfig = { } ;
491+
492+ for ( const resource of pendingResources ) {
493+ patch [ resource . resourceType ] = config [ resource . resourceType ] . map (
494+ ( binding ) => {
495+ if ( binding . binding === resource . binding ) {
496+ // Using an early return here would be nicer but makes TS blow up
497+ binding = resource . handler . binding ;
498+ }
499+
500+ return Object . fromEntries (
501+ Object . entries ( binding ) . filter (
502+ // Make sure all the values are JSON serialisable.
503+ // Otherwise we end up with "undefined" in the config
504+ ( [ _ , value ] ) => typeof value === "string"
505+ )
506+ ) as typeof binding ;
507+ }
508+ ) ;
509+ }
510+
511+ // If the user is performing an interactive deploy, write the provisioned IDs back to the config file.
512+ // This is not necessary, as future deploys can use inherited resources, but it can help with
513+ // portability of the config file, and adds robustness to bindings being renamed.
514+ if ( ! isNonInteractiveOrCI ( ) ) {
515+ try {
516+ await experimental_patchConfig ( config . configPath , patch , false ) ;
517+ logger . log (
518+ "Your Worker was deployed with provisioned resources. We've written the IDs of these resources to your config file, which you can choose to save or discard—either way future deploys will continue to work."
519+ ) ;
520+ } catch ( e ) {
521+ if ( e instanceof PatchConfigError ) {
522+ // no-op — if the user is using TOML config we can't update it.
523+ } else {
524+ throw e ;
525+ }
526+ }
527+ }
528+
471529 const resourceCount = pendingResources . reduce (
472530 ( acc , resource ) => {
473531 acc [ resource . resourceType ] ??= 0 ;
0 commit comments