From c967ba810218524e504c1c649c12f4023216af7e Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Tue, 10 Feb 2026 13:29:26 +0530 Subject: [PATCH 1/6] feat: added --project option to wrangler login --- .changeset/per-directory-auth.md | 27 +++ .../helpers/global-wrangler-config-path.ts | 85 ++++++- packages/miniflare/src/shared/wrangler.ts | 85 ++++++- .../src/environment-variables/factory.ts | 2 + .../src/global-wrangler-config-path.ts | 86 +++++++- packages/workers-utils/src/index.ts | 6 +- packages/wrangler/src/user/auth-variables.ts | 14 ++ packages/wrangler/src/user/commands.ts | 19 +- packages/wrangler/src/user/user.ts | 208 ++++++++++++++---- packages/wrangler/src/user/whoami.ts | 24 +- turbo.json | 1 + 11 files changed, 505 insertions(+), 52 deletions(-) create mode 100644 .changeset/per-directory-auth.md diff --git a/.changeset/per-directory-auth.md b/.changeset/per-directory-auth.md new file mode 100644 index 000000000000..0682a32cd59a --- /dev/null +++ b/.changeset/per-directory-auth.md @@ -0,0 +1,27 @@ +--- +"wrangler": minor +"@cloudflare/workers-utils": minor +--- + +Add per-project authentication with `wrangler login --project` + +You can now store authentication tokens locally in your project directory instead of globally. This makes it easy to work with multiple Cloudflare accounts in different projects: + +```bash +wrangler login --project +``` + +Authentication will be stored in `.wrangler/config/default.toml` in your project directory and automatically detected by all Wrangler commands. + +**Features:** + +- **`--project` flag**: Use `wrangler login --project` to store OAuth tokens in the local `.wrangler` directory +- **Auto-detection**: Once logged in locally, all Wrangler commands automatically use the local authentication +- **`WRANGLER_HOME` environment variable**: Customize the global config directory location +- **Priority**: Environment variables > Local auth > Global auth +- **`wrangler whoami`**: Shows whether you're using local or global authentication +- **`wrangler logout --project`**: Logout from local authentication + +Aliases: `--directory` and `--local` work as aliases for `--project`. + +This feature is particularly useful when working on multiple projects that need different Cloudflare accounts, or in team environments where each developer uses their own account. diff --git a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts index 2474f394c506..bd7202eecaa7 100644 --- a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts +++ b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts @@ -13,8 +13,22 @@ function isDirectory(configPath: string) { } } -export function getGlobalWranglerConfigPath() { - //TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable +/** + * Get the global Wrangler configuration directory path. + * Priority order: + * 1. WRANGLER_HOME environment variable (if set) + * 2. ~/.wrangler/ (legacy, if exists) + * 3. XDG-compliant path (default) + * + * @returns The path to the global Wrangler configuration directory + */ +export function getGlobalWranglerConfigPath(): string { + // Check for WRANGLER_HOME environment variable first + const wranglerHome = process.env.WRANGLER_HOME; + if (wranglerHome) { + return wranglerHome; + } + const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path const legacyConfigDir = nodePath.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory @@ -25,3 +39,70 @@ export function getGlobalWranglerConfigPath() { return configDir; } } + +/** + * Find the project root directory by searching upward for project markers. + * Looks for: wrangler.toml, wrangler.json, wrangler.jsonc, package.json, or .git directory + * + * @param startDir The directory to start searching from (defaults to current working directory) + * @returns The project root directory path, or undefined if not found + */ +export function findProjectRoot(startDir: string = process.cwd()): string | undefined { + const projectMarkers = [ + "wrangler.toml", + "wrangler.json", + "wrangler.jsonc", + "package.json", + ".git", + ]; + + let currentDir = nodePath.resolve(startDir); + const rootDir = nodePath.parse(currentDir).root; + + while (currentDir !== rootDir) { + // Check if any project marker exists in current directory + for (const marker of projectMarkers) { + const markerPath = nodePath.join(currentDir, marker); + if (fs.existsSync(markerPath)) { + return currentDir; + } + } + + // Move up one directory + const parentDir = nodePath.dirname(currentDir); + if (parentDir === currentDir) { + break; // Reached the root + } + currentDir = parentDir; + } + + return undefined; +} + +/** + * Get the local (project-specific) Wrangler configuration directory path. + * Searches for a .wrangler directory in the current directory or project root. + * + * @param projectRoot Optional project root directory (will be auto-detected if not provided) + * @returns The path to the local .wrangler directory, or undefined if not found + */ +export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { + // First, check current directory + const cwdWrangler = nodePath.join(process.cwd(), ".wrangler"); + if (isDirectory(cwdWrangler)) { + return cwdWrangler; + } + + // If not provided, find the project root + const root = projectRoot ?? findProjectRoot(); + if (!root) { + return undefined; + } + + const localWranglerPath = nodePath.join(root, ".wrangler"); + if (isDirectory(localWranglerPath)) { + return localWranglerPath; + } + + return undefined; +} diff --git a/packages/miniflare/src/shared/wrangler.ts b/packages/miniflare/src/shared/wrangler.ts index a88e2181ee99..12a947533764 100644 --- a/packages/miniflare/src/shared/wrangler.ts +++ b/packages/miniflare/src/shared/wrangler.ts @@ -12,8 +12,22 @@ function isDirectory(configPath: string) { } } -export function getGlobalWranglerConfigPath() { - //TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable +/** + * Get the global Wrangler configuration directory path. + * Priority order: + * 1. WRANGLER_HOME environment variable (if set) + * 2. ~/.wrangler/ (legacy, if exists) + * 3. XDG-compliant path (default) + * + * @returns The path to the global Wrangler configuration directory + */ +export function getGlobalWranglerConfigPath(): string { + // Check for WRANGLER_HOME environment variable first + const wranglerHome = process.env.WRANGLER_HOME; + if (wranglerHome) { + return wranglerHome; + } + const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path const legacyConfigDir = path.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory @@ -25,6 +39,73 @@ export function getGlobalWranglerConfigPath() { } } +/** + * Find the project root directory by searching upward for project markers. + * Looks for: wrangler.toml, wrangler.json, wrangler.jsonc, package.json, or .git directory + * + * @param startDir The directory to start searching from (defaults to current working directory) + * @returns The project root directory path, or undefined if not found + */ +export function findProjectRoot(startDir: string = process.cwd()): string | undefined { + const projectMarkers = [ + "wrangler.toml", + "wrangler.json", + "wrangler.jsonc", + "package.json", + ".git", + ]; + + let currentDir = path.resolve(startDir); + const rootDir = path.parse(currentDir).root; + + while (currentDir !== rootDir) { + // Check if any project marker exists in current directory + for (const marker of projectMarkers) { + const markerPath = path.join(currentDir, marker); + if (fs.existsSync(markerPath)) { + return currentDir; + } + } + + // Move up one directory + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { + break; // Reached the root + } + currentDir = parentDir; + } + + return undefined; +} + +/** + * Get the local (project-specific) Wrangler configuration directory path. + * Searches for a .wrangler directory in the current directory or project root. + * + * @param projectRoot Optional project root directory (will be auto-detected if not provided) + * @returns The path to the local .wrangler directory, or undefined if not found + */ +export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { + // First, check current directory + const cwdWrangler = path.join(process.cwd(), ".wrangler"); + if (isDirectory(cwdWrangler)) { + return cwdWrangler; + } + + // If not provided, find the project root + const root = projectRoot ?? findProjectRoot(); + if (!root) { + return undefined; + } + + const localWranglerPath = path.join(root, ".wrangler"); + if (isDirectory(localWranglerPath)) { + return localWranglerPath; + } + + return undefined; +} + export function getGlobalWranglerCachePath() { return xdgAppPaths(".wrangler").cache(); } diff --git a/packages/workers-utils/src/environment-variables/factory.ts b/packages/workers-utils/src/environment-variables/factory.ts index 4d1d3cb7d14c..8b37f16cc2cd 100644 --- a/packages/workers-utils/src/environment-variables/factory.ts +++ b/packages/workers-utils/src/environment-variables/factory.ts @@ -21,6 +21,8 @@ type VariableNames = | "CLOUDFLARE_COMPLIANCE_REGION" /** API token for R2 SQL service. */ | "WRANGLER_R2_SQL_AUTH_TOKEN" + /** Custom directory for global Wrangler configuration and authentication. Overrides the default ~/.wrangler/ location. */ + | "WRANGLER_HOME" // ## Development & Local Testing diff --git a/packages/workers-utils/src/global-wrangler-config-path.ts b/packages/workers-utils/src/global-wrangler-config-path.ts index 1b01f485ec84..4e22ac7c0909 100644 --- a/packages/workers-utils/src/global-wrangler-config-path.ts +++ b/packages/workers-utils/src/global-wrangler-config-path.ts @@ -1,10 +1,25 @@ +import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import xdgAppPaths from "xdg-app-paths"; import { isDirectory } from "./fs-helpers"; -export function getGlobalWranglerConfigPath() { - //TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable +/** + * Get the global Wrangler configuration directory path. + * Priority order: + * 1. WRANGLER_HOME environment variable (if set) + * 2. ~/.wrangler/ (legacy, if exists) + * 3. XDG-compliant path (default) + * + * @returns The path to the global Wrangler configuration directory + */ +export function getGlobalWranglerConfigPath(): string { + // Check for WRANGLER_HOME environment variable first + const wranglerHome = process.env.WRANGLER_HOME; + if (wranglerHome) { + return wranglerHome; + } + const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path const legacyConfigDir = path.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory @@ -15,3 +30,70 @@ export function getGlobalWranglerConfigPath() { return configDir; } } + +/** + * Find the project root directory by searching upward for project markers. + * Looks for: wrangler.toml, wrangler.json, wrangler.jsonc, package.json, or .git directory + * + * @param startDir The directory to start searching from (defaults to current working directory) + * @returns The project root directory path, or undefined if not found + */ +export function findProjectRoot(startDir: string = process.cwd()): string | undefined { + const projectMarkers = [ + "wrangler.toml", + "wrangler.json", + "wrangler.jsonc", + "package.json", + ".git", + ]; + + let currentDir = path.resolve(startDir); + const rootDir = path.parse(currentDir).root; + + while (currentDir !== rootDir) { + // Check if any project marker exists in current directory + for (const marker of projectMarkers) { + const markerPath = path.join(currentDir, marker); + if (fs.existsSync(markerPath)) { + return currentDir; + } + } + + // Move up one directory + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { + break; // Reached the root + } + currentDir = parentDir; + } + + return undefined; +} + +/** + * Get the local (project-specific) Wrangler configuration directory path. + * Searches for a .wrangler directory in the current directory or project root. + * + * @param projectRoot Optional project root directory (will be auto-detected if not provided) + * @returns The path to the local .wrangler directory, or undefined if not found + */ +export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { + // First, check current directory + const cwdWrangler = path.join(process.cwd(), ".wrangler"); + if (isDirectory(cwdWrangler)) { + return cwdWrangler; + } + + // If not provided, find the project root + const root = projectRoot ?? findProjectRoot(); + if (!root) { + return undefined; + } + + const localWranglerPath = path.join(root, ".wrangler"); + if (isDirectory(localWranglerPath)) { + return localWranglerPath; + } + + return undefined; +} diff --git a/packages/workers-utils/src/index.ts b/packages/workers-utils/src/index.ts index 8ee2d3fe3999..6aee54958e11 100644 --- a/packages/workers-utils/src/index.ts +++ b/packages/workers-utils/src/index.ts @@ -80,7 +80,11 @@ export { export * from "./environment-variables/misc-variables"; -export { getGlobalWranglerConfigPath } from "./global-wrangler-config-path"; +export { + findProjectRoot, + getGlobalWranglerConfigPath, + getLocalWranglerConfigPath, +} from "./global-wrangler-config-path"; export { getLocalWorkerdCompatibilityDate, diff --git a/packages/wrangler/src/user/auth-variables.ts b/packages/wrangler/src/user/auth-variables.ts index 218d452febde..4ee83ece2387 100644 --- a/packages/wrangler/src/user/auth-variables.ts +++ b/packages/wrangler/src/user/auth-variables.ts @@ -27,6 +27,20 @@ export const getCloudflareGlobalAuthEmailFromEnv = deprecatedName: "CF_EMAIL", }); +/** + * `WRANGLER_HOME` overrides the directory where Wrangler stores + * global configuration and authentication data. + * + * By default, Wrangler uses: + * - ~/.wrangler/ (legacy) + * - Or XDG-compliant paths (e.g., ~/Library/Application Support/.wrangler on macOS) + * + * Set this to use a custom directory for global config storage. + */ +export const getWranglerHomeFromEnv = getEnvironmentVariableFactory({ + variableName: "WRANGLER_HOME", +}); + /** * `WRANGLER_CLIENT_ID` is a UUID that is used to identify Wrangler * to the Cloudflare APIs. diff --git a/packages/wrangler/src/user/commands.ts b/packages/wrangler/src/user/commands.ts index 29fb111d1e8c..33b5907a2dff 100644 --- a/packages/wrangler/src/user/commands.ts +++ b/packages/wrangler/src/user/commands.ts @@ -46,6 +46,12 @@ export const loginCommand = createCommand({ type: "string", requiresArg: true, }, + project: { + type: "boolean", + describe: + "Store authentication in local .wrangler directory for this project", + alias: ["directory", "local"], + }, "callback-host": { describe: "Use the ip or host address for the temporary login callback server.", @@ -81,6 +87,7 @@ export const loginCommand = createCommand({ browser: args.browser, callbackHost: args.callbackHost, callbackPort: args.callbackPort, + project: args.project, }); return; } @@ -88,6 +95,7 @@ export const loginCommand = createCommand({ browser: args.browser, callbackHost: args.callbackHost, callbackPort: args.callbackPort, + project: args.project, }); metrics.sendMetricsEvent("login user", { sendMetrics: config.send_metrics, @@ -110,8 +118,15 @@ export const logoutCommand = createCommand({ printConfigWarnings: false, provideConfig: false, }, - async handler() { - await logout(); + args: { + project: { + type: "boolean", + describe: "Logout from local .wrangler directory authentication", + alias: ["directory", "local"], + }, + }, + async handler(args) { + await logout({ project: args.project }); try { // If the config file is invalid then we default to not sending metrics. // TODO: Clean this up as part of a general config refactor. diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 9e74f5b64683..086fc1a7c135 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -207,16 +207,18 @@ import assert from "node:assert"; import { webcrypto as crypto } from "node:crypto"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import http from "node:http"; import path from "node:path"; import url from "node:url"; import { TextEncoder } from "node:util"; import { configFileName, + findProjectRoot, getCloudflareApiEnvironmentFromEnv, getCloudflareComplianceRegion, getGlobalWranglerConfigPath, + getLocalWranglerConfigPath, parseTOML, readFileSync, UserError, @@ -306,6 +308,8 @@ interface State extends AuthTokens { stateQueryParam?: string; scopes?: Scope[]; account?: Account; + /** Track whether auth came from local or global config for refresh operations */ + authSource?: "local" | "global"; } /** @@ -398,34 +402,57 @@ export function validateScopeKeys( return scopes.every((scope) => scope in DefaultScopes); } +const initialAuth = getAuthTokens(); let localState: State = { - ...getAuthTokens(), + ...initialAuth.tokens, + authSource: initialAuth.authSource, }; /** - * Compute the current auth tokens. + * Compute the current auth tokens and determine auth source. */ -function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined { +function getAuthTokens(config?: UserAuthConfig): { + tokens: AuthTokens | undefined; + authSource?: "local" | "global"; +} { // get refreshToken/accessToken from fs if exists try { // if the environment variable is available, we don't need to do anything here if (getAuthFromEnv()) { - return; + return { tokens: undefined }; + } + + // Determine the auth source when reading from file + let authSource: "local" | "global" | undefined; + let authConfig: UserAuthConfig; + + if (config) { + authConfig = config; + } else { + const configPath = getAuthConfigFilePath(); + authConfig = readAuthConfigFile(); + // Detect if the config path is local or global + const projectRoot = findProjectRoot(); + const isLocal = + projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); + authSource = isLocal ? "local" : "global"; } - // otherwise try loading from the user auth config file. const { oauth_token, refresh_token, expiration_time, scopes, api_token } = - config || readAuthConfigFile(); + authConfig; if (oauth_token) { return { - accessToken: { - value: oauth_token, - // If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately. - expiry: expiration_time ?? "2000-01-01:00:00:00+00:00", + tokens: { + accessToken: { + value: oauth_token, + // If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately. + expiry: expiration_time ?? "2000-01-01:00:00:00+00:00", + }, + refreshToken: { value: refresh_token ?? "" }, + scopes: scopes as Scope[], }, - refreshToken: { value: refresh_token ?? "" }, - scopes: scopes as Scope[], + authSource, }; } else if (api_token) { logger.warn( @@ -434,11 +461,12 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined { "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." ); - return { apiToken: api_token }; + return { tokens: { apiToken: api_token }, authSource }; } } catch { - return undefined; + return { tokens: undefined }; } + return { tokens: undefined }; } /** @@ -453,11 +481,19 @@ export function reinitialiseAuthTokens(): void; * Reinitialise auth state from an in-memory config, skipping * over the part where we write a file and then read it back into memory */ -export function reinitialiseAuthTokens(config: UserAuthConfig): void; - -export function reinitialiseAuthTokens(config?: UserAuthConfig): void { +export function reinitialiseAuthTokens( + config: UserAuthConfig, + authSource?: "local" | "global" +): void; + +export function reinitialiseAuthTokens( + config?: UserAuthConfig, + authSource?: "local" | "global" +): void { + const { tokens, authSource: detectedSource } = getAuthTokens(config); localState = { - ...getAuthTokens(config), + ...tokens, + authSource: authSource ?? detectedSource, }; } @@ -904,19 +940,65 @@ async function generatePKCECodes(): Promise { return { codeChallenge, codeVerifier }; } -export function getAuthConfigFilePath() { +export interface GetAuthConfigFilePathOptions { + /** Explicitly force local auth path */ + useLocal?: boolean; + /** Explicitly force global auth path */ + useGlobal?: boolean; +} + +export function getAuthConfigFilePath( + options?: GetAuthConfigFilePathOptions +): string { const environment = getCloudflareApiEnvironmentFromEnv(); - const filePath = `${USER_AUTH_CONFIG_PATH}/${environment === "production" ? "default.toml" : `${environment}.toml`}`; + const fileName = + environment === "production" ? "default.toml" : `${environment}.toml`; + const configSubPath = `${USER_AUTH_CONFIG_PATH}/${fileName}`; // "config/default.toml" + + // Explicit flags take priority + if (options?.useGlobal) { + return path.join(getGlobalWranglerConfigPath(), configSubPath); + } + + if (options?.useLocal) { + let localPath = getLocalWranglerConfigPath(); + if (!localPath) { + // .wrangler directory doesn't exist yet, need to create it + const projectRoot = findProjectRoot(); + if (!projectRoot) { + throw new UserError( + "Cannot use --directory: no project directory found.\n" + + "Run this command in a directory with wrangler.toml, package.json, or a .git directory." + ); + } + // Create the .wrangler directory in the project root + localPath = path.join(projectRoot, ".wrangler"); + } + return path.join(localPath, configSubPath); + } + + // Auto-detect: check for local config first + const localPath = getLocalWranglerConfigPath(); + if (localPath) { + const localConfigPath = path.join(localPath, configSubPath); + if (existsSync(localConfigPath)) { + return localConfigPath; + } + } - return path.join(getGlobalWranglerConfigPath(), filePath); + // Fall back to global + return path.join(getGlobalWranglerConfigPath(), configSubPath); } /** * Writes a a wrangler config file (auth credentials) to disk, * and updates the user auth state with the new credentials. */ -export function writeAuthConfigFile(config: UserAuthConfig) { - const configPath = getAuthConfigFilePath(); +export function writeAuthConfigFile( + config: UserAuthConfig, + options?: GetAuthConfigFilePathOptions +) { + const configPath = getAuthConfigFilePath(options); mkdirSync(path.dirname(configPath), { recursive: true, @@ -925,11 +1007,22 @@ export function writeAuthConfigFile(config: UserAuthConfig) { encoding: "utf-8", }); - reinitialiseAuthTokens(); + // Track whether this is local or global auth + const projectRoot = findProjectRoot(); + const isLocal = + configPath.includes(path.join(process.cwd(), ".wrangler")) || + (projectRoot !== undefined && + configPath.includes(path.join(projectRoot, ".wrangler"))); + + reinitialiseAuthTokens(config, isLocal ? "local" : "global"); } -export function readAuthConfigFile(): UserAuthConfig { - return parseTOML(readFileSync(getAuthConfigFilePath())) as UserAuthConfig; +export function readAuthConfigFile( + options?: GetAuthConfigFilePathOptions +): UserAuthConfig { + return parseTOML( + readFileSync(getAuthConfigFilePath(options)) + ) as UserAuthConfig; } type LoginProps = { @@ -937,6 +1030,7 @@ type LoginProps = { browser: boolean; callbackHost: string; callbackPort: number; + project?: boolean; }; export async function loginOrRefreshIfRequired( @@ -1149,14 +1243,26 @@ export async function login( callbackPort: props.callbackPort, }); - writeAuthConfigFile({ - oauth_token: oauth.token?.value ?? "", - expiration_time: oauth.token?.expiry, - refresh_token: oauth.refreshToken?.value, - scopes: oauth.scopes, - }); + const authOptions = props.project ? { useLocal: true } : undefined; + + writeAuthConfigFile( + { + oauth_token: oauth.token?.value ?? "", + expiration_time: oauth.token?.expiry, + refresh_token: oauth.refreshToken?.value, + scopes: oauth.scopes, + }, + authOptions + ); + + const configPath = getAuthConfigFilePath(authOptions); + const isLocal = props.project; logger.log(`Successfully logged in.`); + if (isLocal) { + const relativePath = path.relative(process.cwd(), configPath); + logger.log(`🔐 Authentication stored in: ${relativePath}`); + } purgeConfigCaches(); @@ -1182,19 +1288,29 @@ async function refreshToken(): Promise { refreshToken: { value: refresh_token } = {}, scopes, } = await exchangeRefreshTokenForAccessToken(); - writeAuthConfigFile({ - oauth_token, - expiration_time, - refresh_token, - scopes, - }); + + // Write to the same location where auth was originally read from + const authOptions = + localState.authSource === "local" + ? { useLocal: true } + : { useGlobal: true }; + + writeAuthConfigFile( + { + oauth_token, + expiration_time, + refresh_token, + scopes, + }, + authOptions + ); return true; } catch { return false; } } -export async function logout(): Promise { +export async function logout(options?: { project?: boolean }): Promise { const authFromEnv = getAuthFromEnv(); if (authFromEnv) { // Auth from env overrides any login details, so we cannot log out. @@ -1205,6 +1321,16 @@ export async function logout(): Promise { return; } + // Determine which auth file to remove + const authOptions = options?.project ? { useLocal: true } : undefined; + const configPath = getAuthConfigFilePath(authOptions); + + // Check if the config file exists + if (!existsSync(configPath)) { + logger.log("Not logged in, exiting..."); + return; + } + if (!localState.accessToken) { if (!localState.refreshToken) { logger.log("Not logged in, exiting..."); @@ -1241,7 +1367,7 @@ export async function logout(): Promise { }, }); await response.text(); // blank text? would be nice if it was something meaningful - rmSync(getAuthConfigFilePath()); + rmSync(configPath); logger.log(`Successfully logged out.`); } diff --git a/packages/wrangler/src/user/whoami.ts b/packages/wrangler/src/user/whoami.ts index 30a503b49f9f..f8f1fbefdad3 100644 --- a/packages/wrangler/src/user/whoami.ts +++ b/packages/wrangler/src/user/whoami.ts @@ -1,12 +1,13 @@ -import { getCloudflareComplianceRegion } from "@cloudflare/workers-utils"; +import { getCloudflareComplianceRegion, findProjectRoot } from "@cloudflare/workers-utils"; import chalk from "chalk"; +import path from "node:path"; import { fetchPagedListResult, fetchResult } from "../cfetch"; import { isAuthenticationError } from "../core/handle-errors"; import { isNonInteractiveOrCI } from "../is-interactive"; import { logger } from "../logger"; import { formatMessage } from "../utils/format-message"; import { fetchMembershipRoles } from "./membership"; -import { DefaultScopeKeys, getAPIToken, getAuthFromEnv, getScopes } from "."; +import { DefaultScopeKeys, getAPIToken, getAuthFromEnv, getScopes, getAuthConfigFilePath } from "."; import type { ApiCredentials, Scope } from "."; import type { ComplianceConfig } from "@cloudflare/workers-utils"; @@ -36,6 +37,8 @@ export async function whoami( logger.log( "â„šī¸ The API Token is read from the CLOUDFLARE_API_TOKEN environment variable." ); + } else if (user.authType === "OAuth Token") { + printAuthSource(); } printComplianceRegion(complianceConfig); printAccountList(user); @@ -44,6 +47,23 @@ export async function whoami( await printMembershipInfo(complianceConfig, user, accountFilter); } +function printAuthSource() { + try { + const configPath = getAuthConfigFilePath(); + const projectRoot = findProjectRoot(); + const isLocal = projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); + + if (isLocal) { + const relativePath = path.relative(process.cwd(), configPath); + logger.log(`🔐 Auth source: local (${chalk.blue(relativePath)})`); + } else { + logger.log(`🔐 Auth source: global (${chalk.blue(configPath)})`); + } + } catch { + // If we can't determine auth source, don't show anything + } +} + function printComplianceRegion(complianceConfig: ComplianceConfig) { const complianceRegion = getCloudflareComplianceRegion(complianceConfig); if (complianceRegion !== "public") { diff --git a/turbo.json b/turbo.json index dc3e31265301..8afe7946255f 100644 --- a/turbo.json +++ b/turbo.json @@ -15,6 +15,7 @@ "VSCODE_INSPECTOR_OPTIONS", "WRANGLER_API_ENVIRONMENT", "WRANGLER_DOCKER_HOST", + "WRANGLER_HOME", "WRANGLER_LOG_PATH", "WRANGLER_LOG" ], From e6bfea1e8466cd8381b878df65a77d428fd950fe Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Tue, 10 Feb 2026 13:38:21 +0530 Subject: [PATCH 2/6] feat: added provision to force global auth --- .changeset/per-directory-auth.md | 33 +++++++++++++++++-- .../src/environment-variables/factory.ts | 2 ++ packages/wrangler/src/user/user.ts | 6 ++++ packages/wrangler/src/user/whoami.ts | 20 ++++++++--- turbo.json | 1 + 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/.changeset/per-directory-auth.md b/.changeset/per-directory-auth.md index 0682a32cd59a..00fe51bd2749 100644 --- a/.changeset/per-directory-auth.md +++ b/.changeset/per-directory-auth.md @@ -18,10 +18,39 @@ Authentication will be stored in `.wrangler/config/default.toml` in your project - **`--project` flag**: Use `wrangler login --project` to store OAuth tokens in the local `.wrangler` directory - **Auto-detection**: Once logged in locally, all Wrangler commands automatically use the local authentication - **`WRANGLER_HOME` environment variable**: Customize the global config directory location -- **Priority**: Environment variables > Local auth > Global auth +- **`WRANGLER_AUTH_TYPE=global` environment variable**: Force all commands to use global auth instead of local + - Example: `WRANGLER_AUTH_TYPE=global wrangler kv namespace list` + - Useful when you have local auth but need to temporarily use global auth +- **Priority**: Environment variables (API tokens) > `WRANGLER_AUTH_TYPE=global` > Local auth > Global auth - **`wrangler whoami`**: Shows whether you're using local or global authentication - **`wrangler logout --project`**: Logout from local authentication -Aliases: `--directory` and `--local` work as aliases for `--project`. +**Aliases:** `--directory` and `--local` work as aliases for `--project`. + +**Example workflow:** + +```bash +# Login to global auth (default behavior) +wrangler login + +# In project A, login with a different account +cd ~/project-a +wrangler login --project + +# In project B, login with yet another account +cd ~/project-b +wrangler login --project + +# Now each project automatically uses its own auth +cd ~/project-a +wrangler whoami # Shows project A's account + +cd ~/project-b +wrangler whoami # Shows project B's account + +# Force using global auth in project A +cd ~/project-a +WRANGLER_AUTH_TYPE=global wrangler whoami # Shows global account +``` This feature is particularly useful when working on multiple projects that need different Cloudflare accounts, or in team environments where each developer uses their own account. diff --git a/packages/workers-utils/src/environment-variables/factory.ts b/packages/workers-utils/src/environment-variables/factory.ts index 8b37f16cc2cd..5889280cb10c 100644 --- a/packages/workers-utils/src/environment-variables/factory.ts +++ b/packages/workers-utils/src/environment-variables/factory.ts @@ -23,6 +23,8 @@ type VariableNames = | "WRANGLER_R2_SQL_AUTH_TOKEN" /** Custom directory for global Wrangler configuration and authentication. Overrides the default ~/.wrangler/ location. */ | "WRANGLER_HOME" + /** Force authentication type. Set to "global" to use global auth even when local project auth exists. */ + | "WRANGLER_AUTH_TYPE" // ## Development & Local Testing diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 086fc1a7c135..a81ea70087c6 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -955,6 +955,12 @@ export function getAuthConfigFilePath( environment === "production" ? "default.toml" : `${environment}.toml`; const configSubPath = `${USER_AUTH_CONFIG_PATH}/${fileName}`; // "config/default.toml" + // Check for WRANGLER_AUTH_TYPE environment variable to force global auth + const authType = process.env.WRANGLER_AUTH_TYPE; + if (authType === "global" && !options?.useLocal) { + return path.join(getGlobalWranglerConfigPath(), configSubPath); + } + // Explicit flags take priority if (options?.useGlobal) { return path.join(getGlobalWranglerConfigPath(), configSubPath); diff --git a/packages/wrangler/src/user/whoami.ts b/packages/wrangler/src/user/whoami.ts index f8f1fbefdad3..0420879886fc 100644 --- a/packages/wrangler/src/user/whoami.ts +++ b/packages/wrangler/src/user/whoami.ts @@ -1,13 +1,22 @@ -import { getCloudflareComplianceRegion, findProjectRoot } from "@cloudflare/workers-utils"; -import chalk from "chalk"; import path from "node:path"; +import { + findProjectRoot, + getCloudflareComplianceRegion, +} from "@cloudflare/workers-utils"; +import chalk from "chalk"; import { fetchPagedListResult, fetchResult } from "../cfetch"; import { isAuthenticationError } from "../core/handle-errors"; import { isNonInteractiveOrCI } from "../is-interactive"; import { logger } from "../logger"; import { formatMessage } from "../utils/format-message"; import { fetchMembershipRoles } from "./membership"; -import { DefaultScopeKeys, getAPIToken, getAuthFromEnv, getScopes, getAuthConfigFilePath } from "."; +import { + DefaultScopeKeys, + getAPIToken, + getAuthConfigFilePath, + getAuthFromEnv, + getScopes, +} from "."; import type { ApiCredentials, Scope } from "."; import type { ComplianceConfig } from "@cloudflare/workers-utils"; @@ -51,8 +60,9 @@ function printAuthSource() { try { const configPath = getAuthConfigFilePath(); const projectRoot = findProjectRoot(); - const isLocal = projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); - + const isLocal = + projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); + if (isLocal) { const relativePath = path.relative(process.cwd(), configPath); logger.log(`🔐 Auth source: local (${chalk.blue(relativePath)})`); diff --git a/turbo.json b/turbo.json index 8afe7946255f..16084ef43720 100644 --- a/turbo.json +++ b/turbo.json @@ -14,6 +14,7 @@ "TEST_REPORT_PATH", "VSCODE_INSPECTOR_OPTIONS", "WRANGLER_API_ENVIRONMENT", + "WRANGLER_AUTH_TYPE", "WRANGLER_DOCKER_HOST", "WRANGLER_HOME", "WRANGLER_LOG_PATH", From 7c1579a83c147f2cfcd8ca525fcef074d09a6c35 Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Tue, 10 Feb 2026 15:44:35 +0530 Subject: [PATCH 3/6] chore: prettier fixes --- .../src/helpers/global-wrangler-config-path.ts | 8 ++++++-- packages/miniflare/src/shared/wrangler.ts | 8 ++++++-- packages/workers-utils/src/global-wrangler-config-path.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts index bd7202eecaa7..d3ab0c9755a5 100644 --- a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts +++ b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts @@ -47,7 +47,9 @@ export function getGlobalWranglerConfigPath(): string { * @param startDir The directory to start searching from (defaults to current working directory) * @returns The project root directory path, or undefined if not found */ -export function findProjectRoot(startDir: string = process.cwd()): string | undefined { +export function findProjectRoot( + startDir: string = process.cwd(), +): string | undefined { const projectMarkers = [ "wrangler.toml", "wrangler.json", @@ -86,7 +88,9 @@ export function findProjectRoot(startDir: string = process.cwd()): string | unde * @param projectRoot Optional project root directory (will be auto-detected if not provided) * @returns The path to the local .wrangler directory, or undefined if not found */ -export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { +export function getLocalWranglerConfigPath( + projectRoot?: string, +): string | undefined { // First, check current directory const cwdWrangler = nodePath.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { diff --git a/packages/miniflare/src/shared/wrangler.ts b/packages/miniflare/src/shared/wrangler.ts index 12a947533764..527a35f46a66 100644 --- a/packages/miniflare/src/shared/wrangler.ts +++ b/packages/miniflare/src/shared/wrangler.ts @@ -46,7 +46,9 @@ export function getGlobalWranglerConfigPath(): string { * @param startDir The directory to start searching from (defaults to current working directory) * @returns The project root directory path, or undefined if not found */ -export function findProjectRoot(startDir: string = process.cwd()): string | undefined { +export function findProjectRoot( + startDir: string = process.cwd() +): string | undefined { const projectMarkers = [ "wrangler.toml", "wrangler.json", @@ -85,7 +87,9 @@ export function findProjectRoot(startDir: string = process.cwd()): string | unde * @param projectRoot Optional project root directory (will be auto-detected if not provided) * @returns The path to the local .wrangler directory, or undefined if not found */ -export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { +export function getLocalWranglerConfigPath( + projectRoot?: string +): string | undefined { // First, check current directory const cwdWrangler = path.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { diff --git a/packages/workers-utils/src/global-wrangler-config-path.ts b/packages/workers-utils/src/global-wrangler-config-path.ts index 4e22ac7c0909..23eb82c4b751 100644 --- a/packages/workers-utils/src/global-wrangler-config-path.ts +++ b/packages/workers-utils/src/global-wrangler-config-path.ts @@ -38,7 +38,9 @@ export function getGlobalWranglerConfigPath(): string { * @param startDir The directory to start searching from (defaults to current working directory) * @returns The project root directory path, or undefined if not found */ -export function findProjectRoot(startDir: string = process.cwd()): string | undefined { +export function findProjectRoot( + startDir: string = process.cwd() +): string | undefined { const projectMarkers = [ "wrangler.toml", "wrangler.json", @@ -77,7 +79,9 @@ export function findProjectRoot(startDir: string = process.cwd()): string | unde * @param projectRoot Optional project root directory (will be auto-detected if not provided) * @returns The path to the local .wrangler directory, or undefined if not found */ -export function getLocalWranglerConfigPath(projectRoot?: string): string | undefined { +export function getLocalWranglerConfigPath( + projectRoot?: string +): string | undefined { // First, check current directory const cwdWrangler = path.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { From c89c28ebff398426b920c3cf920dd57bc199a126 Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Tue, 10 Feb 2026 15:46:36 +0530 Subject: [PATCH 4/6] fix: logout --project revokes wrong OAuth tokens when WRANGLER_AUTH_TYPE=global forces global token loading --- packages/wrangler/src/user/user.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index a81ea70087c6..3c9261a73bd6 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -1337,8 +1337,21 @@ export async function logout(options?: { project?: boolean }): Promise { return; } - if (!localState.accessToken) { - if (!localState.refreshToken) { + // Read tokens from the target auth file (not from localState) + // This ensures we revoke the correct tokens when using --project + let targetConfig: UserAuthConfig; + try { + targetConfig = parseTOML(readFileSync(configPath)) as UserAuthConfig; + } catch { + logger.log("Error reading auth config file, exiting..."); + return; + } + + const targetAccessToken = targetConfig.oauth_token; + const targetRefreshToken = targetConfig.refresh_token; + + if (!targetAccessToken) { + if (!targetRefreshToken) { logger.log("Not logged in, exiting..."); return; } @@ -1346,7 +1359,7 @@ export async function logout(options?: { project?: boolean }): Promise { const body = `client_id=${encodeURIComponent(getClientIdFromEnv())}&` + `token_type_hint=refresh_token&` + - `token=${encodeURIComponent(localState.refreshToken?.value || "")}`; + `token=${encodeURIComponent(targetRefreshToken)}`; const response = await fetch(getRevokeUrlFromEnv(), { method: "POST", @@ -1363,7 +1376,7 @@ export async function logout(options?: { project?: boolean }): Promise { const body = `client_id=${encodeURIComponent(getClientIdFromEnv())}&` + `token_type_hint=refresh_token&` + - `token=${encodeURIComponent(localState.refreshToken?.value || "")}`; + `token=${encodeURIComponent(targetRefreshToken || "")}`; const response = await fetch(getRevokeUrlFromEnv(), { method: "POST", From 834b53e1579466d19efde48cdb8055130998d767 Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Tue, 10 Feb 2026 16:34:47 +0530 Subject: [PATCH 5/6] fix: failing tests --- .../helpers/global-wrangler-config-path.ts | 13 +++++++++-- packages/miniflare/src/shared/wrangler.ts | 13 +++++++++-- .../src/global-wrangler-config-path.ts | 13 +++++++++-- .../wrangler/src/__tests__/whoami.test.ts | 7 +++++- packages/wrangler/src/user/user.ts | 22 ++++++++++++++----- packages/wrangler/src/user/whoami.ts | 7 +++++- 6 files changed, 62 insertions(+), 13 deletions(-) diff --git a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts index d3ab0c9755a5..6d2c83a751ca 100644 --- a/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts +++ b/packages/create-cloudflare/src/helpers/global-wrangler-config-path.ts @@ -91,10 +91,16 @@ export function findProjectRoot( export function getLocalWranglerConfigPath( projectRoot?: string, ): string | undefined { + // Get the global wrangler path to compare against + const globalWranglerPath = nodePath.resolve(getGlobalWranglerConfigPath()); + // First, check current directory const cwdWrangler = nodePath.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { - return cwdWrangler; + // Ensure it's not the same as the global config path + if (nodePath.resolve(cwdWrangler) !== globalWranglerPath) { + return cwdWrangler; + } } // If not provided, find the project root @@ -105,7 +111,10 @@ export function getLocalWranglerConfigPath( const localWranglerPath = nodePath.join(root, ".wrangler"); if (isDirectory(localWranglerPath)) { - return localWranglerPath; + // Ensure it's not the same as the global config path + if (nodePath.resolve(localWranglerPath) !== globalWranglerPath) { + return localWranglerPath; + } } return undefined; diff --git a/packages/miniflare/src/shared/wrangler.ts b/packages/miniflare/src/shared/wrangler.ts index 527a35f46a66..04c1f655e107 100644 --- a/packages/miniflare/src/shared/wrangler.ts +++ b/packages/miniflare/src/shared/wrangler.ts @@ -90,10 +90,16 @@ export function findProjectRoot( export function getLocalWranglerConfigPath( projectRoot?: string ): string | undefined { + // Get the global wrangler path to compare against + const globalWranglerPath = path.resolve(getGlobalWranglerConfigPath()); + // First, check current directory const cwdWrangler = path.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { - return cwdWrangler; + // Ensure it's not the same as the global config path + if (path.resolve(cwdWrangler) !== globalWranglerPath) { + return cwdWrangler; + } } // If not provided, find the project root @@ -104,7 +110,10 @@ export function getLocalWranglerConfigPath( const localWranglerPath = path.join(root, ".wrangler"); if (isDirectory(localWranglerPath)) { - return localWranglerPath; + // Ensure it's not the same as the global config path + if (path.resolve(localWranglerPath) !== globalWranglerPath) { + return localWranglerPath; + } } return undefined; diff --git a/packages/workers-utils/src/global-wrangler-config-path.ts b/packages/workers-utils/src/global-wrangler-config-path.ts index 23eb82c4b751..d443681f0ecb 100644 --- a/packages/workers-utils/src/global-wrangler-config-path.ts +++ b/packages/workers-utils/src/global-wrangler-config-path.ts @@ -82,10 +82,16 @@ export function findProjectRoot( export function getLocalWranglerConfigPath( projectRoot?: string ): string | undefined { + // Get the global wrangler path to compare against + const globalWranglerPath = path.resolve(getGlobalWranglerConfigPath()); + // First, check current directory const cwdWrangler = path.join(process.cwd(), ".wrangler"); if (isDirectory(cwdWrangler)) { - return cwdWrangler; + // Ensure it's not the same as the global config path + if (path.resolve(cwdWrangler) !== globalWranglerPath) { + return cwdWrangler; + } } // If not provided, find the project root @@ -96,7 +102,10 @@ export function getLocalWranglerConfigPath( const localWranglerPath = path.join(root, ".wrangler"); if (isDirectory(localWranglerPath)) { - return localWranglerPath; + // Ensure it's not the same as the global config path + if (path.resolve(localWranglerPath) !== globalWranglerPath) { + return localWranglerPath; + } } return undefined; diff --git a/packages/wrangler/src/__tests__/whoami.test.ts b/packages/wrangler/src/__tests__/whoami.test.ts index 5a4243feaaef..ed6343afcc99 100644 --- a/packages/wrangler/src/__tests__/whoami.test.ts +++ b/packages/wrangler/src/__tests__/whoami.test.ts @@ -310,6 +310,7 @@ describe("whoami", () => { ────────────────── Getting User settings... 👋 You are logged in with an OAuth Token, associated with the email user@example.com. + 🔐 Auth source: global (/home/.config/.wrangler/config/default.toml) ┌─â”Ŧ─┐ │ Account Name │ Account ID │ ├─â”ŧ─┤ @@ -375,6 +376,7 @@ describe("whoami", () => { ────────────────── Getting User settings... 👋 You are logged in with an OAuth Token, associated with the email (redacted). + 🔐 Auth source: global (/home/.config/.wrangler/config/default.toml) ┌─â”Ŧ─┐ │ Account Name │ Account ID │ ├─â”ŧ─┤ @@ -414,6 +416,8 @@ describe("whoami", () => { đŸŽĸ Membership roles in \\"(redacted)\\": Contact account super admin to change your permissions. - Test role" `); + expect(std.warn).toMatchInlineSnapshot(`""`); + expect(std.err).toMatchInlineSnapshot(`""`); }); it("should display membership error on authentication error 10000", async ({ @@ -425,7 +429,7 @@ describe("whoami", () => { "*/memberships", () => HttpResponse.json( - createFetchResult(undefined, false, [ + createFetchResult([], false, [ { code: 10000, message: "Authentication error" }, ]) ), @@ -439,6 +443,7 @@ describe("whoami", () => { ────────────────── Getting User settings... 👋 You are logged in with an OAuth Token, associated with the email user@example.com. + 🔐 Auth source: global (/home/.config/.wrangler/config/default.toml) ┌─â”Ŧ─┐ │ Account Name │ Account ID │ ├─â”ŧ─┤ diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 3c9261a73bd6..9ee8aa8a5608 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -432,9 +432,13 @@ function getAuthTokens(config?: UserAuthConfig): { const configPath = getAuthConfigFilePath(); authConfig = readAuthConfigFile(); // Detect if the config path is local or global + const globalConfigPath = path.resolve(getGlobalWranglerConfigPath()); + const resolvedConfigPath = path.resolve(path.dirname(configPath)); const projectRoot = findProjectRoot(); const isLocal = - projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); + resolvedConfigPath !== globalConfigPath && + projectRoot && + configPath.includes(path.join(projectRoot, ".wrangler")); authSource = isLocal ? "local" : "global"; } @@ -1014,13 +1018,21 @@ export function writeAuthConfigFile( }); // Track whether this is local or global auth + const globalConfigPath = path.resolve(getGlobalWranglerConfigPath()); + const resolvedConfigPath = path.resolve(path.dirname(configPath)); const projectRoot = findProjectRoot(); const isLocal = - configPath.includes(path.join(process.cwd(), ".wrangler")) || - (projectRoot !== undefined && - configPath.includes(path.join(projectRoot, ".wrangler"))); + resolvedConfigPath !== globalConfigPath && + (configPath.includes(path.join(process.cwd(), ".wrangler")) || + (projectRoot !== undefined && + configPath.includes(path.join(projectRoot, ".wrangler")))); - reinitialiseAuthTokens(config, isLocal ? "local" : "global"); + // Reinitialise without passing config so warnings show correct file path + const { tokens } = getAuthTokens(); + localState = { + ...tokens, + authSource: isLocal ? "local" : "global", + }; } export function readAuthConfigFile( diff --git a/packages/wrangler/src/user/whoami.ts b/packages/wrangler/src/user/whoami.ts index 0420879886fc..82c9f3ce2ff4 100644 --- a/packages/wrangler/src/user/whoami.ts +++ b/packages/wrangler/src/user/whoami.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { findProjectRoot, getCloudflareComplianceRegion, + getGlobalWranglerConfigPath, } from "@cloudflare/workers-utils"; import chalk from "chalk"; import { fetchPagedListResult, fetchResult } from "../cfetch"; @@ -59,9 +60,13 @@ export async function whoami( function printAuthSource() { try { const configPath = getAuthConfigFilePath(); + const globalConfigPath = path.resolve(getGlobalWranglerConfigPath()); + const resolvedConfigPath = path.resolve(path.dirname(configPath)); const projectRoot = findProjectRoot(); const isLocal = - projectRoot && configPath.includes(path.join(projectRoot, ".wrangler")); + resolvedConfigPath !== globalConfigPath && + projectRoot && + configPath.includes(path.join(projectRoot, ".wrangler")); if (isLocal) { const relativePath = path.relative(process.cwd(), configPath); From 608ebd51db1a1c2e530c0afb5d170efebdd63d41 Mon Sep 17 00:00:00 2001 From: Rohan Mukherjee Date: Mon, 16 Feb 2026 12:45:35 +0530 Subject: [PATCH 6/6] fix: lint --- packages/wrangler/src/user/whoami.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wrangler/src/user/whoami.ts b/packages/wrangler/src/user/whoami.ts index c2616ae121be..3927ad68a56d 100644 --- a/packages/wrangler/src/user/whoami.ts +++ b/packages/wrangler/src/user/whoami.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { - createFatalError, + createFatalError, findProjectRoot, getCloudflareComplianceRegion, getGlobalWranglerConfigPath,