UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

253 lines (219 loc) 7.41 kB
import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; import { AtlasConfig, ProjectMode, StorageMode, ProjectDetectionResult, DashboardConfig } from './types.js'; import { StorageManager } from '../storage/storage-manager.js'; export class ConfigManager { private config: AtlasConfig; private configPath: string | null = null; private storageManager: StorageManager; constructor() { this.storageManager = new StorageManager(); this.config = this.getDefaultConfig(); } private getDefaultConfig(): AtlasConfig { return { projectMode: 'new', workspaceRoot: process.cwd(), dataLocation: '.atlas/', integration: { respectGitignore: true, preserveExisting: true, modules: { kanban: true, development: true, documentation: true, 'product-requirements': true, agile: true, }, }, features: { tddEnforcement: 'strict', autoInit: false, autoBackup: true, }, storage: { mode: 'local', isolation: 'project', }, webDashboard: { enabled: true, port: process.env.ATLAS_DASHBOARD_PORT ? parseInt(process.env.ATLAS_DASHBOARD_PORT) : process.env.NODE_ENV === 'test' ? 0 : 3001, host: 'localhost', autoOpen: false, features: { performance: true, security: true, agile: true, errors: true }, realTimeUpdates: true, exportEnabled: true }, version: '1.0.0', }; } private async getConfigPath(): Promise<string> { // Store config in the storage location, not in project root by default const location = await this.storageManager.getStorageLocation(); return path.join(location.config, 'atlas.config.json'); } async load(): Promise<void> { // First try to load from project root try { const localConfigPath = path.join(process.cwd(), 'atlas.config.json'); const data = await fs.readFile(localConfigPath, 'utf-8'); this.config = { ...this.getDefaultConfig(), ...JSON.parse(data) }; return; } catch { // Not in project root, continue } // Then try from storage location try { if (!this.configPath) { this.configPath = await this.getConfigPath(); } const data = await fs.readFile(this.configPath, 'utf-8'); this.config = { ...this.getDefaultConfig(), ...JSON.parse(data) }; } catch (error) { // Use default config if file doesn't exist this.config = this.getDefaultConfig(); } } async save(): Promise<void> { // Save to current directory for tests/local use const localConfigPath = path.join(process.cwd(), 'atlas.config.json'); await fs.writeFile(localConfigPath, JSON.stringify(this.config, null, 2)); } async detectProject(): Promise<ProjectDetectionResult> { const result: ProjectDetectionResult = { hasGit: false, hasPackageJson: false, hasTsConfig: false, hasTests: false, suggestedMode: 'new', existingTools: [], }; // Check for Git try { await fs.access(path.join(process.cwd(), '.git')); result.hasGit = true; result.existingTools.push('git'); } catch {} // Check for Node.js project try { await fs.access(path.join(process.cwd(), 'package.json')); result.hasPackageJson = true; result.projectType = 'node'; const packageJson = JSON.parse( await fs.readFile(path.join(process.cwd(), 'package.json'), 'utf-8') ); // Detect test framework const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.jest || deps['@jest/core']) { result.testFramework = 'jest'; result.hasTests = true; } else if (deps.mocha) { result.testFramework = 'mocha'; result.hasTests = true; } else if (deps.vitest) { result.testFramework = 'vitest'; result.hasTests = true; } // Check for existing PM tools if (deps['@atlassian/jira']) result.existingTools.push('jira'); if (deps.trello) result.existingTools.push('trello'); } catch {} // Check for TypeScript try { await fs.access(path.join(process.cwd(), 'tsconfig.json')); result.hasTsConfig = true; result.existingTools.push('typescript'); } catch {} // Determine suggested mode if (result.hasGit && result.hasPackageJson) { result.suggestedMode = 'existing'; } // Check for monorepo indicators try { const files = await fs.readdir(process.cwd()); const hasMultiplePackageJsons = 0; for (const file of files) { const filePath = path.join(process.cwd(), file); const stat = await fs.stat(filePath); if (stat.isDirectory() && !file.startsWith('.')) { try { await fs.access(path.join(filePath, 'package.json')); result.suggestedMode = 'multi-repo'; break; } catch {} } } // Check for lerna, yarn workspaces, etc. if (files.includes('lerna.json') || files.includes('pnpm-workspace.yaml')) { result.suggestedMode = 'multi-repo'; } } catch {} return result; } async getStoragePath(): Promise<string> { const location = await this.storageManager.getStorageLocation(); return location.data; } // Backwards compatibility - sync version getDataPath(): string { // Return based on current config if (this.config.storage?.mode === 'home' && this.config.projectName) { return path.join(os.homedir(), '.atlas', 'projects', this.config.projectName); } if (this.config.storage?.mode === 'custom' && this.config.storage?.path) { return this.config.storage.path; } return path.join(this.config.workspaceRoot || process.cwd(), '.atlas'); } getStorageManager(): StorageManager { return this.storageManager; } get(): AtlasConfig { return { ...this.config }; } set(config: Partial<AtlasConfig>): void { this.config = { ...this.config, ...config }; } isModuleEnabled(module: string): boolean { const modules = this.config.integration?.modules as any; return modules?.[module] ?? true; } getProjectMode(): ProjectMode { return this.config.projectMode; } getProjectId(): string | undefined { return this.config.projectId; } isMultiRepo(): boolean { return this.config.projectMode === 'multi-repo'; } getRepositories(): NonNullable<AtlasConfig['repositories']> { return this.config.repositories || []; } isDashboardEnabled(): boolean { return this.config.webDashboard?.enabled ?? true; } getDashboardConfig(): DashboardConfig { const defaults = this.getDefaultConfig().webDashboard!; return { ...defaults, ...this.config.webDashboard } as Required<DashboardConfig>; } updateDashboardConfig(dashboardConfig: Partial<DashboardConfig>): void { this.config.webDashboard = { ...this.config.webDashboard, ...dashboardConfig }; } logModuleLoad(moduleName: string, type: 'new' | 'legacy', toolCount: number): void { console.log(`✅ Loaded ${moduleName} module (${type}) with ${toolCount} tools`); } }