xc-mcp
Version:
MCP server that wraps Xcode command-line tools for iOS/macOS development workflows
171 lines • 6.46 kB
JavaScript
/**
* Build Settings Cache
*
* Caches Xcode build settings to avoid repeated expensive xcodebuild calls.
* Settings include bundle identifier, deployment target, device families, and capabilities.
*
* Cache TTL: 1 hour (can be invalidated on project file changes)
*/
import { join } from 'path';
import { executeCommand, buildXcodebuildCommand } from '../utils/command.js';
import { parseBuildSettingsJson } from '../utils/build-settings-parser.js';
export class BuildSettingsCache {
cache = new Map();
CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
/**
* Get all build settings for a given project, scheme, and configuration
*/
async getBuildSettings(projectPath, scheme, configuration = 'Debug') {
const cacheKey = `${projectPath}:${scheme}:${configuration}`;
// Check cache validity
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
return cached.settings;
}
// Execute xcodebuild -showBuildSettings
const settings = await this.fetchBuildSettings(projectPath, scheme, configuration);
// Store in cache
this.cache.set(cacheKey, {
settings,
timestamp: Date.now(),
});
return settings;
}
/**
* Get bundle identifier for quick lookup
*/
async getBundleIdentifier(projectPath, scheme) {
const settings = await this.getBuildSettings(projectPath, scheme);
return settings.PRODUCT_BUNDLE_IDENTIFIER;
}
/**
* Get app path for installation/launching
*/
async getAppPath(projectPath, scheme, configuration = 'Debug') {
const settings = await this.getBuildSettings(projectPath, scheme, configuration);
return join(settings.CONFIGURATION_BUILD_DIR, `${settings.PRODUCT_NAME}.app`);
}
/**
* Get deployment target version as iOS major version (e.g., 15, 16, 17)
*/
async getDeploymentTarget(projectPath, scheme) {
const settings = await this.getBuildSettings(projectPath, scheme);
const match = settings.DEPLOYMENT_TARGET.match(/(\d+)/);
return match ? parseInt(match[1]) : 14; // Default to iOS 14 if parsing fails
}
/**
* Get supported device families
* Returns: { supportsIPhone: boolean; supportsIPad: boolean }
*/
async getDeviceFamilies(projectPath, scheme) {
const settings = await this.getBuildSettings(projectPath, scheme);
const families = settings.TARGETED_DEVICE_FAMILY.split(',').map(f => f.trim());
return {
supportsIPhone: families.includes('1'),
supportsIPad: families.includes('2'),
};
}
/**
* Get required capabilities from Info.plist keys
*/
async getRequiredCapabilities(projectPath, scheme) {
const settings = await this.getBuildSettings(projectPath, scheme);
const capabilities = [];
if (settings.NSCameraUsageDescription)
capabilities.push('camera');
if (settings.NSLocationWhenInUseUsageDescription ||
settings.NSLocationAlwaysAndWhenInUseUsageDescription) {
capabilities.push('location');
}
if (settings.NSMicrophoneUsageDescription)
capabilities.push('microphone');
if (settings.NSContactsUsageDescription)
capabilities.push('contacts');
if (settings.NSCalendarsUsageDescription)
capabilities.push('calendar');
if (settings.NSRemindersUsageDescription)
capabilities.push('reminders');
if (settings.NSPhotosUsageDescription)
capabilities.push('photos');
if (settings.NSHealthShareUsageDescription || settings.NSHealthUpdateUsageDescription) {
capabilities.push('health');
}
if (settings.NSBluetoothAlwaysUsageDescription)
capabilities.push('bluetooth');
if (settings.NSUserTrackingUsageDescription)
capabilities.push('tracking');
if (settings.NSSiriUsageDescription)
capabilities.push('siri');
return capabilities;
}
/**
* Invalidate cache for specific project (after project changes)
*/
invalidateCache(projectPath, scheme) {
if (!projectPath) {
this.cache.clear();
return;
}
if (!scheme) {
// Clear all entries for this project
for (const key of this.cache.keys()) {
if (key.startsWith(projectPath)) {
this.cache.delete(key);
}
}
return;
}
// Clear specific entry
const cacheKey = `${projectPath}:${scheme}`;
for (const key of this.cache.keys()) {
if (key.startsWith(cacheKey)) {
this.cache.delete(key);
}
}
}
/**
* Get cache statistics
*/
getCacheStats() {
let oldestTimestamp = null;
let newestTimestamp = null;
for (const entry of this.cache.values()) {
if (oldestTimestamp === null || entry.timestamp < oldestTimestamp) {
oldestTimestamp = entry.timestamp;
}
if (newestTimestamp === null || entry.timestamp > newestTimestamp) {
newestTimestamp = entry.timestamp;
}
}
return {
size: this.cache.size,
oldestEntry: oldestTimestamp,
newestEntry: newestTimestamp,
};
}
/**
* Fetch build settings from xcodebuild
*/
async fetchBuildSettings(projectPath, scheme, configuration) {
// Build command using existing utility
const command = buildXcodebuildCommand('-showBuildSettings', projectPath, {
scheme,
configuration,
json: true,
});
try {
const result = await executeCommand(command, { timeout: 30000 });
if (result.code !== 0) {
throw new Error(`xcodebuild -showBuildSettings failed: ${result.stderr}`);
}
// Parse JSON output
return parseBuildSettingsJson(result.stdout);
}
catch (error) {
throw new Error(`Failed to fetch build settings: ${error}`);
}
}
}
// Export singleton instance
export const buildSettingsCache = new BuildSettingsCache();
//# sourceMappingURL=build-settings-cache.js.map