diff --git a/.changeset/fair-fishes-do.md b/.changeset/fair-fishes-do.md new file mode 100644 index 000000000000..a5f84f8ca206 --- /dev/null +++ b/.changeset/fair-fishes-do.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Validate duplicate bindings across all binding types diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 2faaa97f925c..f606e83296fa 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -6831,7 +6831,7 @@ addEventListener('fetch', event => {});` - Text Blobs: - TEXT_BLOB_ONE: my-entire-app-depends-on-this.cfg - TEXT_BLOB_TWO: the-entirety-of-human-knowledge.txt - - Unsafe: + - Unsafe Metadata: - some unsafe thing: UNSAFE_BINDING_ONE - another unsafe thing: UNSAFE_BINDING_TWO - Vars: @@ -6939,27 +6939,28 @@ addEventListener('fetch', event => {});` await expect(runWrangler("deploy index.js")).rejects .toMatchInlineSnapshot(` [Error: Processing wrangler.toml configuration: - - CONFLICTING_NAME_ONE assigned to Durable Object, KV Namespace, and R2 Bucket bindings. - - CONFLICTING_NAME_TWO assigned to Durable Object and KV Namespace bindings. - - CONFLICTING_NAME_THREE assigned to R2 Bucket, Text Blob, Unsafe, Environment Variable, WASM Module, and Data Blob bindings. - - CONFLICTING_NAME_FOUR assigned to Analytics Engine Dataset, Text Blob, and Unsafe bindings. + - CONFLICTING_NAME_THREE assigned to Data Blobs, R2 Buckets, Text Blobs, Unsafe Metadata, Vars, and Wasm Modules bindings. + - CONFLICTING_NAME_ONE assigned to Durable Objects, KV Namespaces, and R2 Buckets bindings. + - CONFLICTING_NAME_TWO assigned to Durable Objects and KV Namespaces bindings. + - CONFLICTING_NAME_FOUR assigned to Analytics Engine Datasets, Text Blobs, and Unsafe Metadata bindings. - Bindings must have unique names, so that they can all be referenced in the worker. Please change your bindings to have unique names.] `); expect(std.out).toMatchInlineSnapshot(`""`); expect(std.err).toMatchInlineSnapshot(` - "X [ERROR] Processing wrangler.toml configuration: + "X [ERROR] Processing wrangler.toml configuration: - - CONFLICTING_NAME_ONE assigned to Durable Object, KV Namespace, and R2 Bucket bindings. - - CONFLICTING_NAME_TWO assigned to Durable Object and KV Namespace bindings. - - CONFLICTING_NAME_THREE assigned to R2 Bucket, Text Blob, Unsafe, Environment Variable, WASM - Module, and Data Blob bindings. - - CONFLICTING_NAME_FOUR assigned to Analytics Engine Dataset, Text Blob, and Unsafe bindings. - - Bindings must have unique names, so that they can all be referenced in the worker. - Please change your bindings to have unique names. + - CONFLICTING_NAME_THREE assigned to Data Blobs, R2 Buckets, Text Blobs, Unsafe Metadata, Vars, + and Wasm Modules bindings. + - CONFLICTING_NAME_ONE assigned to Durable Objects, KV Namespaces, and R2 Buckets bindings. + - CONFLICTING_NAME_TWO assigned to Durable Objects and KV Namespaces bindings. + - CONFLICTING_NAME_FOUR assigned to Analytics Engine Datasets, Text Blobs, and Unsafe Metadata + bindings. + - Bindings must have unique names, so that they can all be referenced in the worker. + Please change your bindings to have unique names. - " - `); + " + `); expect(std.warn).toMatchInlineSnapshot(` "▲ [WARNING] Processing wrangler.toml configuration: @@ -7046,28 +7047,28 @@ addEventListener('fetch', event => {});` await expect(runWrangler("deploy index.js")).rejects .toMatchInlineSnapshot(` [Error: Processing wrangler.toml configuration: - - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Object bindings. - - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespace bindings. - - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Bucket bindings. - - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Dataset bindings. - - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe bindings. + - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Objects bindings. + - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespaces bindings. + - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Buckets bindings. + - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Datasets bindings. + - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe Metadata bindings. - Bindings must have unique names, so that they can all be referenced in the worker. Please change your bindings to have unique names.] `); expect(std.out).toMatchInlineSnapshot(`""`); expect(std.err).toMatchInlineSnapshot(` - "X [ERROR] Processing wrangler.toml configuration: + "X [ERROR] Processing wrangler.toml configuration: - - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Object bindings. - - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespace bindings. - - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Bucket bindings. - - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Dataset bindings. - - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe bindings. - - Bindings must have unique names, so that they can all be referenced in the worker. - Please change your bindings to have unique names. + - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Objects bindings. + - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespaces bindings. + - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Buckets bindings. + - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Datasets bindings. + - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe Metadata bindings. + - Bindings must have unique names, so that they can all be referenced in the worker. + Please change your bindings to have unique names. - " - `); + " + `); expect(std.warn).toMatchInlineSnapshot(` "▲ [WARNING] Processing wrangler.toml configuration: @@ -7196,34 +7197,34 @@ addEventListener('fetch', event => {});` await expect(runWrangler("deploy index.js")).rejects .toMatchInlineSnapshot(` [Error: Processing wrangler.toml configuration: - - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Object bindings. - - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespace bindings. - - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Bucket bindings. - - CONFLICTING_NAME_THREE assigned to R2 Bucket, Analytics Engine Dataset, Text Blob, Unsafe, Environment Variable, WASM Module, and Data Blob bindings. - - CONFLICTING_NAME_FOUR assigned to R2 Bucket, Analytics Engine Dataset, Text Blob, and Unsafe bindings. - - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Dataset bindings. - - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe bindings. + - CONFLICTING_NAME_THREE assigned to Data Blobs, R2 Buckets, Analytics Engine Datasets, Text Blobs, Unsafe Metadata, Vars, and Wasm Modules bindings. + - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Objects bindings. + - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespaces bindings. + - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Buckets bindings. + - CONFLICTING_NAME_FOUR assigned to R2 Buckets, Analytics Engine Datasets, Text Blobs, and Unsafe Metadata bindings. + - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Datasets bindings. + - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe Metadata bindings. - Bindings must have unique names, so that they can all be referenced in the worker. Please change your bindings to have unique names.] `); expect(std.out).toMatchInlineSnapshot(`""`); expect(std.err).toMatchInlineSnapshot(` - "X [ERROR] Processing wrangler.toml configuration: - - - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Object bindings. - - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespace bindings. - - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Bucket bindings. - - CONFLICTING_NAME_THREE assigned to R2 Bucket, Analytics Engine Dataset, Text Blob, Unsafe, - Environment Variable, WASM Module, and Data Blob bindings. - - CONFLICTING_NAME_FOUR assigned to R2 Bucket, Analytics Engine Dataset, Text Blob, and Unsafe - bindings. - - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Dataset bindings. - - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe bindings. - - Bindings must have unique names, so that they can all be referenced in the worker. - Please change your bindings to have unique names. + "X [ERROR] Processing wrangler.toml configuration: + + - CONFLICTING_NAME_THREE assigned to Data Blobs, R2 Buckets, Analytics Engine Datasets, Text + Blobs, Unsafe Metadata, Vars, and Wasm Modules bindings. + - CONFLICTING_DURABLE_OBJECT_NAME assigned to multiple Durable Objects bindings. + - CONFLICTING_KV_NAMESPACE_NAME assigned to multiple KV Namespaces bindings. + - CONFLICTING_R2_BUCKET_NAME assigned to multiple R2 Buckets bindings. + - CONFLICTING_NAME_FOUR assigned to R2 Buckets, Analytics Engine Datasets, Text Blobs, and + Unsafe Metadata bindings. + - CONFLICTING_AE_DATASET_NAME assigned to multiple Analytics Engine Datasets bindings. + - CONFLICTING_UNSAFE_NAME assigned to multiple Unsafe Metadata bindings. + - Bindings must have unique names, so that they can all be referenced in the worker. + Please change your bindings to have unique names. - " - `); + " + `); expect(std.warn).toMatchInlineSnapshot(` "▲ [WARNING] Processing wrangler.toml configuration: @@ -8395,7 +8396,7 @@ addEventListener('fetch', event => {});` "Total Upload: xx KiB / gzip: xx KiB Worker Startup Time: 100 ms Your worker has access to the following bindings: - - Unsafe: + - Unsafe Metadata: - binding-type: my-binding Uploaded test-name (TIMINGS) Deployed test-name triggers (TIMINGS) @@ -8442,7 +8443,7 @@ addEventListener('fetch', event => {});` "Total Upload: xx KiB / gzip: xx KiB Worker Startup Time: 100 ms Your worker has access to the following bindings: - - Unsafe: + - Unsafe Metadata: - plain_text: my-binding Uploaded test-name (TIMINGS) Deployed test-name triggers (TIMINGS) diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index cc843a98bf26..ba3dbe71b59e 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -196,6 +196,36 @@ function addLocalSuffix(id: string, local: boolean = false) { return `${id}${local ? " (local)" : ""}`; } +export const friendlyBindingNames: Record< + keyof CfWorkerInit["bindings"], + string +> = { + data_blobs: "Data Blobs", + durable_objects: "Durable Objects", + kv_namespaces: "KV Namespaces", + send_email: "Send Email", + queues: "Queues", + d1_databases: "D1 Databases", + vectorize: "Vectorize Indexes", + hyperdrive: "Hyperdrive Configs", + r2_buckets: "R2 Buckets", + logfwdr: "logfwdr", + services: "Services", + analytics_engine_datasets: "Analytics Engine Datasets", + text_blobs: "Text Blobs", + browser: "Browser", + ai: "AI", + version_metadata: "Worker Version Metadata", + unsafe: "Unsafe Metadata", + vars: "Vars", + wasm_modules: "Wasm Modules", + dispatch_namespaces: "Dispatch Namespaces", + mtls_certificates: "mTLS Certificates", + workflows: "Workflows", + pipelines: "Pipelines", + assets: "Assets", +} as const; + /** * Print all the bindings a worker using a given config would have access to */ @@ -217,7 +247,7 @@ export function printBindings( }; const output: { - type: string; + name: string; entries: { key: string; value: string | boolean }[]; }[] = []; @@ -249,7 +279,7 @@ export function printBindings( if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) { output.push({ - type: "Data Blobs", + name: friendlyBindingNames.data_blobs, entries: Object.entries(data_blobs).map(([key, value]) => ({ key, value: typeof value === "string" ? truncate(value) : "", @@ -259,7 +289,7 @@ export function printBindings( if (durable_objects !== undefined && durable_objects.bindings.length > 0) { output.push({ - type: "Durable Objects", + name: friendlyBindingNames.durable_objects, entries: durable_objects.bindings.map( ({ name, class_name, script_name }) => { let value = class_name; @@ -288,7 +318,7 @@ export function printBindings( if (workflows !== undefined && workflows.length > 0) { output.push({ - type: "Workflows", + name: friendlyBindingNames.workflows, entries: workflows.map(({ class_name, script_name, binding }) => { let value = class_name; if (script_name) { @@ -305,7 +335,7 @@ export function printBindings( if (kv_namespaces !== undefined && kv_namespaces.length > 0) { output.push({ - type: "KV Namespaces", + name: friendlyBindingNames.kv_namespaces, entries: kv_namespaces.map(({ binding, id }) => { return { key: binding, @@ -317,7 +347,7 @@ export function printBindings( if (send_email !== undefined && send_email.length > 0) { output.push({ - type: "Send Email", + name: friendlyBindingNames.send_email, entries: send_email.map( ({ name, destination_address, allowed_destination_addresses }) => { return { @@ -334,7 +364,7 @@ export function printBindings( if (queues !== undefined && queues.length > 0) { output.push({ - type: "Queues", + name: friendlyBindingNames.queues, entries: queues.map(({ binding, queue_name }) => { return { key: binding, @@ -346,7 +376,7 @@ export function printBindings( if (d1_databases !== undefined && d1_databases.length > 0) { output.push({ - type: "D1 Databases", + name: friendlyBindingNames.d1_databases, entries: d1_databases.map( ({ binding, database_name, database_id, preview_database_id }) => { let databaseValue = `${database_id}`; @@ -368,7 +398,7 @@ export function printBindings( if (vectorize !== undefined && vectorize.length > 0) { output.push({ - type: "Vectorize Indexes", + name: friendlyBindingNames.vectorize, entries: vectorize.map(({ binding, index_name }) => { return { key: binding, @@ -380,7 +410,7 @@ export function printBindings( if (hyperdrive !== undefined && hyperdrive.length > 0) { output.push({ - type: "Hyperdrive Configs", + name: friendlyBindingNames.hyperdrive, entries: hyperdrive.map(({ binding, id }) => { return { key: binding, @@ -392,7 +422,7 @@ export function printBindings( if (r2_buckets !== undefined && r2_buckets.length > 0) { output.push({ - type: "R2 Buckets", + name: friendlyBindingNames.r2_buckets, entries: r2_buckets.map(({ binding, bucket_name, jurisdiction }) => { if (jurisdiction !== undefined) { bucket_name += ` (${jurisdiction})`; @@ -407,7 +437,7 @@ export function printBindings( if (logfwdr !== undefined && logfwdr.bindings.length > 0) { output.push({ - type: "logfwdr", + name: friendlyBindingNames.logfwdr, entries: logfwdr.bindings.map((binding) => { return { key: binding.name, @@ -419,7 +449,7 @@ export function printBindings( if (services !== undefined && services.length > 0) { output.push({ - type: "Services", + name: friendlyBindingNames.services, entries: services.map(({ binding, service, entrypoint }) => { let value = service; if (entrypoint) { @@ -448,7 +478,7 @@ export function printBindings( analytics_engine_datasets.length > 0 ) { output.push({ - type: "Analytics Engine Datasets", + name: friendlyBindingNames.analytics_engine_datasets, entries: analytics_engine_datasets.map(({ binding, dataset }) => { return { key: binding, @@ -460,7 +490,7 @@ export function printBindings( if (text_blobs !== undefined && Object.keys(text_blobs).length > 0) { output.push({ - type: "Text Blobs", + name: friendlyBindingNames.text_blobs, entries: Object.entries(text_blobs).map(([key, value]) => ({ key, value: truncate(value), @@ -470,7 +500,7 @@ export function printBindings( if (browser !== undefined) { output.push({ - type: "Browser", + name: friendlyBindingNames.browser, entries: [{ key: "Name", value: browser.binding }], }); } @@ -484,14 +514,14 @@ export function printBindings( } output.push({ - type: "AI", - entries, + name: friendlyBindingNames.ai, + entries: entries, }); } if (pipelines?.length) { output.push({ - type: "Pipelines", + name: friendlyBindingNames.pipelines, entries: pipelines.map(({ binding, pipeline }) => ({ key: binding, value: pipeline, @@ -501,14 +531,14 @@ export function printBindings( if (version_metadata !== undefined) { output.push({ - type: "Worker Version Metadata", + name: friendlyBindingNames.version_metadata, entries: [{ key: "Name", value: version_metadata.binding }], }); } if (unsafe?.bindings !== undefined && unsafe.bindings.length > 0) { output.push({ - type: "Unsafe", + name: friendlyBindingNames.unsafe, entries: unsafe.bindings.map(({ name, type }) => ({ key: type, value: name, @@ -518,7 +548,7 @@ export function printBindings( if (vars !== undefined && Object.keys(vars).length > 0) { output.push({ - type: "Vars", + name: friendlyBindingNames.vars, entries: Object.entries(vars).map(([key, value]) => { let parsedValue; if (typeof value === "string") { @@ -538,7 +568,7 @@ export function printBindings( if (wasm_modules !== undefined && Object.keys(wasm_modules).length > 0) { output.push({ - type: "Wasm Modules", + name: friendlyBindingNames.wasm_modules, entries: Object.entries(wasm_modules).map(([key, value]) => ({ key, value: typeof value === "string" ? truncate(value) : "", @@ -548,7 +578,7 @@ export function printBindings( if (dispatch_namespaces !== undefined && dispatch_namespaces.length > 0) { output.push({ - type: "Dispatch Namespaces", + name: friendlyBindingNames.dispatch_namespaces, entries: dispatch_namespaces.map(({ binding, namespace, outbound }) => { return { key: binding, @@ -562,7 +592,7 @@ export function printBindings( if (mtls_certificates !== undefined && mtls_certificates.length > 0) { output.push({ - type: "mTLS Certificates", + name: friendlyBindingNames.mtls_certificates, entries: mtls_certificates.map(({ binding, certificate_id }) => { return { key: binding, @@ -574,7 +604,7 @@ export function printBindings( if (unsafe?.metadata !== undefined) { output.push({ - type: "Unsafe Metadata", + name: friendlyBindingNames.unsafe, entries: Object.entries(unsafe.metadata).map(([key, value]) => ({ key, value: JSON.stringify(value), @@ -591,7 +621,7 @@ export function printBindings( ...output .map((bindingGroup) => { return [ - `- ${bindingGroup.type}:`, + `- ${bindingGroup.name}:`, bindingGroup.entries.map(({ key, value }) => ` - ${key}: ${value}`), ]; }) diff --git a/packages/wrangler/src/config/validation-helpers.ts b/packages/wrangler/src/config/validation-helpers.ts index b7bb8e4248d2..45d2116345cd 100644 --- a/packages/wrangler/src/config/validation-helpers.ts +++ b/packages/wrangler/src/config/validation-helpers.ts @@ -569,7 +569,6 @@ export const getBindingNames = (value: unknown): string[] => { if (typeof value !== "object" || value === null) { return []; } - if (isBindingList(value)) { return value.bindings.map(({ name }) => name); } else if (isNamespaceList(value)) { @@ -580,7 +579,6 @@ export const getBindingNames = (value: unknown): string[] => { if (value["binding"] !== undefined) { return [value["binding"] as string]; } - return Object.keys(value).filter((k) => value[k] !== undefined); } else { return []; diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 56385c8f6307..497dc31eaf2a 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -29,6 +29,8 @@ import { validateRequiredProperty, validateTypedArray, } from "./validation-helpers"; +import { friendlyBindingNames } from "."; +import type { CfWorkerInit } from "../deployment-bundle/worker"; import type { Config, DevConfig, RawConfig, RawDevConfig } from "./config"; import type { Assets, @@ -2741,37 +2743,25 @@ const validateHyperdriveBinding: ValidatorFn = (diagnostics, field, value) => { */ const validateBindingsHaveUniqueNames = ( diagnostics: Diagnostics, - { - durable_objects, - kv_namespaces, - r2_buckets, - analytics_engine_datasets, - text_blobs, - browser, - ai, - unsafe, - vars, - define, - wasm_modules, - data_blobs, - }: Partial + config: Partial ): boolean => { let hasDuplicates = false; - const bindingsGroupedByType = { - "Durable Object": getBindingNames(durable_objects), - "KV Namespace": getBindingNames(kv_namespaces), - "R2 Bucket": getBindingNames(r2_buckets), - "Analytics Engine Dataset": getBindingNames(analytics_engine_datasets), - "Text Blob": getBindingNames(text_blobs), - Browser: getBindingNames(browser), - AI: getBindingNames(ai), - Unsafe: getBindingNames(unsafe), - "Environment Variable": getBindingNames(vars), - Definition: getBindingNames(define), - "WASM Module": getBindingNames(wasm_modules), - "Data Blob": getBindingNames(data_blobs), - } as Record; + const bindingNamesArray = Object.entries(friendlyBindingNames) as [ + keyof CfWorkerInit["bindings"], + string, + ][]; + + const bindingsGroupedByType = Object.fromEntries( + bindingNamesArray.map(([bindingType, binding]) => [ + binding, + getBindingNames( + bindingType === "queues" + ? config[bindingType]?.producers + : config[bindingType] + ), + ]) + ); const bindingsGroupedByName: Record = {};