mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
feat(core): improve project root detection with boundary markers (#1531)
This commit is contained in:
@@ -52,6 +52,101 @@ export const TASKMASTER_PROJECT_MARKERS = [
|
||||
LEGACY_CONFIG_FILE // .taskmasterconfig (legacy but still Task Master-specific)
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Project boundary markers - these indicate a project root and STOP upward traversal
|
||||
* during Task Master marker search. If we hit one of these without finding .taskmaster,
|
||||
* we shouldn't traverse beyond it (prevents finding .taskmaster in home directory).
|
||||
*
|
||||
* Ordered by reliability: VCS markers first, then platform-specific, then language-specific
|
||||
*/
|
||||
export const PROJECT_BOUNDARY_MARKERS = [
|
||||
// Version control (strongest indicators)
|
||||
'.git', // Git repository
|
||||
'.svn', // SVN repository
|
||||
'.hg', // Mercurial repository
|
||||
'.fossil', // Fossil repository
|
||||
// CI/CD and platform-specific directories (typically at project root)
|
||||
'.github', // GitHub Actions, configs
|
||||
'.gitlab', // GitLab CI configs
|
||||
'.circleci', // CircleCI configs
|
||||
'.travis.yml', // Travis CI config
|
||||
'.jenkins', // Jenkins configs
|
||||
'.buildkite', // Buildkite configs
|
||||
// Editor/IDE project markers (typically at project root)
|
||||
'.vscode', // VS Code workspace settings
|
||||
'.idea', // JetBrains IDE settings
|
||||
'.project', // Eclipse project
|
||||
'.devcontainer', // Dev containers config
|
||||
// Package manager lock files (strong indicators of project root)
|
||||
'package-lock.json', // npm
|
||||
'yarn.lock', // Yarn
|
||||
'pnpm-lock.yaml', // pnpm
|
||||
'bun.lockb', // Bun
|
||||
'bun.lock', // Bun (text format)
|
||||
'deno.lock', // Deno
|
||||
'deno.json', // Deno config
|
||||
'deno.jsonc', // Deno config (with comments)
|
||||
// Node.js/JavaScript project files
|
||||
'package.json', // Node.js project
|
||||
'lerna.json', // Lerna monorepo
|
||||
'nx.json', // Nx monorepo
|
||||
'turbo.json', // Turborepo
|
||||
'rush.json', // Rush monorepo
|
||||
'pnpm-workspace.yaml', // pnpm workspace
|
||||
// Rust project files
|
||||
'Cargo.toml', // Rust project
|
||||
'Cargo.lock', // Rust lock file
|
||||
// Go project files
|
||||
'go.mod', // Go project
|
||||
'go.sum', // Go checksum file
|
||||
'go.work', // Go workspace
|
||||
// Python project files
|
||||
'pyproject.toml', // Python project (modern)
|
||||
'setup.py', // Python project (legacy)
|
||||
'setup.cfg', // Python setup config
|
||||
'poetry.lock', // Poetry lock file
|
||||
'Pipfile', // Pipenv
|
||||
'Pipfile.lock', // Pipenv lock file
|
||||
'uv.lock', // uv lock file
|
||||
// Ruby project files
|
||||
'Gemfile', // Ruby project
|
||||
'Gemfile.lock', // Ruby lock file
|
||||
// PHP project files
|
||||
'composer.json', // PHP project
|
||||
'composer.lock', // PHP lock file
|
||||
// Java/JVM project files
|
||||
'build.gradle', // Gradle (Java/Kotlin)
|
||||
'build.gradle.kts', // Gradle Kotlin DSL
|
||||
'settings.gradle', // Gradle settings
|
||||
'settings.gradle.kts', // Gradle settings (Kotlin)
|
||||
'pom.xml', // Maven (Java)
|
||||
'build.sbt', // sbt (Scala)
|
||||
'project.clj', // Leiningen (Clojure)
|
||||
'deps.edn', // Clojure deps
|
||||
// Elixir/Erlang project files
|
||||
'mix.exs', // Elixir project
|
||||
'rebar.config', // Erlang rebar
|
||||
// Other language project files
|
||||
'pubspec.yaml', // Dart/Flutter project
|
||||
'Package.swift', // Swift package
|
||||
'CMakeLists.txt', // CMake project
|
||||
'Makefile', // Generic project indicator
|
||||
'meson.build', // Meson build system
|
||||
'BUILD.bazel', // Bazel build
|
||||
'WORKSPACE', // Bazel workspace
|
||||
'flake.nix', // Nix flake
|
||||
'shell.nix', // Nix shell
|
||||
'default.nix', // Nix expression
|
||||
// Container/deployment files (typically at project root)
|
||||
'Dockerfile', // Docker
|
||||
'docker-compose.yml', // Docker Compose
|
||||
'docker-compose.yaml', // Docker Compose
|
||||
'Containerfile', // Podman/OCI container
|
||||
'kubernetes.yml', // Kubernetes manifests
|
||||
'kubernetes.yaml', // Kubernetes manifests
|
||||
'helm/Chart.yaml' // Helm chart
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Other project markers (only checked if no Task Master markers found)
|
||||
* Includes generic task files that could belong to any task runner/build system
|
||||
@@ -59,18 +154,8 @@ export const TASKMASTER_PROJECT_MARKERS = [
|
||||
export const OTHER_PROJECT_MARKERS = [
|
||||
LEGACY_TASKS_FILE, // tasks/tasks.json (NOT Task Master-specific)
|
||||
'tasks.json', // Generic tasks file (NOT Task Master-specific)
|
||||
'.git', // Git repository
|
||||
'.svn', // SVN repository
|
||||
'package.json', // Node.js project
|
||||
'yarn.lock', // Yarn project
|
||||
'package-lock.json', // npm project
|
||||
'pnpm-lock.yaml', // pnpm project
|
||||
'Cargo.toml', // Rust project
|
||||
'go.mod', // Go project
|
||||
'pyproject.toml', // Python project
|
||||
'requirements.txt', // Python project
|
||||
'Gemfile', // Ruby project
|
||||
'composer.json' // PHP project
|
||||
...PROJECT_BOUNDARY_MARKERS, // Include all boundary markers
|
||||
'requirements.txt' // Python requirements (weaker indicator, keep separate)
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,11 +78,10 @@ describe('findProjectRoot', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monorepo behavior - Task Master markers take precedence', () => {
|
||||
it('should find .taskmaster in parent when starting from apps subdirectory', () => {
|
||||
// Simulate exact user scenario:
|
||||
// /project/.taskmaster exists
|
||||
// Starting from /project/apps
|
||||
describe('Project boundary behavior', () => {
|
||||
it('should find .taskmaster in parent when child has NO boundary markers', () => {
|
||||
// Scenario: /project/.taskmaster and /project/apps (no markers)
|
||||
// This is a simple subdirectory, should find .taskmaster in parent
|
||||
const projectRoot = tempDir;
|
||||
const appsDir = path.join(tempDir, 'apps');
|
||||
const taskmasterDir = path.join(projectRoot, '.taskmaster');
|
||||
@@ -90,63 +89,137 @@ describe('findProjectRoot', () => {
|
||||
fs.mkdirSync(taskmasterDir);
|
||||
fs.mkdirSync(appsDir);
|
||||
|
||||
// When called from apps directory
|
||||
const result = findProjectRoot(appsDir);
|
||||
// Should return project root (one level up)
|
||||
expect(result).toBe(projectRoot);
|
||||
});
|
||||
|
||||
it('should prioritize .taskmaster in parent over .git in child', () => {
|
||||
// Create structure: /parent/.taskmaster and /parent/child/.git
|
||||
const parentDir = tempDir;
|
||||
const childDir = path.join(tempDir, 'child');
|
||||
const gitDir = path.join(childDir, '.git');
|
||||
const taskmasterDir = path.join(parentDir, '.taskmaster');
|
||||
it('should stop at project boundary (.git) and NOT find .taskmaster in distant parent', () => {
|
||||
// Scenario: /home/.taskmaster (should be ignored!) and /home/code/project/.git
|
||||
// This is the user's reported issue - .taskmaster in home dir should NOT be found
|
||||
const homeDir = tempDir;
|
||||
const codeDir = path.join(tempDir, 'code');
|
||||
const projectDir = path.join(codeDir, 'project');
|
||||
const taskmasterInHome = path.join(homeDir, '.taskmaster');
|
||||
|
||||
fs.mkdirSync(taskmasterInHome);
|
||||
fs.mkdirSync(codeDir);
|
||||
fs.mkdirSync(projectDir);
|
||||
fs.mkdirSync(path.join(projectDir, '.git')); // Project boundary
|
||||
|
||||
const result = findProjectRoot(projectDir);
|
||||
// Should return project (with .git), NOT home (with .taskmaster)
|
||||
expect(result).toBe(projectDir);
|
||||
});
|
||||
|
||||
it('should stop at project boundary (package.json) and NOT find .taskmaster beyond', () => {
|
||||
// Scenario: /home/.taskmaster and /home/code/project/package.json
|
||||
const homeDir = tempDir;
|
||||
const codeDir = path.join(tempDir, 'code');
|
||||
const projectDir = path.join(codeDir, 'project');
|
||||
const taskmasterInHome = path.join(homeDir, '.taskmaster');
|
||||
|
||||
fs.mkdirSync(taskmasterInHome);
|
||||
fs.mkdirSync(codeDir);
|
||||
fs.mkdirSync(projectDir);
|
||||
fs.writeFileSync(path.join(projectDir, 'package.json'), '{}'); // Project boundary
|
||||
|
||||
const result = findProjectRoot(projectDir);
|
||||
// Should return project (with package.json), NOT home (with .taskmaster)
|
||||
expect(result).toBe(projectDir);
|
||||
});
|
||||
|
||||
it('should stop at project boundary (lock file) and NOT find .taskmaster beyond', () => {
|
||||
// Scenario: /home/.taskmaster and /home/code/project/package-lock.json
|
||||
const homeDir = tempDir;
|
||||
const codeDir = path.join(tempDir, 'code');
|
||||
const projectDir = path.join(codeDir, 'project');
|
||||
const taskmasterInHome = path.join(homeDir, '.taskmaster');
|
||||
|
||||
fs.mkdirSync(taskmasterInHome);
|
||||
fs.mkdirSync(codeDir);
|
||||
fs.mkdirSync(projectDir);
|
||||
fs.writeFileSync(path.join(projectDir, 'package-lock.json'), '{}'); // Project boundary
|
||||
|
||||
const result = findProjectRoot(projectDir);
|
||||
expect(result).toBe(projectDir);
|
||||
});
|
||||
|
||||
it('should find .taskmaster when at SAME level as project boundary', () => {
|
||||
// Scenario: /project/.taskmaster AND /project/.git
|
||||
// This is a properly initialized Task Master project
|
||||
const projectDir = tempDir;
|
||||
const taskmasterDir = path.join(projectDir, '.taskmaster');
|
||||
const gitDir = path.join(projectDir, '.git');
|
||||
|
||||
fs.mkdirSync(taskmasterDir);
|
||||
fs.mkdirSync(childDir);
|
||||
fs.mkdirSync(gitDir);
|
||||
|
||||
// When called from child directory
|
||||
const result = findProjectRoot(childDir);
|
||||
// Should return parent (with .taskmaster), not child (with .git)
|
||||
expect(result).toBe(parentDir);
|
||||
const result = findProjectRoot(projectDir);
|
||||
// Should return project (has both .taskmaster and .git)
|
||||
expect(result).toBe(projectDir);
|
||||
});
|
||||
|
||||
it('should prioritize .taskmaster in grandparent over package.json in child', () => {
|
||||
// Create structure: /grandparent/.taskmaster and /grandparent/parent/child/package.json
|
||||
const grandparentDir = tempDir;
|
||||
const parentDir = path.join(tempDir, 'parent');
|
||||
const childDir = path.join(parentDir, 'child');
|
||||
const taskmasterDir = path.join(grandparentDir, '.taskmaster');
|
||||
it('should find .taskmaster when at same level as boundary, called from subdirectory', () => {
|
||||
// Scenario: /project/.taskmaster, /project/.git, called from /project/src
|
||||
// This is a typical monorepo setup
|
||||
const projectDir = tempDir;
|
||||
const srcDir = path.join(projectDir, 'src');
|
||||
const taskmasterDir = path.join(projectDir, '.taskmaster');
|
||||
const gitDir = path.join(projectDir, '.git');
|
||||
|
||||
fs.mkdirSync(taskmasterDir);
|
||||
fs.mkdirSync(parentDir);
|
||||
fs.mkdirSync(childDir);
|
||||
fs.writeFileSync(path.join(childDir, 'package.json'), '{}');
|
||||
fs.mkdirSync(gitDir);
|
||||
fs.mkdirSync(srcDir);
|
||||
|
||||
const result = findProjectRoot(childDir);
|
||||
expect(result).toBe(grandparentDir);
|
||||
const result = findProjectRoot(srcDir);
|
||||
// Should find project root with .taskmaster (boundary and .taskmaster at same level)
|
||||
expect(result).toBe(projectDir);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Monorepo behavior', () => {
|
||||
it('should find .taskmaster in monorepo root when package has no boundary markers', () => {
|
||||
// Scenario: /monorepo/.taskmaster, /monorepo/.git, /monorepo/packages/pkg/src
|
||||
// pkg directory has no markers
|
||||
const monorepoRoot = tempDir;
|
||||
const pkgDir = path.join(tempDir, 'packages', 'pkg');
|
||||
const srcDir = path.join(pkgDir, 'src');
|
||||
|
||||
fs.mkdirSync(path.join(monorepoRoot, '.taskmaster'));
|
||||
fs.mkdirSync(path.join(monorepoRoot, '.git'));
|
||||
fs.mkdirSync(srcDir, { recursive: true });
|
||||
|
||||
const result = findProjectRoot(srcDir);
|
||||
expect(result).toBe(monorepoRoot);
|
||||
});
|
||||
|
||||
it('should prioritize .taskmaster over multiple other project markers', () => {
|
||||
// Create structure with many markers
|
||||
const parentDir = tempDir;
|
||||
const childDir = path.join(tempDir, 'packages', 'my-package');
|
||||
const taskmasterDir = path.join(parentDir, '.taskmaster');
|
||||
it('should return package root when package HAS its own boundary marker', () => {
|
||||
// Scenario: /monorepo/.taskmaster, /monorepo/packages/pkg/package.json
|
||||
// When a package has its own project marker, it's treated as its own project
|
||||
const monorepoRoot = tempDir;
|
||||
const pkgDir = path.join(tempDir, 'packages', 'pkg');
|
||||
|
||||
fs.mkdirSync(taskmasterDir);
|
||||
fs.mkdirSync(childDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(monorepoRoot, '.taskmaster'));
|
||||
fs.mkdirSync(pkgDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(pkgDir, 'package.json'), '{}'); // Package has its own marker
|
||||
|
||||
// Add multiple other project markers in child
|
||||
fs.mkdirSync(path.join(childDir, '.git'));
|
||||
fs.writeFileSync(path.join(childDir, 'package.json'), '{}');
|
||||
fs.writeFileSync(path.join(childDir, 'go.mod'), '');
|
||||
fs.writeFileSync(path.join(childDir, 'Cargo.toml'), '');
|
||||
const result = findProjectRoot(pkgDir);
|
||||
// Returns package root (the first project boundary encountered)
|
||||
expect(result).toBe(pkgDir);
|
||||
});
|
||||
|
||||
const result = findProjectRoot(childDir);
|
||||
// Should still return parent with .taskmaster
|
||||
expect(result).toBe(parentDir);
|
||||
it('should return package root when package HAS .taskmaster (nested Task Master)', () => {
|
||||
// Scenario: /monorepo/.taskmaster, /monorepo/packages/pkg/.taskmaster
|
||||
// Package has its own Task Master initialization
|
||||
const monorepoRoot = tempDir;
|
||||
const pkgDir = path.join(tempDir, 'packages', 'pkg');
|
||||
|
||||
fs.mkdirSync(path.join(monorepoRoot, '.taskmaster'));
|
||||
fs.mkdirSync(path.join(pkgDir, '.taskmaster'), { recursive: true });
|
||||
|
||||
const result = findProjectRoot(pkgDir);
|
||||
// Returns package (has its own .taskmaster)
|
||||
expect(result).toBe(pkgDir);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -189,10 +262,11 @@ describe('findProjectRoot', () => {
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should return current directory if no markers found', () => {
|
||||
it('should return startDir if no markers found (empty repo)', () => {
|
||||
// Scenario: Empty repo with just a .env file - should use startDir as project root
|
||||
const result = findProjectRoot(tempDir);
|
||||
// Should fall back to process.cwd()
|
||||
expect(result).toBe(process.cwd());
|
||||
// Should fall back to startDir, not process.cwd()
|
||||
expect(result).toBe(tempDir);
|
||||
});
|
||||
|
||||
it('should handle permission errors gracefully', () => {
|
||||
|
||||
@@ -7,20 +7,46 @@ import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
OTHER_PROJECT_MARKERS,
|
||||
PROJECT_BOUNDARY_MARKERS,
|
||||
TASKMASTER_PROJECT_MARKERS
|
||||
} from '../constants/paths.js';
|
||||
|
||||
/**
|
||||
* Check if a marker file/directory exists at the given path
|
||||
*/
|
||||
function markerExists(dir: string, marker: string): boolean {
|
||||
try {
|
||||
return fs.existsSync(path.join(dir, marker));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the given markers exist in a directory
|
||||
*/
|
||||
function hasAnyMarker(dir: string, markers: readonly string[]): boolean {
|
||||
return markers.some((marker) => markerExists(dir, marker));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the project root directory by looking for project markers
|
||||
* Traverses upwards from startDir until a project marker is found or filesystem root is reached
|
||||
* Limited to 50 parent directory levels to prevent excessive traversal
|
||||
*
|
||||
* Strategy: First searches ALL parent directories for .taskmaster (highest priority).
|
||||
* If not found, then searches for other project markers starting from current directory.
|
||||
* This ensures .taskmaster in parent directories takes precedence over other markers in subdirectories.
|
||||
* Strategy:
|
||||
* 1. PASS 1: Search for .taskmaster markers, but STOP at project boundaries
|
||||
* - If .taskmaster found, return that directory
|
||||
* - If a project boundary (package.json, .git, lock files) is found WITHOUT .taskmaster,
|
||||
* stop searching further up (prevents finding .taskmaster in home directory)
|
||||
* 2. PASS 2: If no .taskmaster found, search for other project markers
|
||||
*
|
||||
* This ensures:
|
||||
* - .taskmaster in a parent directory takes precedence (within project boundary)
|
||||
* - .taskmaster outside the project boundary (e.g., home dir) is NOT returned
|
||||
*
|
||||
* @param startDir - Directory to start searching from (defaults to process.cwd())
|
||||
* @returns Project root path (falls back to current directory if no markers found)
|
||||
* @returns Project root path (falls back to startDir if no markers found)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -29,6 +55,12 @@ import {
|
||||
* // /project/packages/my-package/.git
|
||||
* // When called from /project/packages/my-package:
|
||||
* const root = findProjectRoot(); // Returns /project (not /project/packages/my-package)
|
||||
*
|
||||
* // When .taskmaster is outside project boundary:
|
||||
* // /home/user/.taskmaster (should be ignored!)
|
||||
* // /home/user/code/myproject/package.json
|
||||
* // When called from /home/user/code/myproject:
|
||||
* const root = findProjectRoot(); // Returns /home/user/code/myproject (NOT /home/user)
|
||||
* ```
|
||||
*/
|
||||
export function findProjectRoot(startDir: string = process.cwd()): string {
|
||||
@@ -37,26 +69,34 @@ export function findProjectRoot(startDir: string = process.cwd()): string {
|
||||
const maxDepth = 50; // Reasonable limit to prevent infinite loops
|
||||
let depth = 0;
|
||||
|
||||
// FIRST PASS: Traverse ALL parent directories looking ONLY for Task Master markers
|
||||
// This ensures that a .taskmaster in a parent directory takes precedence over
|
||||
// other project markers (like .git, go.mod, etc.) in subdirectories
|
||||
// Track if we've seen a project boundary - we'll stop searching for .taskmaster beyond it
|
||||
let projectBoundaryDir: string | null = null;
|
||||
|
||||
// FIRST PASS: Search for Task Master markers, but respect project boundaries
|
||||
// A project boundary is a directory containing .git, package.json, lock files, etc.
|
||||
// If we find a boundary without .taskmaster, we stop searching further up
|
||||
let searchDir = currentDir;
|
||||
depth = 0;
|
||||
|
||||
while (depth < maxDepth) {
|
||||
// First, check for Task Master markers in this directory
|
||||
for (const marker of TASKMASTER_PROJECT_MARKERS) {
|
||||
const markerPath = path.join(searchDir, marker);
|
||||
try {
|
||||
if (fs.existsSync(markerPath)) {
|
||||
// Found a Task Master marker - this is our project root
|
||||
return searchDir;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore permission errors and continue searching
|
||||
continue;
|
||||
if (markerExists(searchDir, marker)) {
|
||||
// Found a Task Master marker - this is our project root
|
||||
return searchDir;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this directory is a project boundary
|
||||
// (has markers like .git, package.json, lock files, etc.)
|
||||
if (hasAnyMarker(searchDir, PROJECT_BOUNDARY_MARKERS)) {
|
||||
// This is a project boundary - record it and STOP looking for .taskmaster
|
||||
// beyond this point. The .taskmaster in home directory should NOT be found
|
||||
// when the user is inside a different project.
|
||||
projectBoundaryDir = searchDir;
|
||||
break; // Stop Pass 1 - don't look for .taskmaster beyond this boundary
|
||||
}
|
||||
|
||||
// If we're at root, stop after checking it
|
||||
if (searchDir === rootDir) {
|
||||
break;
|
||||
@@ -74,22 +114,17 @@ export function findProjectRoot(startDir: string = process.cwd()): string {
|
||||
depth++;
|
||||
}
|
||||
|
||||
// SECOND PASS: No Task Master markers found in any parent directory
|
||||
// SECOND PASS: No Task Master markers found within project boundary
|
||||
// Now search for other project markers starting from the original directory
|
||||
currentDir = path.resolve(startDir);
|
||||
// If we found a project boundary in Pass 1, start from there (it will match immediately)
|
||||
currentDir = projectBoundaryDir || path.resolve(startDir);
|
||||
depth = 0;
|
||||
|
||||
while (depth < maxDepth) {
|
||||
for (const marker of OTHER_PROJECT_MARKERS) {
|
||||
const markerPath = path.join(currentDir, marker);
|
||||
try {
|
||||
if (fs.existsSync(markerPath)) {
|
||||
// Found another project marker - return this as project root
|
||||
return currentDir;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore permission errors and continue searching
|
||||
continue;
|
||||
if (markerExists(currentDir, marker)) {
|
||||
// Found another project marker - return this as project root
|
||||
return currentDir;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +145,10 @@ export function findProjectRoot(startDir: string = process.cwd()): string {
|
||||
depth++;
|
||||
}
|
||||
|
||||
// Fallback to current working directory if no project root found
|
||||
// This ensures the function always returns a valid, existing path
|
||||
return process.cwd();
|
||||
// Fallback to startDir if no project root found
|
||||
// This handles empty repos or directories with no recognized project markers
|
||||
// (e.g., a repo with just a .env file should still use that directory as root)
|
||||
return path.resolve(startDir);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,10 +59,18 @@ export class OAuthService {
|
||||
private logger = getLogger('OAuthService');
|
||||
private contextStore: ContextStore;
|
||||
private supabaseClient: SupabaseAuthClient;
|
||||
private baseUrl: string;
|
||||
private configOverrides: Partial<AuthConfig>;
|
||||
private authorizationUrl: string | null = null;
|
||||
private keyPair: AuthKeyPair | null = null;
|
||||
|
||||
/**
|
||||
* Get the base URL lazily to ensure environment variables are read fresh.
|
||||
* This allows TM_BASE_DOMAIN to be set after service construction (e.g., in MCP flows).
|
||||
*/
|
||||
private get baseUrl(): string {
|
||||
return getAuthConfig(this.configOverrides).baseUrl;
|
||||
}
|
||||
|
||||
constructor(
|
||||
contextStore: ContextStore,
|
||||
supabaseClient: SupabaseAuthClient,
|
||||
@@ -70,8 +78,7 @@ export class OAuthService {
|
||||
) {
|
||||
this.contextStore = contextStore;
|
||||
this.supabaseClient = supabaseClient;
|
||||
const authConfig = getAuthConfig(config);
|
||||
this.baseUrl = authConfig.baseUrl;
|
||||
this.configOverrides = config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user