Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
PortfolioSyncComparer.tsโ€ข6.14 kB
/** * PortfolioSyncComparer - Compares local and GitHub portfolio elements * * Determines what actions need to be taken based on the sync mode: * - additive: Only add missing elements * - mirror: Make local exactly match GitHub * - backup: Treat GitHub as authoritative source */ import { ElementType } from '../portfolio/types.js'; import { GitHubIndexEntry } from '../portfolio/GitHubPortfolioIndexer.js'; import { logger } from '../utils/logger.js'; export type SyncMode = 'additive' | 'mirror' | 'backup'; export interface SyncAction { type: ElementType; name: string; path: string; action: 'add' | 'update' | 'delete' | 'skip'; reason?: string; localSha?: string; remoteSha?: string; } export interface SyncActions { toAdd: SyncAction[]; toUpdate: SyncAction[]; toDelete: SyncAction[]; toSkip: SyncAction[]; } export class PortfolioSyncComparer { /** * Compare GitHub and local elements to determine sync actions */ compareElements( githubElements: Map<ElementType, GitHubIndexEntry[]>, localElements: Map<ElementType, any[]>, mode: SyncMode ): SyncActions { const actions: SyncActions = { toAdd: [], toUpdate: [], toDelete: [], toSkip: [] }; logger.info('Comparing elements', { githubCount: this.countElements(githubElements), localCount: this.countElements(localElements), mode }); // Process each element type const allTypes = new Set([ ...githubElements.keys(), ...localElements.keys() ]); for (const type of allTypes) { const githubTypeElements = githubElements.get(type) || []; const localTypeElements = localElements.get(type) || []; this.compareTypeElements( type, githubTypeElements, localTypeElements, mode, actions ); } logger.info('Comparison complete', { toAdd: actions.toAdd.length, toUpdate: actions.toUpdate.length, toDelete: actions.toDelete.length, toSkip: actions.toSkip.length }); return actions; } /** * Compare elements of a specific type */ private compareTypeElements( type: ElementType, githubElements: GitHubIndexEntry[], localElements: any[], mode: SyncMode, actions: SyncActions ): void { // Create maps for efficient lookup const githubMap = new Map( githubElements.map(e => [this.normalizeElementName(e.name), e]) ); const localMap = new Map( localElements.map(e => [this.normalizeElementName(e.name || e.metadata?.name), e]) ); // Process GitHub elements (additions and updates) for (const [name, githubElement] of githubMap) { const localElement = localMap.get(name); if (!localElement) { // Element exists on GitHub but not locally actions.toAdd.push({ type, name: githubElement.name, path: githubElement.path, action: 'add', remoteSha: githubElement.sha }); } else if (mode === 'backup' || this.shouldUpdate(githubElement, localElement, mode)) { // Element needs updating actions.toUpdate.push({ type, name: githubElement.name, path: githubElement.path, action: 'update', reason: mode === 'backup' ? 'backup mode' : 'newer on GitHub', localSha: localElement.sha, remoteSha: githubElement.sha }); } else { // Element is up to date or should be skipped actions.toSkip.push({ type, name: githubElement.name, path: githubElement.path, action: 'skip', reason: 'up to date', localSha: localElement.sha, remoteSha: githubElement.sha }); } } // Process local elements (deletions - only in mirror mode) if (mode === 'mirror') { for (const [name, localElement] of localMap) { if (!githubMap.has(name)) { // Element exists locally but not on GitHub actions.toDelete.push({ type, name: localElement.name || localElement.metadata?.name || name, path: `${type}/${name}.md`, action: 'delete', reason: 'not on GitHub', localSha: localElement.sha }); } } } } /** * Determine if an element should be updated */ private shouldUpdate( githubElement: GitHubIndexEntry, localElement: any, mode: SyncMode ): boolean { // In backup mode, always update from GitHub if (mode === 'backup') { return true; } // In additive mode, never update existing elements if (mode === 'additive') { return false; } // In mirror mode, update if SHAs differ // If we don't have SHAs, compare modified dates if (githubElement.sha && localElement.sha) { return githubElement.sha !== localElement.sha; } // Compare modified dates if available if (githubElement.lastModified && localElement.lastModified) { const githubDate = new Date(githubElement.lastModified).getTime(); const localDate = new Date(localElement.lastModified).getTime(); return githubDate > localDate; } // If we can't determine, skip updating in mirror mode return false; } /** * Normalize element name for comparison * Handles different naming formats and extensions */ private normalizeElementName(name: string): string { if (!name) return ''; // Remove .md extension if present let normalized = name.replace(/\.md$/i, ''); // Convert to lowercase for comparison normalized = normalized.toLowerCase(); // Replace spaces with hyphens (some systems use different formats) normalized = normalized.replaceAll(/\s+/g, '-'); return normalized; } /** * Count total elements in a map */ private countElements(elements: Map<ElementType, any[]>): number { let count = 0; for (const typeElements of elements.values()) { count += typeElements.length; } return count; } }

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/DollhouseMCP/DollhouseMCP'

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