1
1
import assert from "node:assert" ;
2
2
import { fetchResult } from "../cfetch" ;
3
+ import { experimental_patchConfig } from "../cli" ;
4
+ import { PatchConfigError } from "../config/patch-config" ;
3
5
import { createD1Database } from "../d1/create" ;
4
6
import { listDatabases } from "../d1/list" ;
5
7
import { getDatabaseInfoFromIdOrName } from "../d1/utils" ;
6
8
import { prompt , select } from "../dialogs" ;
7
9
import { UserError } from "../errors" ;
10
+ import { isNonInteractiveOrCI } from "../is-interactive" ;
8
11
import { createKVNamespace , listKVNamespaces } from "../kv/helpers" ;
9
12
import { logger } from "../logger" ;
10
13
import * as metrics from "../metrics" ;
11
14
import { APIError } from "../parse" ;
12
15
import { createR2Bucket , getR2Bucket , listR2Buckets } from "../r2/helpers" ;
13
16
import { isLegacyEnv } from "../utils/isLegacyEnv" ;
14
17
import { printBindings } from "../utils/print-bindings" ;
15
- import type { Config } from "../config" ;
18
+ import type { Config , RawConfig } from "../config" ;
16
19
import type { ComplianceConfig } from "../environment-variables/misc-variables" ;
17
20
import type { WorkerMetadataBinding } from "./create-worker-upload-form" ;
18
21
import type {
@@ -161,6 +164,13 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
161
164
get name ( ) : string | undefined {
162
165
return this . binding . bucket_name as string ;
163
166
}
167
+
168
+ override inherit ( ) : void {
169
+ if ( ! this . binding . bucket_name ) {
170
+ this . binding . bucket_name = INHERIT_SYMBOL ;
171
+ }
172
+ }
173
+
164
174
async create ( name : string ) {
165
175
await createR2Bucket (
166
176
this . complianceConfig ,
@@ -183,7 +193,10 @@ class R2Handler extends ProvisionResourceHandler<"r2_bucket", CfR2Bucket> {
183
193
( existing ) =>
184
194
existing . type === this . type &&
185
195
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 )
187
200
) ;
188
201
}
189
202
async isConnectedToExistingResource ( ) : Promise < boolean > {
@@ -423,6 +436,7 @@ async function collectPendingResources(
423
436
( a , b ) => HANDLERS [ a . resourceType ] . sort - HANDLERS [ b . resourceType ] . sort
424
437
) ;
425
438
}
439
+
426
440
export async function provisionBindings (
427
441
bindings : CfWorkerInit [ "bindings" ] ,
428
442
accountId : string ,
@@ -438,6 +452,11 @@ export async function provisionBindings(
438
452
) ;
439
453
440
454
if ( pendingResources . length > 0 ) {
455
+ if ( ! config . configPath ) {
456
+ throw new UserError (
457
+ "Provisioning resources is not supported without a config file"
458
+ ) ;
459
+ }
441
460
if ( ! isLegacyEnv ( config ) ) {
442
461
throw new UserError (
443
462
"Provisioning resources is not supported with a service environment"
@@ -468,6 +487,45 @@ export async function provisionBindings(
468
487
) ;
469
488
}
470
489
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
+
471
529
const resourceCount = pendingResources . reduce (
472
530
( acc , resource ) => {
473
531
acc [ resource . resourceType ] ??= 0 ;
0 commit comments