diff --git a/.changeset/cf-auth-injectable-identity.md b/.changeset/cf-auth-injectable-identity.md new file mode 100644 index 0000000000..3060e66482 --- /dev/null +++ b/.changeset/cf-auth-injectable-identity.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/workers-auth": minor +--- + +Make the OAuth identity and token storage injectable, and add a shared env-credential resolver + +`createOAuthFlow` now takes the consumer's OAuth identity (`clientId`, `consent`, `redirectUri`) and token `storage` on its context, so other Cloudflare CLIs can reuse the flow under their own OAuth app and store tokens in their own location/format. Also adds a shared env→credential resolver (`getAuthFromEnv`, `getAPIToken`, `requireApiToken`). diff --git a/.changeset/cf-auth-wrangler-resolver.md b/.changeset/cf-auth-wrangler-resolver.md new file mode 100644 index 0000000000..91274f8ef5 --- /dev/null +++ b/.changeset/cf-auth-wrangler-resolver.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Use the shared env-credential resolver from `@cloudflare/workers-auth` + +No user-facing behaviour change. Credential resolution order (global API key + email → `CLOUDFLARE_API_TOKEN` → stored OAuth token) is preserved. diff --git a/.changeset/cf-wrangler-delegate-entrypoint.md b/.changeset/cf-wrangler-delegate-entrypoint.md index 2428516895..791629810a 100644 --- a/.changeset/cf-wrangler-delegate-entrypoint.md +++ b/.changeset/cf-wrangler-delegate-entrypoint.md @@ -4,6 +4,6 @@ Add an experimental `cf-wrangler` delegate entrypoint for projects that can't use `@cloudflare/vite-plugin` (service workers, old compatibility dates, Python, Rust, etc.). -`cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file. +`cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file. This replaces the separate `@cloudflare/wrangler-bundler` package. This is an internal integration point and is not intended to be run directly by users. diff --git a/.changeset/dependabot-update-14256.md b/.changeset/dependabot-update-14256.md new file mode 100644 index 0000000000..677fe0c372 --- /dev/null +++ b/.changeset/dependabot-update-14256.md @@ -0,0 +1,12 @@ +--- +"miniflare": patch +"wrangler": patch +--- + +Update dependencies of "miniflare", "wrangler" + +The following dependency versions have been updated: + +| Dependency | From | To | +| ---------- | ------------ | ------------ | +| workerd | 1.20260610.1 | 1.20260611.1 | diff --git a/.changeset/gate-network-enable-when-headless.md b/.changeset/gate-network-enable-when-headless.md new file mode 100644 index 0000000000..4dbf947577 --- /dev/null +++ b/.changeset/gate-network-enable-when-headless.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +Fix a memory leak that could make long-running headless `wrangler dev` sessions unresponsive + +Long-running `wrangler dev` sessions with no DevTools attached (for example using the containers feature under sustained traffic) could gradually consume unbounded memory and eventually stop accepting connections. The inspector proxy now only enables network tracking while a DevTools client is connected, so the buildup no longer happens. Interactive debugging is unaffected. Fixes #14191. diff --git a/.changeset/move-build-before-deploy.md b/.changeset/move-build-before-deploy.md new file mode 100644 index 0000000000..726a9bbbbd --- /dev/null +++ b/.changeset/move-build-before-deploy.md @@ -0,0 +1,6 @@ +--- +"wrangler": patch +"@cloudflare/deploy-helpers": patch +--- + +Move worker build step earlier in deploy/upload step, before upload specific config validation diff --git a/packages/deploy-helpers/src/deploy/deploy.ts b/packages/deploy-helpers/src/deploy/deploy.ts index 2c1d7dacdf..81c746883b 100644 --- a/packages/deploy-helpers/src/deploy/deploy.ts +++ b/packages/deploy-helpers/src/deploy/deploy.ts @@ -39,7 +39,6 @@ import { } from "./helpers/environments"; import { helpIfErrorIsSizeOrScriptStartup } from "./helpers/friendly-validator-errors"; import { verifyWorkerMatchesCITag } from "./helpers/match-tag"; -import { validateNodeCompatMode } from "./helpers/node-compat"; import { parseBulkInputToObject } from "./helpers/parse-bulk-input"; import { parseConfigPlacement } from "./helpers/placement"; import { printBindings } from "./helpers/print-bindings"; @@ -60,7 +59,7 @@ import { } from "./helpers/versions-api"; import { isWorkerNotFoundError } from "./helpers/worker-not-found-error"; import { addWorkersSitesBindings } from "./helpers/workers-sites-bindings"; -import type { DeployProps, HandleBuild } from "../shared/types"; +import type { DeployProps, WorkerBuildResult } from "../shared/types"; import type { RetrieveSourceMapFunction } from "./helpers/sourcemap"; import type { ApiVersion, @@ -144,7 +143,7 @@ export type DeployCallbacks = { export default async function deploy( props: DeployProps, config: Config, - buildWorker: HandleBuild, + buildResult: WorkerBuildResult, callbacks: DeployCallbacks ): Promise<{ sourceMapSize?: number; @@ -165,8 +164,6 @@ export default async function deploy( compatibilityDate, compatibilityFlags, keepVars, - minify, - noBundle, uploadSourceMaps, accountId, } = props; @@ -318,19 +315,6 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m validateRoutes(allDeploymentRoutes, props.assetsOptions); - const nodejsCompatMode = validateNodeCompatMode( - compatibilityDate, - compatibilityFlags, - { noBundle } - ); - - // Warn if user tries minify with no-bundle - if (noBundle && minify) { - logger.warn( - "`--minify` and `--no-bundle` can't be used together. If you want to minify your Worker and disable Wrangler's bundling, please minify as part of your own bundling process." - ); - } - const scriptName = name; assert( @@ -415,10 +399,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m bundleType, content, bundle, - } = await buildWorker.build(props, config, { - nodejsCompatMode, - metafile: props.metafile, - }); + } = buildResult; // durable object migrations const migrations = !isDryRun ? await getMigrationsToUpload(scriptName, { diff --git a/packages/deploy-helpers/src/deploy/versions-upload.ts b/packages/deploy-helpers/src/deploy/versions-upload.ts index ce718dbee9..a22470a23e 100644 --- a/packages/deploy-helpers/src/deploy/versions-upload.ts +++ b/packages/deploy-helpers/src/deploy/versions-upload.ts @@ -27,7 +27,6 @@ import { } from "./helpers/environments"; import { helpIfErrorIsSizeOrScriptStartup } from "./helpers/friendly-validator-errors"; import { verifyWorkerMatchesCITag } from "./helpers/match-tag"; -import { validateNodeCompatMode } from "./helpers/node-compat"; import { parseBulkInputToObject } from "./helpers/parse-bulk-input"; import { parseConfigPlacement } from "./helpers/placement"; import { printBindings } from "./helpers/print-bindings"; @@ -43,7 +42,7 @@ import { import { useServiceEnvironments as useServiceEnvironmentsConfig } from "./helpers/use-service-environments"; import { patchNonVersionedScriptSettings } from "./helpers/versions-api"; import { isWorkerNotFoundError } from "./helpers/worker-not-found-error"; -import type { HandleBuild, VersionsUploadProps } from "../shared/types"; +import type { VersionsUploadProps, WorkerBuildResult } from "../shared/types"; import type { DeployCallbacks } from "./deploy"; import type { RetrieveSourceMapFunction } from "./helpers/sourcemap"; import type { CfWorkerInit, Config } from "@cloudflare/workers-utils"; @@ -57,7 +56,7 @@ export type VersionsUploadCallbacks = Pick< export default async function versionsUpload( props: VersionsUploadProps, config: Config, - buildWorker: HandleBuild, + buildResult: WorkerBuildResult, callbacks: VersionsUploadCallbacks ): Promise<{ versionId: string | null; @@ -77,8 +76,6 @@ export default async function versionsUpload( compatibilityDate, compatibilityFlags, keepVars, - minify, - noBundle, uploadSourceMaps, accountId, } = props; @@ -155,19 +152,6 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m ); } - const nodejsCompatMode = validateNodeCompatMode( - compatibilityDate, - compatibilityFlags, - { noBundle } - ); - - // Warn if user tries minify or node-compat with no-bundle - if (noBundle && minify) { - logger.warn( - "`--minify` and `--no-bundle` can't be used together. If you want to minify your Worker and disable Wrangler's bundling, please minify as part of your own bundling process." - ); - } - const scriptName = name; if (config.site && !config.site.bucket) { @@ -223,9 +207,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m bundleType, content, bundle, - } = await buildWorker.build(props, config, { - nodejsCompatMode, - }); + } = buildResult; const bindings = getBindings(config); // Vars from the CLI (--var) are hidden so their values aren't logged to the terminal diff --git a/packages/deploy-helpers/src/shared/types.ts b/packages/deploy-helpers/src/shared/types.ts index 0e858c1f79..25d50b442b 100644 --- a/packages/deploy-helpers/src/shared/types.ts +++ b/packages/deploy-helpers/src/shared/types.ts @@ -13,7 +13,6 @@ import type { Route, Entry, } from "@cloudflare/workers-utils"; -import type { NodeJSCompatMode } from "miniflare"; /** * client needs to handle logger and fetch/auth implementation @@ -143,7 +142,7 @@ export type BuildBundleInfo = { sourceMapMetadata?: { tmpDir: string; entryDirectory: string } | undefined; }; -export type HandleBuildResult = { +export type WorkerBuildResult = { modules: CfModule[]; dependencies: Record; resolvedEntryPointPath: string; @@ -152,17 +151,6 @@ export type HandleBuildResult = { bundle: BuildBundleInfo; }; -export type HandleBuild = { - build: ( - props: SharedDeployVersionsProps, - config: Config, - options: { - nodejsCompatMode: NodeJSCompatMode; - metafile?: string | boolean; - } - ) => Promise; -}; - export interface TriggerDeployment { targets: string[]; error?: Error; diff --git a/packages/miniflare/package.json b/packages/miniflare/package.json index 642ca0ab2f..85664a506a 100644 --- a/packages/miniflare/package.json +++ b/packages/miniflare/package.json @@ -52,7 +52,7 @@ "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "catalog:default", - "workerd": "1.20260610.1", + "workerd": "1.20260611.1", "ws": "catalog:default", "youch": "4.1.0-beta.10" }, diff --git a/packages/workers-auth/AGENTS.md b/packages/workers-auth/AGENTS.md index 62609c34b1..f11c5d576b 100644 --- a/packages/workers-auth/AGENTS.md +++ b/packages/workers-auth/AGENTS.md @@ -8,14 +8,14 @@ CLIs. Internal-only — published as `prerelease: true`. - `src/pkce.ts` — PKCE code-verifier / code-challenge generation (RFC 7636) - `src/errors.ts` — `ErrorOAuth2` class hierarchy + `toErrorClass` mapper -- `src/generate-auth-url.ts` — authorize URL builder + `OAUTH_CALLBACK_URL` +- `src/generate-auth-url.ts` — authorize URL builder - `src/generate-random-state.ts` — CSRF state generator - `src/env-vars.ts` — `WRANGLER_*` env-var getters for OAuth endpoints - `src/access.ts` — Cloudflare Access detection + service-token / `cloudflared` headers -- `src/auth-config-file.ts` — read/write the persisted TOML at `/config/.toml` +- `src/auth-config-file.ts` — the `AuthConfigStorage` / `UserAuthConfig` storage contract (interfaces only; the default TOML-on-disk implementation lives in the consumer, e.g. wrangler's `src/user/auth-config-file.ts`) - `src/state.ts` — `readStoredAuthState()` + `StoredAuthState` shape - `src/token-exchange.ts` — auth-code → token + refresh-token rotation + `fetchAuthToken` -- `src/callback-server.ts` — local HTTP server on `localhost:8976` for the OAuth callback +- `src/callback-server.ts` — local HTTP server for the OAuth callback (listens on the host/port from the consumer's `redirectUri`) - `src/flow.ts` — `createOAuthFlow(ctx)` factory wiring everything together - `src/context.ts` — `OAuthFlowContext` interface (DI surface) - `src/test-helpers/` — MSW handlers for consumers' tests (`@cloudflare/workers-auth/test-helpers`) @@ -28,10 +28,22 @@ CLIs. Internal-only — published as `prerelease: true`. - `isNonInteractiveOrCI()` — whether to suppress interactive prompts - `openInBrowser(url)` — opens the browser to the OAuth authorize URL - `hasEnvCredentials()` — short-circuits refresh logic when env-based auth is set -- `purgeOnLoginOrLogout()` — invalidate consumer-side caches after login/logout +- `clientId` (required) — the consumer's registered OAuth app ID; `string` or + `() => string` for lazy (e.g. env-driven prod/staging) resolution +- `consent` (required) — the consumer's branded granted/denied consent pages +- `redirectUri` (required) — the registered redirect URI / local callback URL. + The callback server's listen host/port and route path are all derived from it + (per-call bind overrides via `LoginProps.callbackHost`/`callbackPort`) +- `storage` (required) — the consumer's `AuthConfigStorage` token-persistence + backend (wrangler's TOML-on-disk default lives in `src/user/auth-config-file.ts`) +- `purgeOnLoginOrLogout?()` — invalidate consumer-side caches after login/logout - `generateAuthUrl?` / `generateRandomState?` — test overrides for deterministic snapshot tests (defaults pull from `./generate-auth-url` / `./generate-random-state`) +`clientId`, `consent`, `redirectUri`, and `storage` are consumer-specific +(Wrangler's live in `packages/wrangler/src/user/`), so they are required rather +than defaulted here. + Wrangler wires this once in `packages/wrangler/src/user/user.ts`. ## CONVENTIONS @@ -46,8 +58,9 @@ Wrangler wires this once in `packages/wrangler/src/user/user.ts`. These labels are part of the telemetry contract — preserve them verbatim. - No direct Cloudflare REST API calls. This package talks to OAuth endpoints (`/oauth2/auth`, `/oauth2/token`, `/oauth2/revoke`) only. -- OAuth callback server listens on `localhost:8976` by default; override via - `LoginProps.callbackHost` / `callbackPort` per-call. +- OAuth callback server listens on the host/port derived from the consumer's + required `ctx.redirectUri`; override the bind address per-call via + `LoginProps.callbackHost` / `callbackPort`. ## BUILD diff --git a/packages/workers-auth/src/auth-config-file.ts b/packages/workers-auth/src/auth-config-file.ts index 5752d82953..d83460f304 100644 --- a/packages/workers-auth/src/auth-config-file.ts +++ b/packages/workers-auth/src/auth-config-file.ts @@ -1,13 +1,3 @@ -import { chmodSync, mkdirSync, writeFileSync } from "node:fs"; -import path from "node:path"; -import { - getCloudflareApiEnvironmentFromEnv, - getGlobalWranglerConfigPath, - parseTOML, - readFileSync, -} from "@cloudflare/workers-utils"; -import TOML from "smol-toml"; - /** * The data that may be read from the on-disk user auth config file. */ @@ -21,56 +11,26 @@ export interface UserAuthConfig { } /** - * The path to the config file that holds user authentication data, - * relative to the user's home directory. - */ -const USER_AUTH_CONFIG_PATH = "config"; - -/** - * Returns the absolute path to the auth config TOML file. - * - * The file lives under the global Wrangler config directory and is named - * `default.toml` in production, or `.toml` for the staging / - * other Cloudflare API environments. - */ -export function getAuthConfigFilePath(): string { - const environment = getCloudflareApiEnvironmentFromEnv(); - const filePath = `${USER_AUTH_CONFIG_PATH}/${environment === "production" ? "default.toml" : `${environment}.toml`}`; - return path.join(getGlobalWranglerConfigPath(), filePath); -} - -/** - * Writes the user auth config to disk. - * - * No in-memory cache to invalidate — auth state is read on demand by every call - * site that needs it. Callers are responsible for any consumer-side cache - * purging (e.g. via the {@link OAuthFlowContext.purgeOnLoginOrLogout} hook). - */ -export function writeAuthConfigFile(config: UserAuthConfig): void { - const configPath = getAuthConfigFilePath(); - - mkdirSync(path.dirname(configPath), { - recursive: true, - }); - // Write with mode 0o600 on creation and re-`chmod` on every save so - // other local users on shared hosts can't read the OAuth tokens. - // `writeFileSync`'s `mode` option only applies when the file is - // being created — the explicit `chmodSync` ensures that pre-existing - // files (e.g. written by an older Wrangler version with the process - // umask) get tightened on the next save too. - writeFileSync(configPath, TOML.stringify(config), { - encoding: "utf-8", - mode: 0o600, - }); - chmodSync(configPath, 0o600); -} - -/** - * Reads the user auth config from disk. + * Pluggable persistence for the user auth config. * - * @throws if the file does not exist or cannot be parsed as TOML. Callers - * typically catch this and treat the failure as "not logged in via local OAuth". + * This package does not ship a default implementation — the consumer injects + * one via {@link OAuthFlowContext.storage} (and into {@link getAPIToken} / + * {@link readStoredAuthState}). Wrangler's default reads/writes a TOML file + * under the global Wrangler config directory; other CLIs can use a different + * location and/or serialization format (e.g. a JSONC file under a different + * CLI's XDG config directory). */ -export function readAuthConfigFile(): UserAuthConfig { - return parseTOML(readFileSync(getAuthConfigFilePath())) as UserAuthConfig; +export interface AuthConfigStorage { + /** + * Read and parse the stored auth config. + * @throws if the backing store is missing or cannot be parsed. Callers treat + * a throw as "not logged in via local OAuth". + */ + read(): UserAuthConfig; + /** Serialize and persist the auth config. */ + write(config: UserAuthConfig): void; + /** Remove the backing store (used on logout). */ + clear(): void; + /** Human-readable location of the backing store, for display and warnings. */ + path(): string; } diff --git a/packages/workers-auth/src/callback-server.ts b/packages/workers-auth/src/callback-server.ts index 68cfd70d37..ee0489106c 100644 --- a/packages/workers-auth/src/callback-server.ts +++ b/packages/workers-auth/src/callback-server.ts @@ -32,6 +32,7 @@ export interface GetOauthTokenOptions { browser: boolean; scopes: string[]; clientId: string; + redirectUri: string; denied: { url: string; error: string; @@ -61,9 +62,16 @@ export async function getOauthToken( const urlToOpen = await getAuthURL( options.scopes, options.clientId, + options.redirectUri, state, generators ); + // The path the local server must route the OAuth provider's redirect to is + // dictated by the registered `redirectUri` — not hardcoded. Without this, + // a consumer that registers e.g. `/my/callback` would have the provider + // redirect the browser there but the server would silently fall through + // with no response. + const callbackPath = new URL(options.redirectUri).pathname; let server: http.Server; let loginTimeoutHandle: ReturnType; const timerPromise = new Promise((_, reject) => { @@ -156,7 +164,7 @@ export async function getOauthToken( return res.end("OK"); } switch (pathname) { - case "/oauth/callback": { + case callbackPath: { let hasAuthCode = false; try { hasAuthCode = isReturningFromAuthServer(query, state, ctx.logger); @@ -207,7 +215,9 @@ export async function getOauthToken( const exchange = await exchangeAuthCodeForAccessToken( state, ctx.logger, - ctx.isNonInteractiveOrCI + ctx.isNonInteractiveOrCI, + options.clientId, + options.redirectUri ); res.writeHead(307, { Location: options.granted.url, @@ -237,12 +247,23 @@ export async function getOauthToken( } }); - if (options.callbackHost !== "localhost" || options.callbackPort !== 8976) { + // Warn only when the local server listens somewhere other than where the + // OAuth provider will redirect to (the registered `redirectUri`) — e.g. + // a container forwarding a different host/port. When they match (the + // common case), there is nothing to forward and no warning is needed. + const redirect = new URL(options.redirectUri); + const redirectPort = Number( + redirect.port || (redirect.protocol === "https:" ? 443 : 80) + ); + if ( + redirect.hostname !== options.callbackHost || + redirectPort !== options.callbackPort + ) { ctx.logger.log( `Temporary login server listening on ${options.callbackHost}:${options.callbackPort}` ); ctx.logger.log( - "Note that the OAuth login page will always redirect to `localhost:8976`.\n" + + `Note that the OAuth login page will always redirect to \`${options.redirectUri}\`.\n` + "If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly." ); } diff --git a/packages/workers-auth/src/context.ts b/packages/workers-auth/src/context.ts index 826228ff11..b1c66348b1 100644 --- a/packages/workers-auth/src/context.ts +++ b/packages/workers-auth/src/context.ts @@ -1,9 +1,19 @@ -import type { - generateAuthUrl as defaultGenerateAuthUrl, - OAUTH_CALLBACK_URL, -} from "./generate-auth-url"; +import type { AuthConfigStorage } from "./auth-config-file"; +import type { generateAuthUrl as defaultGenerateAuthUrl } from "./generate-auth-url"; import type { generateRandomState as defaultGenerateRandomState } from "./generate-random-state"; +/** + * The branded OAuth consent pages the provider redirects the browser to after + * the user grants or denies consent. + */ +export interface OAuthConsentPages { + /** Redirect target shown after the user grants consent. */ + granted: { url: string }; + /** Redirect target shown after the user denies consent, plus the error + * surfaced to the terminal. */ + denied: { url: string; error: string }; +} + /** * Subset of the wrangler `logger` singleton used by the OAuth flow. * Consumers pass in an implementation that maps to their own logging surface. @@ -56,6 +66,30 @@ export interface OAuthFlowContext { */ purgeOnLoginOrLogout?: () => void; + /** + * The OAuth client ID identifying the consuming CLI to the Cloudflare OAuth + * server. Consumer-specific (each CLI registers its own OAuth app), so it is + * required. Pass a function to resolve it lazily — e.g. so an env-var read at + * call time can switch between production and staging apps. + */ + clientId: string | (() => string); + + /** + * The branded consent pages the provider redirects to after the user grants + * or denies consent. + */ + consent: OAuthConsentPages; + + /** + * The `redirect_uri` registered on the consumer's OAuth app + */ + redirectUri: string; + + /** + * Persistence backend for the stored auth config. + */ + storage: AuthConfigStorage; + /** * Override the OAuth authorize URL generator. Used by tests to produce a * deterministic URL for snapshot testing. Defaults to the standard @@ -70,5 +104,3 @@ export interface OAuthFlowContext { */ generateRandomState?: typeof defaultGenerateRandomState; } - -export type { OAUTH_CALLBACK_URL }; diff --git a/packages/workers-auth/src/credentials.ts b/packages/workers-auth/src/credentials.ts new file mode 100644 index 0000000000..79efa25c22 --- /dev/null +++ b/packages/workers-auth/src/credentials.ts @@ -0,0 +1,135 @@ +import { + getEnvironmentVariableFactory, + UserError, +} from "@cloudflare/workers-utils"; +import { type AuthConfigStorage } from "./auth-config-file"; +import { readStoredAuthState } from "./state"; +import type { OAuthFlowLogger } from "./context"; +import type { ApiCredentials } from "@cloudflare/workers-utils"; + +// --------------------------------------------------------------------------- +// Credential environment variables +// +// These read the *credential* env vars (as opposed to the OAuth-flow config +// vars in `env-vars.ts`). They live here, alongside the env→credential +// resolver, so any Cloudflare CLI can share a single, consistent +// implementation rather than reimplementing the precedence rules. +// --------------------------------------------------------------------------- + +/** `CLOUDFLARE_API_TOKEN` (legacy alias `CF_API_TOKEN`): a scoped API token. */ +export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({ + variableName: "CLOUDFLARE_API_TOKEN", + deprecatedName: "CF_API_TOKEN", +}); + +/** `CLOUDFLARE_API_KEY` (legacy alias `CF_API_KEY`): the global API key. */ +export const getCloudflareGlobalAuthKeyFromEnv = getEnvironmentVariableFactory({ + variableName: "CLOUDFLARE_API_KEY", + deprecatedName: "CF_API_KEY", +}); + +/** `CLOUDFLARE_EMAIL` (legacy alias `CF_EMAIL`): the account email, paired with + * the global API key. */ +export const getCloudflareGlobalAuthEmailFromEnv = + getEnvironmentVariableFactory({ + variableName: "CLOUDFLARE_EMAIL", + deprecatedName: "CF_EMAIL", + }); + +export interface GetAuthFromEnvOptions { + /** + * Whether to honour the global API key + email pair + * (`CLOUDFLARE_API_KEY` + `CLOUDFLARE_EMAIL`, surfaced as + * `X-Auth-Key`/`X-Auth-Email`). Defaults to `true` (Wrangler's behaviour). + * CLIs that only support scoped API tokens / OAuth should pass `false`. + */ + allowGlobalAuthKey?: boolean; +} + +/** + * Resolve Cloudflare API credentials from environment variables. + * + * Priority (highest to lowest), matching Wrangler's historical order: + * 1. Global API key + email (`CLOUDFLARE_API_KEY` + `CLOUDFLARE_EMAIL`) — + * only when `allowGlobalAuthKey` is `true`. + * 2. API token (`CLOUDFLARE_API_TOKEN`). + * + * @returns the resolved credentials, or `undefined` when no env credentials + * are present. + */ +export function getAuthFromEnv( + options?: GetAuthFromEnvOptions +): ApiCredentials | undefined { + const allowGlobalAuthKey = options?.allowGlobalAuthKey ?? true; + + if (allowGlobalAuthKey) { + const globalApiKey = getCloudflareGlobalAuthKeyFromEnv(); + const globalApiEmail = getCloudflareGlobalAuthEmailFromEnv(); + if (globalApiKey && globalApiEmail) { + return { authKey: globalApiKey, authEmail: globalApiEmail }; + } + } + + const apiToken = getCloudflareAPITokenFromEnv(); + if (apiToken) { + return { apiToken }; + } + + return undefined; +} + +export interface GetAPITokenOptions extends GetAuthFromEnvOptions { + /** Persistence backend for the stored OAuth token. */ + storage: AuthConfigStorage; + /** Logger used to surface the one-time deprecated-v1-`api_token` warning. */ + warningLogger?: Pick; +} + +/** + * Resolve Cloudflare API credentials from the environment, falling back to the + * locally-stored OAuth token. + * + * Resolution order (highest to lowest): + * 1. {@link getAuthFromEnv} (env credentials). + * 2. The deprecated v1 `api_token` on disk (with a one-time warning). + * 3. The stored OAuth access token (from a previous interactive login). + * + * Note: this does NOT refresh an expired OAuth token. Callers that need a + * guaranteed-valid OAuth token should use the flow's + * `getOAuthTokenFromLocalState()` instead. + */ +export function getAPIToken( + options: GetAPITokenOptions +): ApiCredentials | undefined { + const envAuth = getAuthFromEnv(options); + if (envAuth) { + return envAuth; + } + + const stored = readStoredAuthState({ + storage: options.storage, + warningLogger: options.warningLogger, + }); + if (stored.deprecatedApiToken) { + return { apiToken: stored.deprecatedApiToken }; + } + if (stored.accessToken?.value) { + return { apiToken: stored.accessToken.value }; + } + + return undefined; +} + +/** + * Like {@link getAPIToken}, but throws a {@link UserError} when no credentials + * are available. + */ +export function requireApiToken(options: GetAPITokenOptions): ApiCredentials { + const credentials = getAPIToken(options); + if (!credentials) { + throw new UserError("No API token found.", { + telemetryMessage: "user auth missing api token", + }); + } + return credentials; +} diff --git a/packages/workers-auth/src/env-vars.ts b/packages/workers-auth/src/env-vars.ts index fb6c97f7cb..b61ab7f389 100644 --- a/packages/workers-auth/src/env-vars.ts +++ b/packages/workers-auth/src/env-vars.ts @@ -3,22 +3,6 @@ import { getEnvironmentVariableFactory, } from "@cloudflare/workers-utils"; -/** - * `WRANGLER_CLIENT_ID` is a UUID that is used to identify Wrangler - * to the Cloudflare APIs. - * - * Normally you should not need to set this explicitly. - * If you want to switch to the staging environment set the - * `WRANGLER_API_ENVIRONMENT=staging` environment variable instead. - */ -export const getClientIdFromEnv = getEnvironmentVariableFactory({ - variableName: "WRANGLER_CLIENT_ID", - defaultValue: () => - getCloudflareApiEnvironmentFromEnv() === "staging" - ? "4b2ea6cc-9421-4761-874b-ce550e0e3def" - : "54d11594-84e4-41aa-b438-e81b8fa78ee7", -}); - /** * `WRANGLER_AUTH_DOMAIN` is the URL base domain that is used * to access OAuth URLs for the Cloudflare APIs. diff --git a/packages/workers-auth/src/flow.ts b/packages/workers-auth/src/flow.ts index 8eb094cf9b..2336920415 100644 --- a/packages/workers-auth/src/flow.ts +++ b/packages/workers-auth/src/flow.ts @@ -12,20 +12,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { rmSync } from "node:fs"; import { getCloudflareComplianceRegion, UserError, } from "@cloudflare/workers-utils"; import dedent from "ts-dedent"; import { fetch } from "undici"; -import { getAuthConfigFilePath, writeAuthConfigFile } from "./auth-config-file"; import { getOauthToken } from "./callback-server"; -import { getClientIdFromEnv, getRevokeUrlFromEnv } from "./env-vars"; -import { - generateAuthUrl as defaultGenerateAuthUrl, - OAUTH_CALLBACK_URL, -} from "./generate-auth-url"; +import { getRevokeUrlFromEnv } from "./env-vars"; +import { generateAuthUrl as defaultGenerateAuthUrl } from "./generate-auth-url"; import { generateRandomState as defaultGenerateRandomState } from "./generate-random-state"; import { readStoredAuthState, type OAuthFlowState } from "./state"; import { exchangeRefreshTokenForAccessToken } from "./token-exchange"; @@ -157,6 +152,15 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { generateRandomState: ctx.generateRandomState ?? defaultGenerateRandomState, }; + const storage = ctx.storage; + const getClientId = () => + typeof ctx.clientId === "function" ? ctx.clientId() : ctx.clientId; + const consent = ctx.consent; + + const redirectUrl = new URL(ctx.redirectUri); + const defaultCallbackHost = redirectUrl.hostname; + const defaultCallbackPort = Number(redirectUrl.port); + async function login(props: LoginProps): Promise { if (ctx.hasEnvCredentials()) { // Env credentials override any login details, so no point in allowing @@ -192,25 +196,19 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { { browser: props.browser ?? true, scopes: props.scopes, - clientId: getClientIdFromEnv(), - denied: { - url: "https://welcome.developers.workers.dev/wrangler-oauth-consent-denied", - error: - "Error: Consent denied. You must grant consent to Wrangler in order to login.\n" + - "If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable", - }, - granted: { - url: "https://welcome.developers.workers.dev/wrangler-oauth-consent-granted", - }, - callbackHost: props.callbackHost ?? "localhost", - callbackPort: props.callbackPort ?? 8976, + clientId: getClientId(), + redirectUri: ctx.redirectUri, + denied: consent.denied, + granted: consent.granted, + callbackHost: props.callbackHost ?? defaultCallbackHost, + callbackPort: props.callbackPort ?? defaultCallbackPort, }, oauthFlowState, ctx, generators ); - writeAuthConfigFile({ + storage.write({ oauth_token: oauth.token?.value ?? "", expiration_time: oauth.token?.expiry, refresh_token: oauth.refreshToken?.value, @@ -228,7 +226,10 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { if (ctx.hasEnvCredentials()) { return false; } - const { accessToken } = readStoredAuthState({ warningLogger: ctx.logger }); + const { accessToken } = readStoredAuthState({ + warningLogger: ctx.logger, + storage, + }); return Boolean(accessToken && new Date() >= new Date(accessToken.expiry)); } @@ -249,9 +250,11 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { scopes, } = await exchangeRefreshTokenForAccessToken( ctx.logger, - ctx.isNonInteractiveOrCI + ctx.isNonInteractiveOrCI, + getClientId(), + storage ); - writeAuthConfigFile({ + storage.write({ oauth_token, expiration_time, refresh_token, @@ -276,7 +279,10 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { return { loggedIn: true }; } // TODO: ask permission before opening browser - const stored = readStoredAuthState({ warningLogger: ctx.logger }); + const stored = readStoredAuthState({ + warningLogger: ctx.logger, + storage, + }); if (!stored.accessToken && !stored.deprecatedApiToken) { // Not logged in. // If we are not interactive, we cannot ask the user to login @@ -326,6 +332,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { const storedRefreshToken = readStoredAuthState({ warningLogger: ctx.logger, + storage, }).refreshToken; if (!storedRefreshToken) { ctx.logger.log("Not logged in, exiting..."); @@ -333,7 +340,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { } const body = - `client_id=${encodeURIComponent(getClientIdFromEnv())}&` + + `client_id=${encodeURIComponent(getClientId())}&` + `token_type_hint=refresh_token&` + `token=${encodeURIComponent(storedRefreshToken.value || "")}`; @@ -345,14 +352,14 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { }, }); await response.text(); // blank text? would be nice if it was something meaningful - rmSync(getAuthConfigFilePath()); + storage.clear(); ctx.logger.log(`Successfully logged out.`); ctx.purgeOnLoginOrLogout?.(); } async function getOAuthTokenFromLocalState(): Promise { // Check if we have an OAuth token - let stored = readStoredAuthState({ warningLogger: ctx.logger }); + let stored = readStoredAuthState({ warningLogger: ctx.logger, storage }); if (!stored.accessToken) { return undefined; } @@ -370,7 +377,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { return undefined; } // Re-read after the refresh has persisted the new token to disk. - stored = readStoredAuthState({ warningLogger: ctx.logger }); + stored = readStoredAuthState({ warningLogger: ctx.logger, storage }); } return stored.accessToken?.value; @@ -385,7 +392,3 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI { refreshToken, }; } - -// Re-export the constant for callers that want to know about the redirect URI -// without depending on `./generate-auth-url`. -export { OAUTH_CALLBACK_URL }; diff --git a/packages/workers-auth/src/generate-auth-url.ts b/packages/workers-auth/src/generate-auth-url.ts index ba432112f5..6b75b06dd0 100644 --- a/packages/workers-auth/src/generate-auth-url.ts +++ b/packages/workers-auth/src/generate-auth-url.ts @@ -4,10 +4,9 @@ interface GenerateAuthUrlProps { scopes: string[]; stateQueryParam: string; codeChallenge: string; + redirectUri: string; } -export const OAUTH_CALLBACK_URL = "http://localhost:8976/oauth/callback"; - /** * Build the OAuth 2.0 authorize URL for the Cloudflare auth endpoint. * @@ -21,12 +20,13 @@ export const generateAuthUrl = ({ scopes, stateQueryParam, codeChallenge, + redirectUri, }: GenerateAuthUrlProps) => { return ( authUrl + `?response_type=code&` + `client_id=${encodeURIComponent(clientId)}&` + - `redirect_uri=${encodeURIComponent(OAUTH_CALLBACK_URL)}&` + + `redirect_uri=${encodeURIComponent(redirectUri)}&` + // we add offline_access manually for every request `scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` + `state=${stateQueryParam}&` + diff --git a/packages/workers-auth/src/index.ts b/packages/workers-auth/src/index.ts index 6c645d6259..1a5d5055cb 100644 --- a/packages/workers-auth/src/index.ts +++ b/packages/workers-auth/src/index.ts @@ -6,12 +6,17 @@ // `getAPIToken` resolver), or to inject deterministic implementations into // tests. +export type { AuthConfigStorage, UserAuthConfig } from "./auth-config-file"; + export { - getAuthConfigFilePath, - readAuthConfigFile, - writeAuthConfigFile, -} from "./auth-config-file"; -export type { UserAuthConfig } from "./auth-config-file"; + getAPIToken, + getAuthFromEnv, + getCloudflareAPITokenFromEnv, + getCloudflareGlobalAuthEmailFromEnv, + getCloudflareGlobalAuthKeyFromEnv, + requireApiToken, +} from "./credentials"; +export type { GetAPITokenOptions, GetAuthFromEnvOptions } from "./credentials"; export { clearAccessCaches, @@ -20,7 +25,11 @@ export { getCloudflareAccessHeaders, } from "./access"; -export type { OAuthFlowContext, OAuthFlowLogger } from "./context"; +export type { + OAuthConsentPages, + OAuthFlowContext, + OAuthFlowLogger, +} from "./context"; export { getAccessClientIdFromEnv, @@ -28,7 +37,6 @@ export { getAuthDomainFromEnv, getAuthUrlFromEnv, getCfAuthorizationTokenFromEnv, - getClientIdFromEnv, getRevokeUrlFromEnv, getTokenUrlFromEnv, } from "./env-vars"; @@ -55,7 +63,7 @@ export { toErrorClass, } from "./errors"; -export { createOAuthFlow, OAUTH_CALLBACK_URL } from "./flow"; +export { createOAuthFlow } from "./flow"; export type { LoginOrRefreshFailureReason, LoginOrRefreshResult, diff --git a/packages/workers-auth/src/state.ts b/packages/workers-auth/src/state.ts index 96fd896e33..8d1c5e7225 100644 --- a/packages/workers-auth/src/state.ts +++ b/packages/workers-auth/src/state.ts @@ -1,8 +1,4 @@ -import { - getAuthConfigFilePath, - readAuthConfigFile, - type UserAuthConfig, -} from "./auth-config-file"; +import type { AuthConfigStorage, UserAuthConfig } from "./auth-config-file"; import type { OAuthFlowLogger } from "./context"; export interface RefreshToken { @@ -68,16 +64,20 @@ export function _resetDeprecatedV1ApiTokenWarningLatch(): void { * @param options.warningLogger if provided, a one-time warning is emitted when a * deprecated v1 `api_token` is found on disk. Pass the consumer's logger (e.g. * wrangler's logger singleton) to surface this to the user. + * @param options.storage the persistence backend to read from, injected by the + * consumer (e.g. wrangler's TOML-file-on-disk storage under the global Wrangler + * config directory). */ -export function readStoredAuthState(options?: { +export function readStoredAuthState(options: { configOverride?: UserAuthConfig; warningLogger?: Pick; + storage: AuthConfigStorage; }): StoredAuthState { - const { configOverride, warningLogger } = options ?? {}; + const { configOverride, warningLogger, storage } = options; let parsed: UserAuthConfig; try { - parsed = configOverride ?? readAuthConfigFile(); + parsed = configOverride ?? storage.read(); } catch { return {}; } @@ -102,7 +102,9 @@ export function readStoredAuthState(options?: { hasWarnedAboutDeprecatedV1ApiToken = true; warningLogger.warn( "It looks like you have used Wrangler v1's `config` command to login with an API token\n" + - `from ${configOverride === undefined ? getAuthConfigFilePath() : "in-memory config"}.\n` + + `from ${ + configOverride === undefined ? storage.path() : "in-memory config" + }.\n` + "This is no longer supported in the current version of Wrangler.\n" + "If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable." ); diff --git a/packages/workers-auth/src/token-exchange.ts b/packages/workers-auth/src/token-exchange.ts index 99cd90fbe5..1521ac3b1a 100644 --- a/packages/workers-auth/src/token-exchange.ts +++ b/packages/workers-auth/src/token-exchange.ts @@ -18,7 +18,6 @@ import { domainUsesAccess, getCloudflareAccessHeaders } from "./access"; import { getAuthDomainFromEnv, getAuthUrlFromEnv, - getClientIdFromEnv, getTokenUrlFromEnv, } from "./env-vars"; import { @@ -26,9 +25,9 @@ import { ErrorUnknown, toErrorClass, } from "./errors"; -import { OAUTH_CALLBACK_URL } from "./generate-auth-url"; import { generatePKCECodes, RECOMMENDED_STATE_LENGTH } from "./pkce"; import { readStoredAuthState, type OAuthFlowState } from "./state"; +import type { AuthConfigStorage } from "./auth-config-file"; import type { OAuthFlowContext } from "./context"; import type { generateAuthUrl as defaultGenerateAuthUrl } from "./generate-auth-url"; import type { generateRandomState as defaultGenerateRandomState } from "./generate-random-state"; @@ -101,6 +100,7 @@ export function isReturningFromAuthServer( export async function getAuthURL( scopes: string[], clientId: string, + redirectUri: string, state: OAuthFlowState, generators: { generateAuthUrl: typeof defaultGenerateAuthUrl; @@ -124,6 +124,7 @@ export async function getAuthURL( scopes, stateQueryParam, codeChallenge, + redirectUri, }); } @@ -132,12 +133,15 @@ export async function getAuthURL( */ export async function exchangeRefreshTokenForAccessToken( logger: OAuthFlowContext["logger"], - isNonInteractiveOrCI: OAuthFlowContext["isNonInteractiveOrCI"] + isNonInteractiveOrCI: OAuthFlowContext["isNonInteractiveOrCI"], + clientId: string, + storage: AuthConfigStorage ): Promise { // Read the refresh token fresh from disk on every call so we always pick up // the latest rotation written by a sibling Wrangler process. const storedRefreshToken = readStoredAuthState({ warningLogger: logger, + storage, }).refreshToken; if (!storedRefreshToken) { logger.warn("No refresh token is present."); @@ -146,7 +150,7 @@ export async function exchangeRefreshTokenForAccessToken( const params = new URLSearchParams({ grant_type: "refresh_token", refresh_token: storedRefreshToken?.value ?? "", - client_id: getClientIdFromEnv(), + client_id: clientId, }); const response = await fetchAuthToken(params, logger, isNonInteractiveOrCI); @@ -224,7 +228,9 @@ export async function exchangeRefreshTokenForAccessToken( export async function exchangeAuthCodeForAccessToken( state: OAuthFlowState, logger: OAuthFlowContext["logger"], - isNonInteractiveOrCI: OAuthFlowContext["isNonInteractiveOrCI"] + isNonInteractiveOrCI: OAuthFlowContext["isNonInteractiveOrCI"], + clientId: string, + redirectUri: string ): Promise { const { authorizationCode, codeVerifier = "" } = state; @@ -237,8 +243,8 @@ export async function exchangeAuthCodeForAccessToken( const params = new URLSearchParams({ grant_type: `authorization_code`, code: authorizationCode ?? "", - redirect_uri: OAUTH_CALLBACK_URL, - client_id: getClientIdFromEnv(), + redirect_uri: redirectUri, + client_id: clientId, code_verifier: codeVerifier, }); @@ -357,7 +363,9 @@ async function getJSONFromResponse( ); if (text.match(/challenge-platform/)) { logger.error( - `It looks like you might have hit a bot challenge page. This may be transient but if not, please contact Cloudflare to find out what can be done. When you contact Cloudflare, please provide your Ray ID: ${response.headers.get("cf-ray")}` + `It looks like you might have hit a bot challenge page. This may be transient but if not, please contact Cloudflare to find out what can be done. When you contact Cloudflare, please provide your Ray ID: ${response.headers.get( + "cf-ray" + )}` ); } } diff --git a/packages/workers-auth/tests/credentials.test.ts b/packages/workers-auth/tests/credentials.test.ts new file mode 100644 index 0000000000..130eedb443 --- /dev/null +++ b/packages/workers-auth/tests/credentials.test.ts @@ -0,0 +1,101 @@ +import { afterEach, beforeEach, describe, it, vi } from "vitest"; +import { getAPIToken, getAuthFromEnv } from "../src/credentials"; +import type { + AuthConfigStorage, + UserAuthConfig, +} from "../src/auth-config-file"; + +/** An in-memory storage adapter for tests. */ +function memoryStorage(initial?: UserAuthConfig): AuthConfigStorage { + let value = initial; + return { + read() { + if (value === undefined) { + throw new Error("not logged in"); + } + return value; + }, + write(config) { + value = config; + }, + clear() { + value = undefined; + }, + path() { + return ""; + }, + }; +} + +describe("getAuthFromEnv", () => { + beforeEach(() => { + vi.unstubAllEnvs(); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("returns the API token from CLOUDFLARE_API_TOKEN", ({ expect }) => { + vi.stubEnv("CLOUDFLARE_API_TOKEN", "token-abc"); + expect(getAuthFromEnv()).toEqual({ apiToken: "token-abc" }); + }); + + it("prefers the global API key + email over the API token by default", ({ + expect, + }) => { + vi.stubEnv("CLOUDFLARE_API_TOKEN", "token-abc"); + vi.stubEnv("CLOUDFLARE_API_KEY", "global-key"); + vi.stubEnv("CLOUDFLARE_EMAIL", "user@example.com"); + expect(getAuthFromEnv()).toEqual({ + authKey: "global-key", + authEmail: "user@example.com", + }); + }); + + it("ignores the global API key + email when allowGlobalAuthKey is false", ({ + expect, + }) => { + vi.stubEnv("CLOUDFLARE_API_TOKEN", "token-abc"); + vi.stubEnv("CLOUDFLARE_API_KEY", "global-key"); + vi.stubEnv("CLOUDFLARE_EMAIL", "user@example.com"); + expect(getAuthFromEnv({ allowGlobalAuthKey: false })).toEqual({ + apiToken: "token-abc", + }); + }); + + it("returns undefined when no env credentials are present", ({ expect }) => { + expect(getAuthFromEnv()).toBeUndefined(); + }); +}); + +describe("getAPIToken", () => { + beforeEach(() => { + vi.unstubAllEnvs(); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("falls back to the stored OAuth token from the injected storage", ({ + expect, + }) => { + const storage = memoryStorage({ + oauth_token: "oauth-xyz", + refresh_token: "refresh-xyz", + expiration_time: "2099-01-01T00:00:00.000Z", + }); + expect(getAPIToken({ storage })).toEqual({ apiToken: "oauth-xyz" }); + }); + + it("prefers env credentials over the stored OAuth token", ({ expect }) => { + vi.stubEnv("CLOUDFLARE_API_TOKEN", "token-abc"); + const storage = memoryStorage({ oauth_token: "oauth-xyz" }); + expect(getAPIToken({ storage })).toEqual({ apiToken: "token-abc" }); + }); + + it("returns undefined when neither env nor stored credentials exist", ({ + expect, + }) => { + expect(getAPIToken({ storage: memoryStorage() })).toBeUndefined(); + }); +}); diff --git a/packages/workers-auth/tests/generate-auth-url.test.ts b/packages/workers-auth/tests/generate-auth-url.test.ts new file mode 100644 index 0000000000..169ed833ad --- /dev/null +++ b/packages/workers-auth/tests/generate-auth-url.test.ts @@ -0,0 +1,24 @@ +import { describe, it } from "vitest"; +import { generateAuthUrl } from "../src/generate-auth-url"; + +describe("generateAuthUrl redirectUri injection", () => { + const base = { + authUrl: "https://dash.cloudflare.com/oauth2/auth", + clientId: "client-123", + scopes: ["account:read"], + stateQueryParam: "state-123", + codeChallenge: "challenge-123", + }; + + it("includes the provided redirect_uri", ({ expect }) => { + const redirectUri = "http://localhost:8976/oauth/callback"; + const url = generateAuthUrl({ ...base, redirectUri }); + expect(url).toContain(`redirect_uri=${encodeURIComponent(redirectUri)}`); + }); + + it("uses a different injected redirect_uri when provided", ({ expect }) => { + const redirectUri = "http://localhost:8877/oauth/callback"; + const url = generateAuthUrl({ ...base, redirectUri }); + expect(url).toContain(`redirect_uri=${encodeURIComponent(redirectUri)}`); + }); +}); diff --git a/packages/workers-auth/tests/state.test.ts b/packages/workers-auth/tests/state.test.ts new file mode 100644 index 0000000000..41e9f75239 --- /dev/null +++ b/packages/workers-auth/tests/state.test.ts @@ -0,0 +1,52 @@ +import { describe, it } from "vitest"; +import { readStoredAuthState } from "../src/state"; +import type { + AuthConfigStorage, + UserAuthConfig, +} from "../src/auth-config-file"; + +function memoryStorage(initial?: UserAuthConfig): AuthConfigStorage { + let value = initial; + return { + read() { + if (value === undefined) { + throw new Error("not logged in"); + } + return value; + }, + write(config) { + value = config; + }, + clear() { + value = undefined; + }, + path() { + return ""; + }, + }; +} + +describe("readStoredAuthState storage injection", () => { + it("reads OAuth tokens from an injected storage backend", ({ expect }) => { + const storage = memoryStorage({ + oauth_token: "oauth-xyz", + refresh_token: "refresh-xyz", + expiration_time: "2099-01-01T00:00:00.000Z", + scopes: ["account:read"], + }); + expect(readStoredAuthState({ storage })).toEqual({ + accessToken: { + value: "oauth-xyz", + expiry: "2099-01-01T00:00:00.000Z", + }, + refreshToken: { value: "refresh-xyz" }, + scopes: ["account:read"], + }); + }); + + it("returns an empty object when the injected storage is empty", ({ + expect, + }) => { + expect(readStoredAuthState({ storage: memoryStorage() })).toEqual({}); + }); +}); diff --git a/packages/workers-utils/src/global-wrangler-config-path.ts b/packages/workers-utils/src/global-wrangler-config-path.ts index 1b01f485ec..85c9975d32 100644 --- a/packages/workers-utils/src/global-wrangler-config-path.ts +++ b/packages/workers-utils/src/global-wrangler-config-path.ts @@ -3,15 +3,56 @@ import path from "node:path"; import xdgAppPaths from "xdg-app-paths"; import { isDirectory } from "./fs-helpers"; -export function getGlobalWranglerConfigPath() { +export interface GetGlobalConfigPathOptions { + /** + * The application namespace. Defaults to `"wrangler"`. + */ + appName?: string; + /** + * Whether to prepend a `.` to `appName` when resolving the XDG path and the + * legacy `$HOME` directory. Defaults to `true` to match wrangler's + * historical behaviour (`.wrangler`). + */ + leadingDot?: boolean; + /** + * When `true` (the default, matching wrangler's historical behaviour), a + * pre-existing `~/.` directory takes precedence over the XDG path. + * Pass `false` to always use the XDG-compliant path. + */ + useLegacyHomeDir?: boolean; +} + +/** + * Resolve the global config directory for a Cloudflare CLI. + * + * Defaults to wrangler's directory (`.wrangler`) so existing callers are + * unaffected, but accepts an `appName` so other first-party CLIs (e.g. `cf`) + * can reuse the same XDG-compliant resolution under their own namespace. + */ +export function getGlobalConfigPath({ + appName = "wrangler", + leadingDot = true, + useLegacyHomeDir = true, +}: GetGlobalConfigPathOptions = {}) { //TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable - const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path - const legacyConfigDir = path.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory + const dirName = `${leadingDot ? "." : ""}${appName}`; + const configDir = xdgAppPaths(dirName).config(); // New XDG compliant config path - // Check for the .wrangler directory in root if it is not there then use the XDG compliant path. - if (isDirectory(legacyConfigDir)) { - return legacyConfigDir; - } else { - return configDir; + if (useLegacyHomeDir) { + const legacyConfigDir = path.join(os.homedir(), dirName); // Legacy config in user's home directory + // Check for the legacy directory in $HOME; if it is not there then use the + // XDG compliant path. + if (isDirectory(legacyConfigDir)) { + return legacyConfigDir; + } } + + return configDir; +} + +/** + * @deprecated Use {@link getGlobalConfigPath} instead. + */ +export function getGlobalWranglerConfigPath() { + return getGlobalConfigPath(); } diff --git a/packages/workers-utils/src/index.ts b/packages/workers-utils/src/index.ts index c065627560..31f21b6bd2 100644 --- a/packages/workers-utils/src/index.ts +++ b/packages/workers-utils/src/index.ts @@ -88,7 +88,11 @@ export { export * from "./environment-variables/misc-variables"; -export { getGlobalWranglerConfigPath } from "./global-wrangler-config-path"; +export { + getGlobalConfigPath, + getGlobalWranglerConfigPath, +} from "./global-wrangler-config-path"; +export type { GetGlobalConfigPathOptions } from "./global-wrangler-config-path"; export { isCompatDate, getTodaysCompatDate } from "./compatibility-date"; export type { CompatDate } from "./compatibility-date"; diff --git a/packages/wrangler/e2e/auth-scopes.test.ts b/packages/wrangler/e2e/auth-scopes.test.ts index e7322ab5c0..6d4db6e564 100644 --- a/packages/wrangler/e2e/auth-scopes.test.ts +++ b/packages/wrangler/e2e/auth-scopes.test.ts @@ -1,10 +1,11 @@ -import { - getAuthUrlFromEnv, - getClientIdFromEnv, -} from "@cloudflare/workers-auth"; +import { getAuthUrlFromEnv } from "@cloudflare/workers-auth"; import { fetch } from "undici"; import { describe, it } from "vitest"; -import { generateAuthUrl } from "../src/user/generate-auth-url"; +import { getClientIdFromEnv } from "../src/user/auth-variables"; +import { + generateAuthUrl, + OAUTH_CALLBACK_URL, +} from "../src/user/generate-auth-url"; import { DefaultScopeKeys } from "../src/user/user"; describe("auth scopes", () => { @@ -17,6 +18,7 @@ describe("auth scopes", () => { scopes: DefaultScopeKeys, stateQueryParam: "test-state", codeChallenge: "test-code-challenge", + redirectUri: OAUTH_CALLBACK_URL, }); const response = await fetch(url, { redirect: "manual" }); diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index eeb4966af2..a459c40ebb 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -73,7 +73,7 @@ "miniflare": "workspace:*", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20260610.1" + "workerd": "1.20260611.1" }, "devDependencies": { "@aws-sdk/client-s3": "^3.721.0", diff --git a/packages/workers-auth/tests/auth-config-file.test.ts b/packages/wrangler/src/__tests__/auth-config-file.test.ts similarity index 95% rename from packages/workers-auth/tests/auth-config-file.test.ts rename to packages/wrangler/src/__tests__/auth-config-file.test.ts index 5b2bd065e0..6cc2ce50eb 100644 --- a/packages/workers-auth/tests/auth-config-file.test.ts +++ b/packages/wrangler/src/__tests__/auth-config-file.test.ts @@ -5,8 +5,8 @@ import { getAuthConfigFilePath, readAuthConfigFile, writeAuthConfigFile, -} from "../src/auth-config-file"; -import type { UserAuthConfig } from "../src/auth-config-file"; +} from "../user"; +import type { UserAuthConfig } from "../user"; const SAMPLE_CONFIG: UserAuthConfig = { oauth_token: "test-oauth-token", diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index 5a1b332b30..0b656b6735 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -129,7 +129,7 @@ describe("User", () => { ────────────────── Attempting to login via OAuth... Temporary login server listening on 0.0.0.0:8976 - Note that the OAuth login page will always redirect to \`localhost:8976\`. + Note that the OAuth login page will always redirect to \`http://localhost:8976/oauth/callback\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20websearch.run%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." @@ -175,7 +175,7 @@ describe("User", () => { ────────────────── Attempting to login via OAuth... Temporary login server listening on mylocalhost.local:8976 - Note that the OAuth login page will always redirect to \`localhost:8976\`. + Note that the OAuth login page will always redirect to \`http://localhost:8976/oauth/callback\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20websearch.run%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." @@ -221,7 +221,7 @@ describe("User", () => { ────────────────── Attempting to login via OAuth... Temporary login server listening on localhost:8787 - Note that the OAuth login page will always redirect to \`localhost:8976\`. + Note that the OAuth login page will always redirect to \`http://localhost:8976/oauth/callback\`. If you have changed the callback host or port because you are running in a container, then ensure that you have port forwarding set up correctly. Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20ai%3Awrite%20ai-search%3Awrite%20ai-search%3Arun%20websearch.run%20agent-memory%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20secrets_store%3Awrite%20artifacts%3Awrite%20flagship%3Awrite%20containers%3Awrite%20cloudchamber%3Awrite%20connectivity%3Aadmin%20email_routing%3Awrite%20email_sending%3Awrite%20browser%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 6b6dd5d900..93b21f183f 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -9,7 +9,7 @@ import { sharedDeployVersionsArgs, validateDeployVersionsArgs, } from "../deployment-bundle/deploy-args"; -import { handleBuild } from "../deployment-bundle/maybe-build-worker"; +import { buildWorker } from "../deployment-bundle/maybe-build-worker"; import { cleanupDestination, mergeDeployConfigArgs, @@ -144,10 +144,14 @@ export const deployCommand = createCommand({ const beforeUpload = Date.now(); + const buildResult = await buildWorker(mergedProps, config, { + metafile: mergedProps.metafile, + }); + const { sourceMapSize, versionId, workerTag, targets } = await deploy( mergedProps, config, - handleBuild, + buildResult, { syncWorkersSite, provisionBindings, diff --git a/packages/wrangler/src/deployment-bundle/maybe-build-worker.ts b/packages/wrangler/src/deployment-bundle/maybe-build-worker.ts index 4aef7f8810..b4bf66366b 100644 --- a/packages/wrangler/src/deployment-bundle/maybe-build-worker.ts +++ b/packages/wrangler/src/deployment-bundle/maybe-build-worker.ts @@ -1,5 +1,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; +import { validateNodeCompatMode } from "@cloudflare/deploy-helpers"; +import { logger } from "../logger"; import { isNavigatorDefined } from "../navigator-user-agent"; import { bundleWorker } from "./bundle"; import { logBuildOutput } from "./esbuild-plugins/log-build-output"; @@ -8,142 +10,157 @@ import { getWrangler1xLegacyModuleReferences, } from "./module-collection"; import { noBundleWorker } from "./no-bundle-worker"; -import type { HandleBuild } from "@cloudflare/deploy-helpers"; +import type { WorkerBuildResult } from "@cloudflare/deploy-helpers"; +import type { SharedDeployVersionsProps } from "@cloudflare/deploy-helpers"; +import type { Config } from "@cloudflare/workers-utils"; -export const handleBuild: HandleBuild = { - async build(props, config, options) { - const { - entry, - noBundle, - destination, - uploadSourceMaps, - jsxFactory, - jsxFragment, - minify, - compatibilityDate, - compatibilityFlags, - } = props; - const { projectRoot } = entry; - const { nodejsCompatMode } = options; +export async function buildWorker( + props: SharedDeployVersionsProps, + config: Config, + options: { + metafile?: string | boolean; + } +): Promise { + const nodejsCompatMode = validateNodeCompatMode( + props.compatibilityDate, + props.compatibilityFlags, + { noBundle: props.noBundle } + ); - if (props.outdir) { - // we're using a custom output directory, - // so let's first ensure it exists - mkdirSync(props.outdir, { recursive: true }); - const readmePath = path.join(props.outdir, "README.md"); - writeFileSync( - readmePath, - `This folder contains the built output assets for the worker "${props.name}" generated at ${new Date().toISOString()}.` - ); - } + if (props.noBundle && props.minify) { + logger.warn( + "`--minify` and `--no-bundle` can't be used together. If you want to minify your Worker and disable Wrangler's bundling, please minify as part of your own bundling process." + ); + } + const { + entry, + noBundle, + destination, + uploadSourceMaps, + jsxFactory, + jsxFragment, + minify, + compatibilityDate, + compatibilityFlags, + } = props; - if (noBundle) { - // if we're not building, let's just copy the entry to the destination directory - const destinationDir = - typeof destination === "string" ? destination : destination.path; - mkdirSync(destinationDir, { recursive: true }); - writeFileSync( - path.join(destinationDir, path.basename(entry.file)), - readFileSync(entry.file, "utf-8") - ); - } + if (props.outdir) { + // we're using a custom output directory, + // so let's first ensure it exists + mkdirSync(props.outdir, { recursive: true }); + const readmePath = path.join(props.outdir, "README.md"); + writeFileSync( + readmePath, + `This folder contains the built output assets for the worker "${props.name}" generated at ${new Date().toISOString()}.` + ); + } - const entryDirectory = path.dirname(entry.file); - const moduleCollector = createModuleCollector({ - wrangler1xLegacyModuleReferences: getWrangler1xLegacyModuleReferences( - entryDirectory, - entry.file - ), - entry, - // `moduleCollector` doesn't get used when `noBundle` is set, so - // `findAdditionalModules` always defaults to `false` - findAdditionalModules: config.find_additional_modules ?? false, - rules: config.rules ?? [], - preserveFileNames: config.preserve_file_names ?? false, - }); + if (noBundle) { + // if we're not building, let's just copy the entry to the destination directory + const destinationDir = + typeof destination === "string" ? destination : destination.path; + mkdirSync(destinationDir, { recursive: true }); + writeFileSync( + path.join(destinationDir, path.basename(entry.file)), + readFileSync(entry.file, "utf-8") + ); + } - const { - modules, - dependencies, - resolvedEntryPointPath, - bundleType, - ...bundle - } = noBundle - ? await noBundleWorker( - entry, - config.rules ?? [], - props.outdir, - config.python_modules.exclude - ) - : await bundleWorker( - entry, - typeof destination === "string" ? destination : destination.path, - { - metafile: options.metafile, - bundle: true, - additionalModules: [], - moduleCollector, - doBindings: config.durable_objects.bindings, - workflowBindings: config.workflows ?? [], - jsxFactory, - jsxFragment, - tsconfig: props.tsconfig, - minify, - keepNames: config.keep_names ?? true, - sourcemap: uploadSourceMaps, - nodejsCompatMode, + const entryDirectory = path.dirname(entry.file); + const moduleCollector = createModuleCollector({ + wrangler1xLegacyModuleReferences: getWrangler1xLegacyModuleReferences( + entryDirectory, + entry.file + ), + entry, + // `moduleCollector` doesn't get used when `noBundle` is set, so + // `findAdditionalModules` always defaults to `false` + findAdditionalModules: config.find_additional_modules ?? false, + rules: config.rules ?? [], + preserveFileNames: config.preserve_file_names ?? false, + }); + + const { + modules, + dependencies, + resolvedEntryPointPath, + bundleType, + ...bundle + } = noBundle + ? await noBundleWorker( + entry, + config.rules ?? [], + props.outdir, + config.python_modules.exclude + ) + : await bundleWorker( + entry, + typeof destination === "string" ? destination : destination.path, + { + metafile: options.metafile, + bundle: true, + additionalModules: [], + moduleCollector, + doBindings: config.durable_objects.bindings, + workflowBindings: config.workflows ?? [], + jsxFactory, + jsxFragment, + tsconfig: props.tsconfig, + minify, + keepNames: config.keep_names ?? true, + sourcemap: uploadSourceMaps, + nodejsCompatMode, + compatibilityDate, + compatibilityFlags, + define: props.defines, + checkFetch: false, + alias: props.alias, + // We want to know if the build is for development or publishing + // This could potentially cause issues as we no longer have identical behaviour between dev and deploy? + targetConsumer: "deploy", + local: false, + projectRoot: entry.projectRoot, + defineNavigatorUserAgent: isNavigatorDefined( compatibilityDate, - compatibilityFlags, - define: props.defines, - checkFetch: false, - alias: props.alias, - // We want to know if the build is for development or publishing - // This could potentially cause issues as we no longer have identical behaviour between dev and deploy? - targetConsumer: "deploy", - local: false, - projectRoot, - defineNavigatorUserAgent: isNavigatorDefined( - compatibilityDate, - compatibilityFlags - ), - plugins: [logBuildOutput(nodejsCompatMode)], + compatibilityFlags + ), + plugins: [logBuildOutput(nodejsCompatMode)], - // Pages specific options used by wrangler pages commands - entryName: undefined, - inject: undefined, - isOutfile: undefined, - external: undefined, + // Pages specific options used by wrangler pages commands + entryName: undefined, + inject: undefined, + isOutfile: undefined, + external: undefined, - // These options are dev-only - testScheduled: undefined, - watch: undefined, - } - ); + // These options are dev-only + testScheduled: undefined, + watch: undefined, + } + ); - // Add modules to dependencies for size warning - for (const module of modules) { - const modulePath = - module.filePath === undefined - ? module.name - : path.relative("", module.filePath); - const bytesInOutput = - typeof module.content === "string" - ? Buffer.byteLength(module.content) - : module.content.byteLength; - dependencies[modulePath] = { bytesInOutput }; - } + // Add modules to dependencies for size warning + for (const module of modules) { + const modulePath = + module.filePath === undefined + ? module.name + : path.relative("", module.filePath); + const bytesInOutput = + typeof module.content === "string" + ? Buffer.byteLength(module.content) + : module.content.byteLength; + dependencies[modulePath] = { bytesInOutput }; + } - const content = readFileSync(resolvedEntryPointPath, { - encoding: "utf-8", - }); + const content = readFileSync(resolvedEntryPointPath, { + encoding: "utf-8", + }); - return { - modules, - dependencies, - resolvedEntryPointPath, - bundleType, - content, - bundle, - }; - }, -}; + return { + modules, + dependencies, + resolvedEntryPointPath, + bundleType, + content, + bundle, + }; +} diff --git a/packages/wrangler/src/user/auth-config-file.ts b/packages/wrangler/src/user/auth-config-file.ts new file mode 100644 index 0000000000..be3bd0ab1a --- /dev/null +++ b/packages/wrangler/src/user/auth-config-file.ts @@ -0,0 +1,84 @@ +import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { + getCloudflareApiEnvironmentFromEnv, + getGlobalWranglerConfigPath, + parseTOML, + readFileSync, +} from "@cloudflare/workers-utils"; +import TOML from "smol-toml"; +import type { + AuthConfigStorage, + UserAuthConfig, +} from "@cloudflare/workers-auth"; + +/** + * Wrangler's default `AuthConfigStorage`: a TOML file on disk, located under + * the global Wrangler config directory. + * + * Injected into `@cloudflare/workers-auth` (the OAuth flow, `getAPIToken`, and + * `readStoredAuthState`), which no longer ships a default of its own. + */ +export function defaultAuthConfigStorage(): AuthConfigStorage { + return { + read: readAuthConfigFile, + write: writeAuthConfigFile, + clear: () => rmSync(getAuthConfigFilePath()), + path: getAuthConfigFilePath, + }; +} + +/** + * The path to the config file that holds user authentication data, + * relative to the user's home directory. + */ +const USER_AUTH_CONFIG_PATH = "config"; + +/** + * Returns the absolute path to the auth config TOML file. + * + * The file lives under the global Wrangler config directory and is named + * `default.toml` in production, or `.toml` for the staging / + * other Cloudflare API environments. + */ +export function getAuthConfigFilePath(): string { + const environment = getCloudflareApiEnvironmentFromEnv(); + const filePath = `${USER_AUTH_CONFIG_PATH}/${environment === "production" ? "default.toml" : `${environment}.toml`}`; + return path.join(getGlobalWranglerConfigPath(), filePath); +} + +/** + * Writes the user auth config to disk. + * + * No in-memory cache to invalidate — auth state is read on demand by every call + * site that needs it. Callers are responsible for any consumer-side cache + * purging (e.g. via the `OAuthFlowContext.purgeOnLoginOrLogout` hook). + */ +export function writeAuthConfigFile(config: UserAuthConfig): void { + const configPath = getAuthConfigFilePath(); + + mkdirSync(path.dirname(configPath), { + recursive: true, + }); + // Write with mode 0o600 on creation and re-`chmod` on every save so + // other local users on shared hosts can't read the OAuth tokens. + // `writeFileSync`'s `mode` option only applies when the file is + // being created — the explicit `chmodSync` ensures that pre-existing + // files (e.g. written by an older Wrangler version with the process + // umask) get tightened on the next save too. + writeFileSync(configPath, TOML.stringify(config), { + encoding: "utf-8", + mode: 0o600, + }); + chmodSync(configPath, 0o600); +} + +/** + * Reads the user auth config from disk. + * + * @throws if the file does not exist or cannot be parsed as TOML. Callers + * typically catch this and treat the failure as "not logged in via local OAuth". + */ +export function readAuthConfigFile(): UserAuthConfig { + return parseTOML(readFileSync(getAuthConfigFilePath())) as UserAuthConfig; +} diff --git a/packages/wrangler/src/user/auth-variables.ts b/packages/wrangler/src/user/auth-variables.ts index fdf1a8e64f..5fd5638c51 100644 --- a/packages/wrangler/src/user/auth-variables.ts +++ b/packages/wrangler/src/user/auth-variables.ts @@ -1,4 +1,7 @@ -import { getEnvironmentVariableFactory } from "@cloudflare/workers-utils"; +import { + getCloudflareApiEnvironmentFromEnv, + getEnvironmentVariableFactory, +} from "@cloudflare/workers-utils"; /** * `CLOUDFLARE_ACCOUNT_ID` overrides the account inferred from the current user. @@ -8,28 +11,33 @@ export const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({ deprecatedName: "CF_ACCOUNT_ID", }); -export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({ - variableName: "CLOUDFLARE_API_TOKEN", - deprecatedName: "CF_API_TOKEN", -}); - -export const getCloudflareGlobalAuthKeyFromEnv = getEnvironmentVariableFactory({ - variableName: "CLOUDFLARE_API_KEY", - deprecatedName: "CF_API_KEY", +/** + * `WRANGLER_CLIENT_ID` is the UUID of Wrangler's registered OAuth app, used to + * identify Wrangler to the Cloudflare OAuth server. + * + * Normally you should not need to set this explicitly. + * If you want to switch to the staging environment set the + * `WRANGLER_API_ENVIRONMENT=staging` environment variable instead. + */ +export const getClientIdFromEnv = getEnvironmentVariableFactory({ + variableName: "WRANGLER_CLIENT_ID", + defaultValue: () => + getCloudflareApiEnvironmentFromEnv() === "staging" + ? "4b2ea6cc-9421-4761-874b-ce550e0e3def" + : "54d11594-84e4-41aa-b438-e81b8fa78ee7", }); -export const getCloudflareGlobalAuthEmailFromEnv = - getEnvironmentVariableFactory({ - variableName: "CLOUDFLARE_EMAIL", - deprecatedName: "CF_EMAIL", - }); - export const getWranglerR2SqlAuthToken = getEnvironmentVariableFactory({ variableName: "WRANGLER_R2_SQL_AUTH_TOKEN", }); -// OAuth-flow-related env-var getters (`WRANGLER_CLIENT_ID`, `WRANGLER_AUTH_DOMAIN`, -// `WRANGLER_AUTH_URL`, `WRANGLER_TOKEN_URL`, `WRANGLER_REVOKE_URL`, -// `WRANGLER_CF_AUTHORIZATION_TOKEN`, `CLOUDFLARE_ACCESS_CLIENT_ID`, -// `CLOUDFLARE_ACCESS_CLIENT_SECRET`) have moved to `@cloudflare/workers-auth` -// alongside the OAuth flow itself. +// The *credential* env-var getters (`CLOUDFLARE_API_TOKEN`, +// `CLOUDFLARE_API_KEY`, `CLOUDFLARE_EMAIL`) now live in +// `@cloudflare/workers-auth` alongside the shared env→credential resolver, so +// every Cloudflare CLI shares one implementation. Re-exported here so existing +// `from "./auth-variables"` import paths keep working. +export { + getCloudflareAPITokenFromEnv, + getCloudflareGlobalAuthEmailFromEnv, + getCloudflareGlobalAuthKeyFromEnv, +} from "@cloudflare/workers-auth"; diff --git a/packages/wrangler/src/user/generate-auth-url.ts b/packages/wrangler/src/user/generate-auth-url.ts index 98939251af..30541dec71 100644 --- a/packages/wrangler/src/user/generate-auth-url.ts +++ b/packages/wrangler/src/user/generate-auth-url.ts @@ -1,7 +1,10 @@ -// Re-export shim. The real implementation lives in `@cloudflare/workers-auth`. -// // This file exists so wrangler's tests can continue to `vi.mock("../user/generate-auth-url", ...)` // to produce deterministic OAuth URLs for snapshot testing. The mocked exports // are imported by `./user.ts` and injected into the OAuth flow context, // where the workers-auth package uses them internally. -export { generateAuthUrl, OAUTH_CALLBACK_URL } from "@cloudflare/workers-auth"; +export { generateAuthUrl } from "@cloudflare/workers-auth"; + +/** + * The `redirect_uri` registered on Wrangler's OAuth app + */ +export const OAUTH_CALLBACK_URL = "http://localhost:8976/oauth/callback"; diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 6a5445ba64..62c8e3ee07 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -11,7 +11,12 @@ // wrangler's commands import assert from "node:assert"; -import { readStoredAuthState } from "@cloudflare/workers-auth"; +import { + getAPIToken as getAPITokenShared, + getAuthFromEnv as getAuthFromEnvShared, + readStoredAuthState, + requireApiToken as requireApiTokenShared, +} from "@cloudflare/workers-auth"; import { createOAuthFlow } from "@cloudflare/workers-auth"; import { configFileName, UserError } from "@cloudflare/workers-utils"; import ci from "ci-info"; @@ -21,14 +26,13 @@ import { NoDefaultValueProvided, select } from "../dialogs"; import { isNonInteractiveOrCI } from "../is-interactive"; import { logger } from "../logger"; import openInBrowser from "../open-in-browser"; +import { defaultAuthConfigStorage } from "./auth-config-file"; import { + getClientIdFromEnv, getCloudflareAccountIdFromEnv, - getCloudflareAPITokenFromEnv, - getCloudflareGlobalAuthEmailFromEnv, - getCloudflareGlobalAuthKeyFromEnv, } from "./auth-variables"; import { fetchAllAccounts } from "./fetch-accounts"; -import { generateAuthUrl } from "./generate-auth-url"; +import { generateAuthUrl, OAUTH_CALLBACK_URL } from "./generate-auth-url"; import { generateRandomState } from "./generate-random-state"; import type { Account } from "./shared"; import type { @@ -52,12 +56,34 @@ import type { * apply — the mocked versions are injected via the context here and used * internally by `@cloudflare/workers-auth`. */ +/** + * Wrangler's branded OAuth consent pages, shown to the user after they grant + * or deny consent to Wrangler's OAuth app. + */ +const WRANGLER_CONSENT_PAGES = { + granted: { + url: "https://welcome.developers.workers.dev/wrangler-oauth-consent-granted", + }, + denied: { + url: "https://welcome.developers.workers.dev/wrangler-oauth-consent-denied", + error: + "Error: Consent denied. You must grant consent to Wrangler in order to login.\n" + + "If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable", + }, +}; + +const authConfigStorage = defaultAuthConfigStorage(); + const oauthFlow = createOAuthFlow({ logger, isNonInteractiveOrCI, openInBrowser, hasEnvCredentials: () => getAuthFromEnv() !== undefined, purgeOnLoginOrLogout: purgeConfigCaches, + clientId: getClientIdFromEnv, + consent: WRANGLER_CONSENT_PAGES, + redirectUri: OAUTH_CALLBACK_URL, + storage: authConfigStorage, generateAuthUrl, generateRandomState, }); @@ -65,24 +91,17 @@ const oauthFlow = createOAuthFlow({ /** * Try to read API credentials from environment variables. * + * Delegates to the shared resolver in `@cloudflare/workers-auth`. Wrangler + * supports the global API key + email pair in addition to API tokens, so the + * default (`allowGlobalAuthKey: true`) is used. + * * Authentication priority (highest to lowest): * 1. Global API Key + Email (CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL) * 2. API Token (CLOUDFLARE_API_TOKEN) * 3. OAuth token from local state (via `wrangler login`) - not handled here - * - * Note: Global API Key + Email requires two headers (X-Auth-Key + X-Auth-Email), - * while API Token and OAuth token are both used as Bearer tokens. */ export function getAuthFromEnv(): ApiCredentials | undefined { - const globalApiKey = getCloudflareGlobalAuthKeyFromEnv(); - const globalApiEmail = getCloudflareGlobalAuthEmailFromEnv(); - const apiToken = getCloudflareAPITokenFromEnv(); - - if (globalApiKey && globalApiEmail) { - return { authKey: globalApiKey, authEmail: globalApiEmail }; - } else if (apiToken) { - return { apiToken }; - } + return getAuthFromEnvShared(); } // --------------------------------------------------------------------------- @@ -162,9 +181,10 @@ export function listScopes(message = "💁 Available scopes:"): void { * the user is not logged in via OAuth (e.g. env-based auth). */ export function getScopes(): Scope[] | undefined { - return readStoredAuthState({ warningLogger: logger }).scopes as - | Scope[] - | undefined; + return readStoredAuthState({ + warningLogger: logger, + storage: authConfigStorage, + }).scopes as Scope[] | undefined; } export function printScopes(scopes: Scope[]) { @@ -181,33 +201,20 @@ export function printScopes(scopes: Scope[]) { // --------------------------------------------------------------------------- export function getAPIToken(): ApiCredentials | undefined { - const envAuth = getAuthFromEnv(); - if (envAuth) { - return envAuth; - } - - const stored = readStoredAuthState({ warningLogger: logger }); - if (stored.deprecatedApiToken) { - return { apiToken: stored.deprecatedApiToken }; - } - if (stored.accessToken?.value) { - return { apiToken: stored.accessToken.value }; - } - - return undefined; + return getAPITokenShared({ + warningLogger: logger, + storage: authConfigStorage, + }); } /** * Throw an error if there is no API token available. */ export function requireApiToken(): ApiCredentials { - const credentials = getAPIToken(); - if (!credentials) { - throw new UserError("No API token found.", { - telemetryMessage: "user auth missing api token", - }); - } - return credentials; + return requireApiTokenShared({ + warningLogger: logger, + storage: authConfigStorage, + }); } // --------------------------------------------------------------------------- @@ -230,8 +237,8 @@ function withDefaultScopes( complianceConfig, scopes: props?.scopes ?? DefaultScopeKeys, browser: props?.browser ?? true, - callbackHost: props?.callbackHost ?? "localhost", - callbackPort: props?.callbackPort ?? 8976, + callbackHost: props?.callbackHost, + callbackPort: props?.callbackPort, }; } @@ -270,13 +277,11 @@ export async function getOAuthTokenFromLocalState(): Promise< return oauthFlow.getOAuthTokenFromLocalState(); } -// Re-export the auth-config-file pure helpers from the package so the -// historical `from "../user"` import paths keep working. export { getAuthConfigFilePath, readAuthConfigFile, writeAuthConfigFile, -} from "@cloudflare/workers-auth"; +} from "./auth-config-file"; export type { UserAuthConfig } from "@cloudflare/workers-auth"; // `PKCE_CHARSET` is re-exported for any external consumers that used to // import it from this barrel. @@ -365,12 +370,16 @@ export async function getOrSelectAccountId( const redactAccountName = ci.isCI; throw new UserError( `More than one account available but unable to select one in non-interactive mode. -Please set the appropriate \`account_id\` in your ${configFileName(undefined)} file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. +Please set the appropriate \`account_id\` in your ${configFileName( + undefined + )} file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): ${accounts .map( (account: Account) => - ` \`${redactAccountName ? "(redacted)" : account.name}\`: \`${account.id}\`` + ` \`${redactAccountName ? "(redacted)" : account.name}\`: \`${ + account.id + }\`` ) .join("\n")}`, { telemetryMessage: "user account selection unavailable" } diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 6830a63716..ed513af36c 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -1,4 +1,4 @@ -import { versionsUpload as versionsUploadBase } from "@cloudflare/deploy-helpers"; +import { versionsUpload } from "@cloudflare/deploy-helpers"; import { analyseBundle } from "../check/commands"; import { createCommand } from "../core/create-command"; import { provisionBindings } from "../deployment-bundle/bindings"; @@ -6,7 +6,7 @@ import { sharedDeployVersionsArgs, validateDeployVersionsArgs, } from "../deployment-bundle/deploy-args"; -import { handleBuild } from "../deployment-bundle/maybe-build-worker"; +import { buildWorker } from "../deployment-bundle/maybe-build-worker"; import { cleanupDestination, mergeVersionsUploadConfigArgs, @@ -14,11 +14,6 @@ import { import * as metrics from "../metrics"; import { writeOutput } from "../output"; import { getScriptName } from "../utils/getScriptName"; -import type { - HandleBuild, - VersionsUploadProps, -} from "@cloudflare/deploy-helpers"; -import type { Config } from "@cloudflare/workers-utils"; export const versionsUploadCommand = createCommand({ metadata: { @@ -67,12 +62,17 @@ export const versionsUploadCommand = createCommand({ const workerNameOverridden = mergedProps.name !== undefined && mergedProps.name !== preMergeName; + const buildResult = await buildWorker(mergedProps, config, {}); + const { versionId, workerTag, versionPreviewUrl, versionPreviewAliasUrl, - } = await versionsUpload(mergedProps, config, handleBuild); + } = await versionsUpload(mergedProps, config, buildResult, { + provisionBindings: provisionBindings, + analyseBundle: analyseBundle, + }); writeOutput({ type: "version-upload", @@ -92,19 +92,3 @@ export const versionsUploadCommand = createCommand({ }); export type VersionsUploadArgs = (typeof versionsUploadCommand)["args"]; - -export default async function versionsUpload( - props: VersionsUploadProps, - config: Config, - buildWorker: HandleBuild -): Promise<{ - versionId: string | null; - workerTag: string | null; - versionPreviewUrl?: string | undefined; - versionPreviewAliasUrl?: string | undefined; -}> { - return versionsUploadBase(props, config, buildWorker, { - provisionBindings, - analyseBundle, - }); -} diff --git a/packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts b/packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts index 84b3eac1c4..9cfd3e9f5a 100644 --- a/packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts +++ b/packages/wrangler/templates/startDevWorker/InspectorProxyWorker.ts @@ -414,10 +414,16 @@ export class InspectorProxyWorker implements DurableObject { runtime ); } - this.sendRuntimeMessage( - { method: "Network.enable", id: this.nextCounter() }, - runtime - ); + // Only enable the Network domain when DevTools is attached. Otherwise the + // runtime streams a Network.dataReceived per response body chunk into a + // buffer that is never drained without a client (headless `wrangler dev`), + // flooding the inspector until the dev server stops accepting connections. + if (this.websockets.devtools !== undefined) { + this.sendRuntimeMessage( + { method: "Network.enable", id: this.nextCounter() }, + runtime + ); + } clearInterval(this.runtimeKeepAliveInterval); this.runtimeKeepAliveInterval = setInterval(() => { @@ -563,12 +569,20 @@ export class InspectorProxyWorker implements DurableObject { if (this.websockets.devtools === devtools) { this.websockets.devtools = undefined; - // Notify the runtime to disable the debugger when DevTools disconnects. + // Notify the runtime to disable the Debugger and Network domains when + // DevTools disconnects. Without disabling Network the runtime keeps + // streaming Network.dataReceived into runtimeMessageBuffer, which is no + // longer drained once devtools is undefined, reintroducing the same + // headless flood this proxy otherwise avoids. if (this.websockets.runtime) { this.sendRuntimeMessage({ id: this.nextCounter(), method: "Debugger.disable", }); + this.sendRuntimeMessage({ + id: this.nextCounter(), + method: "Network.disable", + }); } } }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 836ce72067..26f6198415 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,8 +10,8 @@ catalogs: specifier: 0.13.3 version: 0.13.3 '@cloudflare/workers-types': - specifier: ^4.20260610.1 - version: 4.20260610.1 + specifier: ^4.20260611.1 + version: 4.20260611.1 '@hey-api/openapi-ts': specifier: 0.94.0 version: 0.94.0 @@ -182,7 +182,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@fixture/shared': specifier: workspace:* version: link:../shared @@ -230,7 +230,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 ts-dedent: specifier: ^2.2.0 version: 2.2.0 @@ -248,7 +248,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -269,7 +269,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -293,7 +293,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -329,7 +329,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 undici: specifier: catalog:default version: 7.24.8 @@ -344,7 +344,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/mimetext': specifier: ^2.0.4 version: 2.0.4 @@ -383,7 +383,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/jest-image-snapshot': specifier: ^6.4.0 version: 6.4.0 @@ -410,7 +410,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 miniflare: specifier: workspace:* version: link:../../packages/miniflare @@ -480,7 +480,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/is-even': specifier: ^1.0.2 version: 1.0.2 @@ -498,11 +498,11 @@ importers: dependencies: '@sentry/cloudflare': specifier: ^10 - version: 10.50.0(@cloudflare/workers-types@4.20260610.1) + version: 10.50.0(@cloudflare/workers-types@4.20260611.1) devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 vitest: specifier: catalog:default version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@22.15.17)(@vitest/ui@4.1.0)(msw@2.12.4(@types/node@22.15.17)(typescript@5.9.3))(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -533,7 +533,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -561,7 +561,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/node': specifier: 22.15.17 version: 22.15.17 @@ -591,7 +591,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 undici: specifier: catalog:default version: 7.24.8 @@ -609,7 +609,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/debug': specifier: 4.1.12 version: 4.1.12 @@ -642,7 +642,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -667,7 +667,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@fixture/pages-plugin': specifier: workspace:* version: link:../pages-plugin-example @@ -691,7 +691,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -730,7 +730,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -751,7 +751,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -772,7 +772,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -790,7 +790,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 is-odd: specifier: ^3.0.1 version: 3.0.1 @@ -809,7 +809,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@fixture/pages-plugin': specifier: workspace:* version: link:../pages-plugin-example @@ -869,7 +869,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -890,7 +890,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1046,19 +1046,19 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 fixtures/rules-app: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 fixtures/secrets-store: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1085,7 +1085,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/is-even': specifier: ^1.0.2 version: 1.0.2 @@ -1109,7 +1109,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 vitest: specifier: catalog:default version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@22.15.17)(@vitest/ui@4.1.0)(msw@2.12.4(@types/node@22.15.17)(typescript@5.9.3))(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -1124,7 +1124,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 esbuild: specifier: catalog:default version: 0.27.3 @@ -1142,7 +1142,7 @@ importers: devDependencies: '@better-auth/stripe': specifier: ^1.4.6 - version: 1.5.4(282160554b4fc0c0b45522a29c09ee8c) + version: 1.5.4(70d0aabc062167cd58748037a9ade3d1) '@cloudflare/containers': specifier: ^0.2.2 version: 0.2.2 @@ -1151,7 +1151,7 @@ importers: version: link:../../packages/vitest-pool-workers '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@microlabs/otel-cf-workers': specifier: 1.0.0-rc.45 version: 1.0.0-rc.45(@opentelemetry/api@1.9.1) @@ -1166,7 +1166,7 @@ importers: version: 3.2.6 better-auth: specifier: ^1.4.6 - version: 1.5.4(@cloudflare/workers-types@4.20260610.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0) + version: 1.5.4(@cloudflare/workers-types@4.20260611.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0) discord-api-types: specifier: 0.37.98 version: 0.37.98 @@ -1237,7 +1237,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@fixture/shared': specifier: workspace:* version: link:../shared @@ -1292,7 +1292,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1304,7 +1304,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 miniflare: specifier: workspace:* version: link:../../packages/miniflare @@ -1352,7 +1352,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 run-script-os: specifier: ^1.1.6 version: 1.1.6 @@ -1376,7 +1376,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1397,7 +1397,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1418,7 +1418,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1439,7 +1439,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1460,7 +1460,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/jest-image-snapshot': specifier: ^6.4.0 version: 6.4.0 @@ -1493,7 +1493,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/node': specifier: 22.15.17 version: 22.15.17 @@ -1520,7 +1520,7 @@ importers: version: link:../../packages/workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1538,7 +1538,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -1629,7 +1629,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -1695,7 +1695,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -1906,7 +1906,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@octokit/types': specifier: ^13.8.0 version: 13.8.0 @@ -1927,7 +1927,7 @@ importers: version: link:../vitest-pool-workers '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -1951,7 +1951,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -1978,10 +1978,10 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: catalog:default - version: 0.13.3(@cloudflare/workers-types@4.20260610.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) + version: 0.13.3(@cloudflare/workers-types@4.20260611.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/mime': specifier: ^3.0.4 version: 3.0.4 @@ -2131,8 +2131,8 @@ importers: specifier: catalog:default version: 7.24.8 workerd: - specifier: 1.20260610.1 - version: 1.20260610.1 + specifier: 1.20260611.1 + version: 1.20260611.1 ws: specifier: catalog:default version: 8.20.1 @@ -2157,7 +2157,7 @@ importers: version: link:../workers-shared '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -2323,7 +2323,7 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: catalog:default - version: 0.13.3(@cloudflare/workers-types@4.20260610.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) + version: 0.13.3(@cloudflare/workers-types@4.20260611.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) '@cloudflare/workers-shared': specifier: workspace:* version: link:../workers-shared @@ -2332,7 +2332,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -2360,7 +2360,7 @@ importers: devDependencies: '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -2394,7 +2394,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/node': specifier: 22.15.17 version: 22.15.17 @@ -2418,7 +2418,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 esbuild: specifier: catalog:default version: 0.27.3 @@ -2501,7 +2501,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -2597,7 +2597,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2618,7 +2618,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2639,7 +2639,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2660,7 +2660,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2681,7 +2681,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2702,7 +2702,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2723,7 +2723,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2744,7 +2744,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2765,7 +2765,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2786,7 +2786,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2807,7 +2807,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2828,7 +2828,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2849,7 +2849,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2870,7 +2870,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2891,7 +2891,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2912,7 +2912,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2933,7 +2933,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/mimetext': specifier: ^2.0.4 version: 2.0.4 @@ -2966,7 +2966,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -2987,7 +2987,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3008,7 +3008,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3029,7 +3029,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3050,7 +3050,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3071,7 +3071,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3092,7 +3092,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3113,7 +3113,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@playground/main-resolution-package': specifier: file:./package version: file:packages/vite-plugin-cloudflare/playground/main-resolution/package @@ -3137,7 +3137,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/express': specifier: ^5.0.1 version: 5.0.1 @@ -3164,7 +3164,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@playground/module-resolution-excludes': specifier: file:./packages/excludes version: file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/excludes @@ -3176,7 +3176,7 @@ importers: version: file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires '@remix-run/cloudflare': specifier: 2.12.0 - version: 2.12.0(@cloudflare/workers-types@4.20260610.1)(typescript@5.8.3) + version: 2.12.0(@cloudflare/workers-types@4.20260611.1)(typescript@5.8.3) '@types/react': specifier: ^18.3.11 version: 18.3.18 @@ -3209,7 +3209,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3230,7 +3230,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@fixture/shared': specifier: workspace:* version: link:../../../../fixtures/shared @@ -3282,7 +3282,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/react': specifier: 19.1.0 version: 19.1.0 @@ -3303,7 +3303,7 @@ importers: dependencies: partyserver: specifier: ^0.3.3 - version: 0.3.3(@cloudflare/workers-types@4.20260610.1) + version: 0.3.3(@cloudflare/workers-types@4.20260611.1) partysocket: specifier: ^1.1.16 version: 1.1.16(react@19.2.1) @@ -3322,7 +3322,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@tailwindcss/vite': specifier: ^4.2.1 version: 4.2.2(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -3358,7 +3358,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3379,7 +3379,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../../../workers-utils @@ -3419,7 +3419,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/react': specifier: 19.1.0 version: 19.1.0 @@ -3449,7 +3449,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3470,7 +3470,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3498,7 +3498,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/react': specifier: 19.1.0 version: 19.1.0 @@ -3531,7 +3531,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3552,7 +3552,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3573,7 +3573,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3594,7 +3594,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@vitejs/plugin-basic-ssl': specifier: ^2.2.0 version: 2.2.0(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -3618,7 +3618,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3639,7 +3639,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3660,7 +3660,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3681,7 +3681,7 @@ importers: version: link:../../../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 typescript: specifier: catalog:default version: 5.8.3 @@ -3718,7 +3718,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -3973,13 +3973,13 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: catalog:default - version: 0.13.3(@cloudflare/workers-types@4.20260610.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) + version: 0.13.3(@cloudflare/workers-types@4.20260611.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) '@cloudflare/workers-tsconfig': specifier: workspace:* version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@sentry/cli': specifier: ^2.37.0 version: 2.41.1(encoding@0.1.13) @@ -4097,13 +4097,13 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: catalog:default - version: 0.13.3(@cloudflare/workers-types@4.20260610.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) + version: 0.13.3(@cloudflare/workers-types@4.20260611.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0) '@cloudflare/workers-tsconfig': specifier: workspace:* version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@types/mime': specifier: ^3.0.4 version: 3.0.4 @@ -4141,8 +4141,8 @@ importers: specifier: 2.0.0-rc.24 version: 2.0.0-rc.24 workerd: - specifier: 1.20260610.1 - version: 1.20260610.1 + specifier: 1.20260611.1 + version: 1.20260611.1 devDependencies: '@aws-sdk/client-s3': specifier: ^3.721.0 @@ -4179,7 +4179,7 @@ importers: version: link:../workers-tsconfig '@cloudflare/workers-types': specifier: catalog:default - version: 4.20260610.1 + version: 4.20260611.1 '@cloudflare/workers-utils': specifier: workspace:* version: link:../workers-utils @@ -5342,8 +5342,8 @@ packages: cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-64@1.20260610.1': - resolution: {integrity: sha512-qm9m1qL68GWCIlubc0jTMZLUecK4yr0gv1HU4/GSscOMi81BeH5Zbn4XoFlyyd8c6nq4Rrq0M/cuTlBXhVaLMA==} + '@cloudflare/workerd-darwin-64@1.20260611.1': + resolution: {integrity: sha512-iJICldmi4sBGgi7IrQles8cStOGXM/Tmv95C4OODVs6VIbMsJPqThUM5h3uYVQNULuJ8I/aVvnJ3Eh/wZCKwuA==} engines: {node: '>=16'} cpu: [x64] os: [darwin] @@ -5360,8 +5360,8 @@ packages: cpu: [arm64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260610.1': - resolution: {integrity: sha512-gFjF/y6cFjbrQQMBgThKEF9Uu9HZriEdnNgAKUoE9pke8vuhc4Tv+KS3ZKg2ysPI8+FE3Mf7KOHKowKepShSRw==} + '@cloudflare/workerd-darwin-arm64@1.20260611.1': + resolution: {integrity: sha512-yBbVXvbZyltR3I7NJdC4C4ItkItjZSiabcA/3HzEWOUQjLVKFqRh4so6ToHr70VCYh8VGeR8EDZL23igLhXqFQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] @@ -5378,8 +5378,8 @@ packages: cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-64@1.20260610.1': - resolution: {integrity: sha512-AD30OOfQ4oCQVgVm+KaV482NeX53JPkbv3hiu70pCMAajso4iZntTUCbV9Nnhi96ufi+pn7raHcwA2cXwotCHA==} + '@cloudflare/workerd-linux-64@1.20260611.1': + resolution: {integrity: sha512-PfNjpxOlaIgZFYuhD7+neEEewCN2Ud993wEEN0fmbtSOax1AK53LGqmXUDvFhnbkHxJLFAxYCSNISW8QbzaAIg==} engines: {node: '>=16'} cpu: [x64] os: [linux] @@ -5396,8 +5396,8 @@ packages: cpu: [arm64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260610.1': - resolution: {integrity: sha512-cogsFhkaUE4cG1tZt5sGba962W07OipDpUJ1+sMnycHo34f1Il1ufYZ6WUZWlfN/Fco0yz3fzD8DKm8ZVXPqDA==} + '@cloudflare/workerd-linux-arm64@1.20260611.1': + resolution: {integrity: sha512-GEp4XbuIKjlF8pakqXcUDJfKiJosD/Q7S83J0d+r+z9XIlYGfF3ntm08e2aiF5TFTwp3fnG4yMoPUAKNhNJpvQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] @@ -5414,8 +5414,8 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workerd-windows-64@1.20260610.1': - resolution: {integrity: sha512-5mOPFE0CoNW7fA2y9YIj2K7s9lAsIYbARyUrAYcQD8bJaCUiZbcyJbbzDHUucnVBE8y5yXDdBJkp5DXRmXAzwQ==} + '@cloudflare/workerd-windows-64@1.20260611.1': + resolution: {integrity: sha512-S6JkS0kEbcCKs19RGqEPhjCRbP8GBkQwqYLp2fhBJtD/KTlwqLzOJ9E6PQ7gQKgWHtxy1NBG3oXarlNFRNU/dw==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -5428,8 +5428,8 @@ packages: react: ^17.0.2 || ^18.2.21 react-dom: ^17.0.2 || ^18.2.21 - '@cloudflare/workers-types@4.20260610.1': - resolution: {integrity: sha512-Mk/f3lUygeIHzQ4HnJjU/JvGg/kllgp9gISty9nylHE/2M2MFeKO+hgAKSgiPpmwUbuhewdYGgqFGgT/ADK0/g==} + '@cloudflare/workers-types@4.20260611.1': + resolution: {integrity: sha512-DLiz8Ol1OIWLigJ+dGvuQ5Nm66D/CHNPasl8YnPiz6fGo10ggYSIVuEDMlFk6oho+piAHstNmZMl088w8xqW6g==} '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} @@ -15103,8 +15103,8 @@ packages: engines: {node: '>=16'} hasBin: true - workerd@1.20260610.1: - resolution: {integrity: sha512-w6kzOoxphQtwk3ill473tS4Dv/yolWJ8kaxx0jmOq0GMnVGx5h3B7rdSg3RVnfOdX8sIPJaYQTGgvAWYT9WtTg==} + workerd@1.20260611.1: + resolution: {integrity: sha512-CS/640T7pIJ2HYX6x2DwKFGbcSckAWN3tgcdq+ptB6SaqjWUhlzIgA/YhPuwIU+/NnMnGpqOFX/hC18Oyge63w==} engines: {node: '>=16'} hasBin: true @@ -16131,7 +16131,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.13 - '@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1)': + '@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1)': dependencies: '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 @@ -16142,9 +16142,9 @@ snapshots: nanostores: 1.1.1 zod: 4.4.3 optionalDependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 - '@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)': + '@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)': dependencies: '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 @@ -16155,50 +16155,50 @@ snapshots: nanostores: 1.1.1 zod: 4.4.3 optionalDependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 - '@better-auth/drizzle-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))': + '@better-auth/drizzle-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 - drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) + drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) - '@better-auth/kysely-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)': + '@better-auth/kysely-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 kysely: 0.28.11 - '@better-auth/memory-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)': + '@better-auth/memory-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 - '@better-auth/mongo-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)': + '@better-auth/mongo-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 mongodb: 7.1.0 - '@better-auth/prisma-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))': + '@better-auth/prisma-adapter@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 '@prisma/client': 7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3) prisma: 7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3) - '@better-auth/stripe@1.5.4(282160554b4fc0c0b45522a29c09ee8c)': + '@better-auth/stripe@1.5.4(70d0aabc062167cd58748037a9ade3d1)': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) - better-auth: 1.5.4(@cloudflare/workers-types@4.20260610.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + better-auth: 1.5.4(@cloudflare/workers-types@4.20260611.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0) better-call: 1.3.2(zod@4.3.6) defu: 6.1.4 stripe: 20.4.1(@types/node@22.15.17) zod: 4.3.6 - '@better-auth/telemetry@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))': + '@better-auth/telemetry@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))': dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 @@ -16763,7 +16763,7 @@ snapshots: lodash.memoize: 4.1.2 marked: 0.3.19 - '@cloudflare/vitest-pool-workers@0.13.3(@cloudflare/workers-types@4.20260610.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0)': + '@cloudflare/vitest-pool-workers@0.13.3(@cloudflare/workers-types@4.20260611.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(vitest@4.1.0)': dependencies: '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -16771,7 +16771,7 @@ snapshots: esbuild: 0.27.3 miniflare: 4.20260317.1 vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@22.15.17)(@vitest/ui@4.1.0)(msw@2.12.4(@types/node@22.15.17)(typescript@5.9.3))(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) - wrangler: 4.76.0(@cloudflare/workers-types@4.20260610.1) + wrangler: 4.76.0(@cloudflare/workers-types@4.20260611.1) zod: 3.25.76 transitivePeerDependencies: - '@cloudflare/workers-types' @@ -16784,7 +16784,7 @@ snapshots: '@cloudflare/workerd-darwin-64@1.20260423.1': optional: true - '@cloudflare/workerd-darwin-64@1.20260610.1': + '@cloudflare/workerd-darwin-64@1.20260611.1': optional: true '@cloudflare/workerd-darwin-arm64@1.20260317.1': @@ -16793,7 +16793,7 @@ snapshots: '@cloudflare/workerd-darwin-arm64@1.20260423.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260610.1': + '@cloudflare/workerd-darwin-arm64@1.20260611.1': optional: true '@cloudflare/workerd-linux-64@1.20260317.1': @@ -16802,7 +16802,7 @@ snapshots: '@cloudflare/workerd-linux-64@1.20260423.1': optional: true - '@cloudflare/workerd-linux-64@1.20260610.1': + '@cloudflare/workerd-linux-64@1.20260611.1': optional: true '@cloudflare/workerd-linux-arm64@1.20260317.1': @@ -16811,7 +16811,7 @@ snapshots: '@cloudflare/workerd-linux-arm64@1.20260423.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260610.1': + '@cloudflare/workerd-linux-arm64@1.20260611.1': optional: true '@cloudflare/workerd-windows-64@1.20260317.1': @@ -16820,7 +16820,7 @@ snapshots: '@cloudflare/workerd-windows-64@1.20260423.1': optional: true - '@cloudflare/workerd-windows-64@1.20260610.1': + '@cloudflare/workerd-windows-64@1.20260611.1': optional: true '@cloudflare/workers-editor-shared@0.1.1(@cloudflare/style-const@6.1.3(react@19.2.4))(@cloudflare/style-container@7.12.2(@cloudflare/style-const@6.1.3(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': @@ -16831,7 +16831,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) react-split-pane: 0.1.92(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@cloudflare/workers-types@4.20260610.1': {} + '@cloudflare/workers-types@4.20260611.1': {} '@codemirror/autocomplete@6.20.0': dependencies: @@ -18221,7 +18221,7 @@ snapshots: '@prisma/adapter-d1@7.0.1': dependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 '@prisma/driver-adapter-utils': 7.0.1 ky: 1.7.5 @@ -18448,10 +18448,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 - '@remix-run/cloudflare@2.12.0(@cloudflare/workers-types@4.20260610.1)(typescript@5.8.3)': + '@remix-run/cloudflare@2.12.0(@cloudflare/workers-types@4.20260611.1)(typescript@5.8.3)': dependencies: '@cloudflare/kv-asset-handler': 0.1.3 - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 '@remix-run/server-runtime': 2.12.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -18984,12 +18984,12 @@ snapshots: - encoding - supports-color - '@sentry/cloudflare@10.50.0(@cloudflare/workers-types@4.20260610.1)': + '@sentry/cloudflare@10.50.0(@cloudflare/workers-types@4.20260611.1)': dependencies: '@opentelemetry/api': 1.9.1 '@sentry/core': 10.50.0 optionalDependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 '@sentry/core@10.50.0': {} @@ -20627,15 +20627,15 @@ snapshots: before-after-hook@2.2.3: {} - better-auth@1.5.4(@cloudflare/workers-types@4.20260610.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0): + better-auth@1.5.4(@cloudflare/workers-types@4.20260611.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)))(mongodb@7.1.0)(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0): dependencies: - '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) - '@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))) - '@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11) - '@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1) - '@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0) - '@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) - '@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260610.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1)) + '@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))) + '@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11) + '@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1) + '@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0) + '@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) + '@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260611.1)(better-call@1.3.2(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1)) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 @@ -20648,7 +20648,7 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3) - drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) + drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)) mongodb: 7.1.0 mysql2: 3.15.3 pg: 8.16.3 @@ -21459,9 +21459,9 @@ snapshots: dependencies: wordwrap: 1.0.0 - drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260610.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)): + drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260611.1)(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.1)(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3))(@types/pg@8.15.4)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7)(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3)): optionalDependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 '@electric-sql/pglite': 0.3.2 '@opentelemetry/api': 1.9.1 '@prisma/client': 7.0.1(prisma@7.0.1(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.8.3))(typescript@5.8.3) @@ -23911,9 +23911,9 @@ snapshots: parseurl@1.3.3: {} - partyserver@0.3.3(@cloudflare/workers-types@4.20260610.1): + partyserver@0.3.3(@cloudflare/workers-types@4.20260611.1): dependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 nanoid: 5.1.7 partysocket@1.1.16(react@19.2.1): @@ -26716,15 +26716,15 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260423.1 '@cloudflare/workerd-windows-64': 1.20260423.1 - workerd@1.20260610.1: + workerd@1.20260611.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260610.1 - '@cloudflare/workerd-darwin-arm64': 1.20260610.1 - '@cloudflare/workerd-linux-64': 1.20260610.1 - '@cloudflare/workerd-linux-arm64': 1.20260610.1 - '@cloudflare/workerd-windows-64': 1.20260610.1 + '@cloudflare/workerd-darwin-64': 1.20260611.1 + '@cloudflare/workerd-darwin-arm64': 1.20260611.1 + '@cloudflare/workerd-linux-64': 1.20260611.1 + '@cloudflare/workerd-linux-arm64': 1.20260611.1 + '@cloudflare/workerd-windows-64': 1.20260611.1 - wrangler@4.76.0(@cloudflare/workers-types@4.20260610.1): + wrangler@4.76.0(@cloudflare/workers-types@4.20260611.1): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1) @@ -26735,7 +26735,7 @@ snapshots: unenv: 2.0.0-rc.24 workerd: 1.20260317.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260610.1 + '@cloudflare/workers-types': 4.20260611.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 77f849d47d..fcd67b0077 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -118,8 +118,8 @@ catalog: "ws": "8.20.1" esbuild: "0.27.3" playwright-chromium: "1.60.0" - "@cloudflare/workers-types": "^4.20260610.1" - workerd: "1.20260610.1" + "@cloudflare/workers-types": "^4.20260611.1" + workerd: "1.20260611.1" jsonc-parser: "3.2.0" smol-toml: "1.5.2" msw: 2.12.4