UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

230 lines (195 loc) 6.66 kB
import path from 'path'; import crypto from 'crypto'; import { Workspace, Repository, WorkspaceStore, CrossRepoTask } from './types.js'; import { ConfigManager } from '../../config/config-manager.js'; import { FileSystemAdapter, NodeFileSystemAdapter } from './file-system-adapter.js'; export class WorkspaceDataStore { private store: WorkspaceStore = { workspaces: {} }; private configManager: ConfigManager; private readonly MODULE_NAME = 'workspace'; private readonly DATA_FILE = 'workspace.json'; private fs: FileSystemAdapter; constructor(configManager?: ConfigManager, fs?: FileSystemAdapter) { this.configManager = configManager || new ConfigManager(); this.fs = fs || new NodeFileSystemAdapter(); } async init(): Promise<void> { const storageManager = this.configManager.getStorageManager(); await storageManager.ensureStorageDirectories(); const data = await storageManager.loadData(this.MODULE_NAME, this.DATA_FILE); if (data) { this.store = data; } else { await this.save(); } } async save(): Promise<void> { const storageManager = this.configManager.getStorageManager(); await storageManager.saveData(this.MODULE_NAME, this.DATA_FILE, this.store); } createWorkspace(name: string, rootPath: string, description?: string): Workspace { const workspace: Workspace = { id: this.generateId('ws'), name, description, rootPath, repositories: [], createdAt: new Date(), updatedAt: new Date(), settings: { autoSync: true, syncInterval: 3600000, // 1 hour crossRepoTags: true, }, }; this.store.workspaces[workspace.id] = workspace; if (!this.store.activeWorkspaceId) { this.store.activeWorkspaceId = workspace.id; } return workspace; } getWorkspace(nameOrId: string): Workspace | undefined { if (this.store.workspaces[nameOrId]) { return this.store.workspaces[nameOrId]; } return Object.values(this.store.workspaces).find(ws => ws.name === nameOrId); } getActiveWorkspace(): Workspace | undefined { if (!this.store.activeWorkspaceId) { return undefined; } return this.store.workspaces[this.store.activeWorkspaceId]; } setActiveWorkspace(workspaceNameOrId: string): boolean { // Try by ID first if (this.store.workspaces[workspaceNameOrId]) { this.store.activeWorkspaceId = workspaceNameOrId; return true; } // Try by name const workspace = this.getWorkspace(workspaceNameOrId); if (workspace) { this.store.activeWorkspaceId = workspace.id; return true; } return false; } addRepository( workspaceNameOrId: string, repo: Omit<Repository, 'id' | 'lastSync'> ): Repository | null { const workspace = this.getWorkspace(workspaceNameOrId); if (!workspace) { return null; } const newRepo: Repository = { ...repo, id: this.generateId('repo'), lastSync: new Date(), }; workspace.repositories.push(newRepo); workspace.updatedAt = new Date(); return newRepo; } async detectRepositories(rootPath: string): Promise<Repository[]> { const repositories: Repository[] = []; try { const entries = await this.fs.readdir(rootPath); for (const entry of entries) { const isDir = typeof entry.isDirectory === 'function' ? entry.isDirectory() : entry.isDirectory; if (isDir && !entry.name.startsWith('.')) { const repoPath = path.join(rootPath, entry.name); // Check if it's a git repository try { await this.fs.access(path.join(repoPath, '.git')); repositories.push({ id: this.generateId('repo'), name: entry.name, path: repoPath, type: 'git', lastSync: new Date(), }); } catch { // Check if it has package.json (might be a workspace package) try { await this.fs.access(path.join(repoPath, 'package.json')); repositories.push({ id: this.generateId('repo'), name: entry.name, path: repoPath, type: 'local', lastSync: new Date(), }); } catch { // Not a repository } } } } } catch (error) { console.error('Error detecting repositories:', error); } return repositories; } getRepositoriesForWorkspace(workspaceNameOrId: string): Repository[] { const workspace = this.getWorkspace(workspaceNameOrId); return workspace?.repositories || []; } updateRepository( workspaceNameOrId: string, repoId: string, updates: Partial<Omit<Repository, 'id'>> ): boolean { const workspace = this.getWorkspace(workspaceNameOrId); if (!workspace) { return false; } const repo = workspace.repositories.find(r => r.id === repoId); if (!repo) { return false; } Object.assign(repo, updates); repo.lastSync = new Date(); workspace.updatedAt = new Date(); return true; } getWorkspaces(): Workspace[] { return Object.values(this.store.workspaces); } findRepository(name: string): Repository | undefined { for (const workspace of Object.values(this.store.workspaces)) { const repo = workspace.repositories.find(r => r.name === name); if (repo) { return repo; } } return undefined; } findRepositoryByPath(repoPath: string): Repository | undefined { for (const workspace of Object.values(this.store.workspaces)) { const repo = workspace.repositories.find(r => r.path === repoPath); if (repo) { return repo; } } return undefined; } getPrimaryRepository(): Repository | undefined { const activeWorkspace = this.getActiveWorkspace(); if (!activeWorkspace || activeWorkspace.repositories.length === 0) { return undefined; } // Return the first repository marked as primary, or the first repository return activeWorkspace.repositories.find(r => r.primary) || activeWorkspace.repositories[0]; } private generateId(prefix: string = ''): string { let uuid: string; try { uuid = crypto.randomUUID(); } catch { // Fallback for environments where crypto.randomUUID is not available uuid = Date.now().toString(16) + '-' + Math.random().toString(16).substr(2); } return prefix ? `${prefix}-${uuid}` : uuid; } }