Skip to main content
Glama
template-manager.ts4.27 kB
import { existsSync } from 'fs'; import { mkdir, rm } from 'fs/promises'; import { join } from 'path'; import { tmpdir } from 'os'; import { randomUUID } from 'crypto'; import { spawn } from 'child_process'; import { log } from './logger.js'; import { templateVersion } from '../version.js'; /** * Template manager for downloading and managing project templates */ export class TemplateManager { private static readonly GITHUB_ORG = 'cameroncooke'; private static readonly IOS_TEMPLATE_REPO = 'XcodeBuildMCP-iOS-Template'; private static readonly MACOS_TEMPLATE_REPO = 'XcodeBuildMCP-macOS-Template'; /** * Get the template path for a specific platform * Checks for local override via environment variable first */ static async getTemplatePath(platform: 'iOS' | 'macOS'): Promise<string> { // Check for local override const envVar = platform === 'iOS' ? 'XCODEBUILD_MCP_IOS_TEMPLATE_PATH' : 'XCODEBUILD_MCP_MACOS_TEMPLATE_PATH'; const localPath = process.env[envVar]; if (localPath && existsSync(localPath)) { const templateSubdir = join(localPath, 'template'); if (existsSync(templateSubdir)) { log('info', `Using local ${platform} template from: ${templateSubdir}`); return templateSubdir; } else { log('info', `Template directory not found in ${localPath}, using GitHub release`); } } // Download from GitHub release return await this.downloadTemplate(platform); } /** * Download template from GitHub release */ private static async downloadTemplate(platform: 'iOS' | 'macOS'): Promise<string> { const repo = platform === 'iOS' ? this.IOS_TEMPLATE_REPO : this.MACOS_TEMPLATE_REPO; const version = process.env.XCODEBUILD_MCP_TEMPLATE_VERSION || templateVersion; // Create temp directory for download const tempDir = join(tmpdir(), `xcodebuild-mcp-template-${randomUUID()}`); await mkdir(tempDir, { recursive: true }); try { const downloadUrl = `https://github.com/${this.GITHUB_ORG}/${repo}/releases/download/${version}/${repo}-${version.substring(1)}.zip`; const zipPath = join(tempDir, 'template.zip'); log('info', `Downloading ${platform} template ${version} from GitHub...`); log('info', `Download URL: ${downloadUrl}`); // Download the release artifact await new Promise<void>((resolve, reject) => { const curl = spawn('curl', ['-L', '-f', '-o', zipPath, downloadUrl], { cwd: tempDir }); let stderr = ''; curl.stderr.on('data', (data) => { stderr += data.toString(); }); curl.on('close', (code) => { if (code !== 0) { reject(new Error(`Failed to download template: ${stderr}`)); } else { resolve(); } }); curl.on('error', reject); }); // Extract the zip file await new Promise<void>((resolve, reject) => { const unzip = spawn('unzip', ['-q', zipPath], { cwd: tempDir }); let stderr = ''; unzip.stderr.on('data', (data) => { stderr += data.toString(); }); unzip.on('close', (code) => { if (code !== 0) { reject(new Error(`Failed to extract template: ${stderr}`)); } else { resolve(); } }); unzip.on('error', reject); }); // Find the extracted directory and return the template subdirectory const extractedDir = join(tempDir, `${repo}-${version.substring(1)}`); if (!existsSync(extractedDir)) { throw new Error(`Expected template directory not found: ${extractedDir}`); } log('info', `Successfully downloaded ${platform} template ${version}`); return extractedDir; } catch (error) { // Clean up on error log('error', `Failed to download ${platform} template ${version}: ${error}`); await this.cleanup(tempDir); throw error; } } /** * Clean up downloaded template directory */ static async cleanup(templatePath: string): Promise<void> { // Only clean up if it's in temp directory if (templatePath.startsWith(tmpdir())) { await rm(templatePath, { recursive: true, force: true }); } } }

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/SampsonKY/XcodeBuildMCP'

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