Add code
This commit is contained in:
175
src/config.ts
Normal file
175
src/config.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Configuration with priority order:
|
||||
* 1. Environment variables
|
||||
* 2. .env file in project root
|
||||
* 3. Interactive prompt (development fallback)
|
||||
*/
|
||||
|
||||
export interface TokenConfig {
|
||||
email: string;
|
||||
token: string;
|
||||
source: 'env' | 'dotenv' | 'prompt';
|
||||
}
|
||||
|
||||
export interface DefaultConfig {
|
||||
workspace: string | null;
|
||||
repo: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token configuration loader with priority order.
|
||||
* Loads from environment variable, .env file, or prompts user.
|
||||
*/
|
||||
export class TokenConfigLoader {
|
||||
private static readonly ENV_FILE = '.env';
|
||||
private static readonly EMAIL_ENV = 'BITBUCKET_MCP_EMAIL';
|
||||
private static readonly TOKEN_ENV = 'BITBUCKET_MCP_TOKEN';
|
||||
|
||||
/**
|
||||
* Load configuration from configured sources with priority order.
|
||||
* @returns TokenConfig with loaded email, token and source
|
||||
*/
|
||||
public static async load(): Promise<TokenConfig> {
|
||||
// Priority 1: Environment variables (highest precedence)
|
||||
const envEmail = process.env[this.EMAIL_ENV];
|
||||
const envToken = process.env[this.TOKEN_ENV];
|
||||
if (envEmail && envToken && this.isValidToken(envToken)) {
|
||||
return { email: envEmail, token: envToken, source: 'env' };
|
||||
}
|
||||
|
||||
// Debug: Log environment check result
|
||||
console.debug(`[DEBUG] Env var check - email present: ${!!envEmail}, token present: ${!!envToken}, valid: ${!!(envEmail && envToken && this.isValidToken(envToken))}`);
|
||||
|
||||
// Priority 2: .env file in project root
|
||||
const { email: dotenvEmail, token: dotenvToken } = this.loadFromDotEnv();
|
||||
console.debug(`[DEBUG] Dotenv config loaded - email: ${!!dotenvEmail}, token: ${!!dotenvToken}`);
|
||||
if (dotenvEmail && dotenvToken && this.isValidToken(dotenvToken)) {
|
||||
return { email: dotenvEmail, token: dotenvToken, source: 'dotenv' };
|
||||
}
|
||||
|
||||
// Debug: Log dotenv check result
|
||||
console.debug(`[DEBUG] Dotenv check - email present: ${!!dotenvEmail}, token present: ${!!dotenvToken}, valid: ${!!(dotenvEmail && dotenvToken && this.isValidToken(dotenvToken))}`);
|
||||
|
||||
// Priority 3: Interactive prompt (fallback)
|
||||
console.warn(
|
||||
'⚠️ No Bitbucket credentials found. Please set one of:'
|
||||
);
|
||||
console.warn(' 1. Export BITBUCKET_MCP_EMAIL=<your_email> and BITBUCKET_MCP_TOKEN=<your_token>');
|
||||
console.warn(' 2. Add to .env: BITBUCKET_MCP_EMAIL=<your_email> and BITBUCKET_MCP_TOKEN=<your_token>');
|
||||
|
||||
return this.promptForCredentials().then(credentials => {
|
||||
if (credentials.email && this.isValidToken(credentials.token)) {
|
||||
return { ...credentials, source: 'prompt' };
|
||||
}
|
||||
throw new Error(
|
||||
'Invalid or missing Bitbucket credentials. Aborting.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load email and token from .env file in project root directory.
|
||||
*/
|
||||
private static loadFromDotEnv(): { email: string | null; token: string | null } {
|
||||
console.debug('[DEBUG] Attempting to load credentials from .env file');
|
||||
|
||||
try {
|
||||
// Resolve to project root (look for package.json)
|
||||
let currentDir = process.cwd();
|
||||
while (currentDir !== '/') {
|
||||
const pkgPath = path.join(currentDir, 'package.json');
|
||||
if (fs.existsSync(pkgPath)) break;
|
||||
currentDir = path.dirname(currentDir);
|
||||
}
|
||||
|
||||
const envPath = path.join(currentDir, this.ENV_FILE);
|
||||
if (!fs.existsSync(envPath)) return { email: null, token: null };
|
||||
|
||||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||||
|
||||
const emailMatch = envContent.match(
|
||||
new RegExp(`${this.EMAIL_ENV}\\s*=\\s*([^\\r\\n]+)`, 'i')
|
||||
);
|
||||
const tokenMatch = envContent.match(
|
||||
new RegExp(`${this.TOKEN_ENV}\\s*=\\s*([^\\r\\n]+)`, 'i')
|
||||
);
|
||||
|
||||
return {
|
||||
email: emailMatch ? emailMatch[1].trim() : null,
|
||||
token: tokenMatch ? tokenMatch[1].trim() : null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to load .env:', error);
|
||||
return { email: null, token: null };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Bitbucket access token format.
|
||||
*/
|
||||
private static isValidToken(token: string): boolean {
|
||||
if (!token || token.length < 8) return false;
|
||||
const validPattern = /^[A-Za-z0-9=._\-+/]+$/;
|
||||
return validPattern.test(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user for credentials (development fallback).
|
||||
*/
|
||||
private static async promptForCredentials(): Promise<{ email: string; token: string }> {
|
||||
// Skip prompting if running in non-Node.js environment
|
||||
if (typeof require === 'undefined') {
|
||||
return { email: '', token: '' };
|
||||
}
|
||||
|
||||
const readline = await import('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const askQuestion = (question: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer: string) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(
|
||||
'Enter your Bitbucket email (or press Enter to skip): ',
|
||||
async (email: string) => {
|
||||
const token = await askQuestion('Enter your Bitbucket access token (or press Enter to skip): ');
|
||||
rl.close();
|
||||
resolve({ email: email || '', token: token || '' });
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultConfigLoader {
|
||||
private static readonly WORKSPACE_ENV = 'DEFAULT_WORKSPACE';
|
||||
private static readonly REPO_ENV = 'DEFAULT_REPO';
|
||||
|
||||
public static load(): DefaultConfig {
|
||||
return {
|
||||
workspace: process.env[this.WORKSPACE_ENV] || null,
|
||||
repo: process.env[this.REPO_ENV] || null
|
||||
};
|
||||
}
|
||||
|
||||
public static getWorkspace(): string | null {
|
||||
return process.env[this.WORKSPACE_ENV] || null;
|
||||
}
|
||||
|
||||
public static getRepo(): string | null {
|
||||
return process.env[this.REPO_ENV] || null;
|
||||
}
|
||||
}
|
||||
|
||||
export default TokenConfigLoader;
|
||||
Reference in New Issue
Block a user