Skip to main content
Glama
ProjectScanner.ts4.43 kB
import { join, dirname } from 'path'; import { ProjectInfo } from '../types.js'; import { findFilesRecursively, findUpwards, readJsonFile, fileExists } from '../utils/fileSystem.js'; import { Logger } from '../utils/logger.js'; import { ProjectContextManager } from '../context/ProjectContextManager.js'; export class ProjectScanner { private logger = Logger.getInstance(); async scanForProjects(startDir?: string): Promise<ProjectInfo[]> { // Use project context if available and no startDir specified let searchDir = startDir; if (!searchDir) { const contextManager = ProjectContextManager.getInstance(); if (contextManager.isInitialized()) { searchDir = contextManager.getContext().rootDirectory; } else { searchDir = process.cwd(); } } this.logger.info(`Scanning for projects starting from ${searchDir}`); const projects: ProjectInfo[] = []; // First, look for package.json in current and parent directories const upwardsPackageJson = await findUpwards(searchDir, 'package.json'); if (upwardsPackageJson) { const projectInfo = await this.createProjectInfo(upwardsPackageJson); if (projectInfo) { projects.push(projectInfo); } } // Then search recursively in subdirectories const packageJsonFiles = await findFilesRecursively(searchDir, 'package.json', 3); for (const packageJsonPath of packageJsonFiles) { // Skip if we already found this one if (upwardsPackageJson && packageJsonPath === upwardsPackageJson) { continue; } const projectInfo = await this.createProjectInfo(packageJsonPath); if (projectInfo) { projects.push(projectInfo); } } return this.prioritizeProjects(projects); } private async createProjectInfo(packageJsonPath: string): Promise<ProjectInfo | null> { try { const packageJson = await readJsonFile(packageJsonPath); const directory = dirname(packageJsonPath); const hasDevScript = await this.validateDevScript(packageJson); if (!hasDevScript) { return null; } const envPath = await this.findEnvFile(directory); return { directory, packageJson, hasDevScript, envPath: envPath || undefined, priority: this.calculatePriority(directory, packageJson) }; } catch (error) { this.logger.warn(`Failed to process package.json at ${packageJsonPath}`, { error }); return null; } } private async validateDevScript(packageJson: any): Promise<boolean> { return packageJson.scripts && typeof packageJson.scripts.dev === 'string' && packageJson.scripts.dev.trim().length > 0; } private async findEnvFile(directory: string): Promise<string | null> { const envFiles = ['.env', '.env.local', '.env.development']; for (const envFile of envFiles) { const envPath = join(directory, envFile); if (await fileExists(envPath)) { return envPath; } } return null; } private calculatePriority(directory: string, packageJson: any): number { let priority = 0; // Higher priority for root projects (fewer path segments) const depth = directory.split('/').length; priority += Math.max(0, 10 - depth); // Higher priority for common framework names const name = packageJson.name || ''; if (name.includes('app') || name.includes('web') || name.includes('frontend')) { priority += 5; } // Higher priority if it has common dev dependencies const devDeps = packageJson.devDependencies || {}; const deps = packageJson.dependencies || {}; const allDeps = { ...devDeps, ...deps }; const frameworks = ['vite', 'webpack', 'next', 'nuxt', 'react', 'vue', 'svelte']; for (const framework of frameworks) { if (allDeps[framework] || Object.keys(allDeps).some(dep => dep.includes(framework))) { priority += 3; break; } } return priority; } private prioritizeProjects(projects: ProjectInfo[]): ProjectInfo[] { return projects.sort((a, b) => b.priority - a.priority); } async findBestProject(startDir?: string): Promise<ProjectInfo | null> { const projects = await this.scanForProjects(startDir); return projects.length > 0 ? projects[0] : null; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/masamunet/npm-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server