xcode.ts•2.79 kB
import { exec } from 'child_process';
import { promisify } from 'util';
import { existsSync, statSync } from 'fs';
import path from 'path';
const execAsync = promisify(exec);
export interface ListSchemesArgs {
  projectPath: string;
}
export interface SchemeInfo {
  name: string;
  isShared: boolean;
}
export class XcodeHandlers {
  async listSchemes(args: ListSchemesArgs): Promise<any> {
    const { projectPath } = args;
    if (!projectPath) {
      throw new Error('Project path is required');
    }
    if (!existsSync(projectPath)) {
      throw new Error(`Project path does not exist: ${projectPath}`);
    }
    const stats = statSync(projectPath);
    if (!stats.isDirectory()) {
      throw new Error(`Project path is not a directory: ${projectPath}`);
    }
    const isWorkspace = projectPath.endsWith('.xcworkspace');
    const isProject = projectPath.endsWith('.xcodeproj');
    if (!isWorkspace && !isProject) {
      throw new Error('Project path must be either a .xcworkspace or .xcodeproj file');
    }
    try {
      const command = isWorkspace 
        ? `xcodebuild -workspace "${projectPath}" -list`
        : `xcodebuild -project "${projectPath}" -list`;
      const { stdout, stderr } = await execAsync(command);
      if (stderr && stderr.trim() !== '') {
        console.error('xcodebuild stderr:', stderr);
      }
      const schemes = this.parseXcodebuildOutput(stdout);
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({
              projectPath,
              projectType: isWorkspace ? 'workspace' : 'project',
              schemes,
              totalSchemes: schemes.length
            }, null, 2)
          }
        ]
      };
    } catch (error: any) {
      throw new Error(`Failed to list schemes: ${error.message}`);
    }
  }
  private parseXcodebuildOutput(output: string): SchemeInfo[] {
    const lines = output.split('\n');
    const schemes: SchemeInfo[] = [];
    let inSchemesSection = false;
    for (const line of lines) {
      const trimmedLine = line.trim();
      
      if (trimmedLine === 'Schemes:') {
        inSchemesSection = true;
        continue;
      }
      if (inSchemesSection) {
        if (trimmedLine === '' || trimmedLine.startsWith('Build Configurations:') || trimmedLine.startsWith('If no build configuration')) {
          break;
        }
        if (trimmedLine && !trimmedLine.startsWith('Information about project')) {
          const isShared = !trimmedLine.startsWith('    ');
          const schemeName = trimmedLine.trim();
          
          if (schemeName) {
            schemes.push({
              name: schemeName,
              isShared
            });
          }
        }
      }
    }
    return schemes;
  }
}