Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .changeset/per-directory-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
"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
- **`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`.

**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.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,3 +39,83 @@ 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 {
// 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)) {
// 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
const root = projectRoot ?? findProjectRoot();
if (!root) {
return undefined;
}

const localWranglerPath = nodePath.join(root, ".wrangler");
if (isDirectory(localWranglerPath)) {
// Ensure it's not the same as the global config path
if (nodePath.resolve(localWranglerPath) !== globalWranglerPath) {
return localWranglerPath;
}
}

return undefined;
}
98 changes: 96 additions & 2 deletions packages/miniflare/src/shared/wrangler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +39,86 @@ 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 {
// 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)) {
// 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
const root = projectRoot ?? findProjectRoot();
if (!root) {
return undefined;
}

const localWranglerPath = path.join(root, ".wrangler");
if (isDirectory(localWranglerPath)) {
// Ensure it's not the same as the global config path
if (path.resolve(localWranglerPath) !== globalWranglerPath) {
return localWranglerPath;
}
}

return undefined;
}

export function getGlobalWranglerCachePath() {
return xdgAppPaths(".wrangler").cache();
}
4 changes: 4 additions & 0 deletions packages/workers-utils/src/environment-variables/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ 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"
/** Force authentication type. Set to "global" to use global auth even when local project auth exists. */
| "WRANGLER_AUTH_TYPE"

// ## Development & Local Testing

Expand Down
Loading
Loading