UNPKG

sql-talk

Version:

SQL Talk - 自然言語をSQLに変換するMCPサーバー(安全性保護・SSHトンネル対応) / SQL Talk - MCP Server for Natural Language to SQL conversion with safety guards and SSH tunnel support

213 lines 9.45 kB
import { readdirSync, statSync } from 'fs'; import { dirname, join } from 'path'; import chalk from 'chalk'; import { logger } from './logger.js'; /** * ワークスペース検知ユーティリティ * Workspace detection utility */ export class WorkspaceDetector { /** * プロジェクトルートを検出 * Detect project root directory */ static detectProjectRoot() { logger.info('🔍 Detecting project root...'); // 利用可能な環境変数をログに出力 console.error(chalk.blue('🔍 Available environment variables:')); const relevantEnvVars = [ 'CURSOR_WORKSPACE_ROOT', 'VSCODE_WORKSPACE_FOLDER', 'WORKSPACE_FOLDER_PATHS', // Cursorが設定する環境変数 'PROJECT_ROOT', 'PWD', 'CWD', 'WORKSPACE_ROOT', 'PROJECT_DIR', 'CURSOR_PROJECT_ROOT', 'CURSOR_CURRENT_PROJECT', 'CURSOR_OPENED_PROJECT', 'VSCODE_CWD', 'VSCODE_PID', 'VSCODE_INJECTION' ]; relevantEnvVars.forEach(envVar => { const value = process.env[envVar]; if (value) { console.error(chalk.green(` ${envVar}:`), value); } else { console.error(chalk.gray(` ${envVar}:`), chalk.red('(not set)')); } }); // 全ての環境変数の中で、パス関連のものを表示 console.error(chalk.blue('🔍 All path-related environment variables:')); Object.keys(process.env).forEach(key => { if (key.includes('PATH') || key.includes('ROOT') || key.includes('DIR') || key.includes('WORKSPACE') || key.includes('PROJECT')) { const value = process.env[key]; if (value && value.includes('/')) { console.error(chalk.cyan(` ${key}:`), value); } } }); console.error(chalk.blue('🔍 process.cwd():'), process.cwd()); console.error(chalk.blue('🔍 __dirname:'), __dirname); // プロジェクトルート検出の優先順位 const candidates = [ process.env.CURSOR_WORKSPACE_ROOT, process.env.VSCODE_WORKSPACE_FOLDER, process.env.WORKSPACE_FOLDER_PATHS, // Cursorが設定する環境変数 process.env.PROJECT_ROOT, process.env.PWD, process.cwd() ].filter((value) => Boolean(value)); console.error(chalk.blue('🔍 Project root candidates:')); candidates.forEach((candidate, index) => { console.error(chalk.cyan(` ${index + 1}.`), candidate); }); // 候補を検証して最初の有効なものを返す for (const candidate of candidates) { if (this.isValidProjectRoot(candidate)) { console.error(chalk.green(`✅ Valid project root found: ${candidate}`)); console.error(chalk.yellow('⚠️ Initial PROJECT_ROOT (will be overridden by config):'), candidate); return candidate; } } // 有効な候補が見つからない場合は、現在の作業ディレクトリからプロジェクトルートを自動検出 console.error(chalk.yellow('⚠️ No valid project root found in candidates, auto-detecting from current directory...')); let currentDir = process.cwd(); let attempts = 0; const maxAttempts = 10; // 最大10階層まで遡る console.error(chalk.blue('🔍 Starting project root detection from:'), currentDir); console.error(chalk.blue('🔍 This is the directory where MCP server was started')); while (attempts < maxAttempts) { console.error(chalk.gray(` Attempt ${attempts + 1}: ${currentDir}`)); // このディレクトリの内容を確認 try { const dirContents = readdirSync(currentDir); console.error(chalk.gray(` Directory contents: ${dirContents.slice(0, 10).join(', ')}${dirContents.length > 10 ? '...' : ''}`)); } catch (error) { console.error(chalk.red(` Error reading directory: ${error instanceof Error ? error.message : String(error)}`)); } if (this.isValidProjectRoot(currentDir)) { console.error(chalk.green(`✅ Valid project root found: ${currentDir}`)); console.error(chalk.blue('🎯 This directory contains package.json or .git, indicating it is a project root')); console.error(chalk.yellow('⚠️ Initial PROJECT_ROOT (will be overridden by config):'), currentDir); return currentDir; } const parentDir = dirname(currentDir); if (parentDir === currentDir) { console.error(chalk.red('❌ Reached root directory, stopping search')); break; // ルートディレクトリに到達 } currentDir = parentDir; attempts++; } // 最後の手段としてprocess.cwd()を返す const fallbackRoot = process.cwd(); console.error(chalk.red(`❌ No valid project root found, using fallback: ${fallbackRoot}`)); console.error(chalk.yellow('⚠️ Initial PROJECT_ROOT (will be overridden by config):'), fallbackRoot); return fallbackRoot; } /** * プロジェクトルートが有効かチェック * Check if directory is a valid project root */ static isValidProjectRoot(dirPath) { try { const stats = statSync(dirPath); if (!stats.isDirectory()) { return false; } const contents = readdirSync(dirPath); // package.json または .git ディレクトリがあるかチェック const hasPackageJson = contents.includes('package.json'); const hasGitDir = contents.includes('.git'); const hasConfigFile = contents.includes('sql-talk.config.yaml') || contents.includes('sql-talk.config.yml'); logger.debug(` Checking ${dirPath}: package.json=${hasPackageJson}, .git=${hasGitDir}, config=${hasConfigFile}`); // package.json または .git があれば有効なプロジェクトルートとみなす return hasPackageJson || hasGitDir; } catch (error) { logger.debug(` Error checking ${dirPath}: ${error instanceof Error ? error.message : String(error)}`); return false; } } /** * ワークスペース内の設定ファイルパスを取得 * Get configuration file path within workspace */ static getConfigPath(workspaceRoot) { const root = workspaceRoot || this.detectProjectRoot(); // 設定ファイルの候補パス const configCandidates = [ join(root, 'sql-talk.config.yaml'), join(root, 'sql-talk.config.yml'), join(root, 'config', 'sql-talk.config.yaml'), join(root, 'config', 'sql-talk.config.yml'), join(root, '.config', 'sql-talk.config.yaml'), join(root, '.config', 'sql-talk.config.yml') ]; logger.info('🔍 Looking for configuration file...'); configCandidates.forEach((candidate, index) => { try { const stats = statSync(candidate); if (stats.isFile()) { logger.info(`✅ Found config file: ${candidate}`); return candidate; } } catch (error) { logger.debug(` ${index + 1}. ${candidate} (not found)`); } }); // デフォルトの設定ファイルパスを返す const defaultPath = join(root, 'sql-talk.config.yaml'); logger.warn(`⚠️ No config file found, using default: ${defaultPath}`); return defaultPath; } /** * ワークスペース情報を取得 * Get workspace information */ static getWorkspaceInfo() { const root = this.detectProjectRoot(); const configPath = this.getConfigPath(root); let hasConfig = false; try { const stats = statSync(configPath); hasConfig = stats.isFile(); } catch (error) { // ファイルが存在しない } // プロジェクトタイプを判定 let projectType = 'unknown'; try { const contents = readdirSync(root); if (contents.includes('package.json')) { projectType = 'node'; } else if (contents.includes('requirements.txt') || contents.includes('pyproject.toml')) { projectType = 'python'; } else if (contents.includes('Cargo.toml')) { projectType = 'rust'; } else if (contents.includes('go.mod')) { projectType = 'go'; } } catch (error) { logger.debug(`Error detecting project type: ${error instanceof Error ? error.message : String(error)}`); } return { root, configPath, hasConfig, projectType }; } } //# sourceMappingURL=workspace-detector.js.map