diff --git a/packages/cloudflare/src/options.ts b/packages/cloudflare/src/options.ts index 77a37ea51d31..3c62f88f25ed 100644 --- a/packages/cloudflare/src/options.ts +++ b/packages/cloudflare/src/options.ts @@ -1,9 +1,31 @@ import type { CloudflareOptions } from './client'; +/** + * Cloudflare's version metadata binding structure. + * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/ + */ +interface CfVersionMetadata { + id: string; + tag?: string; + timestamp?: string; +} + +/** + * Checks if the value is a valid CF_VERSION_METADATA binding. + */ +function isVersionMetadata(value: unknown): value is CfVersionMetadata { + return typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'string'; +} + /** * Merges the options passed in from the user with the options we read from * the Cloudflare `env` environment variable object. * + * Release is determined with the following priority (highest to lowest): + * 1. User-provided release option + * 2. SENTRY_RELEASE environment variable + * 3. CF_VERSION_METADATA.id binding (if configured in the wrangler config) + * * @param userOptions - The options passed in from the user. * @param env - The environment variables. * @@ -14,7 +36,13 @@ export function getFinalOptions(userOptions: CloudflareOptions, env: unknown): C return userOptions; } - const release = 'SENTRY_RELEASE' in env && typeof env.SENTRY_RELEASE === 'string' ? env.SENTRY_RELEASE : undefined; + // Priority: userOptions.release > SENTRY_RELEASE > CF_VERSION_METADATA.id + const release = + 'SENTRY_RELEASE' in env && typeof env.SENTRY_RELEASE === 'string' + ? env.SENTRY_RELEASE + : 'CF_VERSION_METADATA' in env && isVersionMetadata(env.CF_VERSION_METADATA) + ? env.CF_VERSION_METADATA.id + : undefined; return { release, ...userOptions }; } diff --git a/packages/cloudflare/test/options.test.ts b/packages/cloudflare/test/options.test.ts index ae8a5509b233..6efcf18688c0 100644 --- a/packages/cloudflare/test/options.test.ts +++ b/packages/cloudflare/test/options.test.ts @@ -55,4 +55,75 @@ describe('getFinalOptions', () => { expect(result).toEqual(userOptions); }); + + describe('CF_VERSION_METADATA', () => { + it('uses CF_VERSION_METADATA.id as release when no other release is set', () => { + const userOptions = { dsn: 'test-dsn' }; + const env = { CF_VERSION_METADATA: { id: 'version-123', tag: 'v1.0.0' } }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: 'version-123' }); + }); + + it('prefers SENTRY_RELEASE over CF_VERSION_METADATA.id', () => { + const userOptions = { dsn: 'test-dsn' }; + const env = { + SENTRY_RELEASE: 'env-release', + CF_VERSION_METADATA: { id: 'version-123' }, + }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: 'env-release' }); + }); + + it('prefers user release over CF_VERSION_METADATA.id', () => { + const userOptions = { dsn: 'test-dsn', release: 'user-release' }; + const env = { CF_VERSION_METADATA: { id: 'version-123' } }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: 'user-release' }); + }); + + it('prefers user release over both SENTRY_RELEASE and CF_VERSION_METADATA.id', () => { + const userOptions = { dsn: 'test-dsn', release: 'user-release' }; + const env = { + SENTRY_RELEASE: 'env-release', + CF_VERSION_METADATA: { id: 'version-123' }, + }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: 'user-release' }); + }); + + it('ignores CF_VERSION_METADATA when it is not an object', () => { + const userOptions = { dsn: 'test-dsn' }; + const env = { CF_VERSION_METADATA: 'not-an-object' }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: undefined }); + }); + + it('ignores CF_VERSION_METADATA when id is not a string', () => { + const userOptions = { dsn: 'test-dsn' }; + const env = { CF_VERSION_METADATA: { id: 123 } }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: undefined }); + }); + + it('ignores CF_VERSION_METADATA when id is missing', () => { + const userOptions = { dsn: 'test-dsn' }; + const env = { CF_VERSION_METADATA: { tag: 'v1.0.0' } }; + + const result = getFinalOptions(userOptions, env); + + expect(result).toEqual({ dsn: 'test-dsn', release: undefined }); + }); + }); });