diff --git a/.gitignore b/.gitignore index 16a20aa..178dfd9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ node_modules/ CLAUDE.md coverage/ .DS_Store -dist/CLI_DISCREPANCIES.md +dist/ # MCP Registry tokens .mcpregistry_* diff --git a/README.md b/README.md index fdfca79..b964223 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,10 @@ Model Context Protocol (MCP) server that controls Xcode directly through JavaScr - Opens projects, builds, runs, tests, and debugs from within Xcode - Parses build logs with precise error locations using [XCLogParser](https://github.com/MobileNativeFoundation/XCLogParser) - Provides comprehensive environment validation and health checks +- Adds simulator tooling: list/boot/shutdown, capture live logs, grab screenshots, and drive UI automation with [AXe](https://github.com/cameroncooke/axe) without touching the `xcodebuild` CLI - Supports graceful degradation when optional dependencies are missing - **NEW**: Includes a full-featured CLI with 100% MCP server feature parity +- **NEW**: Coordinates multi-worker build queues with exclusive locks and explicit release commands ## Requirements @@ -148,7 +150,12 @@ npm install -g xcodemcp xcodecontrol --help # Run a tool with flags -xcodecontrol build --xcodeproj /path/to/Project.xcodeproj --scheme MyScheme +xcodecontrol build \ + --xcodeproj /path/to/Project.xcodeproj \ + --scheme MyScheme \ + --reason "Working on onboarding flow" +# Run tests and wait for completion (default) +xcodecontrol test --xcodeproj /path/to/Project.xcodeproj --scheme MyScheme --device-type iphone --os-version 18.0 # Get help for a specific tool xcodecontrol build --help @@ -160,6 +167,82 @@ xcodecontrol build --json-input '{"xcodeproj": "/path/to/Project.xcodeproj", "sc xcodecontrol --json health-check ``` +### Simulator & UI Automation Tools + +XcodeMCP now includes simulator management and UI automation commands that do **not** rely on `xcodebuild`. Everything runs through the existing JXA powered `xcode_build_and_run` workflow, so you can boot, launch, and interact with the simulator from the same toolchain. + +> **Note:** UI automation commands use [AXe](https://github.com/cameroncooke/axe). Install it with `brew install cameroncooke/axe/axe` or set the `XCODEMCP_AXE_PATH` environment variable to an existing binary. + +```bash +# List and boot simulators +xcodecontrol list-sims +xcodecontrol boot-sim --simulator-uuid "" +xcodecontrol open-sim + +# Capture a screenshot from the currently booted simulator +xcodecontrol screenshot --save-path /tmp/screenshot.png + +# Drive the UI with AXe (describe → tap → type) +xcodecontrol describe-ui --simulator-uuid "" +xcodecontrol tap --simulator-uuid "" --x 180 --y 420 +xcodecontrol type-text --simulator-uuid "" --text "Hello world!" +``` + +These tools are also available through any MCP client. Refer to `xcode_list_sims`, `xcode_boot_sim`, `xcode_describe_ui`, `xcode_tap`, `xcode_type_text`, `xcode_swipe`, and `xcode_screenshot` in your client's tool list. + +### Exclusive Build Locks + +Build- and run-style commands now coordinate through a shared lock directory (default: `~/Library/Application Support/XcodeMCP/locks`). Every time you run `xcodecontrol build` or `xcodecontrol build-and-run`, you **must** supply a short `--reason` that summarizes the part of the app you're touching. The command will wait (via file system events, no busy polling) until it's your turn, then print a footer reminding you to release the lock when you're finished inspecting logs or simulator state. + +```bash +# Acquire the lock and build +xcodecontrol build \ + --xcodeproj /path/to/App.xcodeproj \ + --scheme Debug \ + --reason "Tweaking settings tab navigation" + +# Once you've finished reviewing the results, release your slot +xcodecontrol release-lock --xcodeproj /path/to/App.xcodeproj + +# Emergency: clear every outstanding lock (CLI-only safety valve) +xcodecontrol release-all-locks +``` + +The same release command is exposed to MCP clients as `xcode_release_lock`. Locks are represented as YAML files per project/workspace so multiple workers (or multiple MCP servers) can coordinate safely across processes and sandboxes. + +### Inspect Build & Run Logs + +Each `xcode_build` / `build-and-run` command now reports a **Log ID** and filesystem path for the associated `.xcactivitylog`. Use the new viewer to inspect it (optionally while the build is still running): + +```bash +# Show the exact log returned by your last build command +xcodecontrol view-build-log --log-id "" + +# Or grab the latest log for a project/workspace and filter for errors +xcodecontrol view-build-log \ + --xcodeproj /Users/me/ManabiReader/ManabiReader.xcodeproj \ + --filter "error" \ + --max-lines 200 + +# Match multiple patterns with glob syntax (case-insensitive by default) +xcodecontrol view-build-log \ + --log-id "" \ + --filter-globs "*error*,*warning*,type-check failed" + +# Resume where you left off using the cursor returned from the previous command +xcodecontrol view-build-log --log-id "" --cursor "" --filter "warning" + +# Use regex / case-sensitive filters if needed +xcodecontrol view-build-log --log-id "" --filter "The compiler.*type-check" --filter-regex --case-sensitive + +# Inspect the runtime console log (same filters & cursor support) +xcodecontrol build-and-run --xcodeproj ... --scheme ... +# ...after it finishes: +xcodecontrol view-run-log --log-id "" --filter-globs "*# DETENTS*" +``` + +These are exposed as the `xcode_view_build_log` and `xcode_view_run_log` MCP tools (`filter_globs` accepts an array of glob expressions). + ### Path Resolution The CLI supports both absolute and relative paths for convenience: @@ -226,12 +309,11 @@ CLI commands use kebab-case instead of underscores: - `xcode_build_and_run` → `build-and-run` - `xcode_health_check` → `health-check` - `xcresult_browse` → `xcresult-browse` -- `find_xcresults` → `find-xcresults` +- `xcode_find_xcresults` → `find-xcresults` ## Available Tools **Project Management:** -- `xcode_open_project` - Open projects and workspaces - `xcode_get_workspace_info` - Get workspace status and details - `xcode_get_projects` - List projects in workspace - `xcode_open_file` - Open files with optional line number @@ -241,7 +323,6 @@ CLI commands use kebab-case instead of underscores: - `xcode_clean` - Clean build artifacts - `xcode_test` - Run tests with optional arguments - `xcode_build_and_run` - Build and run the active scheme -- `xcode_debug` - Start debugging session - `xcode_stop` - Stop current operation **Configuration:** diff --git a/__tests__/cli-parameter-consistency.vitest.test.ts b/__tests__/cli-parameter-consistency.vitest.test.ts index 0768693..c9b2db5 100644 --- a/__tests__/cli-parameter-consistency.vitest.test.ts +++ b/__tests__/cli-parameter-consistency.vitest.test.ts @@ -1,7 +1,16 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeAll } from 'vitest'; import { promisify } from 'util'; const execAsync = promisify(require('child_process').exec); +const PROJECT_ROOT = process.cwd(); +const CLI_BIN = 'node dist/cli.js'; + +beforeAll(async () => { + await execAsync('npm run build', { + cwd: PROJECT_ROOT, + timeout: 30000 + }); +}); describe('CLI Parameter Consistency', () => { it('should accept new kebab-case parameters for all affected commands', async () => { @@ -19,27 +28,29 @@ describe('CLI Parameter Consistency', () => { expectedError: 'Project file does not exist', // Project not found, but parameters accepted }, { - command: 'build-and-run --xcodeproj /fake/project.xcodeproj --scheme TestScheme --command-line-arguments arg1', - expectedError: 'Project file does not exist', // Project not found, but parameters accepted + command: 'build --xcodeproj /fake/project.xcodeproj --scheme TestScheme --reason "Testing CLI locks"', + expectedError: 'Project file does not exist', }, { - command: 'debug --xcodeproj /fake/project.xcodeproj --scheme TestScheme --skip-building', + command: 'build-and-run --xcodeproj /fake/project.xcodeproj --scheme TestScheme --reason "Testing CLI locks" --command-line-arguments arg1', expectedError: 'Project file does not exist', // Project not found, but parameters accepted }, ]; for (const { command, expectedError } of commands) { - const result = await execAsync(`npm run build && node dist/cli.js ${command}`, { - cwd: process.cwd(), + const result = await execAsync(`${CLI_BIN} ${command}`, { + cwd: PROJECT_ROOT, timeout: 10000 }).catch(err => err); + const combined = `${result.stdout ?? ''}${result.stderr ?? ''}`; + // Should not complain about unknown options or missing required parameters - expect(result.stderr).not.toContain('unknown option'); - expect(result.stderr).not.toContain('Missing required parameter'); + expect(combined).not.toContain('unknown option'); + expect(combined).not.toContain('Missing required parameter'); // Should fail with expected error (file/project not found) - expect(result.stderr).toContain(expectedError); + expect(combined).toContain(expectedError); } }, 60000); @@ -49,17 +60,17 @@ describe('CLI Parameter Consistency', () => { 'open-file --lineNumber 10', 'set-active-scheme --schemeName TestScheme', 'test --commandLineArguments arg1', - 'debug --skipBuilding', ]; for (const command of commands) { - const result = await execAsync(`npm run build && node dist/cli.js ${command}`, { - cwd: process.cwd(), + const result = await execAsync(`${CLI_BIN} ${command}`, { + cwd: PROJECT_ROOT, timeout: 10000 }).catch(err => err); // Should show unknown option error - expect(result.stderr).toContain('unknown option'); + const combined = `${result.stdout ?? ''}${result.stderr ?? ''}`; + expect(combined).toContain('unknown option'); } }, 60000); @@ -68,15 +79,15 @@ describe('CLI Parameter Consistency', () => { { command: 'open-file --help', expected: ['--file-path', '--line-number'] }, { command: 'set-active-scheme --help', expected: ['--scheme-name'] }, { command: 'test --help', expected: ['--command-line-arguments'] }, - { command: 'build-and-run --help', expected: ['--command-line-arguments'] }, - { command: 'debug --help', expected: ['--skip-building'] }, + { command: 'build --help', expected: ['--reason'] }, + { command: 'build-and-run --help', expected: ['--reason', '--command-line-arguments'] }, { command: 'xcresult-get-ui-element --help', expected: ['--hierarchy-json-path', '--element-index'] }, { command: 'xcresult-export-attachment --help', expected: ['--attachment-index'] }, ]; for (const { command, expected } of commands) { - const result = await execAsync(`npm run build && node dist/cli.js ${command}`, { - cwd: process.cwd(), + const result = await execAsync(`${CLI_BIN} ${command}`, { + cwd: PROJECT_ROOT, timeout: 10000 }); @@ -94,25 +105,25 @@ describe('CLI Parameter Consistency', () => { it('should reference CLI command names in help text and usage instructions', async () => { // Test that help text references use CLI command names, not internal tool names - const result1 = await execAsync('npm run build && node dist/cli.js xcresult-get-ui-element --help', { - cwd: process.cwd(), + const result1 = await execAsync(`${CLI_BIN} xcresult-get-ui-element --help`, { + cwd: PROJECT_ROOT, timeout: 10000 }); expect(result1.stdout).toContain('xcresult-get-ui-hierarchy'); - expect(result1.stdout).not.toContain('xcresult_get_ui_hierarchy'); + expect(result1.stdout).not.toContain('xcode_xcresult_get_ui_hierarchy'); - const result2 = await execAsync('npm run build && node dist/cli.js xcresult-export-attachment --help', { - cwd: process.cwd(), + const result2 = await execAsync(`${CLI_BIN} xcresult-export-attachment --help`, { + cwd: PROJECT_ROOT, timeout: 10000 }); expect(result2.stdout).toContain('xcresult-list-attachments'); - expect(result2.stdout).not.toContain('xcresult_list_attachments'); + expect(result2.stdout).not.toContain('xcode_xcresult_list_attachments'); // Test find-xcresults usage instructions with a real project - const result3 = await execAsync('npm run build && node dist/cli.js find-xcresults --xcodeproj __tests__/TestApp/TestApp.xcodeproj', { - cwd: process.cwd(), + const result3 = await execAsync(`${CLI_BIN} find-xcresults --xcodeproj __tests__/TestApp/TestApp.xcodeproj`, { + cwd: PROJECT_ROOT, timeout: 10000 }); @@ -125,8 +136,8 @@ describe('CLI Parameter Consistency', () => { // If there are usage instructions, verify they use kebab-case if (hasUsageInstructions) { expect(result3.stdout).toContain('xcresult-browser-get-console --xcresult-path'); - expect(result3.stdout).not.toContain('xcresult_browse'); - expect(result3.stdout).not.toContain('xcresult_browser_get_console'); + expect(result3.stdout).not.toContain('xcode_xcresult_browse'); + expect(result3.stdout).not.toContain('xcode_xcresult_browser_get_console'); } }, 30000); -}); \ No newline at end of file +}); diff --git a/__tests__/cli.integration.vitest.test.ts b/__tests__/cli.integration.vitest.test.ts index 4493aa2..5f623a0 100644 --- a/__tests__/cli.integration.vitest.test.ts +++ b/__tests__/cli.integration.vitest.test.ts @@ -31,7 +31,7 @@ describe('CLI Integration Tests', () => { expect(stdout).toContain('Available tools organized by category:'); expect(stdout).toContain('📁 Project Management:'); - expect(stdout).toContain('open-project'); + expect(stdout).toContain('get-schemes'); expect(stdout).toContain('build'); expect(stdout).toContain('health-check'); }); @@ -106,7 +106,8 @@ describe('CLI Integration Tests', () => { CLI_PATH, 'build', '--xcodeproj', '/non/existent/project.xcodeproj', - '--scheme', 'Test' + '--scheme', 'Test', + '--reason', 'CLI integration test', ]) ).rejects.toMatchObject({ exitCode: 1, @@ -132,8 +133,7 @@ describe('CLI Integration Tests', () => { const { stdout } = await execa('node', [CLI_PATH, 'list-tools']); // Tool names in list-tools output show CLI command names - expect(stdout).toContain('open-project'); - expect(stdout).toContain('close-project'); + expect(stdout).toContain('get-schemes'); expect(stdout).toContain('build'); }); @@ -175,4 +175,4 @@ describe('CLI Exit Codes', () => { const { exitCode } = await execa('node', [CLI_PATH, '--help']); expect(exitCode).toBe(0); }); -}); \ No newline at end of file +}); diff --git a/__tests__/cli.vitest.test.ts b/__tests__/cli.vitest.test.ts index fdfb03a..f8d40f5 100644 --- a/__tests__/cli.vitest.test.ts +++ b/__tests__/cli.vitest.test.ts @@ -14,6 +14,7 @@ vi.mock('commander', async () => { description() { return this; } option() { return this; } command() { return this; } + alias() { return this; } action() { return this; } parseAsync() { return Promise.resolve(); } } @@ -79,8 +80,6 @@ describe('CLI Tool Argument Parsing', () => { describe('CLI Tool Registration', () => { it('should register hardcoded tools as commands', async () => { // The CLI currently has hardcoded tools: - // - xcode_open_project -> open-project - // - xcode_close_project -> close-project // - xcode_build -> build // - xcode_health_check -> health-check // This is a temporary solution as noted in the CLI code @@ -94,4 +93,4 @@ describe('CLI Help System', () => { // The CLI shows help for its hardcoded tools await expect(main()).resolves.toBeUndefined(); }); -}); \ No newline at end of file +}); diff --git a/__tests__/environment-validator.vitest.test.ts b/__tests__/environment-validator.vitest.test.ts index d4e9453..31d9865 100644 --- a/__tests__/environment-validator.vitest.test.ts +++ b/__tests__/environment-validator.vitest.test.ts @@ -7,7 +7,14 @@ import type { EnvironmentValidation, EnvironmentValidationResult } from '../src/ // Mock the child_process module vi.mock('child_process', () => ({ - spawn: vi.fn() + spawn: vi.fn(), + execFile: vi.fn((...args: any[]) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); // Mock fs module @@ -578,4 +585,4 @@ function createMockProcess(stdout: string, stderr: string, exitCode: number, del }); return mockProcess; -} \ No newline at end of file +} diff --git a/__tests__/functional.test.js b/__tests__/functional.test.js index 098776c..b67403e 100644 --- a/__tests__/functional.test.js +++ b/__tests__/functional.test.js @@ -27,25 +27,24 @@ describe('XcodeMCP Server Functional Tests', () => { test('should validate tool definitions completeness', () => { // Test the tool definitions without instantiating the class const expectedTools = [ - 'xcode_open_project', - 'xcode_build', + 'xcode_build', 'xcode_clean', 'xcode_test', - 'xcode_run', - 'xcode_debug', + 'xcode_build_and_run', + 'xcode_release_lock', 'xcode_stop', 'xcode_get_schemes', 'xcode_get_run_destinations', 'xcode_set_active_scheme', 'xcode_get_workspace_info', 'xcode_get_projects', - 'xcode_open_file' + 'xcode_open_file', ]; - expect(expectedTools.length).toBe(13); + expect(expectedTools.length).toBe(12); expect(expectedTools).toContain('xcode_build'); expect(expectedTools).toContain('xcode_test'); - expect(expectedTools).toContain('xcode_run'); + expect(expectedTools).toContain('xcode_release_lock'); }); test('should validate JXA script generation patterns', () => { @@ -80,4 +79,4 @@ describe('XcodeMCP Server Functional Tests', () => { expect(xcodeFeatures.length).toBeGreaterThan(5); }); -}); \ No newline at end of file +}); diff --git a/__tests__/functional.vitest.test.js b/__tests__/functional.vitest.test.js index a82111c..70e79e8 100644 --- a/__tests__/functional.vitest.test.js +++ b/__tests__/functional.vitest.test.js @@ -27,25 +27,38 @@ describe('XcodeMCP Server Functional Tests', () => { test('should validate tool definitions completeness', () => { // Test the tool definitions without instantiating the class const expectedTools = [ - 'xcode_open_project', - 'xcode_build', + 'xcode_build', 'xcode_clean', 'xcode_test', 'xcode_build_and_run', - 'xcode_debug', + 'xcode_release_lock', 'xcode_stop', 'xcode_get_schemes', 'xcode_get_run_destinations', 'xcode_set_active_scheme', 'xcode_get_workspace_info', 'xcode_get_projects', - 'xcode_open_file' + 'xcode_open_file', ]; - expect(expectedTools.length).toBe(13); + expect(expectedTools.length).toBe(12); expect(expectedTools).toContain('xcode_build'); expect(expectedTools).toContain('xcode_test'); - expect(expectedTools).toContain('xcode_build_and_run'); + expect(expectedTools).toContain('xcode_release_lock'); + }); + + test('health check output includes version metadata', async () => { + const { XcodeServer } = await import('../dist/XcodeServer.js'); + const server = new XcodeServer({ includeClean: true }); + const result = await server.callToolDirect('xcode_health_check', {}); + + const aggregated = (result.content ?? []) + .filter(item => item.type === 'text') + .map(item => item.text) + .join('\n'); + + expect(aggregated).toContain('XcodeMCP Version Information'); + expect(aggregated).toMatch(/Package version:\s+.+/); }); test('should validate JXA script generation patterns', () => { @@ -80,4 +93,4 @@ describe('XcodeMCP Server Functional Tests', () => { expect(xcodeFeatures.length).toBeGreaterThan(5); }); -}); \ No newline at end of file +}); diff --git a/__tests__/integration.test.js b/__tests__/integration.test.js index 3f56e4d..d54d349 100644 --- a/__tests__/integration.test.js +++ b/__tests__/integration.test.js @@ -124,7 +124,7 @@ describeIfXcode('Integration Tests', () => { mockSpawn.mockReturnValue(mockProcess); // Step 3: Build project - const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); + const buildPromise = server.build('/Users/test/TestApp.xcodeproj', 'Debug', null, 'Integration workflow'); setTimeout(() => { mockProcess.stdout.emit('data', '/Users/test/TestApp.xcodeproj\n'); mockProcess.emit('close', 0); @@ -191,7 +191,7 @@ describeIfXcode('Integration Tests', () => { test('should handle Xcode not running error', async () => { const server = new XcodeMCPServer(); - const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); + const buildPromise = server.build('/Users/test/TestApp.xcodeproj', 'Debug', null, 'Integration workflow'); setTimeout(() => { mockProcess.stderr.emit('data', 'Error: Application "Xcode" is not running\n'); mockProcess.emit('close', 1); @@ -229,7 +229,7 @@ describeIfXcode('Integration Tests', () => { test('should handle slow JXA execution', async () => { const server = new XcodeMCPServer(); - const buildPromise = server.build('/Users/test/TestApp.xcodeproj'); + const buildPromise = server.build('/Users/test/TestApp.xcodeproj', 'Debug', null, 'Integration workflow'); // Simulate slow response setTimeout(() => { @@ -343,4 +343,4 @@ describeIfXcode('Integration Tests', () => { expect(script).toContain(lineNumber.toString()); }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/mcp-handlers.test.js b/__tests__/mcp-handlers.test.js index 62e1c32..e5b75ba 100644 --- a/__tests__/mcp-handlers.test.js +++ b/__tests__/mcp-handlers.test.js @@ -32,7 +32,14 @@ jest.mock('@modelcontextprotocol/sdk/types.js', () => ({ // Mock child_process const mockSpawn = jest.fn(); jest.mock('child_process', () => ({ - spawn: mockSpawn + spawn: mockSpawn, + execFile: jest.fn((...args) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); // Suppress console.error during tests @@ -88,22 +95,22 @@ describeIfXcode('MCP Tool Handlers', () => { // Check for essential tools const toolNames = result.tools.map(tool => tool.name); - expect(toolNames).toContain('xcode_open_project'); expect(toolNames).toContain('xcode_build'); expect(toolNames).toContain('xcode_test'); expect(toolNames).toContain('xcode_build_and_run'); - expect(toolNames).toContain('xcode_debug'); + expect(toolNames).toContain('xcode_release_lock'); }); test('should include proper tool schemas', async () => { const result = await listToolsHandler(); - const openProjectTool = result.tools.find(tool => tool.name === 'xcode_open_project'); - expect(openProjectTool).toHaveProperty('description'); - expect(openProjectTool).toHaveProperty('inputSchema'); - expect(openProjectTool.inputSchema).toHaveProperty('properties'); - expect(openProjectTool.inputSchema.properties).toHaveProperty('path'); - expect(openProjectTool.inputSchema.required).toContain('path'); + const buildTool = result.tools.find(tool => tool.name === 'xcode_build'); + expect(buildTool).toHaveProperty('description'); + expect(buildTool).toHaveProperty('inputSchema'); + expect(buildTool.inputSchema).toHaveProperty('properties'); + expect(buildTool.inputSchema.properties).toHaveProperty('xcodeproj'); + expect(buildTool.inputSchema.properties).toHaveProperty('scheme'); + expect(buildTool.inputSchema.required).toEqual(expect.arrayContaining(['reason', 'xcodeproj', 'scheme'])); }); test('should include optional parameter tools', async () => { @@ -113,9 +120,8 @@ describeIfXcode('MCP Tool Handlers', () => { expect(testTool.inputSchema.properties).toHaveProperty('commandLineArguments'); expect(testTool.inputSchema.properties.commandLineArguments.type).toBe('array'); - const debugTool = result.tools.find(tool => tool.name === 'xcode_debug'); - expect(debugTool.inputSchema.properties).toHaveProperty('scheme'); - expect(debugTool.inputSchema.properties).toHaveProperty('skipBuilding'); + const runTool = result.tools.find(tool => tool.name === 'xcode_build_and_run'); + expect(runTool.inputSchema.properties).toHaveProperty('command_line_arguments'); }); }); @@ -162,23 +168,19 @@ describeIfXcode('MCP Tool Handlers', () => { } }); - test('should handle tools with parameters correctly', async () => { - const mockExecuteJXA = jest.fn().mockResolvedValue('Project opened successfully'); - server.executeJXA = mockExecuteJXA; - + test('should handle health check execution without parameters', async () => { const request = { params: { - name: 'xcode_open_project', - arguments: { - path: '/Users/test/TestProject.xcodeproj' - } + name: 'xcode_health_check', + arguments: {} } }; const result = await callToolHandler(request); - expect(result.content[0].text).toBe('Project opened successfully'); - expect(mockExecuteJXA).toHaveBeenCalled(); + expect(result).toHaveProperty('content'); + const textBlocks = result.content.filter(item => item.type === 'text'); + expect(textBlocks.length).toBeGreaterThan(0); }); test('should handle array parameters', async () => { @@ -200,26 +202,6 @@ describeIfXcode('MCP Tool Handlers', () => { expect(mockExecuteJXA).toHaveBeenCalled(); }); - test('should handle optional parameters', async () => { - const mockExecuteJXA = jest.fn().mockResolvedValue('Debug started. Result ID: debug-456'); - server.executeJXA = mockExecuteJXA; - - const request = { - params: { - name: 'xcode_debug', - arguments: { - scheme: 'TestScheme', - skipBuilding: true - } - } - }; - - const result = await callToolHandler(request); - - expect(result.content[0].text).toBe('Debug started. Result ID: debug-456'); - expect(mockExecuteJXA).toHaveBeenCalled(); - }); - test('should propagate execution errors', async () => { const mockExecuteJXA = jest.fn().mockRejectedValue(new Error('JXA execution failed')); server.executeJXA = mockExecuteJXA; @@ -237,18 +219,16 @@ describeIfXcode('MCP Tool Handlers', () => { describe('Tool Input Validation', () => { test('should handle missing required parameters gracefully', async () => { - const mockExecuteJXA = jest.fn(); - server.executeJXA = mockExecuteJXA; + server.validateToolOperation = jest.fn().mockResolvedValue(null); const request = { params: { - name: 'xcode_open_project', - arguments: {} // Missing required 'path' parameter + name: 'xcode_build', + arguments: {} // Missing required xcodeproj and scheme } }; - // The method should handle undefined gracefully - await expect(callToolHandler(request)).rejects.toThrow(); + await expect(callToolHandler(request)).rejects.toThrow('Missing required parameter: xcodeproj'); }); test('should handle undefined optional parameters', async () => { @@ -268,21 +248,5 @@ describeIfXcode('MCP Tool Handlers', () => { expect(result).toBeDefined(); }); - test('should handle boolean parameters correctly', async () => { - const mockExecuteJXA = jest.fn().mockResolvedValue('Debug started'); - server.executeJXA = mockExecuteJXA; - - const request = { - params: { - name: 'xcode_debug', - arguments: { - skipBuilding: false - } - } - }; - - const result = await callToolHandler(request); - expect(result).toBeDefined(); - }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/parameter-mismatch-prevention.vitest.test.js b/__tests__/parameter-mismatch-prevention.vitest.test.js index 74de122..28003c4 100644 --- a/__tests__/parameter-mismatch-prevention.vitest.test.js +++ b/__tests__/parameter-mismatch-prevention.vitest.test.js @@ -73,7 +73,6 @@ describe('Parameter Mismatch Prevention', () => { { tool: 'xcode_clean', method: 'clean', args: ['args.path'] }, { tool: 'xcode_test', method: 'test', args: ['args.path', 'args.commandLineArguments'] }, { tool: 'xcode_build_and_run', method: 'run', args: ['args.path', 'args.commandLineArguments'] }, - { tool: 'xcode_debug', method: 'debug', args: ['args.path', 'args.scheme', 'args.skipBuilding'] }, { tool: 'xcode_stop', method: 'stop', args: ['args.xcodeproj'] }, { tool: 'xcode_get_schemes', method: 'getSchemes', args: ['args.path'] }, { tool: 'xcode_set_active_scheme', method: 'setActiveScheme', args: ['args.path', 'args.schemeName'] }, @@ -116,4 +115,4 @@ describe('Parameter Mismatch Prevention', () => { expect(functionString).not.toContain('actualProjectPath'); }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/preferred-values.vitest.test.ts b/__tests__/preferred-values.vitest.test.ts index 16f45e1..b6519ab 100644 --- a/__tests__/preferred-values.vitest.test.ts +++ b/__tests__/preferred-values.vitest.test.ts @@ -12,6 +12,7 @@ describe('Preferred Values', () => { const buildTool = tools.find(t => t.name === 'xcode_build'); expect(buildTool).toBeDefined(); expect(buildTool!.inputSchema.required).not.toContain('xcodeproj'); + expect(buildTool!.inputSchema.required).toContain('reason'); expect(buildTool!.inputSchema.properties.xcodeproj.description).toContain('defaults to MyApp.xcodeproj'); }); @@ -23,6 +24,7 @@ describe('Preferred Values', () => { const buildTool = tools.find(t => t.name === 'xcode_build'); expect(buildTool).toBeDefined(); expect(buildTool!.inputSchema.required).not.toContain('scheme'); + expect(buildTool!.inputSchema.required).toContain('reason'); expect(buildTool!.inputSchema.properties.scheme.description).toContain('defaults to MyAppScheme'); }); @@ -34,7 +36,7 @@ describe('Preferred Values', () => { const buildTool = tools.find(t => t.name === 'xcode_build'); expect(buildTool).toBeDefined(); - expect(buildTool!.inputSchema.required).toEqual([]); + expect(buildTool!.inputSchema.required).toEqual(['reason']); expect(buildTool!.inputSchema.properties.xcodeproj.description).toContain('defaults to MyApp.xcodeproj'); expect(buildTool!.inputSchema.properties.scheme.description).toContain('defaults to MyAppScheme'); }); @@ -44,6 +46,7 @@ describe('Preferred Values', () => { const buildTool = tools.find(t => t.name === 'xcode_build'); expect(buildTool).toBeDefined(); + expect(buildTool!.inputSchema.required).toContain('reason'); expect(buildTool!.inputSchema.required).toContain('xcodeproj'); expect(buildTool!.inputSchema.required).toContain('scheme'); expect(buildTool!.inputSchema.properties.xcodeproj.description).not.toContain('defaults to'); @@ -56,16 +59,14 @@ describe('Preferred Values', () => { }); const toolsWithXcodeproj = [ - 'xcode_open_project', - 'xcode_close_project', 'xcode_build', 'xcode_get_schemes', 'xcode_set_active_scheme', 'xcode_test', 'xcode_build_and_run', - 'xcode_debug', + 'xcode_release_lock', 'xcode_stop', - 'find_xcresults', + 'xcode_find_xcresults', 'xcode_get_run_destinations', 'xcode_get_workspace_info', 'xcode_get_projects' @@ -160,17 +161,17 @@ describe('Preferred Values', () => { // No preferred values - both required let tools = getToolDefinitions({}); let buildTool = tools.find(t => t.name === 'xcode_build'); - expect(buildTool!.inputSchema.required).toEqual(['xcodeproj', 'scheme']); + expect(buildTool!.inputSchema.required).toEqual(['reason', 'xcodeproj', 'scheme']); // Only preferred xcodeproj - scheme still required tools = getToolDefinitions({ preferredXcodeproj: 'MyApp.xcodeproj' }); buildTool = tools.find(t => t.name === 'xcode_build'); - expect(buildTool!.inputSchema.required).toEqual(['scheme']); + expect(buildTool!.inputSchema.required).toEqual(['reason', 'scheme']); // Only preferred scheme - xcodeproj still required tools = getToolDefinitions({ preferredScheme: 'MyScheme' }); buildTool = tools.find(t => t.name === 'xcode_build'); - expect(buildTool!.inputSchema.required).toEqual(['xcodeproj']); + expect(buildTool!.inputSchema.required).toEqual(['reason', 'xcodeproj']); // Both preferred - nothing required tools = getToolDefinitions({ @@ -178,22 +179,22 @@ describe('Preferred Values', () => { preferredScheme: 'MyScheme' }); buildTool = tools.find(t => t.name === 'xcode_build'); - expect(buildTool!.inputSchema.required).toEqual([]); + expect(buildTool!.inputSchema.required).toEqual(['reason']); }); it('should correctly build required array for xcode_test', () => { - // No preferred values - xcodeproj and destination required + // No preferred values - xcodeproj and scheme required let tools = getToolDefinitions({}); let testTool = tools.find(t => t.name === 'xcode_test'); - expect(testTool!.inputSchema.required).toEqual(['xcodeproj', 'destination']); + expect(testTool!.inputSchema.required).toEqual(['xcodeproj', 'scheme']); - // With preferred xcodeproj - only destination required + // With preferred xcodeproj - only scheme required tools = getToolDefinitions({ preferredXcodeproj: 'MyApp.xcodeproj' }); testTool = tools.find(t => t.name === 'xcode_test'); - expect(testTool!.inputSchema.required).toEqual(['destination']); + expect(testTool!.inputSchema.required).toEqual(['scheme']); }); - it('should correctly handle xcode_set_active_scheme', () => { + it('should correctly handle xcode_set_active_scheme', () => { // No preferred values - both required let tools = getToolDefinitions({}); let schemeTool = tools.find(t => t.name === 'xcode_set_active_scheme'); @@ -205,4 +206,4 @@ describe('Preferred Values', () => { expect(schemeTool!.inputSchema.required).toEqual(['scheme_name']); }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/server-cli-args.vitest.test.ts b/__tests__/server-cli-args.vitest.test.ts new file mode 100644 index 0000000..f156bd9 --- /dev/null +++ b/__tests__/server-cli-args.vitest.test.ts @@ -0,0 +1,31 @@ +import { beforeAll, describe, expect, it } from 'vitest'; + +let findUnsupportedServerArgs: (args: string[]) => string[]; + +beforeAll(async () => { + process.env.NODE_ENV = 'test'; + ({ findUnsupportedServerArgs } = await import('../src/index.js')); +}); + +describe('xcodemcp CLI argument validation', () => { + it('allows supported flags and prefixes', () => { + const args = [ + '--no-clean', + '--preferred-scheme=ManabiReader', + '--preferred-xcodeproj=/tmp/ManabiReader.xcodeproj', + '--port=8080' + ]; + + expect(findUnsupportedServerArgs(args)).toEqual([]); + }); + + it('flags positional commands as unsupported', () => { + const args = ['build', 'test']; + expect(findUnsupportedServerArgs(args)).toEqual(args); + }); + + it('flags a mix of unsupported and supported args', () => { + const args = ['--no-clean', '--preferred-scheme=Foo', 'build']; + expect(findUnsupportedServerArgs(args)).toEqual(['build']); + }); +}); diff --git a/__tests__/setup.ts b/__tests__/setup.ts index 0397591..01e1065 100644 --- a/__tests__/setup.ts +++ b/__tests__/setup.ts @@ -71,10 +71,6 @@ vi.mock('../src/utils/EnvironmentValidator.js', () => { }); // Mock external dependencies that we don't want to actually call in tests -vi.mock('child_process', () => ({ - spawn: vi.fn() -})); - vi.mock('fs', () => ({ existsSync: vi.fn().mockImplementation((path: string) => { // Only return true for specific test files that are supposed to exist @@ -118,70 +114,80 @@ vi.mock('fs/promises', () => ({ // Mock path validation with proper validation logic vi.mock('../src/utils/PathValidator.js', () => { + const mockPathValidator = { + validateProjectPath: vi.fn().mockImplementation((path: string) => { + if ( + !path || + path === '' || + path.includes('/invalid/') || + path.endsWith('.txt') || + path.includes('/nonexistent/') + ) { + return { + content: [ + { + type: 'text', + text: 'Invalid project path. Must be a .xcodeproj or .xcworkspace file.', + }, + ], + }; + } + return null; + }), + validateFilePath: vi.fn().mockImplementation((path: string) => { + if ( + !path || + path === '' || + path.includes('/invalid/') || + path.endsWith('.txt') || + path.includes('/nonexistent/') + ) { + return { + content: [ + { + type: 'text', + text: 'Invalid project path. Must be a .xcodeproj or .xcworkspace file.', + }, + ], + }; + } + return null; + }), + }; return { - PathValidator: { - validateProjectPath: vi.fn().mockImplementation((path: string) => { - // Return error for invalid paths - if (!path || path === '' || - path.includes('/invalid/') || - path.endsWith('.txt') || - path.includes('/nonexistent/')) { - return { - content: [{ - type: 'text', - text: 'Invalid project path. Must be a .xcodeproj or .xcworkspace file.' - }] - }; - } - // Return null for valid paths - return null; - }), - validateFilePath: vi.fn().mockImplementation((path: string) => { - // Return error for invalid file paths - if (!path || path === '' || - path.includes('/invalid/') || - path.endsWith('.txt') || - path.includes('/nonexistent/')) { - return { - content: [{ - type: 'text', - text: 'Invalid project path. Must be a .xcodeproj or .xcworkspace file.' - }] - }; - } - // Return null for valid paths - return null; - }) - } + PathValidator: mockPathValidator, + default: mockPathValidator, }; }); // Mock BuildLogParser vi.mock('../src/utils/BuildLogParser.js', () => { - return { - BuildLogParser: { - parseBuildOutput: vi.fn().mockResolvedValue({ - summary: { warnings: 0, errors: 0, tests: 0, passed: 0, failed: 0 }, - issues: [] - }), - getLatestBuildLog: vi.fn().mockResolvedValue({ + const mockBuildLogParser = { + parseBuildOutput: vi.fn().mockResolvedValue({ + summary: { warnings: 0, errors: 0, tests: 0, passed: 0, failed: 0 }, + issues: [], + }), + getLatestBuildLog: vi.fn().mockResolvedValue({ + path: '/mock/path/to/build.log', + mtime: new Date(Date.now() + 1000), + }), + findProjectDerivedData: vi.fn().mockResolvedValue('/mock/derived/data'), + parseBuildLog: vi.fn().mockResolvedValue({ + summary: { warnings: 0, errors: 0, tests: 0, passed: 0, failed: 0 }, + issues: [], + errors: [], + warnings: [], + }), + getRecentBuildLogs: vi.fn().mockResolvedValue([ + { path: '/mock/path/to/build.log', - mtime: new Date(Date.now() + 1000) // Always newer than build start time - }), - findProjectDerivedData: vi.fn().mockResolvedValue('/mock/derived/data'), - parseBuildLog: vi.fn().mockResolvedValue({ - summary: { warnings: 0, errors: 0, tests: 0, passed: 0, failed: 0 }, - issues: [], - errors: [], // Important: empty errors array so parse is considered successful - warnings: [] - }), - getRecentBuildLogs: vi.fn().mockResolvedValue([ - { - path: '/mock/path/to/build.log', - mtime: new Date(Date.now() + 1000) - } - ]) - } + mtime: new Date(Date.now() + 1000), + }, + ]), + }; + return { + BuildLogParser: mockBuildLogParser, + default: mockBuildLogParser, }; }); @@ -190,8 +196,7 @@ let isLargeOutputTest = false; // Mock XCResultTool command execution - simplified for now vi.mock('../src/tools/XCResultTools.js', () => { - return { - XCResultTools: { + const mockXCResultTools = { getBrowseHandler: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: 'Mock XCResult data' }] }), getConsoleHandler: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: 'Mock console output' }] }), getScreenshotHandler: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: 'Mock screenshot' }] }), @@ -287,7 +292,10 @@ vi.mock('../src/tools/XCResultTools.js', () => { } return { content: [{ type: 'text', text }] }; }) - } + }; + return { + XCResultTools: mockXCResultTools, + default: mockXCResultTools, }; }); @@ -302,6 +310,10 @@ vi.mock('../src/utils/ParameterNormalizer.js', () => { return name; }), normalizeDestinationName: vi.fn().mockImplementation((dest: string) => dest || 'Default Destination'), + getDestinationNameCandidates: vi.fn().mockImplementation((dest: string) => { + const normalized = dest || 'Default Destination'; + return [normalized]; + }), findBestMatch: vi.fn().mockImplementation((input: string, options: string[]) => { if (!input || !options.length) return null; return options.find(opt => opt.toLowerCase().includes(input.toLowerCase())) || options[0]; @@ -312,15 +324,17 @@ vi.mock('../src/utils/ParameterNormalizer.js', () => { // Mock ErrorHelper vi.mock('../src/utils/ErrorHelper.js', () => { + const mockErrorHelper = { + parseCommonErrors: vi.fn().mockReturnValue(null), + createErrorWithGuidance: vi.fn().mockImplementation( + (error: string, guidance?: string) => `${error}${guidance ? '\n\n' + guidance : ''}`, + ), + getSchemeNotFoundGuidance: vi.fn().mockReturnValue('Try checking available schemes'), + getDestinationNotFoundGuidance: vi.fn().mockReturnValue('Try checking available destinations'), + }; return { - ErrorHelper: { - parseCommonErrors: vi.fn().mockReturnValue(null), - createErrorWithGuidance: vi.fn().mockImplementation((error: string, guidance?: string) => - `${error}${guidance ? '\n\n' + guidance : ''}` - ), - getSchemeNotFoundGuidance: vi.fn().mockReturnValue('Try checking available schemes'), - getDestinationNotFoundGuidance: vi.fn().mockReturnValue('Try checking available destinations') - } + ErrorHelper: mockErrorHelper, + default: mockErrorHelper, }; }); @@ -337,4 +351,4 @@ vi.setConfig({ // Export the global JXA mock for use in tests export function getGlobalJXAMock() { return globalJXAMock; -} \ No newline at end of file +} diff --git a/__tests__/simple-unit.test.js b/__tests__/simple-unit.test.js index dd45fce..ec2dce4 100644 --- a/__tests__/simple-unit.test.js +++ b/__tests__/simple-unit.test.js @@ -3,10 +3,20 @@ import { jest } from '@jest/globals'; // Mock dependencies jest.mock('child_process', () => ({ spawn: jest.fn().mockReturnValue({ - stdout: { on: jest.fn() }, - stderr: { on: jest.fn() }, - on: jest.fn() - }) + stdout: { on: jest.fn(), pipe: jest.fn() }, + stderr: { on: jest.fn(), pipe: jest.fn() }, + on: jest.fn(), + kill: jest.fn(), + exitCode: null, + killed: false, + }), + execFile: jest.fn((...args) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ @@ -91,7 +101,7 @@ describe('XcodeMCPServer Basic Tests', () => { // Test that methods exist and can be called (though they will fail without proper mocking) expect(() => server.openProject('/test/path')).not.toThrow(); - expect(() => server.build()).not.toThrow(); + expect(() => server.build('/test/path', 'Debug', null, 'Simple unit smoke')).not.toThrow(); expect(() => server.clean()).not.toThrow(); }); -}); \ No newline at end of file +}); diff --git a/__tests__/simple-unit.vitest.test.js b/__tests__/simple-unit.vitest.test.js index ea29aa8..12eb398 100644 --- a/__tests__/simple-unit.vitest.test.js +++ b/__tests__/simple-unit.vitest.test.js @@ -3,10 +3,20 @@ import { vi, describe, test, expect, beforeAll, afterAll } from 'vitest'; // Mock dependencies vi.mock('child_process', () => ({ spawn: vi.fn().mockReturnValue({ - stdout: { on: vi.fn() }, - stderr: { on: vi.fn() }, - on: vi.fn() - }) + stdout: { on: vi.fn(), pipe: vi.fn() }, + stderr: { on: vi.fn(), pipe: vi.fn() }, + on: vi.fn(), + kill: vi.fn(), + exitCode: null, + killed: false, + }), + execFile: vi.fn((...args) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); // Mock filesystem operations @@ -69,13 +79,10 @@ describe('XcodeMCPServer Basic Tests', () => { // List of required tools that should be available const requiredTools = [ - 'xcode_open_project', - 'xcode_close_project', 'xcode_build', 'xcode_clean', 'xcode_test', 'xcode_build_and_run', - 'xcode_debug', 'xcode_stop', 'xcode_get_schemes', 'xcode_set_active_scheme', @@ -84,15 +91,15 @@ describe('XcodeMCPServer Basic Tests', () => { 'xcode_get_projects', 'xcode_open_file', 'xcode_health_check', - 'find_xcresults', - 'xcresult_browse', - 'xcresult_browser_get_console', - 'xcresult_summary', - 'xcresult_get_screenshot', - 'xcresult_get_ui_hierarchy', - 'xcresult_get_ui_element', - 'xcresult_list_attachments', - 'xcresult_export_attachment' + 'xcode_find_xcresults', + 'xcode_xcresult_browse', + 'xcode_xcresult_browser_get_console', + 'xcode_xcresult_summary', + 'xcode_xcresult_get_screenshot', + 'xcode_xcresult_get_ui_hierarchy', + 'xcode_xcresult_get_ui_element', + 'xcode_xcresult_list_attachments', + 'xcode_xcresult_export_attachment' ]; // Verify the server was created and has the expected MCP server instance @@ -217,4 +224,4 @@ describe('XcodeMCPServer Basic Tests', () => { expect(result.content[0]).toHaveProperty('type', 'text'); expect(result.content[0]).toHaveProperty('text', '{"mockResult": "success"}'); }); -}); \ No newline at end of file +}); diff --git a/__tests__/simple-unit.vitest.test.ts b/__tests__/simple-unit.vitest.test.ts index e6b7a09..7e9ec98 100644 --- a/__tests__/simple-unit.vitest.test.ts +++ b/__tests__/simple-unit.vitest.test.ts @@ -3,10 +3,20 @@ import { vi, describe, test, expect, beforeAll, afterAll } from 'vitest'; // Mock dependencies vi.mock('child_process', () => ({ spawn: vi.fn().mockReturnValue({ - stdout: { on: vi.fn() }, - stderr: { on: vi.fn() }, - on: vi.fn() - }) + stdout: { on: vi.fn(), pipe: vi.fn() }, + stderr: { on: vi.fn(), pipe: vi.fn() }, + on: vi.fn(), + kill: vi.fn(), + exitCode: null, + killed: false, + }), + execFile: vi.fn((...args: any[]) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ @@ -77,4 +87,4 @@ describe('XcodeMCPServer Basic Tests', () => { // Test that methods exist and can be called (though they will fail without proper mocking) expect(() => server.openProject('/test/path')).not.toThrow(); }); -}); \ No newline at end of file +}); diff --git a/__tests__/simulator-tools.vitest.test.ts b/__tests__/simulator-tools.vitest.test.ts new file mode 100644 index 0000000..34c566f --- /dev/null +++ b/__tests__/simulator-tools.vitest.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { XcodeServer } from '../src/XcodeServer.js'; +import { SimulatorTools } from '../src/tools/SimulatorTools.js'; +import { SimulatorUiTools, resetAxeCacheForTesting } from '../src/tools/SimulatorUiTools.js'; +import * as fsPromises from 'fs/promises'; + +describe('Simulator tool integration', () => { + let server: XcodeServer; + + beforeEach(() => { + server = new XcodeServer(); + vi.spyOn(server, 'validateToolOperation').mockResolvedValue(null); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('returns validation error when boot_sim is missing simulator_uuid', async () => { + const result = await server.callToolDirect('xcode_boot_sim', {}); + const text = result.content?.[0]?.type === 'text' ? result.content[0].text : ''; + expect(text).toContain('simulator_uuid'); + expect(result.isError ?? false).toBeFalsy(); + }); + + it('delegates boot_sim to SimulatorTools', async () => { + const mockResult = { content: [{ type: 'text', text: 'booted' }] } as const; + vi.spyOn(SimulatorTools, 'bootSimulator').mockResolvedValue(mockResult as any); + const result = await server.callToolDirect('xcode_boot_sim', { simulator_uuid: 'ABCDE' }); + expect(SimulatorTools.bootSimulator).toHaveBeenCalledWith('ABCDE'); + expect(result.content?.[0]?.type).toBe('text'); + expect(result.content?.[0]?.text).toBe('booted'); + }); + + it('surfaces helpful message when AXe is unavailable', async () => { + const originalAxePath = process.env.XCODEMCP_AXE_PATH; + const originalPathEnv = process.env.PATH; + process.env.XCODEMCP_AXE_PATH = '/nonexistent/axe'; + process.env.PATH = ''; + resetAxeCacheForTesting(); + const accessSpy = vi.spyOn(fsPromises, 'access').mockRejectedValue(new Error('ENOENT')); + const response = await SimulatorUiTools.describeUI('00000000-0000-0000-0000-000000000000'); + const text = response.content?.[0]?.type === 'text' ? response.content[0].text : ''; + expect(text).toContain('AXe binary'); + accessSpy.mockRestore(); + process.env.XCODEMCP_AXE_PATH = originalAxePath; + process.env.PATH = originalPathEnv; + resetAxeCacheForTesting(); + }); +}); diff --git a/__tests__/tools/BuildTools.vitest.test.ts b/__tests__/tools/BuildTools.vitest.test.ts new file mode 100644 index 0000000..82a7116 --- /dev/null +++ b/__tests__/tools/BuildTools.vitest.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest'; +import { BuildTools } from '../../src/tools/BuildTools.js'; + +describe('BuildTools private helpers', () => { + describe('_detectSimulatorCloneFailure', () => { + it('detects simulator clone failures and returns device name', () => { + const output = "Test target ManabiReaderTests encountered an error (Failed to clone device named 'iPhone 16e (26.0)'. (Underlying Error: The operation couldn’t be completed. Device was allocated but was stuck in creation state. Check CoreSimulator.log for more information.))"; + const result = (BuildTools as any)._detectSimulatorCloneFailure(output); + expect(result).toEqual({ matched: true, deviceName: 'iPhone 16e (26.0)' }); + }); + + it('returns matched=false when message is absent', () => { + const result = (BuildTools as any)._detectSimulatorCloneFailure('All tests passed.'); + expect(result).toEqual({ matched: false }); + }); + }); + + describe('_hasArgument', () => { + it('detects arguments provided as separate tokens', () => { + const args = ['-parallel-testing-enabled', 'NO', '-only-testing:Target/Test']; + expect((BuildTools as any)._hasArgument(args, '-parallel-testing-enabled')).toBe(true); + expect((BuildTools as any)._hasArgument(args, '-maximum-concurrent-test-simulator-destinations')).toBe(false); + }); + + it('detects arguments provided as single token with value', () => { + const args = ['-maximum-concurrent-test-simulator-destinations 2']; + expect((BuildTools as any)._hasArgument(args, '-maximum-concurrent-test-simulator-destinations')).toBe(true); + }); + }); +}); diff --git a/__tests__/tools/ProjectTools.vitest.test.ts b/__tests__/tools/ProjectTools.vitest.test.ts index 8d29668..abd0965 100644 --- a/__tests__/tools/ProjectTools.vitest.test.ts +++ b/__tests__/tools/ProjectTools.vitest.test.ts @@ -12,19 +12,31 @@ vi.mock('child_process', () => ({ if (event === 'data') { callback('/Applications/Xcode.app/Contents/Developer\n'); } - }) + }), + pipe: vi.fn(), }, stderr: { - on: vi.fn() + on: vi.fn(), + pipe: vi.fn(), }, on: vi.fn((event, callback) => { if (event === 'close') { callback(0); } - }) + }), + kill: vi.fn(), + exitCode: null, + killed: false, }; return mockProcess; - }) + }), + execFile: vi.fn((...args: any[]) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); // Import the tools to test @@ -324,4 +336,4 @@ describe('ProjectTools', () => { expect(state.activeProject).toBe('/Users/test/Dual/Dual.xcworkspace'); }); }); -}); \ No newline at end of file +}); diff --git a/__tests__/utils/LockManager.vitest.test.ts b/__tests__/utils/LockManager.vitest.test.ts new file mode 100644 index 0000000..25ae2b6 --- /dev/null +++ b/__tests__/utils/LockManager.vitest.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtempSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { rm } from 'fs/promises'; +import LockManager from '../../src/utils/LockManager.js'; + +describe('LockManager', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), 'xcodemcp-locks-')); + LockManager.setCustomLockDirectory(tempDir); + }); + + afterEach(async () => { + await rm(tempDir, { recursive: true, force: true }); + LockManager.setCustomLockDirectory(null); + }); + + it('acquires and releases a lock with metadata', async () => { + const projectPath = '/tmp/FakeApp.xcodeproj'; + const acquisition = await LockManager.acquireLock(projectPath, 'LockManager test', 'xcode_build'); + expect(acquisition.lock.path).toBe(projectPath); + expect(acquisition.lock.reason).toBe('LockManager test'); + expect(acquisition.lock.queueDepth).toBe(1); + expect(acquisition.footerText).toContain('release-lock'); + + const release = await LockManager.releaseLock(projectPath); + expect(release.released).toBe(true); + expect(release.info?.reason).toBe('LockManager test'); + }); + + it('records blocker info when waiting for an existing lock', async () => { + const projectPath = '/tmp/FakeAppWait.xcodeproj'; + await LockManager.acquireLock(projectPath, 'First worker', 'xcode_build'); + + const waitingPromise = LockManager.acquireLock(projectPath, 'Second worker', 'xcode_build'); + + await new Promise(resolve => setTimeout(resolve, 50)); + await LockManager.releaseLock(projectPath); + + const secondLock = await waitingPromise; + expect(secondLock.blockedBy?.reason).toBe('First worker'); + expect(secondLock.footerText).toContain('Waited'); + + await LockManager.releaseLock(projectPath); + }); +}); diff --git a/__tests__/utils/build-log-parser.vitest.test.ts b/__tests__/utils/build-log-parser.vitest.test.ts new file mode 100644 index 0000000..1235ec0 --- /dev/null +++ b/__tests__/utils/build-log-parser.vitest.test.ts @@ -0,0 +1,114 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { EventEmitter } from 'events'; + +const spawnMock = vi.hoisted(() => vi.fn()) as ReturnType; + +vi.mock('child_process', () => ({ + spawn: spawnMock, + execFile: vi.fn((...args: any[]) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), +})); + +import { BuildLogParser } from '../../src/utils/BuildLogParser.js'; + +class MockChildProcess extends EventEmitter { + public stdout: EventEmitter; + public stderr: EventEmitter; + public killed = false; + + constructor(private readonly stdoutData: string, private readonly stderrData = '', private readonly exitCode = 0) { + super(); + this.stdout = new EventEmitter(); + this.stderr = new EventEmitter(); + this.scheduleEmit(); + } + + kill(): boolean { + this.killed = true; + return true; + } + + private scheduleEmit(): void { + setImmediate(() => { + if (this.stdoutData) { + this.stdout.emit('data', Buffer.from(this.stdoutData)); + } + if (this.stderrData) { + this.stderr.emit('data', Buffer.from(this.stderrData)); + } + this.emit('close', this.exitCode); + }); + } +} + +describe('BuildLogParser.parseBuildLog', () => { + beforeEach(() => { + spawnMock.mockReset(); + }); + + it('recovers errors from JSON reporter when issues reporter misses them', async () => { + spawnMock.mockImplementation((_command: string, args: string[]) => { + const reporterIndex = args.indexOf('--reporter'); + const reporter = reporterIndex >= 0 ? args[reporterIndex + 1] : 'issues'; + + if (reporter === 'summaryJson') { + return new MockChildProcess(JSON.stringify({ + buildStatus: 'failed', + errorCount: 0, + warnings: [], + errors: [], + notes: [] + })); + } + + if (reporter === 'json') { + return new MockChildProcess(JSON.stringify({ + errorCount: 1, + warningCount: 0, + errors: [], + warnings: [], + notes: [], + subSteps: [ + { + errorCount: 1, + warningCount: 0, + errors: [], + warnings: [], + notes: [ + { + title: "Type 'Void' cannot conform to 'View'", + documentURL: 'file:///path/to/TextsList.swift', + startingLineNumber: 235, + startingColumnNumber: 18, + detail: "/path/to/TextsList.swift:235:18: error: type 'Void' cannot conform to 'View'", + severity: 2, + type: 'swiftError' + } + ], + subSteps: [] + } + ] + })); + } + + return new MockChildProcess(JSON.stringify({ + errors: [], + warnings: [] + })); + }); + + const result = await BuildLogParser.parseBuildLog('/mock/path/to/log.xcactivitylog'); + + expect(spawnMock).toHaveBeenCalledTimes(3); + expect(result.buildStatus).toBe('failed'); + expect(result.errorCount).toBe(1); + expect(result.errors).not.toHaveLength(0); + expect(result.errors[0]).toContain("TextsList.swift:235"); + expect(result.errors[0]).toContain("Type 'Void' cannot conform to 'View'"); + }); +}); diff --git a/__tests__/xcode-server.test.js b/__tests__/xcode-server.test.js index ab73c1c..adc8725 100644 --- a/__tests__/xcode-server.test.js +++ b/__tests__/xcode-server.test.js @@ -8,7 +8,14 @@ const spawn = mockSpawn; // Add alias for tests // Mock the child_process module jest.mock('child_process', () => ({ - spawn: mockSpawn + spawn: mockSpawn, + execFile: jest.fn((...args) => { + const callback = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : undefined; + if (callback) { + callback(null, '', ''); + } + return { pid: 123 }; + }), })); // Mock the MCP SDK @@ -281,14 +288,15 @@ describeIfXcode('XcodeMCPServer', () => { const server = new XcodeMCPServer(); // Test multiple methods to ensure they generate valid JS + const fakeProject = '/Users/test/TestApp.xcodeproj'; const methods = [ - () => server.build(), - () => server.clean(), - () => server.stop(), - () => server.getSchemes(), - () => server.getRunDestinations(), - () => server.getWorkspaceInfo(), - () => server.getProjects() + () => server.build(fakeProject, 'Debug', null, 'Script generation test'), + () => server.clean(fakeProject), + () => server.stop(fakeProject), + () => server.getSchemes(fakeProject), + () => server.getRunDestinations(fakeProject), + () => server.getWorkspaceInfo(fakeProject), + () => server.getProjects(fakeProject) ]; for (const method of methods) { @@ -313,4 +321,4 @@ describeIfXcode('XcodeMCPServer', () => { } }); }); -}); \ No newline at end of file +}); diff --git a/dist/XcodeServer.d.ts b/dist/XcodeServer.d.ts deleted file mode 100644 index 52b1745..0000000 --- a/dist/XcodeServer.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import type { EnvironmentValidation, McpResult } from './types/index.js'; -export declare class XcodeServer { - server: Server; - currentProjectPath: string | null; - private environmentValidation; - private isValidated; - private canOperateInDegradedMode; - private includeClean; - private preferredScheme; - private preferredXcodeproj; - constructor(options?: { - includeClean?: boolean; - preferredScheme?: string; - preferredXcodeproj?: string; - }); - /** - * Validates the environment and sets up the server accordingly - */ - validateEnvironment(): Promise; - /** - * Checks if a tool operation should be blocked due to environment issues - */ - validateToolOperation(toolName: string): Promise; - /** - * Determines tool limitations based on environment validation - */ - private getToolLimitations; - /** - * Enhances error messages with configuration guidance - */ - enhanceErrorWithGuidance(error: Error | { - message?: string; - }, _toolName: string): Promise; - private setupToolHandlers; - openProject(projectPath: string): Promise; - executeJXA(script: string): Promise; - validateProjectPath(projectPath: string): McpResult | null; - findProjectDerivedData(projectPath: string): Promise; - getLatestBuildLog(projectPath: string): Promise; - build(projectPath: string, schemeName?: string, destination?: string | null): Promise; - clean(projectPath: string): Promise; - test(projectPath: string, destination: string, commandLineArguments?: string[]): Promise; - run(projectPath: string, commandLineArguments?: string[]): Promise; - debug(projectPath: string, scheme: string, skipBuilding?: boolean): Promise; - stop(projectPath?: string): Promise; - getSchemes(projectPath: string): Promise; - getRunDestinations(projectPath: string): Promise; - setActiveScheme(projectPath: string, schemeName: string): Promise; - getWorkspaceInfo(projectPath: string): Promise; - getProjects(projectPath: string): Promise; - openFile(filePath: string, lineNumber?: number): Promise; - parseBuildLog(logPath: string, retryCount?: number, maxRetries?: number): Promise; - canParseLog(logPath: string): Promise; - getCustomDerivedDataLocationFromXcodePreferences(): Promise; - /** - * Call a tool directly without going through the MCP protocol - * This is used by the CLI to bypass the JSON-RPC layer - */ - callToolDirect(name: string, args?: Record): Promise; -} -//# sourceMappingURL=XcodeServer.d.ts.map \ No newline at end of file diff --git a/dist/XcodeServer.d.ts.map b/dist/XcodeServer.d.ts.map deleted file mode 100644 index fcede30..0000000 --- a/dist/XcodeServer.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XcodeServer.d.ts","sourceRoot":"","sources":["../src/XcodeServer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/XcodeServer.js b/dist/XcodeServer.js deleted file mode 100644 index 9f3bde9..0000000 --- a/dist/XcodeServer.js +++ /dev/null @@ -1,880 +0,0 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; -import { BuildTools } from './tools/BuildTools.js'; -import { ProjectTools } from './tools/ProjectTools.js'; -import { InfoTools } from './tools/InfoTools.js'; -import { XCResultTools } from './tools/XCResultTools.js'; -import { PathValidator } from './utils/PathValidator.js'; -import { EnvironmentValidator } from './utils/EnvironmentValidator.js'; -import { Logger } from './utils/Logger.js'; -import { getToolDefinitions } from './shared/toolDefinitions.js'; -export class XcodeServer { - server; - currentProjectPath = null; - environmentValidation = null; - isValidated = false; - canOperateInDegradedMode = false; - includeClean; - preferredScheme; - preferredXcodeproj; - constructor(options = {}) { - this.includeClean = options.includeClean ?? true; - this.preferredScheme = options.preferredScheme; - this.preferredXcodeproj = options.preferredXcodeproj; - // Log preferred values if set - if (this.preferredScheme) { - Logger.info(`Using preferred scheme: ${this.preferredScheme}`); - } - if (this.preferredXcodeproj) { - Logger.info(`Using preferred xcodeproj: ${this.preferredXcodeproj}`); - } - this.server = new Server({ - name: 'xcode-mcp-server', - version: '1.0.0', - }, { - capabilities: { - tools: {}, - }, - }); - this.setupToolHandlers(); - } - /** - * Validates the environment and sets up the server accordingly - */ - async validateEnvironment() { - if (this.isValidated && this.environmentValidation) { - return this.environmentValidation; - } - try { - this.environmentValidation = await EnvironmentValidator.validateEnvironment(); - this.isValidated = true; - this.canOperateInDegradedMode = this.environmentValidation.overall.canOperateInDegradedMode; - // Log validation results - const validationStatus = this.environmentValidation.overall.valid ? 'PASSED' : - this.canOperateInDegradedMode ? 'DEGRADED' : 'FAILED'; - Logger.info('Environment Validation:', validationStatus); - if (!this.environmentValidation.overall.valid) { - Logger.warn('Environment issues detected:'); - [...this.environmentValidation.overall.criticalFailures, - ...this.environmentValidation.overall.nonCriticalFailures].forEach(component => { - const result = this.environmentValidation[component]; - if (result && 'valid' in result) { - const validationResult = result; - Logger.warn(` ${component}: ${validationResult.message || 'Status unknown'}`); - } - }); - } - return this.environmentValidation; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error('Environment validation failed:', errorMessage); - // Create minimal validation result for graceful degradation - this.environmentValidation = { - overall: { - valid: false, - canOperateInDegradedMode: false, - criticalFailures: ['validation'], - nonCriticalFailures: [] - } - }; - this.isValidated = true; - return this.environmentValidation; - } - } - /** - * Checks if a tool operation should be blocked due to environment issues - */ - async validateToolOperation(toolName) { - // Health check tool should never be blocked - if (toolName === 'xcode_health_check') { - return null; - } - const validation = await this.validateEnvironment(); - if (validation.overall.valid) { - return null; // All good - } - // Check for critical failures that prevent all operations - if (!validation.overall.canOperateInDegradedMode) { - const criticalFailures = validation.overall.criticalFailures - .map(component => { - const result = validation[component]; - if (result && 'valid' in result) { - const validationResult = result; - return validationResult.message || 'Unknown failure'; - } - return 'Unknown failure'; - }) - .filter(Boolean) - .join(', '); - return { - content: [{ - type: 'text', - text: `❌ Cannot execute ${toolName}: Critical environment failures detected.\n\n${criticalFailures}\n\nPlease run the 'xcode_health_check' tool for detailed recovery instructions.` - }] - }; - } - // Check for specific tool limitations in degraded mode - const limitations = this.getToolLimitations(toolName, validation); - if (limitations.blocked) { - return { - content: [{ - type: 'text', - text: `❌ Cannot execute ${toolName}: ${limitations.reason}\n\nRecovery instructions:\n${limitations.instructions?.map(i => `• ${i}`).join('\n') || ''}` - }] - }; - } - // Issue warning for degraded functionality but allow operation - if (limitations.degraded) { - Logger.warn(`${toolName} operating in degraded mode - ${limitations.reason}`); - } - return null; // Operation can proceed - } - /** - * Determines tool limitations based on environment validation - */ - getToolLimitations(toolName, validation) { - // Health check tool should never be limited - if (toolName === 'xcode_health_check') { - return { blocked: false, degraded: false }; - } - const buildTools = ['xcode_build', 'xcode_test', 'xcode_build_and_run', 'xcode_debug', 'xcode_clean']; - const xcodeTools = [...buildTools, 'xcode_open_project', 'xcode_get_schemes', 'xcode_set_active_scheme', - 'xcode_get_run_destinations', 'xcode_get_workspace_info', 'xcode_get_projects']; - const xcresultTools = ['xcresult_browse', 'xcresult_browser_get_console', 'xcresult_summary', 'xcresult_get_screenshot', 'xcresult_get_ui_hierarchy', 'xcresult_get_ui_element', 'xcresult_list_attachments', 'xcresult_export_attachment']; - // Check Xcode availability - if (xcodeTools.includes(toolName) && !validation.xcode?.valid) { - return { - blocked: true, - degraded: false, - reason: 'Xcode is not properly installed or accessible', - instructions: validation.xcode?.recoveryInstructions || [ - 'Install Xcode from the Mac App Store', - 'Launch Xcode once to complete installation' - ] - }; - } - // Check osascript availability - if (xcodeTools.includes(toolName) && !validation.osascript?.valid) { - return { - blocked: true, - degraded: false, - reason: 'JavaScript for Automation (JXA) is not available', - instructions: validation.osascript?.recoveryInstructions || [ - 'This tool requires macOS', - 'Ensure osascript is available' - ] - }; - } - // Build tools have additional dependencies and warnings - if (buildTools.includes(toolName)) { - if (!validation.xclogparser?.valid) { - return { - blocked: false, - degraded: true, - reason: 'XCLogParser not available - build results will have limited detail', - instructions: validation.xclogparser?.recoveryInstructions || [ - 'Install XCLogParser with: brew install xclogparser' - ] - }; - } - if (!validation.permissions?.valid && - !validation.permissions?.degradedMode?.available) { - return { - blocked: true, - degraded: false, - reason: 'Automation permissions not granted', - instructions: validation.permissions?.recoveryInstructions || [ - 'Grant automation permissions in System Preferences' - ] - }; - } - } - // XCResult tools only need xcresulttool (part of Xcode Command Line Tools) - if (xcresultTools.includes(toolName)) { - // Check if we can run xcresulttool - this is included with Xcode Command Line Tools - if (!validation.xcode?.valid) { - return { - blocked: true, - degraded: false, - reason: 'XCResult tools require Xcode Command Line Tools for xcresulttool', - instructions: [ - 'Install Xcode Command Line Tools: xcode-select --install', - 'Or install full Xcode from the Mac App Store' - ] - }; - } - } - return { blocked: false, degraded: false }; - } - /** - * Enhances error messages with configuration guidance - */ - async enhanceErrorWithGuidance(error, _toolName) { - const errorMessage = error.message || error.toString(); - // Import ErrorHelper for common error patterns - const { ErrorHelper } = await import('./utils/ErrorHelper.js'); - const commonError = ErrorHelper.parseCommonErrors(error); - if (commonError) { - return commonError; - } - // Additional configuration-specific error patterns - if (errorMessage.includes('command not found')) { - if (errorMessage.includes('xclogparser')) { - return `❌ XCLogParser not found\n\n💡 To fix this:\n• Install XCLogParser: brew install xclogparser\n• Or download from: https://github.com/MobileNativeFoundation/XCLogParser\n\nNote: Build operations will work but with limited error details.`; - } - if (errorMessage.includes('osascript')) { - return `❌ macOS scripting tools not available\n\n💡 This indicates a critical system issue:\n• This MCP server requires macOS\n• Ensure you're running on a Mac with system tools available\n• Try restarting your terminal`; - } - } - if (errorMessage.includes('No such file or directory')) { - if (errorMessage.includes('Xcode.app')) { - return `❌ Xcode application not found\n\n💡 To fix this:\n• Install Xcode from the Mac App Store\n• Ensure Xcode is in /Applications/Xcode.app\n• Launch Xcode once to complete installation`; - } - } - // Only convert actual operation timeouts, not build errors containing 'timeout:' or transport errors - if ((errorMessage.includes(' timeout') || errorMessage.includes('timed out') || errorMessage.includes('timeout after')) && - !errorMessage.includes('Body Timeout Error') && - !errorMessage.includes('Transport error') && - !errorMessage.includes('SSE error') && - !errorMessage.includes('terminated') && - !errorMessage.includes("'timeout:'") && - !errorMessage.includes("timeout:' in call") && - !errorMessage.includes('argument label') && - !errorMessage.includes('TEST BUILD FAILED')) { - return `❌ Operation timed out\n\n💡 This might indicate:\n• Xcode is not responding (try restarting Xcode)\n• System performance issues\n• Large project taking longer than expected\n• Network issues if downloading dependencies`; - } - return null; // No specific guidance available - } - setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => { - const toolOptions = { includeClean: this.includeClean }; - if (this.preferredScheme) - toolOptions.preferredScheme = this.preferredScheme; - if (this.preferredXcodeproj) - toolOptions.preferredXcodeproj = this.preferredXcodeproj; - const toolDefinitions = getToolDefinitions(toolOptions); - return { - tools: toolDefinitions.map(tool => ({ - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema - })), - }; - }); - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args = {} } = request.params; - // Apply preferred values if parameters not provided - if (!args.xcodeproj && this.preferredXcodeproj) { - args.xcodeproj = this.preferredXcodeproj; - } - if (!args.scheme && this.preferredScheme) { - args.scheme = this.preferredScheme; - } - // Resolve relative paths to absolute paths - if (args.xcodeproj && typeof args.xcodeproj === 'string') { - const { resolvedPath, error } = PathValidator.resolveAndValidateProjectPath(args.xcodeproj, 'xcodeproj'); - if (error) { - return error; - } - args.xcodeproj = resolvedPath; - } - if (args.filePath && typeof args.filePath === 'string') { - const path = await import('path'); - if (!path.default.isAbsolute(args.filePath)) { - args.filePath = path.default.resolve(process.cwd(), args.filePath); - } - } - try { - // Handle health check tool first (no environment validation needed) - if (name === 'xcode_health_check') { - const report = await EnvironmentValidator.createHealthCheckReport(); - return { content: [{ type: 'text', text: report }] }; - } - // Validate environment for all other tools - const validationError = await this.validateToolOperation(name); - if (validationError) { - return validationError; - } - switch (name) { - case 'xcode_open_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, this.preferredXcodeproj - ? `Missing required parameter: xcodeproj (no preferred value was applied)\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file` - : `Missing required parameter: xcodeproj\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file`); - } - const result = await ProjectTools.openProject(args.xcodeproj); - if (result && 'content' in result && result.content?.[0] && 'text' in result.content[0]) { - const textContent = result.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - if (!textContent.text.includes('Error') && !textContent.text.includes('does not exist')) { - this.currentProjectPath = args.xcodeproj; - } - } - } - return result; - case 'xcode_close_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - try { - const validationError = PathValidator.validateProjectPath(args.xcodeproj); - if (validationError) - return validationError; - const closeResult = await ProjectTools.closeProject(args.xcodeproj); - this.currentProjectPath = null; - return closeResult; - } - catch (closeError) { - // Ensure close project never crashes the server - Logger.error('Close project error (handled):', closeError); - this.currentProjectPath = null; - return { content: [{ type: 'text', text: 'Project close attempted - may have completed with dialogs' }] }; - } - case 'xcode_build': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.build(args.xcodeproj, args.scheme, args.destination || null, this.openProject.bind(this)); - case 'xcode_clean': - if (!this.includeClean) { - throw new McpError(ErrorCode.MethodNotFound, `Clean tool is disabled`); - } - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await BuildTools.clean(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_test': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj\n\n💡 To fix this:\n• Specify the absolute path to your .xcodeproj or .xcworkspace file using the "xcodeproj" parameter\n• Example: /Users/username/MyApp/MyApp.xcodeproj\n• You can drag the project file from Finder to get the path`); - } - if (!args.destination) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: destination\n\n💡 To fix this:\n• Specify the test destination (e.g., "iPhone 15 Pro Simulator")\n• Use 'get-run-destinations' to see available destinations\n• Example: "iPad Air Simulator" or "iPhone 16 Pro"`); - } - const testOptions = {}; - if (args.test_plan_path) - testOptions.testPlanPath = args.test_plan_path; - if (args.selected_tests) - testOptions.selectedTests = args.selected_tests; - if (args.selected_test_classes) - testOptions.selectedTestClasses = args.selected_test_classes; - if (args.test_target_identifier) - testOptions.testTargetIdentifier = args.test_target_identifier; - if (args.test_target_name) - testOptions.testTargetName = args.test_target_name; - return await BuildTools.test(args.xcodeproj, args.destination, args.command_line_arguments || [], this.openProject.bind(this), Object.keys(testOptions).length > 0 ? testOptions : undefined); - case 'xcode_build_and_run': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.run(args.xcodeproj, args.scheme, args.command_line_arguments || [], this.openProject.bind(this)); - case 'xcode_debug': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.debug(args.xcodeproj, args.scheme, args.skip_building, this.openProject.bind(this)); - case 'xcode_stop': - if (!args.xcodeproj) { - return { content: [{ type: 'text', text: 'Error: xcodeproj parameter is required' }] }; - } - return await BuildTools.stop(args.xcodeproj); - case 'find_xcresults': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await BuildTools.findXCResults(args.xcodeproj); - case 'xcode_get_schemes': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getSchemes(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_get_run_destinations': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getRunDestinations(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_set_active_scheme': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme_name) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme_name`); - } - return await ProjectTools.setActiveScheme(args.xcodeproj, args.scheme_name, this.openProject.bind(this)); - case 'xcode_get_workspace_info': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await InfoTools.getWorkspaceInfo(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_get_projects': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await InfoTools.getProjects(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_open_file': - if (!args.file_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: file_path`); - } - return await InfoTools.openFile(args.file_path, args.line_number); - case 'xcresult_browse': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - return await XCResultTools.xcresultBrowse(args.xcresult_path, args.test_id, args.include_console || false); - case 'xcresult_browser_get_console': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultBrowserGetConsole(args.xcresult_path, args.test_id); - case 'xcresult_summary': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - return await XCResultTools.xcresultSummary(args.xcresult_path); - case 'xcresult_get_screenshot': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - if (args.timestamp === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: timestamp`); - } - return await XCResultTools.xcresultGetScreenshot(args.xcresult_path, args.test_id, args.timestamp); - case 'xcresult_get_ui_hierarchy': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultGetUIHierarchy(args.xcresult_path, args.test_id, args.timestamp, args.full_hierarchy, args.raw_format); - case 'xcresult_get_ui_element': - if (!args.hierarchy_json_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: hierarchy_json_path`); - } - if (args.element_index === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: element_index`); - } - return await XCResultTools.xcresultGetUIElement(args.hierarchy_json_path, args.element_index, args.include_children); - case 'xcresult_list_attachments': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultListAttachments(args.xcresult_path, args.test_id); - case 'xcresult_export_attachment': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - if (args.attachment_index === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: attachment_index`); - } - return await XCResultTools.xcresultExportAttachment(args.xcresult_path, args.test_id, args.attachment_index, args.convert_to_json); - case 'xcode_get_test_targets': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getTestTargets(args.xcodeproj); - case 'xcode_refresh_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - // Close and reopen the project to refresh it - await ProjectTools.closeProject(args.xcodeproj); - const refreshResult = await ProjectTools.openProjectAndWaitForLoad(args.xcodeproj); - return { - content: [{ - type: 'text', - text: `Project refreshed: ${refreshResult.content?.[0]?.type === 'text' ? refreshResult.content[0].text : 'Completed'}` - }] - }; - default: - throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); - } - } - catch (error) { - // Enhanced error handling that doesn't crash the server - Logger.error(`Tool execution error for ${name}:`, error); - // Check if it's a configuration-related error that we can provide guidance for - const enhancedError = await this.enhanceErrorWithGuidance(error, name); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - // For other errors, provide a helpful message but don't crash - const errorMessage = error instanceof McpError ? error.message : - error instanceof Error ? `Tool execution failed: ${error.message}` : - `Tool execution failed: ${String(error)}`; - return { - content: [{ - type: 'text', - text: `❌ ${name} failed: ${errorMessage}` - }] - }; - } - }); - } - async openProject(projectPath) { - const result = await ProjectTools.openProjectAndWaitForLoad(projectPath); - if (result && 'content' in result && result.content?.[0] && 'text' in result.content[0]) { - const textContent = result.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - if (!textContent.text.includes('❌') && !textContent.text.includes('Error') && !textContent.text.includes('does not exist')) { - this.currentProjectPath = projectPath; - } - } - } - return result; - } - async executeJXA(script) { - const { JXAExecutor } = await import('./utils/JXAExecutor.js'); - return JXAExecutor.execute(script); - } - validateProjectPath(projectPath) { - return PathValidator.validateProjectPath(projectPath); - } - async findProjectDerivedData(projectPath) { - const { BuildLogParser } = await import('./utils/BuildLogParser.js'); - return BuildLogParser.findProjectDerivedData(projectPath); - } - async getLatestBuildLog(projectPath) { - const { BuildLogParser } = await import('./utils/BuildLogParser.js'); - return BuildLogParser.getLatestBuildLog(projectPath); - } - // Direct method interfaces for testing/CLI compatibility - async build(projectPath, schemeName = 'Debug', destination = null) { - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.build(projectPath, schemeName, destination, this.openProject.bind(this)); - } - async clean(projectPath) { - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.clean(projectPath, this.openProject.bind(this)); - } - async test(projectPath, destination, commandLineArguments = []) { - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.test(projectPath, destination, commandLineArguments, this.openProject.bind(this)); - } - async run(projectPath, commandLineArguments = []) { - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.run(projectPath, 'Debug', commandLineArguments, this.openProject.bind(this)); - } - async debug(projectPath, scheme, skipBuilding = false) { - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.debug(projectPath, scheme, skipBuilding, this.openProject.bind(this)); - } - async stop(projectPath) { - if (!projectPath) { - return { content: [{ type: 'text', text: 'Error: projectPath parameter is required' }] }; - } - const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.stop(projectPath); - } - async getSchemes(projectPath) { - const { ProjectTools } = await import('./tools/ProjectTools.js'); - return ProjectTools.getSchemes(projectPath, this.openProject.bind(this)); - } - async getRunDestinations(projectPath) { - const { ProjectTools } = await import('./tools/ProjectTools.js'); - return ProjectTools.getRunDestinations(projectPath, this.openProject.bind(this)); - } - async setActiveScheme(projectPath, schemeName) { - const { ProjectTools } = await import('./tools/ProjectTools.js'); - return ProjectTools.setActiveScheme(projectPath, schemeName, this.openProject.bind(this)); - } - async getWorkspaceInfo(projectPath) { - const { InfoTools } = await import('./tools/InfoTools.js'); - return InfoTools.getWorkspaceInfo(projectPath, this.openProject.bind(this)); - } - async getProjects(projectPath) { - const { InfoTools } = await import('./tools/InfoTools.js'); - return InfoTools.getProjects(projectPath, this.openProject.bind(this)); - } - async openFile(filePath, lineNumber) { - const { InfoTools } = await import('./tools/InfoTools.js'); - return InfoTools.openFile(filePath, lineNumber); - } - async parseBuildLog(logPath, retryCount, maxRetries) { - const { BuildLogParser } = await import('./utils/BuildLogParser.js'); - return BuildLogParser.parseBuildLog(logPath, retryCount, maxRetries); - } - async canParseLog(logPath) { - const { BuildLogParser } = await import('./utils/BuildLogParser.js'); - return BuildLogParser.canParseLog(logPath); - } - async getCustomDerivedDataLocationFromXcodePreferences() { - const { BuildLogParser } = await import('./utils/BuildLogParser.js'); - return BuildLogParser.getCustomDerivedDataLocationFromXcodePreferences(); - } - /** - * Call a tool directly without going through the MCP protocol - * This is used by the CLI to bypass the JSON-RPC layer - */ - async callToolDirect(name, args = {}) { - // This is essentially the same logic as the CallToolRequestSchema handler - // Resolve relative paths to absolute paths (this is actually handled by CLI now, but keep for safety) - if (args.xcodeproj && typeof args.xcodeproj === 'string') { - const { resolvedPath, error } = PathValidator.resolveAndValidateProjectPath(args.xcodeproj, 'xcodeproj'); - if (error) { - return error; - } - args.xcodeproj = resolvedPath; - } - if (args.filePath && typeof args.filePath === 'string') { - const path = await import('path'); - if (!path.default.isAbsolute(args.filePath)) { - args.filePath = path.default.resolve(process.cwd(), args.filePath); - } - } - try { - // Handle health check tool first (no environment validation needed) - if (name === 'xcode_health_check') { - const report = await EnvironmentValidator.createHealthCheckReport(); - return { content: [{ type: 'text', text: report }] }; - } - // Validate environment for all other tools - const validationError = await this.validateToolOperation(name); - if (validationError) { - return validationError; - } - switch (name) { - case 'xcode_open_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file`); - } - const result = await ProjectTools.openProject(args.xcodeproj); - if (result && 'content' in result && result.content?.[0] && 'text' in result.content[0]) { - const textContent = result.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - if (!textContent.text.includes('Error') && !textContent.text.includes('does not exist')) { - this.currentProjectPath = args.xcodeproj; - } - } - } - return result; - case 'xcode_close_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - try { - const validationError = PathValidator.validateProjectPath(args.xcodeproj); - if (validationError) - return validationError; - const closeResult = await ProjectTools.closeProject(args.xcodeproj); - this.currentProjectPath = null; - return closeResult; - } - catch (closeError) { - // Ensure close project never crashes the server - Logger.error('Close project error (handled):', closeError); - this.currentProjectPath = null; - return { content: [{ type: 'text', text: 'Project close attempted - may have completed with dialogs' }] }; - } - case 'xcode_build': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.build(args.xcodeproj, args.scheme, args.destination || null, this.openProject.bind(this)); - case 'xcode_clean': - if (!this.includeClean) { - throw new McpError(ErrorCode.MethodNotFound, `Clean tool is disabled`); - } - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await BuildTools.clean(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_test': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj\n\n💡 To fix this:\n• Specify the absolute path to your .xcodeproj or .xcworkspace file using the "xcodeproj" parameter\n• Example: /Users/username/MyApp/MyApp.xcodeproj\n• You can drag the project file from Finder to get the path`); - } - if (!args.destination) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: destination\n\n💡 To fix this:\n• Specify the test destination (e.g., "iPhone 15 Pro Simulator")\n• Use 'get-run-destinations' to see available destinations\n• Example: "iPad Air Simulator" or "iPhone 16 Pro"`); - } - return await BuildTools.test(args.xcodeproj, args.destination, args.command_line_arguments || [], this.openProject.bind(this)); - case 'xcode_build_and_run': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.run(args.xcodeproj, args.scheme, args.command_line_arguments || [], this.openProject.bind(this)); - case 'xcode_debug': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.debug(args.xcodeproj, args.scheme, args.skip_building, this.openProject.bind(this)); - case 'xcode_stop': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await BuildTools.stop(args.xcodeproj); - case 'find_xcresults': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await BuildTools.findXCResults(args.xcodeproj); - case 'xcode_get_schemes': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getSchemes(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_get_run_destinations': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getRunDestinations(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_set_active_scheme': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - if (!args.scheme_name) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme_name`); - } - return await ProjectTools.setActiveScheme(args.xcodeproj, args.scheme_name, this.openProject.bind(this)); - case 'xcode_get_workspace_info': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await InfoTools.getWorkspaceInfo(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_get_projects': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await InfoTools.getProjects(args.xcodeproj, this.openProject.bind(this)); - case 'xcode_open_file': - if (!args.file_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: file_path`); - } - return await InfoTools.openFile(args.file_path, args.line_number); - case 'xcresult_browse': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - return await XCResultTools.xcresultBrowse(args.xcresult_path, args.test_id, args.include_console || false); - case 'xcresult_browser_get_console': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultBrowserGetConsole(args.xcresult_path, args.test_id); - case 'xcresult_summary': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - return await XCResultTools.xcresultSummary(args.xcresult_path); - case 'xcresult_get_screenshot': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - if (args.timestamp === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: timestamp`); - } - return await XCResultTools.xcresultGetScreenshot(args.xcresult_path, args.test_id, args.timestamp); - case 'xcresult_get_ui_hierarchy': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultGetUIHierarchy(args.xcresult_path, args.test_id, args.timestamp, args.full_hierarchy, args.raw_format); - case 'xcresult_get_ui_element': - if (!args.hierarchy_json_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: hierarchy_json_path`); - } - if (args.element_index === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: element_index`); - } - return await XCResultTools.xcresultGetUIElement(args.hierarchy_json_path, args.element_index, args.include_children); - case 'xcresult_list_attachments': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - return await XCResultTools.xcresultListAttachments(args.xcresult_path, args.test_id); - case 'xcresult_export_attachment': - if (!args.xcresult_path) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); - } - if (!args.test_id) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: test_id`); - } - if (args.attachment_index === undefined) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: attachment_index`); - } - return await XCResultTools.xcresultExportAttachment(args.xcresult_path, args.test_id, args.attachment_index, args.convert_to_json); - case 'xcode_get_test_targets': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - return await ProjectTools.getTestTargets(args.xcodeproj); - case 'xcode_refresh_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - // Close and reopen the project to refresh it - await ProjectTools.closeProject(args.xcodeproj); - const refreshResult = await ProjectTools.openProjectAndWaitForLoad(args.xcodeproj); - return { - content: [{ - type: 'text', - text: `Project refreshed: ${refreshResult.content?.[0]?.type === 'text' ? refreshResult.content[0].text : 'Completed'}` - }] - }; - default: - throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); - } - } - catch (error) { - // Enhanced error handling that doesn't crash the server - Logger.error(`Tool execution error for ${name}:`, error); - // Check if it's a configuration-related error that we can provide guidance for - const enhancedError = await this.enhanceErrorWithGuidance(error, name); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - // For other errors, provide a helpful message but don't crash - const errorMessage = error instanceof McpError ? error.message : - error instanceof Error ? `Tool execution failed: ${error.message}` : - `Tool execution failed: ${String(error)}`; - return { - content: [{ - type: 'text', - text: `❌ ${name} failed: ${errorMessage}\n\n💡 If this persists, try running 'xcode_health_check' to diagnose potential configuration issues.` - }] - }; - } - } -} -//# sourceMappingURL=XcodeServer.js.map \ No newline at end of file diff --git a/dist/XcodeServer.js.map b/dist/XcodeServer.js.map deleted file mode 100644 index 2c13a05..0000000 --- a/dist/XcodeServer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XcodeServer.js","sourceRoot":"","sources":["../src/XcodeServer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cli.d.ts b/dist/cli.d.ts deleted file mode 100644 index dc56352..0000000 --- a/dist/cli.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -/** - * Main CLI function - */ -declare function main(): Promise; -export { main }; -//# sourceMappingURL=cli.d.ts.map \ No newline at end of file diff --git a/dist/cli.d.ts.map b/dist/cli.d.ts.map deleted file mode 100644 index 1cd1cd5..0000000 --- a/dist/cli.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAyHA;;GAEG;AACH,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgUnC;AAYD,OAAO,EAAE,IAAI,EAAE,CAAC"} \ No newline at end of file diff --git a/dist/cli.js b/dist/cli.js deleted file mode 100755 index 6ac6874..0000000 --- a/dist/cli.js +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env node -import { Command } from 'commander'; -import { readFile } from 'fs/promises'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -import { XcodeServer } from './XcodeServer.js'; -import { Logger } from './utils/Logger.js'; -import { getToolDefinitions } from './shared/toolDefinitions.js'; -const __dirname = dirname(fileURLToPath(import.meta.url)); -/** - * Load package.json to get version info - */ -async function loadPackageJson() { - try { - const packagePath = join(__dirname, '../package.json'); - const packageContent = await readFile(packagePath, 'utf-8'); - return JSON.parse(packageContent); - } - catch (error) { - Logger.error('Failed to load package.json:', error); - return { version: '0.0.0' }; - } -} -/** - * Convert JSON schema property to commander option - */ -function schemaPropertyToOption(name, property) { - // Convert underscores to dashes for CLI consistency - const dashName = name.replace(/_/g, '-'); - const flags = property.type === 'boolean' ? `--${dashName}` : `--${dashName} `; - const description = property.description || `${name} parameter`; - const option = { flags, description }; - if (property.default !== undefined) { - option.defaultValue = property.default; - } - return option; -} -/** - * Parse command line arguments into tool arguments - */ -function parseToolArgs(tool, cliArgs) { - const toolArgs = {}; - if (!tool.inputSchema?.properties) { - return toolArgs; - } - for (const [propName, propSchema] of Object.entries(tool.inputSchema.properties)) { - const propDef = propSchema; - // Convert underscores to dashes, then to camelCase to match commander.js behavior - const dashPropName = propName.replace(/_/g, '-'); - const camelPropName = dashPropName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); - const cliValue = cliArgs[camelPropName]; - if (cliValue !== undefined) { - // Handle array types - if (propDef.type === 'array') { - if (Array.isArray(cliValue)) { - toolArgs[propName] = cliValue; - } - else { - // Split string by comma for array values - toolArgs[propName] = cliValue.split(',').map((s) => s.trim()); - } - } - else if (propDef.type === 'number') { - toolArgs[propName] = parseFloat(cliValue); - } - else if (propDef.type === 'boolean') { - toolArgs[propName] = cliValue === true || cliValue === 'true'; - } - else { - toolArgs[propName] = cliValue; - } - } - } - return toolArgs; -} -/** - * Format tool result for console output - */ -function formatResult(result, jsonOutput) { - if (jsonOutput) { - return JSON.stringify(result, null, 2); - } - // Pretty format for console - if (result?.content && Array.isArray(result.content)) { - return result.content - .map((item) => { - if (item.type === 'text') { - return item.text; - } - else if (item.type === 'image') { - return `[Image: ${item.source?.data ? 'base64 data' : item.source?.url || 'unknown'}]`; - } - else { - return `[${item.type}: ${JSON.stringify(item)}]`; - } - }) - .join('\n'); - } - return JSON.stringify(result, null, 2); -} -// Note: handleSseEvent is defined but not used in CLI-first architecture -// Events are handled by the spawning process (MCP library) -// function handleSseEvent(event: SseEvent): void { -// const eventData = `event:${event.type}\ndata:${JSON.stringify(event.data)}\n\n`; -// process.stderr.write(eventData); -// } -/** - * Main CLI function - */ -async function main() { - try { - const pkg = await loadPackageJson(); - // Check for --no-clean argument early to configure both server and tools - const noCleanArg = process.argv.includes('--no-clean'); - const includeClean = !noCleanArg; - // Parse preferred values from command-line or environment - const preferredScheme = process.env.XCODE_MCP_PREFERRED_SCHEME || - process.argv.find(arg => arg.startsWith('--preferred-scheme='))?.split('=')[1]; - const preferredXcodeproj = process.env.XCODE_MCP_PREFERRED_XCODEPROJ || - process.argv.find(arg => arg.startsWith('--preferred-xcodeproj='))?.split('=')[1]; - const serverOptions = { includeClean }; - if (preferredScheme) - serverOptions.preferredScheme = preferredScheme; - if (preferredXcodeproj) - serverOptions.preferredXcodeproj = preferredXcodeproj; - const server = new XcodeServer(serverOptions); - // Get tool definitions from shared source to ensure CLI is always in sync with MCP - const toolOptions = { includeClean }; - if (preferredScheme) - toolOptions.preferredScheme = preferredScheme; - if (preferredXcodeproj) - toolOptions.preferredXcodeproj = preferredXcodeproj; - const tools = getToolDefinitions(toolOptions); - // Build description with preferred values if set - let description = `Command-line interface for Xcode automation and control`; - if (preferredScheme || preferredXcodeproj) { - description += '\n\n📌 Preferred Values:'; - if (preferredScheme) { - description += `\n • Scheme: ${preferredScheme}`; - } - if (preferredXcodeproj) { - description += `\n • Project: ${preferredXcodeproj}`; - } - } - description += ` - -📁 Command Categories: - • Project Management - Open/close projects, manage schemes and workspaces - • Build & Run - Build, clean, run, and debug your projects - • Testing - Run tests and manage test targets - • Test Results - Analyze XCResult files and test artifacts - • System - Health checks and diagnostics - -⏱️ Command Execution: - • Commands have built-in timeouts and handle long-running operations - • Build, test, and run operations can take minutes to hours depending on project size - • The CLI will wait for completion - do not manually timeout or interrupt - -💡 Use 'xcodecontrol list-tools' to see all commands organized by category -💡 Use 'xcodecontrol --help' for detailed help on any command - -🚫 Use 'xcodecontrol --no-clean' to disable the clean tool for safety`; - const program = new Command('xcodecontrol') - .version(pkg.version) - .description(description) - .option('--json', 'Output results in JSON format', false) - .option('-v, --verbose', 'Enable verbose output (shows INFO logs)', false) - .option('-q, --quiet', 'Suppress all logs except errors', false) - .option('--no-clean', 'Disable the clean tool', false) - .option('--preferred-scheme ', 'Set a preferred scheme to use as default') - .option('--preferred-xcodeproj ', 'Set a preferred xcodeproj/xcworkspace to use as default'); - // Add global help command - program - .command('help') - .description('Show help information') - .action(() => { - program.help(); - }); - // Add list-tools command for compatibility - program - .command('list-tools') - .description('List all available tools') - .action(() => { - console.log('Available tools organized by category:'); - console.log(''); - // Define command categories - const buildAndRunCommands = [ - 'build', 'build-and-run', 'debug', 'stop', - 'get-run-destinations' - ]; - // Add clean only if not disabled - if (includeClean) { - buildAndRunCommands.splice(1, 0, 'clean'); // Insert clean after build - } - const categories = { - 'Project Management': [ - 'open-project', 'close-project', 'refresh-project', - 'get-schemes', 'set-active-scheme', 'get-projects', - 'get-workspace-info', 'open-file' - ], - 'Build & Run': buildAndRunCommands, - 'Testing': [ - 'test', 'get-test-targets' - ], - 'Test Results Analysis': [ - 'find-xcresults', 'xcresult-browse', 'xcresult-summary', - 'xcresult-browser-get-console', 'xcresult-get-screenshot', - 'xcresult-get-ui-hierarchy', 'xcresult-get-ui-element', - 'xcresult-list-attachments', 'xcresult-export-attachment' - ], - 'System & Diagnostics': [ - 'health-check', 'list-tools', 'help' - ] - }; - // Create a map of command name to tool for quick lookup - const toolMap = new Map(); - for (const tool of tools) { - const commandName = tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); - toolMap.set(commandName, tool); - } - // Add non-tool commands - toolMap.set('help', { description: 'Show help information' }); - toolMap.set('list-tools', { description: 'List all available tools' }); - // Display categorized commands - for (const [category, commands] of Object.entries(categories)) { - console.log(`📁 ${category}:`); - for (const cmdName of commands) { - const tool = toolMap.get(cmdName); - if (tool) { - console.log(` ${cmdName.padEnd(30)} ${tool.description}`); - } - } - console.log(''); - } - console.log('💡 Usage:'); - console.log(' xcodecontrol --help Show help for specific command'); - console.log(' xcodecontrol --help Show general help'); - console.log(''); - console.log('⏱️ Note: Build, test, and run commands can take minutes to hours.'); - console.log(' The CLI handles long operations automatically - do not timeout.'); - }); - // Dynamically create subcommands for each tool - for (const tool of tools) { - // Convert tool name: remove "xcode_" prefix and replace underscores with dashes - const commandName = tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); - const cmd = program - .command(commandName) - .description(tool.description); - // Add options based on the tool's input schema - if (tool.inputSchema?.properties) { - for (const [propName, propSchema] of Object.entries(tool.inputSchema.properties)) { - const propDef = propSchema; - const option = schemaPropertyToOption(propName, propDef); - if (option.defaultValue !== undefined) { - cmd.option(option.flags, option.description, option.defaultValue); - } - else { - cmd.option(option.flags, option.description); - } - } - } - // Handle JSON input option - cmd.option('--json-input ', 'Provide arguments as JSON string'); - // Set up the action handler - cmd.action(async (cliArgs) => { - try { - // Set log level based on CLI options - const globalOpts = program.opts(); - if (globalOpts.quiet) { - process.env.LOG_LEVEL = 'ERROR'; - } - else if (globalOpts.verbose) { - process.env.LOG_LEVEL = 'DEBUG'; - } - else { - process.env.LOG_LEVEL = 'WARN'; // Default: only show warnings and errors - } - let toolArgs; - // Parse arguments from JSON input or CLI flags - if (cliArgs.jsonInput) { - try { - toolArgs = JSON.parse(cliArgs.jsonInput); - } - catch (error) { - console.error('❌ Invalid JSON input:', error); - process.exit(1); - } - } - else { - toolArgs = parseToolArgs(tool, cliArgs); - } - // Resolve relative paths for xcodeproj parameter - if (toolArgs.xcodeproj && typeof toolArgs.xcodeproj === 'string') { - // Import PathValidator here to avoid circular dependencies - const { PathValidator } = await import('./utils/PathValidator.js'); - const { resolvedPath, error } = PathValidator.resolveAndValidateProjectPath(toolArgs.xcodeproj, 'xcodeproj'); - if (error) { - const output = formatResult(error, program.opts().json); - console.error(output); - process.exit(1); - } - toolArgs.xcodeproj = resolvedPath; - } - // Resolve relative paths for file_path parameter (used by xcode_open_file) - if (toolArgs.file_path && typeof toolArgs.file_path === 'string') { - const path = await import('path'); - if (!path.default.isAbsolute(toolArgs.file_path)) { - toolArgs.file_path = path.default.resolve(process.cwd(), toolArgs.file_path); - } - } - // Validate required parameters - if (tool.inputSchema?.required) { - const missingParams = tool.inputSchema.required.filter((param) => toolArgs[param] === undefined); - if (missingParams.length > 0) { - console.error(`❌ Missing required parameter${missingParams.length > 1 ? 's' : ''}: ${missingParams.join(', ')}\n`); - cmd.help(); - return; // cmd.help() calls process.exit(), but adding return for clarity - } - } - // Call the tool directly on server - const result = await server.callToolDirect(tool.name, toolArgs); - // Check if the result indicates an error - let hasError = false; - if (result?.content && Array.isArray(result.content)) { - for (const item of result.content) { - if (item.type === 'text' && item.text) { - const text = item.text; - // Special case for health-check: don't treat degraded mode as error - if (tool.name === 'xcode_health_check') { - // Only treat as error if there are critical failures - hasError = text.includes('⚠️ CRITICAL ERRORS DETECTED') || - text.includes('❌ OS:') || - text.includes('❌ OSASCRIPT:'); - } - else if (tool.name === 'xcode_test') { - // Special case for test results: check if tests actually failed - if (text.includes('✅ All tests passed!')) { - hasError = false; - } - else { - // Look for actual test failures or build errors - hasError = text.includes('❌ TEST BUILD FAILED') || - text.includes('❌ TESTS FAILED') || - text.includes('⏹️ TEST BUILD INTERRUPTED') || - (text.includes('Failed:') && !text.includes('Failed: 0')); - } - } - else { - // Check for common error patterns - if (text.includes('❌') || - text.includes('does not exist') || - text.includes('failed') || - text.includes('error') || - text.includes('Error') || - text.includes('missing required parameter') || - text.includes('cannot find') || - text.includes('not found') || - text.includes('invalid') || - text.includes('Invalid')) { - hasError = true; - break; - } - } - } - } - } - // Output the result - const output = formatResult(result, program.opts().json); - if (hasError) { - console.error(output); - } - else { - console.log(output); - } - // Exit with appropriate code - process.exit(hasError ? 1 : 0); - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - console.error(`❌ ${tool.name} failed:`, errorMsg); - process.exit(1); - } - }); - } - // Parse command line arguments - await program.parseAsync(process.argv); - } - catch (error) { - Logger.error('CLI initialization failed:', error); - console.error('❌ Failed to initialize CLI:', error); - // Re-throw the error so it can be caught by tests - throw error; - } -} -// Run the CLI if this file is executed directly -// Don't run if we're in test mode and not executing the CLI directly -if (process.env.NODE_ENV !== 'test' || process.argv[1]?.includes('cli.js')) { - main().catch((error) => { - Logger.error('CLI execution failed:', error); - console.error('❌ CLI execution failed:', error); - process.exit(1); - }); -} -export { main }; -//# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map deleted file mode 100644 index 32a6e48..0000000 --- a/dist/cli.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 0372710..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node -import { XcodeServer } from './XcodeServer.js'; -export declare class XcodeMCPServer extends XcodeServer { - constructor(options?: { - includeClean?: boolean; - preferredScheme?: string; - preferredXcodeproj?: string; - }); - start(port?: number): Promise; -} -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map deleted file mode 100644 index a4578fa..0000000 --- a/dist/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+B/C,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,GAAE;QACnB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KACxB;IAIO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAyJjD"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100755 index f781e51..0000000 --- a/dist/index.js +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env node -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; -import { createServer } from 'http'; -import { XcodeServer } from './XcodeServer.js'; -import { Logger } from './utils/Logger.js'; -// Handle uncaught exceptions and unhandled promise rejections -process.on('uncaughtException', (error) => { - Logger.error('🚨 UNCAUGHT EXCEPTION - This may indicate a bug that needs fixing:', error); - Logger.error('Stack trace:', error.stack); - // Log to stderr as well for visibility - console.error('🚨 XcodeMCP: Uncaught exception detected:', error.message); - // Don't exit immediately - log and continue for MCP server stability -}); -process.on('unhandledRejection', (reason, promise) => { - Logger.error('🚨 UNHANDLED PROMISE REJECTION - This may indicate a bug that needs fixing:', reason); - Logger.error('Promise:', promise); - // Log to stderr as well for visibility - console.error('🚨 XcodeMCP: Unhandled promise rejection:', reason); - // Don't exit - log and continue -}); -process.on('SIGTERM', () => { - Logger.info('Received SIGTERM, shutting down gracefully'); - process.exit(0); -}); -process.on('SIGINT', () => { - Logger.info('Received SIGINT, shutting down gracefully'); - process.exit(0); -}); -export class XcodeMCPServer extends XcodeServer { - constructor(options = {}) { - super(options); - } - async start(port) { - try { - // Initialize logging system - Logger.info('Starting server and validating environment...'); - Logger.debug('Log level set to:', Logger.getLogLevel()); - const validation = await this.validateEnvironment(); - if (!validation.overall.valid) { - if (validation.overall.canOperateInDegradedMode) { - Logger.warn('Starting in degraded mode due to configuration issues'); - Logger.warn('Some features may be unavailable'); - Logger.info('Run the "xcode_health_check" tool for detailed diagnostics'); - } - else { - Logger.error('Critical configuration issues detected'); - Logger.error('Server may not function properly'); - Logger.error('Please run the "xcode_health_check" tool to resolve issues'); - } - } - else { - Logger.info('Environment validation passed - all systems operational'); - } - if (port) { - // Store active SSE transports by session ID - const sseTransports = new Map(); - // Create HTTP server for SSE transport - const httpServer = createServer(async (req, res) => { - const url = new URL(req.url, `http://localhost:${port}`); - if (req.method === 'GET' && url.pathname === '/sse') { - // Handle SSE connection - const transport = new SSEServerTransport('/message', res); - await this.server.connect(transport); - // Store the transport by session ID - sseTransports.set(transport.sessionId, transport); - Logger.info(`SSE connection established with session ID: ${transport.sessionId}`); - // Clean up when connection closes - transport.onclose = () => { - sseTransports.delete(transport.sessionId); - Logger.info(`SSE connection closed for session: ${transport.sessionId}`); - }; - } - else if (req.method === 'POST' && url.pathname === '/message') { - // Handle POST messages - route to the correct transport - const sessionId = url.searchParams.get('sessionId'); - if (!sessionId) { - res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('Missing sessionId parameter'); - return; - } - const transport = sseTransports.get(sessionId); - if (!transport) { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('Session not found. Please establish SSE connection first.'); - return; - } - try { - await transport.handlePostMessage(req, res); - } - catch (error) { - Logger.error('Error handling POST message:', error); - if (!res.headersSent) { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Internal server error'); - } - } - } - else if (req.method === 'GET' && url.pathname === '/') { - // MCP Server Discovery - provide server capabilities and endpoints - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - protocolVersion: '2024-11-05', - capabilities: { - tools: {}, - logging: {} - }, - serverInfo: { - name: 'xcode-mcp-server', - version: '1.7.4' - }, - transport: { - type: 'sse', - endpoints: { - sse: '/sse', - message: '/message' - } - }, - status: 'running', - activeSessions: sseTransports.size - })); - } - else if (req.method === 'GET' && url.pathname === '/.well-known/mcp') { - // MCP Discovery endpoint - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - protocolVersion: '2024-11-05', - serverInfo: { - name: 'xcode-mcp-server', - version: '1.7.4' - }, - capabilities: { - tools: {}, - logging: {} - }, - transport: { - type: 'sse', - sseEndpoint: '/sse', - messageEndpoint: '/message' - } - })); - } - else { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('Not found'); - } - }); - httpServer.listen(port, () => { - Logger.info(`Server started successfully on port ${port} with SSE transport`); - Logger.info(`SSE endpoint: http://localhost:${port}/sse`); - Logger.info(`Status page: http://localhost:${port}/`); - }); - } - else { - const transport = new StdioServerTransport(); - await this.server.connect(transport); - Logger.info('Server started successfully on stdio'); - } - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error('Failed to start server:', errorMessage); - Logger.warn('Attempting to start in minimal mode...'); - try { - // Attempt to start in minimal mode without validation - if (port) { - Logger.warn('Minimal mode does not support HTTP/SSE - falling back to stdio'); - } - const transport = new StdioServerTransport(); - await this.server.connect(transport); - Logger.warn('Server started in minimal mode (validation failed)'); - Logger.info('Use "xcode_health_check" tool to diagnose startup issues'); - } - catch (fallbackError) { - const fallbackErrorMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); - Logger.error('Critical failure - unable to start server:', fallbackErrorMessage); - await Logger.flush(); - process.exit(1); - } - } - } -} -// Only run if not in test environment -if (process.env.NODE_ENV !== 'test') { - // Check for --no-clean argument - const noCleanArg = process.argv.includes('--no-clean'); - const includeClean = !noCleanArg; - // Check for preferred values from environment variables or command-line arguments - const preferredScheme = process.env.XCODE_MCP_PREFERRED_SCHEME || - process.argv.find(arg => arg.startsWith('--preferred-scheme='))?.split('=')[1]; - const preferredXcodeproj = process.env.XCODE_MCP_PREFERRED_XCODEPROJ || - process.argv.find(arg => arg.startsWith('--preferred-xcodeproj='))?.split('=')[1]; - const serverOptions = { includeClean }; - if (preferredScheme) - serverOptions.preferredScheme = preferredScheme; - if (preferredXcodeproj) - serverOptions.preferredXcodeproj = preferredXcodeproj; - const server = new XcodeMCPServer(serverOptions); - // Check for port argument - const portArg = process.argv.find(arg => arg.startsWith('--port=')); - const portValue = portArg?.split('=')[1]; - const port = portValue ? parseInt(portValue, 10) : undefined; - server.start(port).catch(async (error) => { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error('Server startup failed:', errorMessage); - await Logger.flush(); - process.exit(1); - }); -} -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index a6e9c78..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,+DAA+D;AAC/D,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;IACxC,MAAM,CAAC,KAAK,CAAC,oEAAoE,EAAE,KAAK,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,uCAAuC;IACvC,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1E,qEAAqE;AACvE,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;IACnD,MAAM,CAAC,KAAK,CAAC,6EAA6E,EAAE,MAAM,CAAC,CAAC;IACpG,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAClC,uCAAuC;IACvC,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,MAAM,CAAC,CAAC;IACnE,gCAAgC;AAClC,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,MAAM,OAAO,cAAe,SAAQ,WAAW;IAC7C,YAAY,UAIR,EAAE;QACJ,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,IAAa;QAC9B,IAAI,CAAC;YACH,4BAA4B;YAC5B,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;YAC7D,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;YAExD,MAAM,UAAU,GAA0B,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAE3E,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;gBAC9B,IAAI,UAAU,CAAC,OAAO,CAAC,wBAAwB,EAAE,CAAC;oBAChD,MAAM,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;oBACrE,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;oBAChD,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;gBAC5E,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;oBACvD,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;oBACjD,MAAM,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;gBAC7E,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;YACzE,CAAC;YAED,IAAI,IAAI,EAAE,CAAC;gBACT,4CAA4C;gBAC5C,MAAM,aAAa,GAAG,IAAI,GAAG,EAA8B,CAAC;gBAE5D,uCAAuC;gBACvC,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;oBACjD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAI,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;oBAE1D,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;wBACpD,wBAAwB;wBACxB,MAAM,SAAS,GAAG,IAAI,kBAAkB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;wBAC1D,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;wBAErC,oCAAoC;wBACpC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;wBAClD,MAAM,CAAC,IAAI,CAAC,+CAA+C,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;wBAElF,kCAAkC;wBAClC,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE;4BACvB,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;4BAC1C,MAAM,CAAC,IAAI,CAAC,sCAAsC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;wBAC3E,CAAC,CAAC;oBAEJ,CAAC;yBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;wBAChE,wDAAwD;wBACxD,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;wBAEpD,IAAI,CAAC,SAAS,EAAE,CAAC;4BACf,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;4BACrD,GAAG,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;4BACvC,OAAO;wBACT,CAAC;wBAED,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;wBAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;4BACf,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;4BACrD,GAAG,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;4BACrE,OAAO;wBACT,CAAC;wBAED,IAAI,CAAC;4BACH,MAAM,SAAS,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;wBAC9C,CAAC;wBAAC,OAAO,KAAK,EAAE,CAAC;4BACf,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;4BACpD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gCACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;gCACrD,GAAG,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;4BACnC,CAAC;wBACH,CAAC;oBAEH,CAAC;yBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;wBACxD,mEAAmE;wBACnE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;4BACrB,eAAe,EAAE,YAAY;4BAC7B,YAAY,EAAE;gCACZ,KAAK,EAAE,EAAE;gCACT,OAAO,EAAE,EAAE;6BACZ;4BACD,UAAU,EAAE;gCACV,IAAI,EAAE,kBAAkB;gCACxB,OAAO,EAAE,OAAO;6BACjB;4BACD,SAAS,EAAE;gCACT,IAAI,EAAE,KAAK;gCACX,SAAS,EAAE;oCACT,GAAG,EAAE,MAAM;oCACX,OAAO,EAAE,UAAU;iCACpB;6BACF;4BACD,MAAM,EAAE,SAAS;4BACjB,cAAc,EAAE,aAAa,CAAC,IAAI;yBACnC,CAAC,CAAC,CAAC;oBACN,CAAC;yBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;wBACvE,yBAAyB;wBACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;4BACrB,eAAe,EAAE,YAAY;4BAC7B,UAAU,EAAE;gCACV,IAAI,EAAE,kBAAkB;gCACxB,OAAO,EAAE,OAAO;6BACjB;4BACD,YAAY,EAAE;gCACZ,KAAK,EAAE,EAAE;gCACT,OAAO,EAAE,EAAE;6BACZ;4BACD,SAAS,EAAE;gCACT,IAAI,EAAE,KAAK;gCACX,WAAW,EAAE,MAAM;gCACnB,eAAe,EAAE,UAAU;6BAC5B;yBACF,CAAC,CAAC,CAAC;oBACN,CAAC;yBAAM,CAAC;wBACN,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;wBACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;oBACvB,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;oBAC3B,MAAM,CAAC,IAAI,CAAC,uCAAuC,IAAI,qBAAqB,CAAC,CAAC;oBAC9E,MAAM,CAAC,IAAI,CAAC,kCAAkC,IAAI,MAAM,CAAC,CAAC;oBAC1D,MAAM,CAAC,IAAI,CAAC,iCAAiC,IAAI,GAAG,CAAC,CAAC;gBACxD,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;YACtD,CAAC;QAEH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,YAAY,CAAC,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;YAEtD,IAAI,CAAC;gBACH,sDAAsD;gBACtD,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;gBAChF,CAAC;gBACD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrC,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;gBAClE,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YAC1E,CAAC;YAAC,OAAO,aAAa,EAAE,CAAC;gBACvB,MAAM,oBAAoB,GAAG,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBAC5G,MAAM,CAAC,KAAK,CAAC,4CAA4C,EAAE,oBAAoB,CAAC,CAAC;gBACjF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,sCAAsC;AACtC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;IACpC,gCAAgC;IAChC,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,CAAC,UAAU,CAAC;IAEjC,kFAAkF;IAClF,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B;QAC5D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjF,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B;QAClE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEpF,MAAM,aAAa,GAIf,EAAE,YAAY,EAAE,CAAC;IAErB,IAAI,eAAe;QAAE,aAAa,CAAC,eAAe,GAAG,eAAe,CAAC;IACrE,IAAI,kBAAkB;QAAE,aAAa,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAE9E,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,aAAa,CAAC,CAAC;IAEjD,0BAA0B;IAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAc,EAAE,EAAE;QAChD,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;QACrD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"} \ No newline at end of file diff --git a/dist/mcp/index.d.ts b/dist/mcp/index.d.ts deleted file mode 100644 index 4665625..0000000 --- a/dist/mcp/index.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { EventEmitter } from 'events'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -export interface SseEvent { - type: string; - data: any; -} -export interface CallToolOptions { - onEvent?: (event: SseEvent) => void; -} -export interface ToolDefinition { - name: string; - description: string; - inputSchema: any; -} -export declare class McpLibrary extends EventEmitter { - private initialized; - private cliPath; - constructor(); - /** - * Initialize the MCP library if not already initialized - */ - private initialize; - /** - * Get all available tools with their schemas - */ - getTools(): Promise; - /** - * Spawn CLI subprocess and execute command - */ - private spawnCli; - /** - * Call a tool with the given name and arguments - * This spawns the CLI subprocess to execute the tool - */ - callTool(name: string, args?: Record, options?: CallToolOptions): Promise; - /** - * Get CLI path (for advanced use cases) - */ - getCliPath(): string; -} -/** - * Get or create the default MCP library instance - */ -export declare function getDefaultLibrary(): McpLibrary; -/** - * Call a tool using the default library instance - */ -export declare function callTool(name: string, args?: Record, options?: CallToolOptions): Promise; -/** - * Get all available tools using the default library instance - */ -export declare function getTools(): Promise; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/mcp/index.d.ts.map b/dist/mcp/index.d.ts.map deleted file mode 100644 index 01174af..0000000 --- a/dist/mcp/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAIzE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,GAAG,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,GAAG,CAAC;CAClB;AAED,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;;IAQxB;;OAEG;YACW,UAAU;IAgBxB;;OAEG;IACU,QAAQ,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAuclD;;OAEG;YACW,QAAQ;IA4BtB;;;OAGG;IACU,QAAQ,CACnB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC;IAuD1B;;OAEG;IACI,UAAU,IAAI,MAAM;CAG5B;AAKD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,CAK9C;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAGzB;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAG1D"} \ No newline at end of file diff --git a/dist/mcp/index.js b/dist/mcp/index.js deleted file mode 100644 index 084ecf8..0000000 --- a/dist/mcp/index.js +++ /dev/null @@ -1,597 +0,0 @@ -import { spawn } from 'child_process'; -import { Logger } from '../utils/Logger.js'; -import { EventEmitter } from 'events'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; -const __dirname = dirname(fileURLToPath(import.meta.url)); -export class McpLibrary extends EventEmitter { - initialized = false; - cliPath; - constructor() { - super(); - // Path to the CLI executable - this.cliPath = join(__dirname, '../cli.js'); - } - /** - * Initialize the MCP library if not already initialized - */ - async initialize() { - if (this.initialized) { - return; - } - try { - // Test CLI is available by calling help - await this.spawnCli(['--help']); - this.initialized = true; - Logger.debug('MCP library initialized successfully'); - } - catch (error) { - Logger.error('Failed to initialize MCP library:', error); - throw error; - } - } - /** - * Get all available tools with their schemas - */ - async getTools() { - await this.initialize(); - try { - // Get tools from CLI subprocess - const result = await this.spawnCli(['--json', 'list-tools']); - if (result.exitCode !== 0) { - throw new Error(`Failed to get tools: ${result.stderr || result.stdout}`); - } - // Parse and return tool definitions - const toolsData = JSON.parse(result.stdout); - if (Array.isArray(toolsData)) { - return toolsData.map(tool => ({ - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema - })); - } - // Fallback to hardcoded tools if CLI doesn't return expected format - Logger.warn('CLI returned unexpected format, using fallback tool definitions'); - } - catch (error) { - Logger.error('Failed to get tools from CLI, using fallback:', error); - } - // Fallback to hardcoded tool definitions - return [ - { - name: 'xcode_open_project', - description: 'Open an Xcode project or workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_close_project', - description: 'Close the currently active Xcode project or workspace (automatically stops any running actions first)', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_build', - description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination.', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: 'Name of the scheme to build', - }, - destination: { - type: 'string', - description: 'Build destination (optional - uses active destination if not provided)', - }, - }, - required: ['xcodeproj', 'scheme'], - }, - }, - { - name: 'xcode_get_schemes', - description: 'Get list of available schemes for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_set_active_scheme', - description: 'Set the active scheme for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme_name: { - type: 'string', - description: 'Name of the scheme to activate', - }, - }, - required: ['xcodeproj', 'scheme_name'], - }, - }, - { - name: 'xcode_clean', - description: 'Clean the build directory for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_test', - description: 'Run tests for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - command_line_arguments: { - type: 'array', - items: { type: 'string' }, - description: 'Additional command line arguments', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_build_and_run', - description: 'Build and run a specific project with the specified scheme', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: 'Name of the scheme to run', - }, - command_line_arguments: { - type: 'array', - items: { type: 'string' }, - description: 'Additional command line arguments', - }, - }, - required: ['xcodeproj', 'scheme'], - }, - }, - { - name: 'xcode_debug', - description: 'Start debugging session for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: 'Scheme name (optional)', - }, - skip_building: { - type: 'boolean', - description: 'Whether to skip building', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_stop', - description: 'Stop the current scheme action', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - { - name: 'find_xcresults', - description: 'Find all XCResult files for a specific project with timestamps and file information', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_get_run_destinations', - description: 'Get list of available run destinations for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_get_workspace_info', - description: 'Get information about a specific workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_get_projects', - description: 'Get list of projects in a specific workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_open_file', - description: 'Open a file in Xcode', - inputSchema: { - type: 'object', - properties: { - file_path: { - type: 'string', - description: 'Absolute path to the file to open', - }, - line_number: { - type: 'number', - description: 'Optional line number to navigate to', - }, - }, - required: ['file_path'], - }, - }, - { - name: 'xcode_health_check', - description: 'Perform a comprehensive health check of the XcodeMCP environment and configuration', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - { - name: 'xcresult_browse', - description: 'Browse XCResult files - list all tests or show details for a specific test. Returns comprehensive test results including pass/fail status, failure details, and browsing instructions. Large console output (>20 lines or >2KB) is automatically saved to a temporary file.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Optional test ID or index number to show details for a specific test', - }, - include_console: { - type: 'boolean', - description: 'Whether to include console output and test activities (only used with test_id)', - default: false, - }, - }, - required: ['xcresult_path'], - }, - }, - { - name: 'xcresult_browser_get_console', - description: 'Get console output and test activities for a specific test in an XCResult file. Large output (>20 lines or >2KB) is automatically saved to a temporary file.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get console output for', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_summary', - description: 'Get a quick summary of test results from an XCResult file', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - }, - required: ['xcresult_path'], - }, - }, - { - name: 'xcresult_get_screenshot', - description: 'Get screenshot from a failed test at specific timestamp - extracts frame from video attachment using ffmpeg', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get screenshot for', - }, - timestamp: { - type: 'number', - description: 'Timestamp in seconds when to extract the screenshot. WARNING: Use a timestamp BEFORE the failure (e.g., if failure is at 30.71s, use 30.69s) as failure timestamps often show the home screen after the app has crashed or reset.', - }, - }, - required: ['xcresult_path', 'test_id', 'timestamp'], - }, - }, - { - name: 'xcresult_get_ui_hierarchy', - description: 'Get UI hierarchy attachment from test. Returns raw accessibility tree (best for AI), slim AI-readable JSON (default), or full JSON.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get UI hierarchy for', - }, - timestamp: { - type: 'number', - description: 'Optional timestamp in seconds to find the closest UI snapshot. If not provided, uses the first available UI snapshot.', - }, - full_hierarchy: { - type: 'boolean', - description: 'Set to true to get the full hierarchy (several MB). Default is false for AI-readable slim version.', - }, - raw_format: { - type: 'boolean', - description: 'Set to true to get the raw accessibility tree text (most AI-friendly). Default is false for JSON format.', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_get_ui_element', - description: 'Get full details of a specific UI element by index from a previously exported UI hierarchy JSON file', - inputSchema: { - type: 'object', - properties: { - hierarchy_json_path: { - type: 'string', - description: 'Absolute path to the UI hierarchy JSON file (the full version saved by xcresult-get-ui-hierarchy)', - }, - element_index: { - type: 'number', - description: 'Index of the element to get details for (the "j" value from the slim hierarchy)', - }, - include_children: { - type: 'boolean', - description: 'Whether to include children in the response. Defaults to false.', - }, - }, - required: ['hierarchy_json_path', 'element_index'], - }, - }, - { - name: 'xcresult_list_attachments', - description: 'List all attachments for a specific test - shows attachment names, types, and indices for export', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to list attachments for', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_export_attachment', - description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number that contains the attachment', - }, - attachment_index: { - type: 'number', - description: 'Index number of the attachment to export (1-based, from xcresult-list-attachments)', - }, - convert_to_json: { - type: 'boolean', - description: 'If true and attachment is an App UI hierarchy, convert to JSON format', - }, - }, - required: ['xcresult_path', 'test_id', 'attachment_index'], - }, - }, - ]; - } - /** - * Spawn CLI subprocess and execute command - */ - async spawnCli(args) { - return new Promise((resolve, reject) => { - const child = spawn('node', [this.cliPath, ...args], { - stdio: ['inherit', 'pipe', 'pipe'], - env: process.env - }); - let stdout = ''; - let stderr = ''; - child.stdout.on('data', (data) => { - stdout += data.toString(); - }); - child.stderr.on('data', (data) => { - stderr += data.toString(); - }); - child.on('close', (code) => { - resolve({ stdout, stderr, exitCode: code || 0 }); - }); - child.on('error', (error) => { - reject(error); - }); - }); - } - /** - * Call a tool with the given name and arguments - * This spawns the CLI subprocess to execute the tool - */ - async callTool(name, args = {}, options = {}) { - await this.initialize(); - try { - Logger.debug(`Calling tool: ${name} with args:`, args); - // Convert tool name to CLI command name - const commandName = name.replace(/^xcode_/, '').replace(/_/g, '-'); - // Build CLI arguments - const cliArgs = ['--json', commandName, '--json-input', JSON.stringify(args)]; - // Execute CLI subprocess - const result = await this.spawnCli(cliArgs); - // Parse events from stderr if callback provided - if (options.onEvent && result.stderr) { - const lines = result.stderr.split('\n'); - for (const line of lines) { - if (line.startsWith('event:')) { - const eventType = line.replace('event:', ''); - const nextLine = lines[lines.indexOf(line) + 1]; - if (nextLine?.startsWith('data:')) { - try { - const eventData = JSON.parse(nextLine.replace('data:', '')); - options.onEvent({ type: eventType, data: eventData }); - } - catch (parseError) { - Logger.debug('Failed to parse SSE event:', parseError); - } - } - } - } - } - // Handle CLI exit code - if (result.exitCode !== 0) { - throw new Error(`CLI command failed with exit code ${result.exitCode}: ${result.stderr || result.stdout}`); - } - // Parse JSON output from CLI - try { - const parsedResult = JSON.parse(result.stdout); - Logger.debug(`Tool ${name} completed successfully`); - return parsedResult; - } - catch (parseError) { - // If JSON parsing fails, wrap stdout in text content - Logger.debug(`Tool ${name} completed with non-JSON output`); - return { content: [{ type: 'text', text: result.stdout }] }; - } - } - catch (error) { - Logger.error(`Tool ${name} failed:`, error); - throw error; - } - } - /** - * Get CLI path (for advanced use cases) - */ - getCliPath() { - return this.cliPath; - } -} -// Export convenience functions for direct usage -let defaultLibrary = null; -/** - * Get or create the default MCP library instance - */ -export function getDefaultLibrary() { - if (!defaultLibrary) { - defaultLibrary = new McpLibrary(); - } - return defaultLibrary; -} -/** - * Call a tool using the default library instance - */ -export async function callTool(name, args = {}, options = {}) { - const library = getDefaultLibrary(); - return library.callTool(name, args, options); -} -/** - * Get all available tools using the default library instance - */ -export async function getTools() { - const library = getDefaultLibrary(); - return library.getTools(); -} -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/mcp/index.js.map b/dist/mcp/index.js.map deleted file mode 100644 index 1e8d141..0000000 --- a/dist/mcp/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/shared/toolDefinitions.d.ts b/dist/shared/toolDefinitions.d.ts deleted file mode 100644 index dca9f45..0000000 --- a/dist/shared/toolDefinitions.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface ToolDefinition { - name: string; - description: string; - inputSchema: any; -} -/** - * Get all tool definitions shared between CLI and MCP - */ -export declare function getToolDefinitions(options?: { - includeClean?: boolean; - preferredScheme?: string; - preferredXcodeproj?: string; -}): ToolDefinition[]; -//# sourceMappingURL=toolDefinitions.d.ts.map \ No newline at end of file diff --git a/dist/shared/toolDefinitions.d.ts.map b/dist/shared/toolDefinitions.d.ts.map deleted file mode 100644 index f035903..0000000 --- a/dist/shared/toolDefinitions.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"toolDefinitions.d.ts","sourceRoot":"","sources":["../../src/shared/toolDefinitions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE;IAC1C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACJ,GAAG,cAAc,EAAE,CAqhB5C"} \ No newline at end of file diff --git a/dist/shared/toolDefinitions.js b/dist/shared/toolDefinitions.js deleted file mode 100644 index 0e0a776..0000000 --- a/dist/shared/toolDefinitions.js +++ /dev/null @@ -1,536 +0,0 @@ -/** - * Get all tool definitions shared between CLI and MCP - */ -export function getToolDefinitions(options = { includeClean: true }) { - const { includeClean = true, preferredScheme, preferredXcodeproj } = options; - const tools = [ - { - name: 'xcode_open_project', - description: 'Open an Xcode project or workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_close_project', - description: 'Close the currently active Xcode project or workspace (automatically stops any running actions first)', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_build', - description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination. ⏱️ Can take minutes to hours - do not timeout.', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file to build (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: preferredScheme - ? `Name of the scheme to build - defaults to ${preferredScheme}` - : 'Name of the scheme to build', - }, - destination: { - type: 'string', - description: 'Build destination (optional - uses active destination if not provided)', - }, - }, - required: [ - ...(!preferredXcodeproj ? ['xcodeproj'] : []), - ...(!preferredScheme ? ['scheme'] : []) - ], - }, - }, - { - name: 'xcode_get_schemes', - description: 'Get list of available schemes for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_set_active_scheme', - description: 'Set the active scheme for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme_name: { - type: 'string', - description: 'Name of the scheme to activate', - }, - }, - required: preferredXcodeproj ? ['scheme_name'] : ['xcodeproj', 'scheme_name'], - }, - }, - { - name: 'xcode_test', - description: 'Run tests for a specific project. Optionally run only specific tests or test classes by temporarily modifying the test plan (automatically restored after completion). ⏱️ Can take minutes to hours - do not timeout.', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - destination: { - type: 'string', - description: 'Test destination (required for predictable test environments) - e.g., "iPhone 15 Pro Simulator", "iPad Air Simulator"', - }, - command_line_arguments: { - type: 'array', - items: { type: 'string' }, - description: 'Additional command line arguments', - }, - test_plan_path: { - type: 'string', - description: 'Optional: Absolute path to .xctestplan file to temporarily modify for selective test execution', - }, - selected_tests: { - type: 'array', - items: { type: 'string' }, - description: 'Optional: Array of specific test identifiers to run. Format depends on test framework: XCTest: "TestAppUITests/testExample" (no parentheses), Swift Testing: "TestAppTests/example". Requires test_plan_path.', - }, - selected_test_classes: { - type: 'array', - items: { type: 'string' }, - description: 'Optional: Array of test class names to run (e.g., ["TestAppTests", "TestAppUITests"]). This runs ALL tests in the specified classes. Requires test_plan_path.', - }, - test_target_identifier: { - type: 'string', - description: 'Optional: Target identifier for the test target (required when using test filtering). Can be found in project.pbxproj.', - }, - test_target_name: { - type: 'string', - description: 'Optional: Target name for the test target (alternative to test_target_identifier). Example: "TestAppTests".', - }, - }, - required: preferredXcodeproj ? ['destination'] : ['xcodeproj', 'destination'], - }, - }, - { - name: 'xcode_build_and_run', - description: 'Build and run a specific project with the specified scheme. ⏱️ Can run indefinitely - do not timeout.', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: preferredScheme - ? `Name of the scheme to run - defaults to ${preferredScheme}` - : 'Name of the scheme to run', - }, - command_line_arguments: { - type: 'array', - items: { type: 'string' }, - description: 'Additional command line arguments', - }, - }, - required: [ - ...(!preferredXcodeproj ? ['xcodeproj'] : []), - ...(!preferredScheme ? ['scheme'] : []) - ], - }, - }, - { - name: 'xcode_debug', - description: 'Start debugging session for a specific project. ⏱️ Can run indefinitely - do not timeout.', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: preferredScheme - ? `Scheme name (optional) - defaults to ${preferredScheme}` - : 'Scheme name (optional)', - }, - skip_building: { - type: 'boolean', - description: 'Whether to skip building', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_stop', - description: 'Stop the current scheme action for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'find_xcresults', - description: 'Find all XCResult files for a specific project with timestamps and file information', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_get_run_destinations', - description: 'Get list of available run destinations for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_get_workspace_info', - description: 'Get information about a specific workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_get_projects', - description: 'Get list of projects in a specific workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_open_file', - description: 'Open a file in Xcode', - inputSchema: { - type: 'object', - properties: { - file_path: { - type: 'string', - description: 'Absolute path to the file to open', - }, - line_number: { - type: 'number', - description: 'Optional line number to navigate to', - }, - }, - required: ['file_path'], - }, - }, - { - name: 'xcode_health_check', - description: 'Perform a comprehensive health check of the XcodeMCP environment and configuration', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - { - name: 'xcresult_browse', - description: 'Browse XCResult files - list all tests or show details for a specific test. Returns comprehensive test results including pass/fail status, failure details, and browsing instructions. Large console output (>20 lines or >2KB) is automatically saved to a temporary file.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Optional test ID or index number to show details for a specific test', - }, - include_console: { - type: 'boolean', - description: 'Whether to include console output and test activities (only used with test_id)', - default: false, - }, - }, - required: ['xcresult_path'], - }, - }, - { - name: 'xcresult_browser_get_console', - description: 'Get console output and test activities for a specific test in an XCResult file. Large output (>20 lines or >2KB) is automatically saved to a temporary file.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get console output for', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_summary', - description: 'Get a quick summary of test results from an XCResult file', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - }, - required: ['xcresult_path'], - }, - }, - { - name: 'xcresult_get_screenshot', - description: 'Get screenshot from a failed test at specific timestamp - extracts frame from video attachment using ffmpeg', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get screenshot for', - }, - timestamp: { - type: 'number', - description: 'Timestamp in seconds when to extract the screenshot. WARNING: Use a timestamp BEFORE the failure (e.g., if failure is at 30.71s, use 30.69s) as failure timestamps often show the home screen after the app has crashed or reset.', - }, - }, - required: ['xcresult_path', 'test_id', 'timestamp'], - }, - }, - { - name: 'xcresult_get_ui_hierarchy', - description: 'Get UI hierarchy attachment from test. Returns raw accessibility tree (best for AI), slim AI-readable JSON (default), or full JSON.', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to get UI hierarchy for', - }, - timestamp: { - type: 'number', - description: 'Optional timestamp in seconds to find the closest UI snapshot. If not provided, uses the first available UI snapshot.', - }, - full_hierarchy: { - type: 'boolean', - description: 'Set to true to get the full hierarchy (several MB). Default is false for AI-readable slim version.', - }, - raw_format: { - type: 'boolean', - description: 'Set to true to get the raw accessibility tree text (most AI-friendly). Default is false for JSON format.', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_get_ui_element', - description: 'Get full details of a specific UI element by index from a previously exported UI hierarchy JSON file', - inputSchema: { - type: 'object', - properties: { - hierarchy_json_path: { - type: 'string', - description: 'Absolute path to the UI hierarchy JSON file (the full version saved by xcresult-get-ui-hierarchy)', - }, - element_index: { - type: 'number', - description: 'Index of the element to get details for (the "j" value from the slim hierarchy)', - }, - include_children: { - type: 'boolean', - description: 'Whether to include children in the response. Defaults to false.', - }, - }, - required: ['hierarchy_json_path', 'element_index'], - }, - }, - { - name: 'xcresult_list_attachments', - description: 'List all attachments for a specific test - shows attachment names, types, and indices for export', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number to list attachments for', - }, - }, - required: ['xcresult_path', 'test_id'], - }, - }, - { - name: 'xcresult_export_attachment', - description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON', - inputSchema: { - type: 'object', - properties: { - xcresult_path: { - type: 'string', - description: 'Absolute path to the .xcresult file', - }, - test_id: { - type: 'string', - description: 'Test ID or index number that contains the attachment', - }, - attachment_index: { - type: 'number', - description: 'Index number of the attachment to export (1-based, from xcresult-list-attachments)', - }, - convert_to_json: { - type: 'boolean', - description: 'If true and attachment is an App UI hierarchy, convert to JSON format', - }, - }, - required: ['xcresult_path', 'test_id', 'attachment_index'], - }, - }, - { - name: 'xcode_refresh_project', - description: 'Refresh/reload an Xcode project by closing and reopening it to pick up external changes like modified .xctestplan files', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) to refresh', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_get_test_targets', - description: 'Get information about test targets in a project, including names and identifiers', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available)', - }, - }, - required: ['xcodeproj'], - }, - }, - ]; - // Conditionally add the clean tool - if (includeClean) { - tools.splice(5, 0, { - name: 'xcode_clean', - description: 'Clean the build directory for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }); - } - return tools; -} -//# sourceMappingURL=toolDefinitions.js.map \ No newline at end of file diff --git a/dist/shared/toolDefinitions.js.map b/dist/shared/toolDefinitions.js.map deleted file mode 100644 index a72b22a..0000000 --- a/dist/shared/toolDefinitions.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"toolDefinitions.js","sourceRoot":"","sources":["../../src/shared/toolDefinitions.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/BuildTools.d.ts b/dist/tools/BuildTools.d.ts deleted file mode 100644 index 07954c8..0000000 --- a/dist/tools/BuildTools.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { McpResult, OpenProjectCallback } from '../types/index.js'; -export declare class BuildTools { - static build(projectPath: string, schemeName: string, destination: string | null | undefined, openProject: OpenProjectCallback): Promise; - static clean(projectPath: string, openProject: OpenProjectCallback): Promise; - static test(projectPath: string, destination: string, commandLineArguments: string[] | undefined, openProject: OpenProjectCallback, options?: { - testPlanPath?: string; - selectedTests?: string[]; - selectedTestClasses?: string[]; - testTargetIdentifier?: string; - testTargetName?: string; - }): Promise; - /** - * Helper method to restore test plan and return result - */ - private static _restoreTestPlanAndReturn; - static run(projectPath: string, schemeName: string, commandLineArguments: string[] | undefined, openProject: OpenProjectCallback): Promise; - static debug(projectPath: string, scheme?: string, skipBuilding?: boolean, openProject?: OpenProjectCallback): Promise; - static stop(projectPath: string): Promise; - private static _getAvailableSchemes; - private static _getAvailableDestinations; - private static _findXCResultFiles; - private static _findNewXCResultFile; - /** - * Find XCResult files for a given project - */ - static findXCResults(projectPath: string): Promise; - private static _getTimeAgo; - private static _formatFileSize; - /** - * Handle alerts that appear when starting builds/tests while another operation is in progress. - * This includes "replace existing build" alerts and similar dialog overlays. - */ - private static _handleReplaceExistingBuildAlert; -} -//# sourceMappingURL=BuildTools.d.ts.map \ No newline at end of file diff --git a/dist/tools/BuildTools.d.ts.map b/dist/tools/BuildTools.d.ts.map deleted file mode 100644 index ff08e5b..0000000 --- a/dist/tools/BuildTools.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BuildTools.d.ts","sourceRoot":"","sources":["../../src/tools/BuildTools.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExE,qBAAa,UAAU;WACD,KAAK,CACvB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAAG,IAAI,YAAO,EACjC,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,SAAS,CAAC;WA4RD,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;WA2BhF,IAAI,CACtB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,oBAAoB,EAAE,MAAM,EAAE,YAAK,EACnC,WAAW,EAAE,mBAAmB,EAChC,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GACA,OAAO,CAAC,SAAS,CAAC;IA8kBrB;;OAEG;mBACkB,yBAAyB;WA0B1B,GAAG,CACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,oBAAoB,EAAE,MAAM,EAAE,YAAK,EACnC,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,SAAS,CAAC;WA4MD,KAAK,CACvB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,YAAY,UAAQ,EACpB,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,SAAS,CAAC;WAiCD,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;mBAc5C,oBAAoB;mBAoBpB,yBAAyB;mBAmBzB,kBAAkB;mBAuClB,oBAAoB;IAsDzC;;OAEG;WACiB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IA6C1E,OAAO,CAAC,MAAM,CAAC,WAAW;IAc1B,OAAO,CAAC,MAAM,CAAC,eAAe;IAU9B;;;OAGG;mBACkB,gCAAgC;CA2KtD"} \ No newline at end of file diff --git a/dist/tools/BuildTools.js b/dist/tools/BuildTools.js deleted file mode 100644 index 24bd1a9..0000000 --- a/dist/tools/BuildTools.js +++ /dev/null @@ -1,1409 +0,0 @@ -import { stat } from 'fs/promises'; -import { readdir } from 'fs/promises'; -import { join } from 'path'; -import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; -import { JXAExecutor } from '../utils/JXAExecutor.js'; -import { BuildLogParser } from '../utils/BuildLogParser.js'; -import { PathValidator } from '../utils/PathValidator.js'; -import { ErrorHelper } from '../utils/ErrorHelper.js'; -import { ParameterNormalizer } from '../utils/ParameterNormalizer.js'; -import { Logger } from '../utils/Logger.js'; -import { XCResultParser } from '../utils/XCResultParser.js'; -import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; -export class BuildTools { - static async build(projectPath, schemeName, destination = null, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - // Normalize the scheme name for better matching - const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); - const setSchemeScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const schemes = workspace.schemes(); - const schemeNames = schemes.map(scheme => scheme.name()); - - // Try exact match first - let targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(normalizedSchemeName)}); - - // If not found, try original name - if (!targetScheme) { - targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(schemeName)}); - } - - if (!targetScheme) { - throw new Error('Scheme not found. Available: ' + JSON.stringify(schemeNames)); - } - - workspace.activeScheme = targetScheme; - return 'Scheme set to ' + targetScheme.name(); - })() - `; - try { - await JXAExecutor.execute(setSchemeScript); - } - catch (error) { - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('not found')) { - try { - // Get available schemes - const availableSchemes = await this._getAvailableSchemes(projectPath); - // Try to find a close match with fuzzy matching - const bestMatch = ParameterNormalizer.findBestMatch(schemeName, availableSchemes); - let message = `❌ Scheme '${schemeName}' not found\n\nAvailable schemes:\n`; - availableSchemes.forEach(scheme => { - if (scheme === bestMatch) { - message += ` • ${scheme} ← Did you mean this?\n`; - } - else { - message += ` • ${scheme}\n`; - } - }); - return { content: [{ type: 'text', text: message }] }; - } - catch { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Scheme '${schemeName}' not found`, ErrorHelper.getSchemeNotFoundGuidance(schemeName)) }] }; - } - } - return { content: [{ type: 'text', text: `Failed to set scheme '${schemeName}': ${errorMessage}` }] }; - } - if (destination) { - // Normalize the destination name for better matching - const normalizedDestination = ParameterNormalizer.normalizeDestinationName(destination); - const setDestinationScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const destinations = workspace.runDestinations(); - const destinationNames = destinations.map(dest => dest.name()); - - // Try exact match first - let targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(normalizedDestination)}); - - // If not found, try original name - if (!targetDestination) { - targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(destination)}); - } - - if (!targetDestination) { - throw new Error('Destination not found. Available: ' + JSON.stringify(destinationNames)); - } - - workspace.activeRunDestination = targetDestination; - return 'Destination set to ' + targetDestination.name(); - })() - `; - try { - await JXAExecutor.execute(setDestinationScript); - } - catch (error) { - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('not found')) { - try { - // Extract available destinations from error message if present - let availableDestinations = []; - if (errorMessage.includes('Available:')) { - const availablePart = errorMessage.split('Available: ')[1]; - // Find the JSON array part - const jsonMatch = availablePart?.match(/\[.*?\]/); - if (jsonMatch) { - try { - availableDestinations = JSON.parse(jsonMatch[0]); - } - catch { - availableDestinations = await this._getAvailableDestinations(projectPath); - } - } - } - else { - availableDestinations = await this._getAvailableDestinations(projectPath); - } - // Try to find a close match with fuzzy matching - const bestMatch = ParameterNormalizer.findBestMatch(destination, availableDestinations); - let guidance = ErrorHelper.getDestinationNotFoundGuidance(destination, availableDestinations); - if (bestMatch && bestMatch !== destination) { - guidance += `\n• Did you mean '${bestMatch}'?`; - } - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Destination '${destination}' not found`, guidance) }] }; - } - catch { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Destination '${destination}' not found`, ErrorHelper.getDestinationNotFoundGuidance(destination)) }] }; - } - } - return { content: [{ type: 'text', text: `Failed to set destination '${destination}': ${errorMessage}` }] }; - } - } - const buildScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - workspace.build(); - - return 'Build started'; - })() - `; - const buildStartTime = Date.now(); - try { - await JXAExecutor.execute(buildScript); - // Check for and handle "replace existing build" alert - await this._handleReplaceExistingBuildAlert(); - } - catch (error) { - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Failed to start build: ${errorMessage}` }] }; - } - Logger.info('Waiting for new build log to appear after build start...'); - let attempts = 0; - let newLog = null; - const initialWaitAttempts = 3600; // 1 hour max to wait for build log - while (attempts < initialWaitAttempts) { - const currentLog = await BuildLogParser.getLatestBuildLog(projectPath); - if (currentLog) { - const logTime = currentLog.mtime.getTime(); - const buildTime = buildStartTime; - Logger.debug(`Checking log: ${currentLog.path}, log time: ${logTime}, build time: ${buildTime}, diff: ${logTime - buildTime}ms`); - if (logTime > buildTime) { - newLog = currentLog; - Logger.info(`Found new build log created after build start: ${newLog.path}`); - break; - } - } - else { - Logger.debug(`No build log found yet, attempt ${attempts + 1}/${initialWaitAttempts}`); - } - await new Promise(resolve => setTimeout(resolve, 1000)); - attempts++; - } - if (!newLog) { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Build started but no new build log appeared within ${initialWaitAttempts} seconds`, ErrorHelper.getBuildLogNotFoundGuidance()) }] }; - } - Logger.info(`Monitoring build completion for log: ${newLog.path}`); - attempts = 0; - const maxAttempts = 3600; // 1 hour max for build completion - let lastLogSize = 0; - let stableCount = 0; - while (attempts < maxAttempts) { - try { - const logStats = await stat(newLog.path); - const currentLogSize = logStats.size; - if (currentLogSize === lastLogSize) { - stableCount++; - if (stableCount >= 1) { - Logger.debug(`Log stable for ${stableCount}s, trying to parse...`); - const results = await BuildLogParser.parseBuildLog(newLog.path); - Logger.debug(`Parse result has ${results.errors.length} errors, ${results.warnings.length} warnings`); - const isParseFailure = results.errors.some(error => typeof error === 'string' && error.includes('XCLogParser failed to parse the build log.')); - if (results && !isParseFailure) { - Logger.info(`Build completed, log parsed successfully: ${newLog.path}`); - break; - } - } - } - else { - lastLogSize = currentLogSize; - stableCount = 0; - } - } - catch (error) { - const currentLog = await BuildLogParser.getLatestBuildLog(projectPath); - if (currentLog && currentLog.path !== newLog.path && currentLog.mtime.getTime() > buildStartTime) { - Logger.debug(`Build log changed to: ${currentLog.path}`); - newLog = currentLog; - lastLogSize = 0; - stableCount = 0; - } - } - await new Promise(resolve => setTimeout(resolve, 1000)); - attempts++; - } - if (attempts >= maxAttempts) { - return { content: [{ type: 'text', text: `Build timed out after ${maxAttempts} seconds` }] }; - } - const results = await BuildLogParser.parseBuildLog(newLog.path); - let message = ''; - const schemeInfo = schemeName ? ` for scheme '${schemeName}'` : ''; - const destInfo = destination ? ` and destination '${destination}'` : ''; - Logger.info(`Build completed${schemeInfo}${destInfo} - ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); - // Handle stopped/interrupted builds - if (results.buildStatus === 'stopped') { - message = `⏹️ BUILD INTERRUPTED${schemeInfo}${destInfo}\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the build again to complete it.`; - return { content: [{ type: 'text', text: message }] }; - } - if (results.errors.length > 0) { - message = `❌ BUILD FAILED${schemeInfo}${destInfo} (${results.errors.length} errors)\n\nERRORS:\n`; - results.errors.forEach(error => { - message += ` • ${error}\n`; - Logger.error('Build error:', error); - }); - throw new McpError(ErrorCode.InternalError, message); - } - else if (results.warnings.length > 0) { - message = `⚠️ BUILD COMPLETED WITH WARNINGS${schemeInfo}${destInfo} (${results.warnings.length} warnings)\n\nWARNINGS:\n`; - results.warnings.forEach(warning => { - message += ` • ${warning}\n`; - Logger.warn('Build warning:', warning); - }); - } - else { - message = `✅ BUILD SUCCESSFUL${schemeInfo}${destInfo}`; - } - return { content: [{ type: 'text', text: message }] }; - } - static async clean(projectPath, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const actionResult = workspace.clean(); - - while (true) { - if (actionResult.completed()) { - break; - } - delay(0.5); - } - - return \`Clean completed. Result ID: \${actionResult.id()}\`; - })() - `; - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } - static async test(projectPath, destination, commandLineArguments = [], openProject, options) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - // Set the destination for testing - { - // Normalize the destination name for better matching - const normalizedDestination = ParameterNormalizer.normalizeDestinationName(destination); - const setDestinationScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const destinations = workspace.runDestinations(); - const destinationNames = destinations.map(dest => dest.name()); - - // Try exact match first - let targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(normalizedDestination)}); - - // If not found, try original name - if (!targetDestination) { - targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(destination)}); - } - - if (!targetDestination) { - throw new Error('Destination not found. Available: ' + JSON.stringify(destinationNames)); - } - - workspace.activeRunDestination = targetDestination; - return 'Destination set to ' + targetDestination.name(); - })() - `; - try { - await JXAExecutor.execute(setDestinationScript); - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('Destination not found')) { - // Extract available destinations from error message - try { - const availableMatch = errorMessage.match(/Available: (\[.*\])/); - if (availableMatch) { - const availableDestinations = JSON.parse(availableMatch[1]); - const bestMatch = ParameterNormalizer.findBestMatch(destination, availableDestinations); - let message = `❌ Destination '${destination}' not found\n\nAvailable destinations:\n`; - availableDestinations.forEach((dest) => { - if (dest === bestMatch) { - message += ` • ${dest} ← Did you mean this?\n`; - } - else { - message += ` • ${dest}\n`; - } - }); - return { content: [{ type: 'text', text: message }] }; - } - } - catch { - // Fall through to generic error - } - } - return { content: [{ type: 'text', text: `Failed to set destination '${destination}': ${errorMessage}` }] }; - } - } - // Handle test plan modification if selective tests are requested - let originalTestPlan = null; - let shouldRestoreTestPlan = false; - if (options?.testPlanPath && (options?.selectedTests?.length || options?.selectedTestClasses?.length)) { - if (!options.testTargetIdentifier && !options.testTargetName) { - return { - content: [{ - type: 'text', - text: 'Error: either test_target_identifier or test_target_name is required when using test filtering' - }] - }; - } - // If target name is provided but no identifier, look up the identifier - let targetIdentifier = options.testTargetIdentifier; - let targetName = options.testTargetName; - if (options.testTargetName && !options.testTargetIdentifier) { - try { - const { ProjectTools } = await import('./ProjectTools.js'); - const targetInfo = await ProjectTools.getTestTargets(projectPath); - // Parse the target info to find the identifier - const targetText = targetInfo.content?.[0]?.type === 'text' ? targetInfo.content[0].text : ''; - const namePattern = new RegExp(`\\*\\*${options.testTargetName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\*\\*\\s*\\n\\s*•\\s*Identifier:\\s*([A-F0-9]{24})`, 'i'); - const match = targetText.match(namePattern); - if (match && match[1]) { - targetIdentifier = match[1]; - targetName = options.testTargetName; - } - else { - return { - content: [{ - type: 'text', - text: `Error: Test target '${options.testTargetName}' not found. Use 'xcode_get_test_targets' to see available targets.` - }] - }; - } - } - catch (lookupError) { - return { - content: [{ - type: 'text', - text: `Error: Failed to lookup test target '${options.testTargetName}': ${lookupError instanceof Error ? lookupError.message : String(lookupError)}` - }] - }; - } - } - try { - // Import filesystem operations - const { promises: fs } = await import('fs'); - // Backup original test plan - originalTestPlan = await fs.readFile(options.testPlanPath, 'utf8'); - shouldRestoreTestPlan = true; - // Build selected tests array - let selectedTests = []; - // Add individual tests - if (options.selectedTests?.length) { - selectedTests.push(...options.selectedTests); - } - // Add all tests from selected test classes - if (options.selectedTestClasses?.length) { - // For now, add the class names - we'd need to scan for specific test methods later - selectedTests.push(...options.selectedTestClasses); - } - // Get project name from path for container reference - const { basename } = await import('path'); - const projectName = basename(projectPath, '.xcodeproj'); - // Create test target configuration - const testTargets = [{ - target: { - containerPath: `container:${projectName}.xcodeproj`, - identifier: targetIdentifier, - name: targetName || targetIdentifier - }, - selectedTests: selectedTests - }]; - // Update test plan temporarily - const { TestPlanTools } = await import('./TestPlanTools.js'); - await TestPlanTools.updateTestPlanAndReload(options.testPlanPath, projectPath, testTargets); - Logger.info(`Temporarily modified test plan to run ${selectedTests.length} selected tests`); - } - catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - return { - content: [{ - type: 'text', - text: `Failed to modify test plan: ${errorMsg}` - }] - }; - } - } - // Get initial xcresult files to detect new ones - const initialXCResults = await this._findXCResultFiles(projectPath); - const testStartTime = Date.now(); - Logger.info(`Test start time: ${new Date(testStartTime).toISOString()}, found ${initialXCResults.length} initial XCResult files`); - // Start the test action - const hasArgs = commandLineArguments && commandLineArguments.length > 0; - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - ${hasArgs - ? `const actionResult = workspace.test({withCommandLineArguments: ${JSON.stringify(commandLineArguments)}});` - : `const actionResult = workspace.test();`} - - // Return immediately - we'll monitor the build separately - return JSON.stringify({ - actionId: actionResult.id(), - message: 'Test started' - }); - })() - `; - try { - const startResult = await JXAExecutor.execute(script); - const { actionId, message } = JSON.parse(startResult); - Logger.info(`${message} with action ID: ${actionId}`); - // Check for and handle "replace existing build" alert - await this._handleReplaceExistingBuildAlert(); - // Check for build errors with polling approach - Logger.info('Monitoring for build logs...'); - // Poll for build logs for up to 30 seconds - let foundLogs = false; - for (let i = 0; i < 6; i++) { - await new Promise(resolve => setTimeout(resolve, 5000)); - const logs = await BuildLogParser.getRecentBuildLogs(projectPath, testStartTime); - if (logs.length > 0) { - Logger.info(`Found ${logs.length} build logs after ${(i + 1) * 5} seconds`); - foundLogs = true; - break; - } - Logger.info(`No logs found after ${(i + 1) * 5} seconds, continuing to wait...`); - } - if (!foundLogs) { - Logger.info('No build logs found after 30 seconds - build may not have started yet'); - } - Logger.info('Build monitoring complete, proceeding to analysis...'); - // Get ALL recent build logs for analysis (test might create multiple logs) - Logger.info(`DEBUG: testStartTime = ${testStartTime} (${new Date(testStartTime)})`); - Logger.info(`DEBUG: projectPath = ${projectPath}`); - // First check if we can find DerivedData - const derivedData = await BuildLogParser.findProjectDerivedData(projectPath); - Logger.info(`DEBUG: derivedData = ${derivedData}`); - const recentLogs = await BuildLogParser.getRecentBuildLogs(projectPath, testStartTime); - Logger.info(`DEBUG: recentLogs.length = ${recentLogs.length}`); - if (recentLogs.length > 0) { - Logger.info(`Analyzing ${recentLogs.length} recent build logs created during test...`); - let totalErrors = []; - let totalWarnings = []; - let hasStoppedBuild = false; - // Analyze each recent log to catch build errors in any of them - for (const log of recentLogs) { - try { - Logger.info(`Analyzing build log: ${log.path}`); - const results = await BuildLogParser.parseBuildLog(log.path); - Logger.info(`Log analysis: ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); - // Check for stopped builds - if (results.buildStatus === 'stopped') { - hasStoppedBuild = true; - } - // Accumulate errors and warnings from all logs - totalErrors.push(...results.errors); - totalWarnings.push(...results.warnings); - } - catch (error) { - Logger.warn(`Failed to parse build log ${log.path}: ${error instanceof Error ? error.message : error}`); - } - } - Logger.info(`Total build analysis: ${totalErrors.length} errors, ${totalWarnings.length} warnings, stopped builds: ${hasStoppedBuild}`); - Logger.info(`DEBUG: totalErrors = ${JSON.stringify(totalErrors)}`); - Logger.info(`DEBUG: totalErrors.length = ${totalErrors.length}`); - Logger.info(`DEBUG: totalErrors.length > 0 = ${totalErrors.length > 0}`); - Logger.info(`DEBUG: hasStoppedBuild = ${hasStoppedBuild}`); - // Handle stopped builds first - if (hasStoppedBuild && totalErrors.length === 0) { - let message = `⏹️ TEST BUILD INTERRUPTED${hasArgs ? ` (test with arguments ${JSON.stringify(commandLineArguments)})` : ''}\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the test again to complete it.`; - return { content: [{ type: 'text', text: message }] }; - } - if (totalErrors.length > 0) { - let message = `❌ TEST BUILD FAILED (${totalErrors.length} errors)\n\nERRORS:\n`; - totalErrors.forEach(error => { - message += ` • ${error}\n`; - Logger.error('Test build error:', error); - }); - if (totalWarnings.length > 0) { - message += `\n⚠️ WARNINGS (${totalWarnings.length}):\n`; - totalWarnings.slice(0, 10).forEach(warning => { - message += ` • ${warning}\n`; - Logger.warn('Test build warning:', warning); - }); - if (totalWarnings.length > 10) { - message += ` ... and ${totalWarnings.length - 10} more warnings\n`; - } - } - Logger.error('ABOUT TO THROW McpError for test build failure'); - throw new McpError(ErrorCode.InternalError, message); - } - else if (totalWarnings.length > 0) { - Logger.warn(`Test build completed with ${totalWarnings.length} warnings`); - totalWarnings.slice(0, 10).forEach(warning => { - Logger.warn('Test build warning:', warning); - }); - if (totalWarnings.length > 10) { - Logger.warn(`... and ${totalWarnings.length - 10} more warnings`); - } - } - } - else { - Logger.info(`DEBUG: No recent build logs found since ${new Date(testStartTime)}`); - } - // Since build passed, now wait for test execution to complete - Logger.info('Build succeeded, waiting for test execution to complete...'); - // Monitor test completion with proper AppleScript checking and 6-hour safety timeout - const maxTestTime = 21600000; // 6 hours safety timeout - let testCompleted = false; - let monitoringSeconds = 0; - Logger.info('Monitoring test completion with 6-hour safety timeout...'); - while (!testCompleted && (Date.now() - testStartTime) < maxTestTime) { - try { - // Check test completion via AppleScript every 30 seconds - const checkScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) return 'No workspace'; - - const actions = workspace.schemeActionResults(); - for (let i = 0; i < actions.length; i++) { - const action = actions[i]; - if (action.id() === "${actionId}") { - const status = action.status(); - const completed = action.completed(); - return status + ':' + completed; - } - } - return 'Action not found'; - })() - `; - const result = await JXAExecutor.execute(checkScript, 15000); - const [status, completed] = result.split(':'); - // Log progress every 2 minutes - if (monitoringSeconds % 120 === 0) { - Logger.info(`Test monitoring: ${Math.floor(monitoringSeconds / 60)}min - Action ${actionId}: status=${status}, completed=${completed}`); - } - // Check if test is complete - if (completed === 'true' && (status === 'succeeded' || status === 'failed' || status === 'cancelled' || status === 'error occurred')) { - testCompleted = true; - Logger.info(`Test completed after ${Math.floor(monitoringSeconds / 60)} minutes: status=${status}`); - break; - } - } - catch (error) { - Logger.warn(`Test monitoring error at ${Math.floor(monitoringSeconds / 60)}min: ${error instanceof Error ? error.message : error}`); - } - // Wait 30 seconds before next check - await new Promise(resolve => setTimeout(resolve, 30000)); - monitoringSeconds += 30; - } - if (!testCompleted) { - Logger.warn('Test monitoring reached 6-hour timeout - proceeding anyway'); - } - Logger.info('Test monitoring result: Test completion detected or timeout reached'); - // Only AFTER test completion is confirmed, look for the xcresult file - Logger.info('Test execution completed, now looking for XCResult file...'); - let newXCResult = await this._findNewXCResultFile(projectPath, initialXCResults, testStartTime); - // If no xcresult found yet, wait for it to appear (should be quick now that tests are done) - if (!newXCResult) { - Logger.info('No xcresult file found yet, waiting for it to appear...'); - let attempts = 0; - const maxWaitAttempts = 15; // 15 seconds to find the file after test completion - while (attempts < maxWaitAttempts && !newXCResult) { - await new Promise(resolve => setTimeout(resolve, 1000)); - newXCResult = await this._findNewXCResultFile(projectPath, initialXCResults, testStartTime); - attempts++; - } - // If still no XCResult found, the test likely didn't run at all - if (!newXCResult) { - Logger.warn('No XCResult file found - test may not have run or current scheme has no tests'); - return { - content: [{ - type: 'text', - text: `⚠️ TEST EXECUTION UNCLEAR\n\nNo XCResult file was created, which suggests:\n• The current scheme may not have test targets configured\n• Tests may have been skipped\n• There may be configuration issues\n\n💡 Try:\n• Use a scheme with test targets (look for schemes ending in '-Tests')\n• Check that the project has test targets configured\n• Run tests manually in Xcode first to verify setup\n\nAvailable schemes: Use 'xcode_get_schemes' to see all schemes` - }] - }; - } - } - let testResult = { status: 'completed', error: undefined }; - if (newXCResult) { - Logger.info(`Found xcresult file: ${newXCResult}, waiting for it to be fully written...`); - // Calculate how long the test took - const testEndTime = Date.now(); - const testDurationMs = testEndTime - testStartTime; - const testDurationMinutes = Math.round(testDurationMs / 60000); - // Wait 8% of test duration before even attempting to read XCResult - // This gives Xcode plenty of time to finish writing everything - const proportionalWaitMs = Math.round(testDurationMs * 0.08); - const proportionalWaitSeconds = Math.round(proportionalWaitMs / 1000); - Logger.info(`Test ran for ${testDurationMinutes} minutes`); - Logger.info(`Applying 8% wait time: ${proportionalWaitSeconds} seconds before checking XCResult`); - Logger.info(`This prevents premature reads that could contribute to file corruption`); - await new Promise(resolve => setTimeout(resolve, proportionalWaitMs)); - // Now use the robust waiting method with the test duration for context - const isReady = await XCResultParser.waitForXCResultReadiness(newXCResult, testDurationMs); // Pass test duration for proportional timeouts - if (isReady) { - // File is ready, verify analysis works - try { - Logger.info('XCResult file is ready, performing final verification...'); - const parser = new XCResultParser(newXCResult); - const analysis = await parser.analyzeXCResult(); - if (analysis && analysis.totalTests >= 0) { - Logger.info(`XCResult parsing successful! Found ${analysis.totalTests} tests`); - testResult = { status: 'completed', error: undefined }; - } - else { - Logger.error('XCResult parsed but incomplete test data found'); - testResult = { - status: 'failed', - error: `XCResult file exists but contains incomplete test data. This may indicate an Xcode bug.` - }; - } - } - catch (parseError) { - Logger.error(`XCResult file appears to be corrupt: ${parseError instanceof Error ? parseError.message : parseError}`); - testResult = { - status: 'failed', - error: `XCResult file is corrupt or unreadable. This is likely an Xcode bug. Parse error: ${parseError instanceof Error ? parseError.message : parseError}` - }; - } - } - else { - Logger.error('XCResult file failed to become ready within 3 minutes'); - testResult = { - status: 'failed', - error: `XCResult file failed to become readable within 3 minutes despite multiple verification attempts. This indicates an Xcode bug where the file remains corrupt or incomplete.` - }; - } - } - else { - Logger.warn('No xcresult file found after test completion'); - testResult = { status: 'completed', error: 'No XCResult file found' }; - } - if (newXCResult) { - Logger.info(`Found xcresult: ${newXCResult}`); - // Check if the xcresult file is corrupt - if (testResult.status === 'failed' && testResult.error) { - // XCResult file is corrupt - let message = `❌ XCODE BUG DETECTED${hasArgs ? ` (test with arguments ${JSON.stringify(commandLineArguments)})` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n\n`; - message += `⚠️ ${testResult.error}\n\n`; - message += `This is a known Xcode issue where the .xcresult file becomes corrupt even though Xcode reports test completion.\n\n`; - message += `💡 Troubleshooting steps:\n`; - message += ` 1. Restart Xcode and retry\n`; - message += ` 2. Delete DerivedData and retry\n\n`; - message += `The corrupt XCResult file is at:\n${newXCResult}`; - return { content: [{ type: 'text', text: message }] }; - } - // We already confirmed the xcresult is readable in our completion detection loop - // No need to wait again - proceed directly to analysis - if (testResult.status === 'completed') { - try { - // Use shared utility to format test results with individual test details - const parser = new XCResultParser(newXCResult); - const testSummary = await parser.formatTestResultsSummary(true, 5); - let message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += testSummary + `\n\n`; - const analysis = await parser.analyzeXCResult(); - if (analysis.failedTests > 0) { - message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult-browse --xcresult-path \n`; - message += ` • Get console output: xcresult-browser-get-console --xcresult-path --test-id \n`; - message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path --test-id --timestamp \n`; - message += ` • Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path --test-id --timestamp \n`; - message += ` • Get element details: xcresult-get-ui-element --hierarchy-json --index \n`; - message += ` • List attachments: xcresult-list-attachments --xcresult-path --test-id \n`; - message += ` • Export attachments: xcresult-export-attachment --xcresult-path --test-id --index \n`; - message += ` • Quick summary: xcresult-summary --xcresult-path \n`; - message += `\n💡 Tip: Use console output to find failure timestamps for screenshots and UI hierarchies`; - } - else { - message += `✅ All tests passed!\n\n`; - message += `💡 Explore test results:\n`; - message += ` • Browse results: xcresult-browse --xcresult-path \n`; - message += ` • Get console output: xcresult-browser-get-console --xcresult-path --test-id \n`; - message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path --test-id --timestamp \n`; - message += ` • Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path --test-id --timestamp \n`; - message += ` • Get element details: xcresult-get-ui-element --hierarchy-json --index \n`; - message += ` • List attachments: xcresult-list-attachments --xcresult-path --test-id \n`; - message += ` • Export attachments: xcresult-export-attachment --xcresult-path --test-id --index \n`; - message += ` • Quick summary: xcresult-summary --xcresult-path `; - } - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - catch (parseError) { - Logger.warn(`Failed to parse xcresult: ${parseError}`); - // Fall back to basic result - let message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += `Status: ${testResult.status}\n\n`; - message += `Note: XCResult parsing failed, but test file is available for manual inspection.\n\n`; - message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult_browse \n`; - message += ` • Get console output: xcresult_browser_get_console \n`; - message += ` • Get screenshots: xcresult_get_screenshot \n`; - message += ` • Get UI hierarchy: xcresult_get_ui_hierarchy \n`; - message += ` • Get element details: xcresult_get_ui_element \n`; - message += ` • List attachments: xcresult_list_attachments \n`; - message += ` • Export attachments: xcresult_export_attachment \n`; - message += ` • Quick summary: xcresult_summary `; - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - } - else { - // Test completion detection timed out - let message = `🧪 TESTS ${testResult.status.toUpperCase()}${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += `Status: ${testResult.status}\n\n`; - message += `⚠️ Test completion detection timed out, but XCResult file is available.\n\n`; - message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult_browse \n`; - message += ` • Get console output: xcresult_browser_get_console \n`; - message += ` • Get screenshots: xcresult_get_screenshot \n`; - message += ` • Get UI hierarchy: xcresult_get_ui_hierarchy \n`; - message += ` • Get element details: xcresult_get_ui_element \n`; - message += ` • List attachments: xcresult_list_attachments \n`; - message += ` • Export attachments: xcresult_export_attachment \n`; - message += ` • Quick summary: xcresult_summary `; - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - } - else { - // No xcresult found - fall back to basic result - if (testResult.status === 'failed') { - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: `❌ TEST FAILED\n\n${testResult.error || 'Test execution failed'}\n\nNote: No XCResult file found for detailed analysis.` }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - const message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\nStatus: ${testResult.status}\n\nNote: No XCResult file found for detailed analysis.`; - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - } - catch (error) { - // Restore test plan even on error - if (shouldRestoreTestPlan && originalTestPlan && options?.testPlanPath) { - try { - const { promises: fs } = await import('fs'); - await fs.writeFile(options.testPlanPath, originalTestPlan, 'utf8'); - Logger.info('Restored original test plan after error'); - } - catch (restoreError) { - Logger.error(`Failed to restore test plan: ${restoreError}`); - } - } - // Re-throw McpErrors to properly signal build failures - if (error instanceof McpError) { - throw error; - } - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Failed to run tests: ${errorMessage}` }] }; - } - } - /** - * Helper method to restore test plan and return result - */ - static async _restoreTestPlanAndReturn(result, shouldRestoreTestPlan, originalTestPlan, testPlanPath) { - if (shouldRestoreTestPlan && originalTestPlan && testPlanPath) { - try { - const { promises: fs } = await import('fs'); - await fs.writeFile(testPlanPath, originalTestPlan, 'utf8'); - Logger.info('Restored original test plan'); - // Trigger reload after restoration - const { TestPlanTools } = await import('./TestPlanTools.js'); - await TestPlanTools.triggerTestPlanReload(testPlanPath, testPlanPath); - } - catch (restoreError) { - Logger.error(`Failed to restore test plan: ${restoreError}`); - // Append restoration error to result - if (result.content?.[0]?.type === 'text') { - result.content[0].text += `\n\n⚠️ Warning: Failed to restore original test plan: ${restoreError}`; - } - } - } - return result; - } - static async run(projectPath, schemeName, commandLineArguments = [], openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - // Set the scheme - const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); - const setSchemeScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const schemes = workspace.schemes(); - const schemeNames = schemes.map(scheme => scheme.name()); - - // Try exact match first - let targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(normalizedSchemeName)}); - - // If not found, try original name - if (!targetScheme) { - targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(schemeName)}); - } - - if (!targetScheme) { - throw new Error('Scheme not found. Available: ' + JSON.stringify(schemeNames)); - } - - workspace.activeScheme = targetScheme; - return 'Scheme set to ' + targetScheme.name(); - })() - `; - try { - await JXAExecutor.execute(setSchemeScript); - } - catch (error) { - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('not found')) { - try { - // Get available schemes - const availableSchemes = await this._getAvailableSchemes(projectPath); - // Try to find a close match with fuzzy matching - const bestMatch = ParameterNormalizer.findBestMatch(schemeName, availableSchemes); - let message = `❌ Scheme '${schemeName}' not found\n\nAvailable schemes:\n`; - availableSchemes.forEach(scheme => { - if (scheme === bestMatch) { - message += ` • ${scheme} ← Did you mean this?\n`; - } - else { - message += ` • ${scheme}\n`; - } - }); - return { content: [{ type: 'text', text: message }] }; - } - catch { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Scheme '${schemeName}' not found`, ErrorHelper.getSchemeNotFoundGuidance(schemeName)) }] }; - } - } - return { content: [{ type: 'text', text: `Failed to set scheme '${schemeName}': ${errorMessage}` }] }; - } - // Note: No longer need to track initial log since we use AppleScript completion detection - const hasArgs = commandLineArguments && commandLineArguments.length > 0; - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - ${hasArgs - ? `const result = workspace.run({withCommandLineArguments: ${JSON.stringify(commandLineArguments)}});` - : `const result = workspace.run();`} - return \`Run started. Result ID: \${result.id()}\`; - })() - `; - const runResult = await JXAExecutor.execute(script); - // Extract the action ID from the result - const actionIdMatch = runResult.match(/Result ID: (.+)/); - const actionId = actionIdMatch ? actionIdMatch[1] : null; - if (!actionId) { - return { content: [{ type: 'text', text: `${runResult}\n\nError: Could not extract action ID from run result` }] }; - } - Logger.info(`Run started with action ID: ${actionId}`); - // Check for and handle "replace existing build" alert - await this._handleReplaceExistingBuildAlert(); - // Monitor run completion using AppleScript instead of build log detection - Logger.info(`Monitoring run completion using AppleScript for action ID: ${actionId}`); - const maxRunTime = 3600000; // 1 hour safety timeout - const runStartTime = Date.now(); - let runCompleted = false; - let monitoringSeconds = 0; - while (!runCompleted && (Date.now() - runStartTime) < maxRunTime) { - try { - // Check run completion via AppleScript every 10 seconds - const checkScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) return 'No workspace'; - - const actions = workspace.schemeActionResults(); - for (let i = 0; i < actions.length; i++) { - const action = actions[i]; - if (action.id() === "${actionId}") { - const status = action.status(); - const completed = action.completed(); - return status + ':' + completed; - } - } - return 'Action not found'; - })() - `; - const result = await JXAExecutor.execute(checkScript, 15000); - const [status, completed] = result.split(':'); - // Log progress every 2 minutes - if (monitoringSeconds % 120 === 0) { - Logger.info(`Run monitoring: ${Math.floor(monitoringSeconds / 60)}min - Action ${actionId}: status=${status}, completed=${completed}`); - } - // For run actions, we need different completion logic than build/test - // Run actions stay "running" even after successful app launch - if (completed === 'true' && (status === 'failed' || status === 'cancelled' || status === 'error occurred')) { - // Run failed/cancelled - this is a true completion - runCompleted = true; - Logger.info(`Run completed after ${Math.floor(monitoringSeconds / 60)} minutes: status=${status}`); - break; - } - else if (status === 'running' && monitoringSeconds >= 60) { - // If still running after 60 seconds, assume the app launched successfully - // We'll check for build errors in the log parsing step - runCompleted = true; - Logger.info(`Run appears successful after ${Math.floor(monitoringSeconds / 60)} minutes (app likely launched)`); - break; - } - else if (status === 'succeeded') { - // This might happen briefly during transition, wait a bit more - Logger.info(`Run status shows 'succeeded', waiting to see if it transitions to 'running'...`); - } - } - catch (error) { - Logger.warn(`Run monitoring error at ${Math.floor(monitoringSeconds / 60)}min: ${error instanceof Error ? error.message : error}`); - } - // Wait 10 seconds before next check - await new Promise(resolve => setTimeout(resolve, 10000)); - monitoringSeconds += 10; - } - if (!runCompleted) { - Logger.warn('Run monitoring reached 1-hour timeout - proceeding anyway'); - } - // Now find the build log that was created during this run - const newLog = await BuildLogParser.getLatestBuildLog(projectPath); - if (!newLog) { - return { content: [{ type: 'text', text: `${runResult}\n\nNote: Run completed but no build log found (app may have launched without building)` }] }; - } - Logger.info(`Run completed, parsing build log: ${newLog.path}`); - const results = await BuildLogParser.parseBuildLog(newLog.path); - let message = `${runResult}\n\n`; - Logger.info(`Run build completed - ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); - // Handle stopped/interrupted builds - if (results.buildStatus === 'stopped') { - message += `⏹️ BUILD INTERRUPTED\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the build again to complete it.`; - return { content: [{ type: 'text', text: message }] }; - } - if (results.errors.length > 0) { - message += `❌ BUILD FAILED (${results.errors.length} errors)\n\nERRORS:\n`; - results.errors.forEach(error => { - message += ` • ${error}\n`; - }); - throw new McpError(ErrorCode.InternalError, message); - } - else if (results.warnings.length > 0) { - message += `⚠️ BUILD COMPLETED WITH WARNINGS (${results.warnings.length} warnings)\n\nWARNINGS:\n`; - results.warnings.forEach(warning => { - message += ` • ${warning}\n`; - }); - } - else { - message += '✅ BUILD SUCCESSFUL - App should be launching'; - } - return { content: [{ type: 'text', text: message }] }; - } - static async debug(projectPath, scheme, skipBuilding = false, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - if (openProject) { - await openProject(projectPath); - } - const hasParams = scheme || skipBuilding; - let paramsObj = {}; - if (scheme) - paramsObj.scheme = scheme; - if (skipBuilding) - paramsObj.skipBuilding = skipBuilding; - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - ${hasParams - ? `const result = workspace.debug(${JSON.stringify(paramsObj)});` - : `const result = workspace.debug();`} - return \`Debug started. Result ID: \${result.id()}\`; - })() - `; - const result = await JXAExecutor.execute(script); - // Check for and handle "replace existing build" alert - await this._handleReplaceExistingBuildAlert(); - return { content: [{ type: 'text', text: result }] }; - } - static async stop(projectPath) { - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - workspace.stop(); - return 'Stop command sent'; - })() - `; - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } - static async _getAvailableSchemes(projectPath) { - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) return JSON.stringify([]); - - const schemes = workspace.schemes(); - const schemeNames = schemes.map(scheme => scheme.name()); - return JSON.stringify(schemeNames); - })() - `; - try { - const result = await JXAExecutor.execute(script); - return JSON.parse(result); - } - catch { - return []; - } - } - static async _getAvailableDestinations(projectPath) { - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) return []; - - const destinations = workspace.runDestinations(); - return destinations.map(dest => dest.name()); - })() - `; - try { - const result = await JXAExecutor.execute(script); - return JSON.parse(result); - } - catch { - return []; - } - } - static async _findXCResultFiles(projectPath) { - const xcresultFiles = []; - try { - // Use existing BuildLogParser logic to find the correct DerivedData directory - const derivedData = await BuildLogParser.findProjectDerivedData(projectPath); - if (derivedData) { - // Look for xcresult files in the Test logs directory - const testLogsDir = join(derivedData, 'Logs', 'Test'); - try { - const files = await readdir(testLogsDir); - const xcresultDirs = files.filter(file => file.endsWith('.xcresult')); - for (const xcresultDir of xcresultDirs) { - const fullPath = join(testLogsDir, xcresultDir); - try { - const stats = await stat(fullPath); - xcresultFiles.push({ - path: fullPath, - mtime: stats.mtime.getTime(), - size: stats.size - }); - } - catch { - // Ignore files we can't stat - } - } - } - catch (error) { - Logger.debug(`Could not read test logs directory: ${error}`); - } - } - } - catch (error) { - Logger.warn(`Error finding xcresult files: ${error}`); - } - return xcresultFiles.sort((a, b) => b.mtime - a.mtime); - } - static async _findNewXCResultFile(projectPath, initialFiles, testStartTime) { - const maxAttempts = 30; // 30 seconds - let attempts = 0; - while (attempts < maxAttempts) { - const currentFiles = await this._findXCResultFiles(projectPath); - // Look for new files created after test start - for (const file of currentFiles) { - const wasInitialFile = initialFiles.some(initial => initial.path === file.path && initial.mtime === file.mtime); - if (!wasInitialFile && file.mtime >= testStartTime - 5000) { // 5s buffer - Logger.info(`Found new xcresult file: ${file.path}, mtime: ${new Date(file.mtime)}, test start: ${new Date(testStartTime)}`); - return file.path; - } - else if (!wasInitialFile) { - Logger.warn(`Found xcresult file but too old: ${file.path}, mtime: ${new Date(file.mtime)}, test start: ${new Date(testStartTime)}, diff: ${file.mtime - testStartTime}ms`); - } - else { - Logger.debug(`Skipping initial file: ${file.path}, mtime: ${new Date(file.mtime)}`); - } - } - await new Promise(resolve => setTimeout(resolve, 1000)); - attempts++; - } - // If no new file found, look for files created AFTER test start time - const allFiles = await this._findXCResultFiles(projectPath); - // Find files created after the test started (not just within the timeframe) - const filesAfterTestStart = allFiles.filter(file => file.mtime > testStartTime); - if (filesAfterTestStart.length > 0) { - // Return the newest file that was created after the test started - const mostRecentAfterTest = filesAfterTestStart[0]; // Already sorted newest first - if (mostRecentAfterTest) { - Logger.warn(`Using most recent xcresult file created after test start: ${mostRecentAfterTest.path}`); - return mostRecentAfterTest.path; - } - } - else if (allFiles.length > 0) { - const mostRecent = allFiles[0]; - if (mostRecent) { - Logger.debug(`Most recent file too old: ${mostRecent.path}, mtime: ${new Date(mostRecent.mtime)}, test start: ${new Date(testStartTime)}`); - } - } - return null; - } - /** - * Find XCResult files for a given project - */ - static async findXCResults(projectPath) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - try { - const xcresultFiles = await this._findXCResultFiles(projectPath); - if (xcresultFiles.length === 0) { - return { - content: [{ - type: 'text', - text: `No XCResult files found for project: ${projectPath}\n\nXCResult files are created when you run tests. Try running tests first with 'xcode_test'.` - }] - }; - } - let message = `🔍 Found ${xcresultFiles.length} XCResult file(s) for project: ${projectPath}\n\n`; - message += `📁 XCResult Files (sorted by newest first):\n`; - message += '='.repeat(80) + '\n'; - xcresultFiles.forEach((file, index) => { - const date = new Date(file.mtime); - const timeAgo = this._getTimeAgo(file.mtime); - message += `${index + 1}. ${file.path}\n`; - message += ` 📅 Created: ${date.toLocaleString()} (${timeAgo})\n`; - message += ` 📊 Size: ${this._formatFileSize(file.size || 0)}\n\n`; - }); - message += `💡 Usage:\n`; - message += ` • View results: xcresult-browse --xcresult-path ""\n`; - message += ` • Get console: xcresult-browser-get-console --xcresult-path "" --test-id \n`; - return { content: [{ type: 'text', text: message }] }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { - content: [{ - type: 'text', - text: `Failed to find XCResult files: ${errorMessage}` - }] - }; - } - } - static _getTimeAgo(timestamp) { - const now = Date.now(); - const diff = now - timestamp; - const minutes = Math.floor(diff / (1000 * 60)); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - if (minutes < 1) - return 'just now'; - if (minutes < 60) - return `${minutes} minute${minutes === 1 ? '' : 's'} ago`; - if (hours < 24) - return `${hours} hour${hours === 1 ? '' : 's'} ago`; - return `${days} day${days === 1 ? '' : 's'} ago`; - } - static _formatFileSize(bytes) { - if (bytes === 0) - return '0 bytes'; - const k = 1024; - const sizes = ['bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; - } - /** - * Handle alerts that appear when starting builds/tests while another operation is in progress. - * This includes "replace existing build" alerts and similar dialog overlays. - */ - static async _handleReplaceExistingBuildAlert() { - const alertScript = ` - (function() { - try { - // Use System Events approach first as it's more reliable for sheet dialogs - const systemEvents = Application('System Events'); - const xcodeProcesses = systemEvents.processes.whose({name: 'Xcode'}); - - if (xcodeProcesses.length > 0) { - const xcodeProcess = xcodeProcesses[0]; - const windows = xcodeProcess.windows(); - - // Check for sheets in regular windows (most common case) - for (let i = 0; i < windows.length; i++) { - try { - const window = windows[i]; - const sheets = window.sheets(); - - if (sheets && sheets.length > 0) { - const sheet = sheets[0]; - const buttons = sheet.buttons(); - - // Look for Replace, Continue, OK, Yes buttons (in order of preference) - const preferredButtons = ['Replace', 'Continue', 'OK', 'Yes', 'Stop and Replace']; - - for (const preferredButton of preferredButtons) { - for (let b = 0; b < buttons.length; b++) { - try { - const button = buttons[b]; - const buttonTitle = button.title(); - - if (buttonTitle === preferredButton) { - button.click(); - return 'Sheet alert handled: clicked ' + buttonTitle; - } - } catch (e) { - // Continue to next button - } - } - } - - // If no preferred button found, try partial matches - for (let b = 0; b < buttons.length; b++) { - try { - const button = buttons[b]; - const buttonTitle = button.title(); - - if (buttonTitle && ( - buttonTitle.toLowerCase().includes('replace') || - buttonTitle.toLowerCase().includes('continue') || - buttonTitle.toLowerCase().includes('stop') || - buttonTitle.toLowerCase() === 'ok' || - buttonTitle.toLowerCase() === 'yes' - )) { - button.click(); - return 'Sheet alert handled: clicked ' + buttonTitle + ' (partial match)'; - } - } catch (e) { - // Continue to next button - } - } - - // Log available buttons for debugging - const availableButtons = []; - for (let b = 0; b < buttons.length; b++) { - try { - availableButtons.push(buttons[b].title()); - } catch (e) { - availableButtons.push('(unnamed)'); - } - } - - return 'Sheet found but no suitable button. Available: ' + JSON.stringify(availableButtons); - } - } catch (e) { - // Continue to next window - } - } - - // Check for modal dialogs - const dialogs = xcodeProcess.windows.whose({subrole: 'AXDialog'}); - for (let d = 0; d < dialogs.length; d++) { - try { - const dialog = dialogs[d]; - const buttons = dialog.buttons(); - - for (let b = 0; b < buttons.length; b++) { - try { - const button = buttons[b]; - const buttonTitle = button.title(); - - if (buttonTitle && ( - buttonTitle.toLowerCase().includes('replace') || - buttonTitle.toLowerCase().includes('continue') || - buttonTitle.toLowerCase().includes('stop') || - buttonTitle.toLowerCase() === 'ok' || - buttonTitle.toLowerCase() === 'yes' - )) { - button.click(); - return 'Dialog alert handled: clicked ' + buttonTitle; - } - } catch (e) { - // Continue to next button - } - } - } catch (e) { - // Continue to next dialog - } - } - } - - // Fallback to Xcode app approach for embedded alerts - const app = Application('Xcode'); - const windows = app.windows(); - - for (let i = 0; i < windows.length; i++) { - try { - const window = windows[i]; - const sheets = window.sheets(); - - if (sheets && sheets.length > 0) { - const sheet = sheets[0]; - const buttons = sheet.buttons(); - - for (let j = 0; j < buttons.length; j++) { - try { - const button = buttons[j]; - const buttonName = button.name(); - - if (buttonName && ( - buttonName.toLowerCase().includes('replace') || - buttonName.toLowerCase().includes('continue') || - buttonName.toLowerCase().includes('stop') || - buttonName.toLowerCase() === 'ok' || - buttonName.toLowerCase() === 'yes' - )) { - button.click(); - return 'Xcode app sheet handled: clicked ' + buttonName; - } - } catch (e) { - // Continue to next button - } - } - } - } catch (e) { - // Continue to next window - } - } - - return 'No alert found'; - - } catch (error) { - return 'Alert check failed: ' + error.message; - } - })() - `; - try { - Logger.info('Running alert detection script...'); - const result = await JXAExecutor.execute(alertScript); - Logger.info(`Alert detection result: ${result}`); - if (result && result !== 'No alert found') { - Logger.info(`Alert handling: ${result}`); - } - else { - Logger.info('No alerts detected'); - } - } - catch (error) { - // Don't fail the main operation if alert handling fails - Logger.info(`Alert handling failed: ${error instanceof Error ? error.message : String(error)}`); - } - } -} -//# sourceMappingURL=BuildTools.js.map \ No newline at end of file diff --git a/dist/tools/BuildTools.js.map b/dist/tools/BuildTools.js.map deleted file mode 100644 index a53ea64..0000000 --- a/dist/tools/BuildTools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BuildTools.js","sourceRoot":"","sources":["../../src/tools/BuildTools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/InfoTools.d.ts b/dist/tools/InfoTools.d.ts deleted file mode 100644 index 18c2d91..0000000 --- a/dist/tools/InfoTools.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { McpResult, OpenProjectCallback } from '../types/index.js'; -export declare class InfoTools { - static getWorkspaceInfo(projectPath: string, openProject: OpenProjectCallback): Promise; - static getProjects(projectPath: string, openProject: OpenProjectCallback): Promise; - static openFile(filePath: string, lineNumber?: number): Promise; -} -//# sourceMappingURL=InfoTools.d.ts.map \ No newline at end of file diff --git a/dist/tools/InfoTools.d.ts.map b/dist/tools/InfoTools.d.ts.map deleted file mode 100644 index 6cd38d9..0000000 --- a/dist/tools/InfoTools.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"InfoTools.d.ts","sourceRoot":"","sources":["../../src/tools/InfoTools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExE,qBAAa,SAAS;WACA,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;WA0B3F,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;WAwBtF,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAuBxF"} \ No newline at end of file diff --git a/dist/tools/InfoTools.js b/dist/tools/InfoTools.js deleted file mode 100644 index 2080af4..0000000 --- a/dist/tools/InfoTools.js +++ /dev/null @@ -1,72 +0,0 @@ -import { JXAExecutor } from '../utils/JXAExecutor.js'; -import { PathValidator } from '../utils/PathValidator.js'; -import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; -export class InfoTools { - static async getWorkspaceInfo(projectPath, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const info = { - name: workspace.name(), - path: workspace.path(), - loaded: workspace.loaded(), - activeScheme: workspace.activeScheme() ? workspace.activeScheme().name() : null, - activeRunDestination: workspace.activeRunDestination() ? workspace.activeRunDestination().name() : null - }; - - return JSON.stringify(info, null, 2); - })() - `; - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } - static async getProjects(projectPath, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const projects = workspace.projects(); - const projectInfo = projects.map(project => ({ - name: project.name(), - id: project.id() - })); - - return JSON.stringify(projectInfo, null, 2); - })() - `; - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } - static async openFile(filePath, lineNumber) { - const validationError = PathValidator.validateFilePath(filePath); - if (validationError) - return validationError; - const script = ` - (function() { - const app = Application('Xcode'); - app.open(${JSON.stringify(filePath)}); - - ${lineNumber ? ` - const docs = app.sourceDocuments(); - const doc = docs.find(d => d.path().includes(${JSON.stringify(filePath.split('/').pop())})); - if (doc) { - app.hack({document: doc, start: ${lineNumber}, stop: ${lineNumber}}); - }` : ''} - - return 'File opened successfully'; - })() - `; - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } -} -//# sourceMappingURL=InfoTools.js.map \ No newline at end of file diff --git a/dist/tools/InfoTools.js.map b/dist/tools/InfoTools.js.map deleted file mode 100644 index d09beea..0000000 --- a/dist/tools/InfoTools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"InfoTools.js","sourceRoot":"","sources":["../../src/tools/InfoTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGlE,MAAM,OAAO,SAAS;IACb,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,WAAmB,EAAE,WAAgC;QACxF,MAAM,eAAe,GAAG,aAAa,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,eAAe;YAAE,OAAO,eAAe,CAAC;QAE5C,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG;;UAET,wBAAwB,CAAC,WAAW,CAAC;;;;;;;;;;;;KAY1C,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,WAAmB,EAAE,WAAgC;QACnF,MAAM,eAAe,GAAG,aAAa,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,eAAe;YAAE,OAAO,eAAe,CAAC;QAE5C,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG;;UAET,wBAAwB,CAAC,WAAW,CAAC;;;;;;;;;;KAU1C,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAgB,EAAE,UAAmB;QAChE,MAAM,eAAe,GAAG,aAAa,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,eAAe;YAAE,OAAO,eAAe,CAAC;QAE5C,MAAM,MAAM,GAAG;;;mBAGA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;;UAEjC,UAAU,CAAC,CAAC,CAAC;;uDAEgC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;;4CAEpD,UAAU,WAAW,UAAU;UACjE,CAAC,CAAC,CAAC,EAAE;;;;KAIV,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;CACF"} \ No newline at end of file diff --git a/dist/tools/ProjectTools.d.ts b/dist/tools/ProjectTools.d.ts deleted file mode 100644 index 3f91802..0000000 --- a/dist/tools/ProjectTools.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { McpResult, OpenProjectCallback } from '../types/index.js'; -export declare class ProjectTools { - static ensureXcodeIsRunning(): Promise; - static openProject(projectPath: string): Promise; - static waitForProjectToLoad(projectPath: string, maxRetries?: number, retryDelayMs?: number): Promise; - static openProjectAndWaitForLoad(projectPath: string): Promise; - static closeProject(projectPath: string): Promise; - static getSchemes(projectPath: string, openProject: OpenProjectCallback): Promise; - static setActiveScheme(projectPath: string, schemeName: string, openProject: OpenProjectCallback): Promise; - static getRunDestinations(projectPath: string, openProject: OpenProjectCallback): Promise; - /** - * Get test targets information from project - */ - static getTestTargets(projectPath: string): Promise; -} -//# sourceMappingURL=ProjectTools.d.ts.map \ No newline at end of file diff --git a/dist/tools/ProjectTools.d.ts.map b/dist/tools/ProjectTools.d.ts.map deleted file mode 100644 index 6dd541a..0000000 --- a/dist/tools/ProjectTools.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ProjectTools.d.ts","sourceRoot":"","sources":["../../src/tools/ProjectTools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExE,qBAAa,YAAY;WACH,oBAAoB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;WAwHjD,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;WAwCpD,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,EAAE,YAAY,GAAE,MAAa,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;WA6D1H,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;WAmDlE,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;WA6BrD,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;WAsCrF,eAAe,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,SAAS,CAAC;WA0ED,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAuCjH;;OAEG;WACiB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAgF5E"} \ No newline at end of file diff --git a/dist/tools/ProjectTools.js b/dist/tools/ProjectTools.js deleted file mode 100644 index 7b2a53a..0000000 --- a/dist/tools/ProjectTools.js +++ /dev/null @@ -1,496 +0,0 @@ -import { JXAExecutor } from '../utils/JXAExecutor.js'; -import { PathValidator } from '../utils/PathValidator.js'; -import { ParameterNormalizer } from '../utils/ParameterNormalizer.js'; -import { ErrorHelper } from '../utils/ErrorHelper.js'; -import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; -export class ProjectTools { - static async ensureXcodeIsRunning() { - // First check if Xcode is already running - const checkScript = ` - (function() { - try { - const app = Application('Xcode'); - if (app.running()) { - return 'Xcode is already running'; - } else { - return 'Xcode is not running'; - } - } catch (error) { - return 'Xcode is not running: ' + error.message; - } - })() - `; - try { - const checkResult = await JXAExecutor.execute(checkScript); - if (checkResult.includes('already running')) { - return null; // All good, Xcode is running - } - } - catch (error) { - // Continue to launch Xcode - } - // Get the Xcode path from xcode-select - let xcodePath; - try { - const { spawn } = await import('child_process'); - const xcodeSelectResult = await new Promise((resolve, reject) => { - const process = spawn('xcode-select', ['-p']); - let stdout = ''; - let stderr = ''; - process.stdout.on('data', (data) => { - stdout += data.toString(); - }); - process.stderr.on('data', (data) => { - stderr += data.toString(); - }); - process.on('close', (code) => { - if (code === 0) { - resolve(stdout.trim()); - } - else { - reject(new Error(`xcode-select failed with code ${code}: ${stderr}`)); - } - }); - }); - if (!xcodeSelectResult || xcodeSelectResult.trim() === '') { - return { - content: [{ - type: 'text', - text: '❌ No Xcode installation found\n\n💡 To fix this:\n• Install Xcode from the Mac App Store\n• Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer' - }] - }; - } - // Convert from Developer path to app path - xcodePath = xcodeSelectResult.replace('/Contents/Developer', ''); - } - catch (error) { - return { - content: [{ - type: 'text', - text: `❌ Failed to determine Xcode path: ${error instanceof Error ? error.message : String(error)}\n\n💡 Ensure Xcode is properly installed and xcode-select is configured` - }] - }; - } - // Launch Xcode - const launchScript = ` - (function() { - try { - const app = Application(${JSON.stringify(xcodePath)}); - app.launch(); - - // Wait for Xcode to start - let attempts = 0; - while (!app.running() && attempts < 30) { - delay(1); - attempts++; - } - - if (app.running()) { - return 'Xcode launched successfully from ' + ${JSON.stringify(xcodePath)}; - } else { - return 'Failed to launch Xcode - timed out after 30 seconds'; - } - } catch (error) { - return 'Failed to launch Xcode: ' + error.message; - } - })() - `; - try { - const launchResult = await JXAExecutor.execute(launchScript); - if (launchResult.includes('launched successfully')) { - return null; // Success - } - else { - return { - content: [{ - type: 'text', - text: `❌ ${launchResult}\n\n💡 Try:\n• Manually launching Xcode once\n• Checking Xcode installation\n• Ensuring sufficient system resources` - }] - }; - } - } - catch (error) { - return { - content: [{ - type: 'text', - text: `❌ Failed to launch Xcode: ${error instanceof Error ? error.message : String(error)}` - }] - }; - } - } - static async openProject(projectPath) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - // Check for workspace preference: if we're opening a .xcodeproj file, - // check if there's a corresponding .xcworkspace file in the same directory - let actualPath = projectPath; - if (projectPath.endsWith('.xcodeproj')) { - const { existsSync } = await import('fs'); - const workspacePath = projectPath.replace(/\.xcodeproj$/, '.xcworkspace'); - if (existsSync(workspacePath)) { - actualPath = workspacePath; - } - } - // Ensure Xcode is running before trying to open project - const xcodeError = await this.ensureXcodeIsRunning(); - if (xcodeError) - return xcodeError; - const script = ` - const app = Application('Xcode'); - app.open(${JSON.stringify(actualPath)}); - 'Project opened successfully'; - `; - try { - const result = await JXAExecutor.execute(script); - // If we automatically chose a workspace over a project, indicate this in the response - if (actualPath !== projectPath && actualPath.endsWith('.xcworkspace')) { - return { content: [{ type: 'text', text: `Opened workspace instead of project: ${result}` }] }; - } - return { content: [{ type: 'text', text: result }] }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Failed to open project: ${errorMessage}` }] }; - } - } - static async waitForProjectToLoad(projectPath, maxRetries = 30, retryDelayMs = 1000) { - const checkScript = ` - (function() { - try { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) { - return JSON.stringify({ loaded: false, reason: 'Workspace not found' }); - } - - // Try to access schemes - this will fail if project is still loading - const schemes = workspace.schemes(); - if (schemes.length === 0) { - return JSON.stringify({ loaded: false, reason: 'Schemes not loaded yet' }); - } - - // Try to access destinations - this might also fail during loading - const destinations = workspace.runDestinations(); - - return JSON.stringify({ loaded: true, schemes: schemes.length, destinations: destinations.length }); - } catch (error) { - return JSON.stringify({ loaded: false, reason: error.message }); - } - })() - `; - for (let retry = 0; retry < maxRetries; retry++) { - try { - const result = await JXAExecutor.execute(checkScript); - const status = JSON.parse(result); - if (status.loaded) { - return null; // Success - project is loaded - } - if (retry === maxRetries - 1) { - return { - content: [{ - type: 'text', - text: `❌ Project failed to load after ${maxRetries} attempts (${maxRetries * retryDelayMs / 1000}s)\n\nLast status: ${status.reason}\n\n💡 Try:\n• Manually opening the project in Xcode\n• Checking if the project file is corrupted\n• Ensuring sufficient system resources` - }] - }; - } - // Wait before next retry - await new Promise(resolve => setTimeout(resolve, retryDelayMs)); - } - catch (error) { - if (retry === maxRetries - 1) { - return { - content: [{ - type: 'text', - text: `❌ Failed to check project loading status: ${error instanceof Error ? error.message : String(error)}` - }] - }; - } - await new Promise(resolve => setTimeout(resolve, retryDelayMs)); - } - } - return null; // This shouldn't be reached - } - static async openProjectAndWaitForLoad(projectPath) { - // First check if project is already open and loaded - try { - const checkScript = ` - (function() { - try { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) { - return JSON.stringify({ isOpen: false }); - } - - // Check if it's the right project - const workspacePath = workspace.path(); - if (workspacePath === ${JSON.stringify(projectPath)}) { - // Try to access schemes to see if it's fully loaded - const schemes = workspace.schemes(); - return JSON.stringify({ isOpen: true, isLoaded: schemes.length > 0 }); - } - - return JSON.stringify({ isOpen: false, differentProject: workspacePath }); - } catch (error) { - return JSON.stringify({ isOpen: false, error: error.message }); - } - })() - `; - const result = await JXAExecutor.execute(checkScript); - const status = JSON.parse(result); - if (status.isOpen && status.isLoaded) { - return { content: [{ type: 'text', text: 'Project is already open and loaded' }] }; - } - } - catch (error) { - // Continue with opening the project - } - // Open the project - const openResult = await this.openProject(projectPath); - if (openResult.content?.[0]?.type === 'text' && openResult.content[0].text.includes('Error')) { - return openResult; - } - // Wait for the project to load - const waitResult = await this.waitForProjectToLoad(projectPath); - if (waitResult) { - return waitResult; - } - return { content: [{ type: 'text', text: 'Project opened and loaded successfully' }] }; - } - static async closeProject(projectPath) { - // Simplified close project to prevent crashes - just close without complex error handling - const closeScript = ` - (function() { - try { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) { - return 'No workspace to close (already closed)'; - } - - // Simple close (no options) to align with test mocks and avoid dialogs - workspace.close(); - return 'Project close initiated'; - } catch (error) { - return 'Close completed (may have had dialogs): ' + error.message; - } - })() - `; - try { - const result = await JXAExecutor.execute(closeScript); - return { content: [{ type: 'text', text: result }] }; - } - catch (error) { - // Even if JXA fails, consider it successful to prevent crashes - const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Project close completed with issues: ${errorMessage}` }] }; - } - } - static async getSchemes(projectPath, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const schemes = workspace.schemes(); - const activeScheme = workspace.activeScheme(); - - const schemeInfo = schemes.map(scheme => ({ - name: scheme.name(), - id: scheme.id(), - isActive: activeScheme && scheme.id() === activeScheme.id() - })); - - return JSON.stringify(schemeInfo, null, 2); - })() - `; - const result = await JXAExecutor.execute(script); - // Parse the result to check if schemes array is empty - try { - const schemeInfo = JSON.parse(result); - if (Array.isArray(schemeInfo) && schemeInfo.length === 0) { - return { content: [{ type: 'text', text: 'No schemes found in the project' }] }; - } - } - catch (error) { - // If parsing fails, return the raw result - } - return { content: [{ type: 'text', text: result }] }; - } - static async setActiveScheme(projectPath, schemeName, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - // Normalize the scheme name for better matching - const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const schemes = workspace.schemes(); - const schemeNames = schemes.map(scheme => scheme.name()); - - // Try exact match first - let targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(normalizedSchemeName)}); - - // If not found, try original name - if (!targetScheme) { - targetScheme = schemes.find(scheme => scheme.name() === ${JSON.stringify(schemeName)}); - } - - if (!targetScheme) { - throw new Error('Scheme not found. Available: ' + JSON.stringify(schemeNames)); - } - - workspace.activeScheme = targetScheme; - return 'Active scheme set to: ' + targetScheme.name(); - })() - `; - try { - const result = await JXAExecutor.execute(script); - return { content: [{ type: 'text', text: result }] }; - } - catch (error) { - const enhancedError = ErrorHelper.parseCommonErrors(error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('not found')) { - try { - // Extract available schemes from error message if present - let availableSchemes = []; - if (errorMessage.includes('Available:')) { - const availablePart = errorMessage.split('Available: ')[1]; - // Find the JSON array part - const jsonMatch = availablePart?.match(/\[.*?\]/); - if (jsonMatch) { - availableSchemes = JSON.parse(jsonMatch[0]); - } - } - // Try to find a close match with fuzzy matching - const bestMatch = ParameterNormalizer.findBestMatch(schemeName, availableSchemes); - let guidance = ErrorHelper.getSchemeNotFoundGuidance(schemeName, availableSchemes); - if (bestMatch && bestMatch !== schemeName) { - guidance += `\n• Did you mean '${bestMatch}'?`; - } - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Scheme '${schemeName}' not found`, guidance) }] }; - } - catch { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Scheme '${schemeName}' not found`, ErrorHelper.getSchemeNotFoundGuidance(schemeName)) }] }; - } - } - return { content: [{ type: 'text', text: `Failed to set active scheme: ${errorMessage}` }] }; - } - } - static async getRunDestinations(projectPath, openProject) { - const validationError = PathValidator.validateProjectPath(projectPath); - if (validationError) - return validationError; - await openProject(projectPath); - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const destinations = workspace.runDestinations(); - const activeDestination = workspace.activeRunDestination(); - - const destInfo = destinations.map(dest => ({ - name: dest.name(), - platform: dest.platform(), - architecture: dest.architecture(), - isActive: activeDestination && dest.name() === activeDestination.name() - })); - - return JSON.stringify(destInfo, null, 2); - })() - `; - const result = await JXAExecutor.execute(script); - // Parse the result to check if destinations array is empty - try { - const destInfo = JSON.parse(result); - if (Array.isArray(destInfo) && destInfo.length === 0) { - return { content: [{ type: 'text', text: 'No run destinations found for the project' }] }; - } - } - catch (error) { - // If parsing fails, return the raw result - } - return { content: [{ type: 'text', text: result }] }; - } - /** - * Get test targets information from project - */ - static async getTestTargets(projectPath) { - try { - const { promises: fs } = await import('fs'); - // Read the project.pbxproj file - const pbxprojPath = `${projectPath}/project.pbxproj`; - const projectContent = await fs.readFile(pbxprojPath, 'utf8'); - // Parse test targets from the project file - const testTargets = []; - // Find PBXNativeTarget sections that are test targets - const targetMatches = projectContent.matchAll(/([A-F0-9]{24}) \/\* (.+?) \*\/ = {\s*isa = PBXNativeTarget;[\s\S]*?productType = "([^"]+)";/g); - for (const match of targetMatches) { - const [, identifier, name, productType] = match; - // Only include test targets (with null checks) - if (identifier && name && productType && - (productType.includes('test') || productType.includes('xctest'))) { - testTargets.push({ - name: name.trim(), - identifier: identifier.trim(), - productType: productType.trim() - }); - } - } - if (testTargets.length === 0) { - return { - content: [{ - type: 'text', - text: `📋 TEST TARGETS\n\n⚠️ No test targets found in project.\n\nThis could mean:\n • No test targets are configured\n • Project file parsing failed\n • Test targets use a different naming convention` - }] - }; - } - // Helper function to convert product type to human-readable name - const getHumanReadableProductType = (productType) => { - switch (productType) { - case 'com.apple.product-type.bundle.unit-test': - return 'Unit Tests'; - case 'com.apple.product-type.bundle.ui-testing': - return 'UI Tests'; - default: - return 'Tests'; - } - }; - let message = `📋 TEST TARGETS\n\n`; - message += `Found ${testTargets.length} test target(s):\n\n`; - testTargets.forEach((target, index) => { - const testType = getHumanReadableProductType(target.productType); - message += `${index + 1}. **${target.name}** (${testType})\n\n`; - }); - message += `💡 Usage Examples:\n`; - if (testTargets.length > 0) { - message += ` • --test-target-name "${testTargets[0]?.name}"\n\n`; - } - message += `📝 Use --test-target-name with the target name for test filtering`; - return { - content: [{ - type: 'text', - text: message - }] - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { - content: [{ - type: 'text', - text: `Failed to get test targets: ${errorMessage}` - }] - }; - } - } -} -//# sourceMappingURL=ProjectTools.js.map \ No newline at end of file diff --git a/dist/tools/ProjectTools.js.map b/dist/tools/ProjectTools.js.map deleted file mode 100644 index 2031403..0000000 --- a/dist/tools/ProjectTools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ProjectTools.js","sourceRoot":"","sources":["../../src/tools/ProjectTools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/TestPlanTools.d.ts b/dist/tools/TestPlanTools.d.ts deleted file mode 100644 index e4f394a..0000000 --- a/dist/tools/TestPlanTools.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { McpResult } from '../types/index.js'; -interface TestTarget { - containerPath: string; - identifier: string; - name: string; -} -interface TestTargetConfig { - target: TestTarget; - selectedTests?: string[]; - skippedTests?: string[]; -} -export declare class TestPlanTools { - /** - * Update an .xctestplan file to run specific tests - */ - static updateTestPlan(testPlanPath: string, testTargets: TestTargetConfig[]): Promise; - /** - * Update test plan and automatically trigger lightweight reload - */ - static updateTestPlanAndReload(testPlanPath: string, projectPath: string, testTargets: TestTargetConfig[]): Promise; - /** - * Attempt to trigger Xcode to reload test plan without closing project - */ - static triggerTestPlanReload(testPlanPath: string, projectPath: string): Promise; - /** - * Scan project for all available test classes and methods - */ - static scanAvailableTests(projectPath: string, testPlanPath?: string): Promise; -} -export {}; -//# sourceMappingURL=TestPlanTools.d.ts.map \ No newline at end of file diff --git a/dist/tools/TestPlanTools.d.ts.map b/dist/tools/TestPlanTools.d.ts.map deleted file mode 100644 index 6f52cd9..0000000 --- a/dist/tools/TestPlanTools.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TestPlanTools.d.ts","sourceRoot":"","sources":["../../src/tools/TestPlanTools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,UAAU,UAAU;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAwBD,qBAAa,aAAa;IACxB;;OAEG;WACiB,cAAc,CAChC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,SAAS,CAAC;IAyErB;;OAEG;WACiB,uBAAuB,CACzC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,SAAS,CAAC;IAqCrB;;OAEG;WACiB,qBAAqB,CACvC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC;IAoDrB;;OAEG;WACiB,kBAAkB,CACpC,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,SAAS,CAAC;CA6JtB"} \ No newline at end of file diff --git a/dist/tools/TestPlanTools.js b/dist/tools/TestPlanTools.js deleted file mode 100644 index 1dcde04..0000000 --- a/dist/tools/TestPlanTools.js +++ /dev/null @@ -1,293 +0,0 @@ -import { promises as fs } from 'fs'; -import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; -import { Logger } from '../utils/Logger.js'; -export class TestPlanTools { - /** - * Update an .xctestplan file to run specific tests - */ - static async updateTestPlan(testPlanPath, testTargets) { - try { - Logger.info(`Updating test plan: ${testPlanPath}`); - // Read existing test plan - let testPlan; - try { - const content = await fs.readFile(testPlanPath, 'utf8'); - testPlan = JSON.parse(content); - } - catch (error) { - throw new McpError(ErrorCode.InvalidParams, `Failed to read test plan file: ${testPlanPath}. Error: ${error instanceof Error ? error.message : String(error)}`); - } - // Update test targets - testPlan.testTargets = testTargets.map(config => ({ - target: { - containerPath: config.target.containerPath, - identifier: config.target.identifier, - name: config.target.name - }, - ...(config.selectedTests && config.selectedTests.length > 0 && { selectedTests: config.selectedTests }), - ...(config.skippedTests && config.skippedTests.length > 0 && { skippedTests: config.skippedTests }) - })); - // Ensure version is set - if (!testPlan.version) { - testPlan.version = 1; - } - // Write updated test plan - try { - await fs.writeFile(testPlanPath, JSON.stringify(testPlan, null, 2), 'utf8'); - } - catch (error) { - throw new McpError(ErrorCode.InternalError, `Failed to write test plan file: ${testPlanPath}. Error: ${error instanceof Error ? error.message : String(error)}`); - } - const selectedTestCount = testTargets.reduce((count, target) => count + (target.selectedTests?.length || 0), 0); - const message = selectedTestCount > 0 - ? `Test plan updated with ${selectedTestCount} selected tests across ${testTargets.length} target(s)` - : `Test plan updated to run all tests in ${testTargets.length} target(s)`; - Logger.info(message); - return { - content: [{ - type: 'text', - text: message - }] - }; - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error(`Error updating test plan: ${errorMessage}`); - throw new McpError(ErrorCode.InternalError, `Failed to update test plan: ${errorMessage}`); - } - } - /** - * Update test plan and automatically trigger lightweight reload - */ - static async updateTestPlanAndReload(testPlanPath, projectPath, testTargets) { - try { - // First update the test plan - await this.updateTestPlan(testPlanPath, testTargets); - // Then trigger lightweight reload - const reloadResult = await this.triggerTestPlanReload(testPlanPath, projectPath); - const selectedTestCount = testTargets.reduce((count, target) => count + (target.selectedTests?.length || 0), 0); - const message = selectedTestCount > 0 - ? `Test plan updated with ${selectedTestCount} selected tests and reload triggered` - : `Test plan updated to run all tests in ${testTargets.length} target(s) and reload triggered`; - return { - content: [{ - type: 'text', - text: `${message}\n\n${reloadResult.content?.[0]?.type === 'text' ? reloadResult.content[0].text : 'Reload completed'}` - }] - }; - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error(`Error updating test plan and reloading: ${errorMessage}`); - throw new McpError(ErrorCode.InternalError, `Failed to update test plan and reload: ${errorMessage}`); - } - } - /** - * Attempt to trigger Xcode to reload test plan without closing project - */ - static async triggerTestPlanReload(testPlanPath, projectPath) { - try { - Logger.info(`Attempting to trigger test plan reload for: ${testPlanPath}`); - const { exec } = await import('child_process'); - const { promisify } = await import('util'); - const execAsync = promisify(exec); - const results = []; - // Method 1: Touch the test plan file to update its timestamp - try { - await execAsync(`touch "${testPlanPath}"`); - results.push('✓ Updated test plan file timestamp'); - Logger.info('Updated test plan file timestamp'); - } - catch (error) { - results.push(`✗ Failed to touch test plan file: ${error instanceof Error ? error.message : String(error)}`); - } - // Method 2: Touch the project directory - try { - await execAsync(`touch "${projectPath}"`); - results.push('✓ Updated project directory timestamp'); - Logger.info('Updated project directory timestamp'); - } - catch (error) { - results.push(`✗ Failed to touch project directory: ${error instanceof Error ? error.message : String(error)}`); - } - // Method 3: Brief delay to allow file system events to propagate - await new Promise(resolve => setTimeout(resolve, 500)); - results.push('✓ Waited for file system events to propagate'); - return { - content: [{ - type: 'text', - text: `Test plan reload triggered:\n${results.join('\n')}\n\nNote: If Xcode doesn't reload the test plan automatically, you may need to use xcode_refresh_project as a fallback.` - }] - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error(`Error triggering test plan reload: ${errorMessage}`); - return { - content: [{ - type: 'text', - text: `Failed to trigger test plan reload: ${errorMessage}\n\nRecommendation: Use xcode_refresh_project tool to ensure test plan changes are loaded.` - }] - }; - } - } - /** - * Scan project for all available test classes and methods - */ - static async scanAvailableTests(projectPath, testPlanPath) { - try { - const { exec } = await import('child_process'); - const { promisify } = await import('util'); - const execAsync = promisify(exec); - Logger.info(`Scanning for available tests in: ${projectPath}`); - let testClasses = []; - let testMethods = []; - try { - // Use xcodebuild to list tests (this gives us the most accurate info) - const { stdout } = await execAsync(`xcodebuild test -project "${projectPath}" -scheme TestApp -destination "platform=iOS Simulator,name=iPhone 15" -dry-run 2>/dev/null | grep -E "Test.*:\\s*$" || true`); - const lines = stdout.split('\n').filter(line => line.trim()); - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed.includes('Test Suite')) { - // Extract test class name - const match = trimmed.match(/Test Suite '([^']+)'/); - if (match && match[1] && !match[1].includes('.xctest')) { - testClasses.push(match[1]); - } - } - else if (trimmed.includes('Test Case')) { - // Extract test method name - const match = trimmed.match(/Test Case '([^']+)'/); - if (match && match[1]) { - testMethods.push(match[1]); - } - } - } - } - catch (xcodebuildError) { - Logger.warn(`xcodebuild scan failed: ${xcodebuildError}, falling back to file scanning`); - // Fallback: scan Swift test files for test methods - try { - const { stdout: findResult } = await execAsync(`find "${projectPath}/.." -name "*Test*.swift" -type f 2>/dev/null || true`); - const testFiles = findResult.split('\n').filter(line => line.trim()); - for (const file of testFiles) { - if (!file.trim()) - continue; - try { - const { stdout: grepResult } = await execAsync(`grep -n "func test\\|class.*Test" "${file}" 2>/dev/null || true`); - const lines = grepResult.split('\n').filter(line => line.trim()); - for (const line of lines) { - if (line.includes('class') && line.includes('Test')) { - const match = line.match(/class\s+(\w+Test\w*)/); - if (match && match[1]) { - testClasses.push(match[1]); - } - } - else if (line.includes('func test')) { - const match = line.match(/func\s+(test\w+)/); - if (match && match[1]) { - testMethods.push(match[1]); - } - } - } - } - catch (fileError) { - Logger.warn(`Failed to scan file ${file}: ${fileError}`); - } - } - } - catch (findError) { - Logger.warn(`File scanning failed: ${findError}`); - } - } - // Remove duplicates and sort - testClasses = [...new Set(testClasses)].sort(); - testMethods = [...new Set(testMethods)].sort(); - let message = `📋 AVAILABLE TESTS\n\n`; - if (testClasses.length > 0) { - message += `🏷️ Test Classes (${testClasses.length}):\n`; - testClasses.forEach(cls => { - message += ` • ${cls}\n`; - }); - message += '\n'; - } - if (testMethods.length > 0) { - message += `🧪 Test Methods (${testMethods.length}):\n`; - testMethods.forEach(method => { - message += ` • ${method}\n`; - }); - message += '\n'; - } - if (testClasses.length === 0 && testMethods.length === 0) { - message += `⚠️ No test classes or methods found.\n\n`; - message += `This could mean:\n`; - message += ` • No test targets in the project\n`; - message += ` • Tests are not properly configured\n`; - message += ` • Project build is required first\n`; - } - else { - message += `💡 Usage Examples:\n`; - message += ` • Run specific XCTest method: --selected-tests '["TestAppUITests/testExample"]' (no parentheses)\n`; - message += ` • Run specific Swift Testing test: --selected-tests '["TestAppTests/example"]'\n`; - message += ` • Run entire test class: --selected-test-classes '["TestAppTests"]' (all tests in class)\n`; - message += ` • Run multiple classes: --selected-test-classes '["TestAppTests", "TestAppUITests"]'\n`; - message += ` • Combine both: --selected-tests '["TestAppTests/example"]' --selected-test-classes '["TestAppUITests"]'\n`; - } - if (testPlanPath) { - try { - const { promises: fs } = await import('fs'); - const testPlanContent = await fs.readFile(testPlanPath, 'utf8'); - const testPlan = JSON.parse(testPlanContent); - message += `\n📄 Current Test Plan: ${testPlanPath}\n`; - if (testPlan.testTargets && testPlan.testTargets.length > 0) { - message += ` • Configured targets: ${testPlan.testTargets.length}\n`; - testPlan.testTargets.forEach((target, index) => { - message += ` • Target ${index + 1}: ${target.target?.name || 'Unknown'}\n`; - if (target.selectedTests && target.selectedTests.length > 0) { - message += ` - Selected tests: ${target.selectedTests.length}\n`; - target.selectedTests.slice(0, 3).forEach((test) => { - message += ` • ${test}\n`; - }); - if (target.selectedTests.length > 3) { - message += ` • ... and ${target.selectedTests.length - 3} more\n`; - } - } - else { - message += ` - Running all tests in target\n`; - } - }); - } - else { - message += ` • No test targets configured\n`; - } - } - catch (planError) { - message += `\n⚠️ Could not read test plan: ${planError instanceof Error ? planError.message : String(planError)}\n`; - } - } - return { - content: [{ - type: 'text', - text: message - }] - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error(`Error scanning available tests: ${errorMessage}`); - return { - content: [{ - type: 'text', - text: `Failed to scan available tests: ${errorMessage}` - }] - }; - } - } -} -//# sourceMappingURL=TestPlanTools.js.map \ No newline at end of file diff --git a/dist/tools/TestPlanTools.js.map b/dist/tools/TestPlanTools.js.map deleted file mode 100644 index 625eb96..0000000 --- a/dist/tools/TestPlanTools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"TestPlanTools.js","sourceRoot":"","sources":["../../src/tools/TestPlanTools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/tools/XCResultTools.d.ts b/dist/tools/XCResultTools.d.ts deleted file mode 100644 index a7ea29b..0000000 --- a/dist/tools/XCResultTools.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { McpResult } from '../types/index.js'; -export declare class XCResultTools { - /** - * Browse xcresult file - list tests or show specific test details - */ - static xcresultBrowse(xcresultPath: string, testId?: string, includeConsole?: boolean): Promise; - /** - * Get console output for a specific test - */ - static xcresultBrowserGetConsole(xcresultPath: string, testId: string): Promise; - /** - * Get a quick summary of an xcresult file - */ - static xcresultSummary(xcresultPath: string): Promise; - /** - * List all attachments for a test - */ - static xcresultListAttachments(xcresultPath: string, testId: string): Promise; - /** - * Export a specific attachment by index - */ - static xcresultExportAttachment(xcresultPath: string, testId: string, attachmentIndex: number, convertToJson?: boolean): Promise; - /** - * Get UI hierarchy attachment from test as JSON (slim AI-readable version by default) - */ - static xcresultGetUIHierarchy(xcresultPath: string, testId: string, timestamp?: number, fullHierarchy?: boolean, rawFormat?: boolean): Promise; - /** - * Get screenshot from failed test - returns direct screenshot or extracts from video - */ - static xcresultGetScreenshot(xcresultPath: string, testId: string, timestamp: number): Promise; - /** - * Find App UI hierarchy attachments (text-based) - */ - private static findAppUIHierarchyAttachments; - /** - * Find UI Snapshot attachments (legacy method) - */ - /** - * Find the UI snapshot closest to a given timestamp - */ - private static findClosestUISnapshot; - /** - * Export UI hierarchy attachment and convert to JSON (legacy plist method) - */ - /** - * Convert UI hierarchy plist to JSON using plutil -p (readable format) - */ - private static convertUIHierarchyToJSON; - /** - * Parse plutil -p output to extract UI hierarchy information - */ - private static parsePlutilOutput; - /** - * Export text-based UI hierarchy attachment and convert to JSON - */ - private static exportTextUIHierarchyAsJSON; - /** - * Convert indented text-based UI hierarchy to structured JSON - */ - private static convertIndentedUIHierarchyToJSON; - /** - * Parse a single line of UI element information - */ - private static parseUIElementLine; - /** - * Save UI hierarchy data to a JSON file - */ - private static saveUIHierarchyJSON; - /** - * Create slim AI-readable UI hierarchy with index mapping - */ - private static createSlimUIHierarchy; - /** - * Get UI element details by index from previously exported hierarchy - */ - static xcresultGetUIElement(hierarchyJsonPath: string, elementIndex: number, includeChildren?: boolean): Promise; - /** - * Find the closest image attachment to a specific timestamp - */ - private static findClosestImageAttachment; - /** - * Find video attachment by type identifier and filename extension - */ - private static findVideoAttachment; - /** - * Export screenshot attachment to temporary directory - */ - private static exportScreenshotAttachment; - /** - * Extract screenshot from video attachment using ffmpeg at specific timestamp - */ - private static extractScreenshotFromVideo; - /** - * Run ffmpeg to extract a frame from video as PNG at specific timestamp - */ - private static runFFmpeg; -} -//# sourceMappingURL=XCResultTools.d.ts.map \ No newline at end of file diff --git a/dist/tools/XCResultTools.d.ts.map b/dist/tools/XCResultTools.d.ts.map deleted file mode 100644 index f75fe29..0000000 --- a/dist/tools/XCResultTools.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XCResultTools.d.ts","sourceRoot":"","sources":["../../src/tools/XCResultTools.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAEnE,qBAAa,aAAa;IACxB;;OAEG;WACiB,cAAc,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,cAAc,GAAE,OAAe,GAC9B,OAAO,CAAC,SAAS,CAAC;IAkErB;;OAEG;WACiB,yBAAyB,CAC3C,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,CAAC;IA4GrB;;OAEG;WACiB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAkE7E;;OAEG;WACiB,uBAAuB,CACzC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,CAAC;IAkHrB;;OAEG;WACiB,wBAAwB,CAC1C,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,aAAa,GAAE,OAAe,GAC7B,OAAO,CAAC,SAAS,CAAC;IAiKrB;;OAEG;WACiB,sBAAsB,CACxC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,aAAa,GAAE,OAAe,EAC9B,SAAS,GAAE,OAAe,GACzB,OAAO,CAAC,SAAS,CAAC;IAwNrB;;OAEG;WACiB,qBAAqB,CACvC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,SAAS,CAAC;IAyHrB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,6BAA6B;IAO5C;;OAEG;IASH;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA0EpC;;OAEG;IAiBH;;OAEG;mBACkB,wBAAwB;IAoC7C;;OAEG;mBACkB,iBAAiB;IAwItC;;OAEG;mBACkB,2BAA2B;IAgBhD;;OAEG;mBACkB,gCAAgC;IA6DrD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAmEjC;;OAEG;mBACkB,mBAAmB;IAqBxC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IA8CpC;;OAEG;WACiB,oBAAoB,CACtC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,eAAe,GAAE,OAAe,GAC/B,OAAO,CAAC,SAAS,CAAC;IA6DrB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,0BAA0B;IAgDzC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAelC;;OAEG;mBACkB,0BAA0B;IAU/C;;OAEG;mBACkB,0BAA0B;IA6B/C;;OAEG;mBACkB,SAAS;CAgE/B"} \ No newline at end of file diff --git a/dist/tools/XCResultTools.js b/dist/tools/XCResultTools.js deleted file mode 100644 index c6d9e25..0000000 --- a/dist/tools/XCResultTools.js +++ /dev/null @@ -1,1257 +0,0 @@ -import { existsSync } from 'fs'; -import { spawn } from 'child_process'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; -import { XCResultParser } from '../utils/XCResultParser.js'; -import { Logger } from '../utils/Logger.js'; -export class XCResultTools { - /** - * Browse xcresult file - list tests or show specific test details - */ - static async xcresultBrowse(xcresultPath, testId, includeConsole = false) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - try { - const parser = new XCResultParser(xcresultPath); - if (testId) { - // Show specific test details - const details = await parser.formatTestDetails(testId, includeConsole); - return { content: [{ type: 'text', text: details }] }; - } - else { - // List all tests - const testList = await parser.formatTestList(); - let usage = '\n\n💡 Usage:\n'; - usage += ' View test details: xcresult-browse --xcresult-path --test-id \n'; - usage += ' View with console: xcresult-browse --xcresult-path --test-id --include-console\n'; - usage += ' Get console only: xcresult-browser-get-console --xcresult-path --test-id \n'; - usage += ' Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path --test-id --timestamp [timestamp]\n'; - usage += ' Get screenshot: xcresult-get-screenshot --xcresult-path --test-id --timestamp \n'; - usage += ' Examples:\n'; - usage += ` xcresult-browse --xcresult-path "${xcresultPath}" --test-id 5\n`; - usage += ` xcresult-browse --xcresult-path "${xcresultPath}" --test-id "SomeTest/testMethod()" --include-console\n`; - usage += ` xcresult-get-ui-hierarchy --xcresult-path "${xcresultPath}" --test-id 5 --timestamp 120.5\n`; - usage += ` xcresult-get-screenshot --xcresult-path "${xcresultPath}" --test-id 5 --timestamp 120.5\n`; - return { content: [{ type: 'text', text: testList + usage }] }; - } - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to analyze XCResult: ${errorMessage}`); - } - } - /** - * Get console output for a specific test - */ - static async xcresultBrowserGetConsole(xcresultPath, testId) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - if (!testId || testId.trim() === '') { - throw new McpError(ErrorCode.InvalidParams, 'Test ID or index is required'); - } - try { - const parser = new XCResultParser(xcresultPath); - // First find the test node to get the actual test identifier - const testNode = await parser.findTestNode(testId); - if (!testNode) { - return { - content: [{ - type: 'text', - text: `❌ Test '${testId}' not found\n\nRun xcresult_browse "${xcresultPath}" to see all available tests` - }] - }; - } - let output = `📟 Console Output for: ${testNode.name}\n`; - output += '='.repeat(80) + '\n\n'; - // Get console output - const consoleOutput = await parser.getConsoleOutput(testNode.nodeIdentifier); - output += `Console Log:\n${consoleOutput}\n\n`; - // Get test activities - if (testNode.nodeIdentifier) { - output += `🔬 Test Activities:\n`; - const activities = await parser.getTestActivities(testNode.nodeIdentifier); - output += activities; - } - // Check if output is very long and should be saved to a file - const lineCount = output.split('\n').length; - const charCount = output.length; - // If output is longer than 20 lines or 2KB, save to file - if (lineCount > 20 || charCount > 2000) { - const { writeFile } = await import('fs/promises'); - const { tmpdir } = await import('os'); - const { join } = await import('path'); - // Create a unique filename - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const safeTestName = testNode.name.replace(/[^a-zA-Z0-9]/g, '_'); - const filename = `console_output_${safeTestName}_${timestamp}.txt`; - const filePath = join(tmpdir(), filename); - await writeFile(filePath, output, 'utf-8'); - const fileSizeKB = Math.round(charCount / 1024); - return { - content: [{ - type: 'text', - text: `📟 Console Output for: ${testNode.name}\n` + - `📄 Output saved to file (${lineCount} lines, ${fileSizeKB} KB): ${filePath}\n\n` + - `💡 The console output was too large to display directly. ` + - `You can read the file to access the complete console log and test activities.` - }] - }; - } - return { content: [{ type: 'text', text: output }] }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to get console output: ${errorMessage}`); - } - } - /** - * Get a quick summary of an xcresult file - */ - static async xcresultSummary(xcresultPath) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - try { - const parser = new XCResultParser(xcresultPath); - const analysis = await parser.analyzeXCResult(); - let output = `📊 XCResult Summary - ${xcresultPath}\n`; - output += '='.repeat(80) + '\n\n'; - output += `Result: ${analysis.summary.result === 'Failed' ? '❌' : '✅'} ${analysis.summary.result}\n`; - output += `Total: ${analysis.totalTests} | Passed: ${analysis.passedTests} ✅ | Failed: ${analysis.failedTests} ❌ | Skipped: ${analysis.skippedTests} ⏭️\n`; - output += `Pass Rate: ${analysis.passRate.toFixed(1)}%\n`; - output += `Duration: ${analysis.duration}\n\n`; - if (analysis.failedTests > 0) { - output += `❌ Failed Tests:\n`; - for (const failure of analysis.summary.testFailures.slice(0, 5)) { - output += ` • ${failure.testName}: ${failure.failureText.substring(0, 100)}${failure.failureText.length > 100 ? '...' : ''}\n`; - } - if (analysis.summary.testFailures.length > 5) { - output += ` ... and ${analysis.summary.testFailures.length - 5} more\n`; - } - output += '\n'; - } - output += `💡 Use 'xcresult_browse "${xcresultPath}"' to explore detailed results.`; - return { content: [{ type: 'text', text: output }] }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to analyze XCResult: ${errorMessage}`); - } - } - /** - * List all attachments for a test - */ - static async xcresultListAttachments(xcresultPath, testId) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - if (!testId || testId.trim() === '') { - throw new McpError(ErrorCode.InvalidParams, 'Test ID or index is required'); - } - try { - const parser = new XCResultParser(xcresultPath); - // First find the test node to get the actual test identifier - const testNode = await parser.findTestNode(testId); - if (!testNode) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' not found. Run xcresult_browse "${xcresultPath}" to see all available tests`); - } - if (!testNode.nodeIdentifier) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' does not have a valid identifier for attachment retrieval`); - } - // Get test attachments - const attachments = await parser.getTestAttachments(testNode.nodeIdentifier); - let output = `📎 Attachments for test: ${testNode.name}\n`; - output += `Found ${attachments.length} attachments\n`; - output += '='.repeat(80) + '\n\n'; - if (attachments.length === 0) { - output += 'No attachments found for this test.\n'; - } - else { - attachments.forEach((att, index) => { - const filename = att.name || att.filename || 'unnamed'; - output += `[${index + 1}] ${filename}\n`; - // Determine type from identifier or filename - let type = att.uniform_type_identifier || att.uniformTypeIdentifier || ''; - if (!type || type === 'unknown') { - // Infer type from filename extension or special patterns - const ext = filename.toLowerCase().split('.').pop(); - if (ext === 'jpeg' || ext === 'jpg') - type = 'public.jpeg'; - else if (ext === 'png') - type = 'public.png'; - else if (ext === 'mp4') - type = 'public.mpeg-4'; - else if (ext === 'mov') - type = 'com.apple.quicktime-movie'; - else if (ext === 'txt') - type = 'public.plain-text'; - else if (filename.toLowerCase().includes('app ui hierarchy')) - type = 'ui-hierarchy'; - else if (filename.toLowerCase().includes('ui snapshot')) - type = 'ui-snapshot'; - else if (filename.toLowerCase().includes('synthesized event')) - type = 'synthesized-event'; - else - type = 'unknown'; - } - output += ` Type: ${type}\n`; - if (att.payloadSize || att.payload_size) { - output += ` Size: ${att.payloadSize || att.payload_size} bytes\n`; - } - output += '\n'; - }); - output += '\n💡 To export a specific attachment, use xcresult_export_attachment with the attachment index.\n'; - } - return { content: [{ type: 'text', text: output }] }; - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to list attachments: ${errorMessage}`); - } - } - /** - * Export a specific attachment by index - */ - static async xcresultExportAttachment(xcresultPath, testId, attachmentIndex, convertToJson = false) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - if (!testId || testId.trim() === '') { - throw new McpError(ErrorCode.InvalidParams, 'Test ID or index is required'); - } - if (attachmentIndex < 1) { - throw new McpError(ErrorCode.InvalidParams, 'Attachment index must be 1 or greater'); - } - try { - const parser = new XCResultParser(xcresultPath); - // First find the test node to get the actual test identifier - const testNode = await parser.findTestNode(testId); - if (!testNode) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' not found. Run xcresult_browse "${xcresultPath}" to see all available tests`); - } - if (!testNode.nodeIdentifier) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' does not have a valid identifier for attachment retrieval`); - } - // Get test attachments - const attachments = await parser.getTestAttachments(testNode.nodeIdentifier); - if (attachments.length === 0) { - throw new McpError(ErrorCode.InvalidParams, `No attachments found for test '${testNode.name}'.`); - } - if (attachmentIndex > attachments.length) { - throw new McpError(ErrorCode.InvalidParams, `Invalid attachment index ${attachmentIndex}. Test has ${attachments.length} attachments.`); - } - const attachment = attachments[attachmentIndex - 1]; - if (!attachment) { - throw new McpError(ErrorCode.InternalError, `Attachment at index ${attachmentIndex} not found`); - } - const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID; - if (!attachmentId) { - throw new McpError(ErrorCode.InternalError, 'Attachment does not have a valid ID for export'); - } - const filename = attachment.filename || attachment.name || `attachment_${attachmentIndex}`; - // Determine type from identifier or filename first - let type = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; - if (!type || type === 'unknown') { - // Infer type from filename extension or special patterns - const ext = filename.toLowerCase().split('.').pop(); - if (ext === 'jpeg' || ext === 'jpg') - type = 'public.jpeg'; - else if (ext === 'png') - type = 'public.png'; - else if (ext === 'mp4') - type = 'public.mpeg-4'; - else if (ext === 'mov') - type = 'com.apple.quicktime-movie'; - else if (ext === 'txt') - type = 'public.plain-text'; - else if (filename.toLowerCase().includes('app ui hierarchy')) - type = 'ui-hierarchy'; - else if (filename.toLowerCase().includes('ui snapshot')) - type = 'ui-snapshot'; - else if (filename.toLowerCase().includes('synthesized event')) - type = 'synthesized-event'; - else - type = 'unknown'; - } - const exportedPath = await parser.exportAttachment(attachmentId, filename); - // Handle UI hierarchy files specially - if (type === 'ui-hierarchy') { - if (convertToJson) { - const hierarchyJson = await this.convertUIHierarchyToJSON(exportedPath); - return { - content: [{ - type: 'text', - text: JSON.stringify(hierarchyJson) - }] - }; - } - // Return the raw UI hierarchy content (it's already AI-friendly) - const { readFile } = await import('fs/promises'); - const hierarchyContent = await readFile(exportedPath, 'utf-8'); - return { - content: [{ - type: 'text', - text: `UI Hierarchy for: ${filename}\nType: ${type}\n\n${hierarchyContent}` - }] - }; - } - return { - content: [{ - type: 'text', - text: `Attachment exported to: ${exportedPath}\nFilename: ${filename}\nType: ${type}` - }] - }; - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to export attachment: ${errorMessage}`); - } - } - /** - * Get UI hierarchy attachment from test as JSON (slim AI-readable version by default) - */ - static async xcresultGetUIHierarchy(xcresultPath, testId, timestamp, fullHierarchy = false, rawFormat = false) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - if (!testId || testId.trim() === '') { - throw new McpError(ErrorCode.InvalidParams, 'Test ID or index is required'); - } - try { - const parser = new XCResultParser(xcresultPath); - // First find the test node to get the actual test identifier - const testNode = await parser.findTestNode(testId); - if (!testNode) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' not found. Run xcresult_browse "${xcresultPath}" to see all available tests`); - } - if (!testNode.nodeIdentifier) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' does not have a valid identifier for attachment retrieval`); - } - // Get test attachments - const attachments = await parser.getTestAttachments(testNode.nodeIdentifier); - if (attachments.length === 0) { - throw new McpError(ErrorCode.InvalidParams, `No attachments found for test '${testNode.name}'. This test may not have UI snapshots.`); - } - Logger.info(`Found ${attachments.length} attachments for test ${testNode.name}`); - // Log all attachment details for debugging - Logger.info('All attachments:'); - attachments.forEach((att, index) => { - Logger.info(` ${index + 1}. Name: ${att.name || att.filename || 'unnamed'}, Type: ${att.uniform_type_identifier || att.uniformTypeIdentifier || 'unknown'}`); - }); - // Look for App UI hierarchy attachments (text-based) - const uiHierarchyAttachments = this.findAppUIHierarchyAttachments(attachments); - if (uiHierarchyAttachments.length === 0) { - const attachmentNames = attachments.map(a => a.name || a.filename || 'unnamed').join(', '); - throw new McpError(ErrorCode.InvalidParams, `No App UI hierarchy attachments found for test '${testNode.name}'. Available attachments: ${attachmentNames}`); - } - // If timestamp is provided, find the closest UI hierarchy attachment - let selectedAttachment = uiHierarchyAttachments[0]; - if (timestamp !== undefined && uiHierarchyAttachments.length > 1) { - Logger.info(`Looking for UI hierarchy closest to timestamp ${timestamp}s`); - const closestAttachment = this.findClosestUISnapshot(uiHierarchyAttachments, timestamp); - if (closestAttachment) { - selectedAttachment = closestAttachment; - } - } - else if (uiHierarchyAttachments.length > 1) { - Logger.info(`Multiple UI hierarchy attachments found (${uiHierarchyAttachments.length}). Using the first one. Specify a timestamp to select a specific one.`); - } - if (!selectedAttachment) { - throw new McpError(ErrorCode.InternalError, `No valid UI hierarchy found for test '${testNode.name}'`); - } - // If raw format is requested, return the original accessibility tree text - if (rawFormat) { - const attachmentId = selectedAttachment.payloadId || selectedAttachment.payload_uuid || selectedAttachment.payloadUUID; - if (!attachmentId) { - throw new McpError(ErrorCode.InternalError, 'UI hierarchy attachment does not have a valid ID for export'); - } - const filename = selectedAttachment.filename || selectedAttachment.name || 'ui-hierarchy'; - const exportedPath = await parser.exportAttachment(attachmentId, filename); - const { readFile } = await import('fs/promises'); - const hierarchyContent = await readFile(exportedPath, 'utf-8'); - const timestampInfo = timestamp !== undefined && selectedAttachment.timestamp !== undefined - ? ` (closest to ${timestamp}s, actual: ${selectedAttachment.timestamp}s)` - : ''; - return { - content: [{ - type: 'text', - text: `🌲 Raw UI Hierarchy for test '${testNode.name}'${timestampInfo}:\n\n${hierarchyContent}` - }] - }; - } - // Export and convert text-based UI hierarchy to JSON - const hierarchyData = await this.exportTextUIHierarchyAsJSON(parser, selectedAttachment, testNode.name); - if (fullHierarchy) { - // Save full JSON to file with warning - const jsonFilename = `ui_hierarchy_full_${testNode.name.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.json`; - const jsonPath = await this.saveUIHierarchyJSON(hierarchyData, jsonFilename); - const fileSizeKB = Math.round(JSON.stringify(hierarchyData).length / 1024); - return { - content: [{ - type: 'text', - text: `⚠️ LARGE FILE WARNING: Full UI hierarchy exported (${fileSizeKB} KB)\n\n` + - `📄 Full hierarchy: ${jsonPath}\n\n` + - `💡 For AI analysis, consider using the slim version instead:\n` + - ` xcresult_get_ui_hierarchy "${xcresultPath}" "${testId}" ${timestamp || ''} false` - }] - }; - } - else { - // Default: Create and save slim AI-readable version - const slimData = this.createSlimUIHierarchy(hierarchyData); - const slimFilename = `ui_hierarchy_${testNode.name.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.json`; - const slimPath = await this.saveUIHierarchyJSON(slimData, slimFilename); - // Also save full data for element lookup - const fullFilename = `ui_hierarchy_full_${testNode.name.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.json`; - const fullPath = await this.saveUIHierarchyJSON(hierarchyData, fullFilename); - // Try to find a screenshot at the specified timestamp - let screenshotInfo = ''; - const screenshotTimestamp = timestamp || selectedAttachment.timestamp; - if (screenshotTimestamp !== undefined) { - try { - const screenshotResult = await this.xcresultGetScreenshot(xcresultPath, testId, screenshotTimestamp); - if (screenshotResult && screenshotResult.content?.[0] && 'text' in screenshotResult.content[0]) { - const textContent = screenshotResult.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - // Extract the screenshot path from the result text - const pathMatch = textContent.text.match(/Screenshot extracted .+: (.+)/); - if (pathMatch && pathMatch[1]) { - screenshotInfo = `\n📸 Screenshot at timestamp ${screenshotTimestamp}s: ${pathMatch[1]}`; - } - } - } - } - catch (error) { - // Screenshot extraction failed, continue without it - Logger.info(`Could not extract screenshot at timestamp ${screenshotTimestamp}s: ${error}`); - } - } - return { - content: [{ - type: 'text', - text: `🤖 AI-readable UI hierarchy: ${slimPath}\n\n` + - `💡 Slim version properties:\n` + - ` • t = type (element type like Button, StaticText, etc.)\n` + - ` • l = label (visible text/accessibility label)\n` + - ` • c = children (array of child elements)\n` + - ` • j = index (reference to full element in original JSON)\n\n` + - `🔍 Use xcresult_get_ui_element "${fullPath}" to get full details of any element.\n` + - `⚠️ To get the full hierarchy (several MB), use: full_hierarchy=true${screenshotInfo}` - }] - }; - } - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to get UI hierarchy: ${errorMessage}`); - } - } - /** - * Get screenshot from failed test - returns direct screenshot or extracts from video - */ - static async xcresultGetScreenshot(xcresultPath, testId, timestamp) { - // Validate xcresult path - if (!existsSync(xcresultPath)) { - throw new McpError(ErrorCode.InvalidParams, `XCResult file not found: ${xcresultPath}`); - } - if (!xcresultPath.endsWith('.xcresult')) { - throw new McpError(ErrorCode.InvalidParams, `Path must be an .xcresult file: ${xcresultPath}`); - } - // Check if xcresult is readable - if (!XCResultParser.isXCResultReadable(xcresultPath)) { - throw new McpError(ErrorCode.InternalError, `XCResult file is not readable or incomplete: ${xcresultPath}`); - } - if (!testId || testId.trim() === '') { - throw new McpError(ErrorCode.InvalidParams, 'Test ID or index is required'); - } - try { - const parser = new XCResultParser(xcresultPath); - // First find the test node to get the actual test identifier - const testNode = await parser.findTestNode(testId); - if (!testNode) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' not found. Run xcresult_browse "${xcresultPath}" to see all available tests`); - } - if (!testNode.nodeIdentifier) { - throw new McpError(ErrorCode.InvalidParams, `Test '${testId}' does not have a valid identifier for attachment retrieval`); - } - // Get test attachments - const attachments = await parser.getTestAttachments(testNode.nodeIdentifier); - if (attachments.length === 0) { - throw new McpError(ErrorCode.InvalidParams, `No attachments found for test '${testNode.name}'. This test may not have failed or may not have generated screenshots/videos.`); - } - Logger.info(`Found ${attachments.length} attachments for test ${testNode.name}`); - // Look for video attachment first (gives us actual PNG images) - const videoAttachment = this.findVideoAttachment(attachments); - if (videoAttachment) { - const screenshotPath = await this.extractScreenshotFromVideo(parser, videoAttachment, testNode.name, timestamp); - return { - content: [{ - type: 'text', - text: `Screenshot extracted from video for test '${testNode.name}' at ${timestamp}s: ${screenshotPath}` - }] - }; - } - // Look for direct image attachment (PNG or JPEG) as fallback - const closestImageResult = this.findClosestImageAttachment(attachments, timestamp); - if (closestImageResult) { - const screenshotPath = await this.exportScreenshotAttachment(parser, closestImageResult.attachment); - const timeDiff = closestImageResult.timeDifference; - const timeDiffText = timeDiff === 0 - ? 'at exact timestamp' - : timeDiff > 0 - ? `${timeDiff.toFixed(2)}s after requested time` - : `${Math.abs(timeDiff).toFixed(2)}s before requested time`; - return { - content: [{ - type: 'text', - text: `Screenshot exported for test '${testNode.name}' (${timeDiffText}): ${screenshotPath}` - }] - }; - } - // No suitable attachments found - const attachmentTypes = attachments.map(a => a.uniform_type_identifier || a.uniformTypeIdentifier || 'unknown').join(', '); - throw new McpError(ErrorCode.InvalidParams, `No screenshot or video attachments found for test '${testNode.name}'. Available attachment types: ${attachmentTypes}`); - } - catch (error) { - if (error instanceof McpError) { - throw error; - } - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('xcresulttool')) { - throw new McpError(ErrorCode.InternalError, `XCResult parsing failed. Make sure Xcode Command Line Tools are installed: ${errorMessage}`); - } - throw new McpError(ErrorCode.InternalError, `Failed to get screenshot: ${errorMessage}`); - } - } - /** - * Find App UI hierarchy attachments (text-based) - */ - static findAppUIHierarchyAttachments(attachments) { - return attachments.filter(attachment => { - const name = attachment.name || attachment.filename || ''; - return name.includes('App UI hierarchy'); - }); - } - /** - * Find UI Snapshot attachments (legacy method) - */ - // private static findUISnapshotAttachments(attachments: TestAttachment[]): TestAttachment[] { - // return attachments.filter(attachment => { - // const name = attachment.name || attachment.filename || ''; - // // Look for both "UI Snapshot" and "App UI hierarchy" attachments - // return name.includes('UI Snapshot') || name.includes('App UI hierarchy'); - // }); - // } - /** - * Find the UI snapshot closest to a given timestamp - */ - static findClosestUISnapshot(attachments, timestamp) { - if (attachments.length === 0) { - return undefined; - } - // If only one attachment, return it - if (attachments.length === 1) { - return attachments[0]; - } - let closest = attachments[0]; - let minDifference = Infinity; - let attachmentsWithTimestamps = 0; - // Log available attachments and their timestamps for debugging - Logger.info(`Finding closest attachment to timestamp ${timestamp}s among ${attachments.length} attachments`); - for (const attachment of attachments) { - // Use timestamp if available, otherwise try to extract from name - let attachmentTime = attachment.timestamp; - // If no direct timestamp, try alternative approaches - if (attachmentTime === undefined || isNaN(attachmentTime)) { - // Try to extract timestamp from attachment name if it contains time info - if (attachment.name) { - const timeMatch = attachment.name.match(/t\s*=\s*([\d.]+)s/); - if (timeMatch && timeMatch[1]) { - attachmentTime = parseFloat(timeMatch[1]); - } - } - } - // Log attachment details for debugging - Logger.info(` Attachment "${attachment.name}" - timestamp: ${attachmentTime}, type: ${attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || 'unknown'}`); - if (attachmentTime !== undefined && !isNaN(attachmentTime)) { - attachmentsWithTimestamps++; - // Both timestamps should be in seconds - const difference = Math.abs(attachmentTime - timestamp); - Logger.info(` Time difference: ${difference}s`); - if (difference < minDifference) { - minDifference = difference; - closest = attachment; - } - } - } - // If no attachments have timestamps, use a different strategy - if (attachmentsWithTimestamps === 0) { - Logger.info(`No timestamp information found for UI hierarchy attachments. Using attachment order heuristic.`); - // For UI hierarchy attachments without timestamps, prefer the later one - // when the requested timestamp is > 60s (indicating late in test execution) - if (timestamp > 60 && attachments.length >= 2) { - const lastAttachment = attachments[attachments.length - 1]; - if (lastAttachment) { - closest = lastAttachment; // Use last attachment - Logger.info(`Selected last attachment "${closest.name || 'unnamed'}" based on late timestamp heuristic (${timestamp}s > 60s)`); - } - } - else { - const firstAttachment = attachments[0]; - if (firstAttachment) { - closest = firstAttachment; // Use first attachment for early timestamps - Logger.info(`Selected first attachment "${closest.name || 'unnamed'}" based on early timestamp heuristic (${timestamp}s <= 60s)`); - } - } - } - else if (closest) { - Logger.info(`Selected attachment "${closest.name}" with minimum time difference of ${minDifference}s (found timestamps on ${attachmentsWithTimestamps}/${attachments.length} attachments)`); - } - return closest; - } - /** - * Export UI hierarchy attachment and convert to JSON (legacy plist method) - */ - // private static async exportUIHierarchyAsJSON(parser: XCResultParser, attachment: TestAttachment, testName: string): Promise { - // const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID; - // if (!attachmentId) { - // throw new Error('UI Snapshot attachment does not have a valid ID for export'); - // } - // // Export the UI snapshot to a temporary file - // const filename = `ui_hierarchy_${testName.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.plist`; - // const plistPath = await parser.exportAttachment(attachmentId, filename); - // Logger.info(`Exported UI hierarchy to: ${plistPath}`); - // // Convert the plist to JSON using a more robust approach - // return await this.convertUIHierarchyToJSON(plistPath); - // } - /** - * Convert UI hierarchy plist to JSON using plutil -p (readable format) - */ - static async convertUIHierarchyToJSON(plistPath) { - return new Promise((resolve, reject) => { - // Use plutil -p to get a readable format, then parse it - const process = spawn('plutil', ['-p', plistPath], { - stdio: ['pipe', 'pipe', 'pipe'] - }); - let stdout = ''; - let stderr = ''; - process.stdout.on('data', (data) => { - stdout += data.toString(); - }); - process.stderr.on('data', (data) => { - stderr += data.toString(); - }); - process.on('close', (code) => { - if (code === 0) { - // Parse the plutil -p output - this.parsePlutilOutput(stdout) - .then(resolve) - .catch(reject); - } - else { - Logger.error(`plutil failed with code ${code}: ${stderr}`); - reject(new Error(`Failed to read plist: ${stderr}`)); - } - }); - process.on('error', (error) => { - reject(new Error(`Failed to run plutil: ${error.message}`)); - }); - }); - } - /** - * Parse plutil -p output to extract UI hierarchy information - */ - static async parsePlutilOutput(plutilOutput) { - return new Promise((resolve) => { - try { - // Extract meaningful UI hierarchy data from plutil output - const lines = plutilOutput.split('\n'); - // Look for the main UI element structure - const uiElement = { - parseMethod: 'plutil_readable', - rawPlistSize: lines.length, - }; - // Extract key information - for (let i = 0; i < lines.length; i++) { - const line = lines[i]?.trim(); - if (!line) - continue; - // Look for elementType - if (line.includes('"elementType"')) { - const nextLine = lines[i + 1]?.trim(); - if (nextLine && nextLine.includes('value =')) { - const valueMatch = nextLine.match(/value = (\d+)/); - if (valueMatch && valueMatch[1]) { - const elementType = parseInt(valueMatch[1]); - uiElement.elementType = elementType; - // Add description - const descriptions = { - 1: 'Application', 2: 'Group', 3: 'Window', 8: 'Button', - 20: 'NavigationBar', 21: 'TabBar', 22: 'TabGroup', 23: 'Toolbar', 25: 'Table', - 31: 'CollectionView', 36: 'SegmentedControl', 45: 'ScrollView', 47: 'StaticText', 48: 'TextField' - }; - if (descriptions[elementType]) { - uiElement.elementTypeDescription = descriptions[elementType]; - } - } - } - } - // Look for label - if (line.includes('"label"')) { - const nextLine = lines[i + 1]?.trim(); - if (nextLine && nextLine.includes('value =')) { - const valueMatch = nextLine.match(/value = (\d+)/); - if (valueMatch && valueMatch[1]) { - const labelIndex = parseInt(valueMatch[1]); - // Find the actual label value by looking for index references - for (let j = 0; j < lines.length; j++) { - const currentLine = lines[j]; - if (currentLine && currentLine.includes(`${labelIndex} =>`)) { - const labelLine = lines[j + 1]?.trim(); - if (labelLine && labelLine.startsWith('"') && labelLine.endsWith('"')) { - uiElement.label = labelLine.slice(1, -1); // Remove quotes - } - break; - } - } - } - } - } - // Look for identifier - if (line.includes('"identifier"')) { - const nextLine = lines[i + 1]?.trim(); - if (nextLine && nextLine.includes('value =')) { - const valueMatch = nextLine.match(/value = (\d+)/); - if (valueMatch && valueMatch[1]) { - const idIndex = parseInt(valueMatch[1]); - // Find the actual identifier value - for (let j = 0; j < lines.length; j++) { - const currentLine = lines[j]; - if (currentLine && currentLine.includes(`${idIndex} =>`)) { - const idLine = lines[j + 1]?.trim(); - if (idLine && idLine.startsWith('"')) { - uiElement.identifier = idLine.slice(1, -1); // Remove quotes - } - break; - } - } - } - } - } - // Look for enabled - if (line.includes('"enabled"')) { - const nextLine = lines[i + 1]?.trim(); - if (nextLine && nextLine.includes('value =')) { - const valueMatch = nextLine.match(/value = (\d+)/); - if (valueMatch && valueMatch[1]) { - const enabledIndex = parseInt(valueMatch[1]); - // Find the actual enabled value - for (let j = 0; j < lines.length; j++) { - const currentLine = lines[j]; - if (currentLine && currentLine.includes(`${enabledIndex} =>`)) { - const enabledLine = lines[j + 1]?.trim(); - if (enabledLine === '1' || enabledLine === '0') { - uiElement.enabled = enabledLine === '1'; - } - break; - } - } - } - } - } - } - // Extract some statistics - const objectCount = plutilOutput.match(/\d+ =>/g)?.length || 0; - uiElement.totalObjectsInPlist = objectCount; - // Look for Transit app information and other string values - if (plutilOutput.includes('Transit')) { - const transitMatches = plutilOutput.match(/"[^"]*Transit[^"]*"/g) || []; - uiElement.transitAppReferences = transitMatches; - } - // Try to extract the label directly from known patterns - if (plutilOutput.includes('19 => "Transit χ"')) { - uiElement.label = 'Transit χ'; - } - // Extract all string values for debugging - const allStrings = plutilOutput.match(/"[^"]+"/g) || []; - uiElement.allStringsFound = allStrings.slice(0, 10); // First 10 strings - resolve(uiElement); - } - catch (error) { - resolve({ - parseMethod: 'plutil_readable', - error: `Failed to parse plutil output: ${error instanceof Error ? error.message : String(error)}`, - rawOutputPreview: plutilOutput.slice(0, 500) - }); - } - }); - } - /** - * Export text-based UI hierarchy attachment and convert to JSON - */ - static async exportTextUIHierarchyAsJSON(parser, attachment, testName) { - const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID; - if (!attachmentId) { - throw new Error('App UI hierarchy attachment does not have a valid ID for export'); - } - // Export the UI hierarchy to a temporary file - const filename = `ui_hierarchy_${testName.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.txt`; - const textPath = await parser.exportAttachment(attachmentId, filename); - Logger.info(`Exported UI hierarchy to: ${textPath}`); - // Convert the indented text format to JSON - return await this.convertIndentedUIHierarchyToJSON(textPath); - } - /** - * Convert indented text-based UI hierarchy to structured JSON - */ - static async convertIndentedUIHierarchyToJSON(textPath) { - const fs = await import('fs'); - try { - const content = fs.readFileSync(textPath, 'utf8'); - const lines = content.split('\n'); - const result = { - parseMethod: 'indented_text', - totalLines: lines.length, - rootElement: null, - flatElements: [] - }; - let elementStack = []; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (!line || !line.trim()) - continue; - // Calculate indentation level - const indentLevel = line.search(/\S/); - const trimmedLine = line.trim(); - // Parse element information - const element = this.parseUIElementLine(trimmedLine, indentLevel); - if (element) { - // Handle hierarchy based on indentation - while (elementStack.length > 0 && elementStack[elementStack.length - 1].indentLevel >= indentLevel) { - elementStack.pop(); - } - element.indentLevel = indentLevel; - element.children = []; - if (elementStack.length === 0) { - // Root element - result.rootElement = element; - } - else { - // Child element - const parent = elementStack[elementStack.length - 1]; - parent.children.push(element); - element.parent = parent.type || 'unknown'; - } - elementStack.push(element); - result.flatElements.push(element); - } - } - return result; - } - catch (error) { - return { - parseMethod: 'indented_text', - error: `Failed to parse indented UI hierarchy: ${error instanceof Error ? error.message : String(error)}`, - rawContentPreview: '' - }; - } - } - /** - * Parse a single line of UI element information - */ - static parseUIElementLine(line, indentLevel) { - // Common patterns in UI hierarchy text format: - // Application, pid: 12345 - // Window (Main) - // Button "Submit" - // StaticText "Hello World" - // TextField (secure) "password" - if (!line || line.trim() === '') - return null; - const element = { - raw: line, - indentLevel, - type: 'unknown', - label: '', - attributes: {}, - children: [] - }; - // Extract element type (first word usually) - const typeMatch = line.match(/^(\w+)/); - if (typeMatch) { - element.type = typeMatch[1]; - } - // Extract quoted text (labels) - const quotedTextMatch = line.match(/"([^"]*)"/); - if (quotedTextMatch) { - element.label = quotedTextMatch[1]; - } - // Extract parenthesized attributes - const parenthesesMatch = line.match(/\(([^)]*)\)/); - if (parenthesesMatch) { - element.attributes.details = parenthesesMatch[1]; - } - // Extract specific patterns - if (line.includes('pid:')) { - const pidMatch = line.match(/pid:\s*(\d+)/); - if (pidMatch && pidMatch[1]) { - element.attributes.processId = parseInt(pidMatch[1]); - } - } - // Extract coordinates/bounds if present - const boundsMatch = line.match(/\{\{([\d.-]+),\s*([\d.-]+)\},\s*\{([\d.-]+),\s*([\d.-]+)\}\}/); - if (boundsMatch && boundsMatch[1] && boundsMatch[2] && boundsMatch[3] && boundsMatch[4]) { - element.attributes.frame = { - x: parseFloat(boundsMatch[1]), - y: parseFloat(boundsMatch[2]), - width: parseFloat(boundsMatch[3]), - height: parseFloat(boundsMatch[4]) - }; - } - // Extract accessibility identifiers - if (line.includes('identifier:')) { - const idMatch = line.match(/identifier:\s*"([^"]*)"/); - if (idMatch) { - element.attributes.identifier = idMatch[1]; - } - } - return element; - } - /** - * Save UI hierarchy data to a JSON file - */ - static async saveUIHierarchyJSON(hierarchyData, filename) { - const fs = await import('fs'); - const path = await import('path'); - // Use same temp directory as other attachments - const tempDir = path.join(tmpdir(), 'xcode-mcp-attachments'); - // Ensure directory exists - if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir, { recursive: true }); - } - const jsonPath = path.join(tempDir, filename); - // Write compact JSON - fs.writeFileSync(jsonPath, JSON.stringify(hierarchyData), 'utf8'); - Logger.info(`Saved UI hierarchy JSON to: ${jsonPath}`); - return jsonPath; - } - /** - * Create slim AI-readable UI hierarchy with index mapping - */ - static createSlimUIHierarchy(hierarchyData) { - const flatElements = hierarchyData.flatElements || []; - let globalIndex = 0; - // Create index mapping for quick lookup - const indexMap = new Map(); - flatElements.forEach((element, index) => { - indexMap.set(element, index); - }); - function slim(node) { - if (node == null || typeof node !== 'object') - return node; - const currentIndex = indexMap.get(node) ?? globalIndex++; - // Extract label from raw field (pattern: label: 'text') or use existing label - const labelMatch = node.raw?.match(/label: '([^']+)'/); - const extractedLabel = labelMatch ? labelMatch[1] : undefined; - const slimmed = { - t: node.type, - l: extractedLabel || node.label || undefined, - j: currentIndex // Index reference to full element - }; - // Frame removed to reduce noise - // Recurse if children present - if (Array.isArray(node.children) && node.children.length) { - slimmed.c = node.children.map(slim); - } - // Drop undefined keys to save bytes - Object.keys(slimmed).forEach(k => slimmed[k] === undefined && delete slimmed[k]); - return slimmed; - } - const slimRoot = slim(hierarchyData.rootElement || hierarchyData); - return { - parseMethod: 'slim_ui_tree', - originalElementCount: flatElements.length, - rootElement: slimRoot - }; - } - /** - * Get UI element details by index from previously exported hierarchy - */ - static async xcresultGetUIElement(hierarchyJsonPath, elementIndex, includeChildren = false) { - const fs = await import('fs'); - if (!fs.existsSync(hierarchyJsonPath)) { - throw new McpError(ErrorCode.InvalidParams, `UI hierarchy JSON file not found: ${hierarchyJsonPath}`); - } - try { - const hierarchyData = JSON.parse(fs.readFileSync(hierarchyJsonPath, 'utf8')); - const flatElements = hierarchyData.flatElements || []; - if (elementIndex < 0 || elementIndex >= flatElements.length) { - throw new McpError(ErrorCode.InvalidParams, `Element index ${elementIndex} out of range. Available indices: 0-${flatElements.length - 1}`); - } - const element = flatElements[elementIndex]; - // Create result with full element details - const result = { - index: elementIndex, - type: element.type, - label: element.label, - raw: element.raw, - indentLevel: element.indentLevel, - attributes: element.attributes || {} - }; - if (includeChildren && element.children) { - result.children = element.children; - } - else if (element.children) { - result.childrenCount = element.children.length; - result.hasChildren = true; - } - if (element.parent) { - result.parent = element.parent; - } - return { - content: [{ - type: 'text', - text: JSON.stringify(result) - }] - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - throw new McpError(ErrorCode.InternalError, `Failed to read UI element: ${errorMessage}`); - } - } - /** - * Find the closest image attachment to a specific timestamp - */ - static findClosestImageAttachment(attachments, targetTimestamp) { - // Filter to only image attachments - const imageAttachments = attachments.filter(attachment => { - const typeId = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; - const filename = attachment.filename || attachment.name || ''; - return typeId.includes('png') || - typeId === 'public.png' || - typeId.includes('jpeg') || - typeId.includes('jpg') || - typeId === 'public.jpeg' || - filename.toLowerCase().endsWith('.png') || - filename.toLowerCase().endsWith('.jpg') || - filename.toLowerCase().endsWith('.jpeg'); - }); - if (imageAttachments.length === 0) { - return undefined; - } - // Find the attachment with the smallest time difference - let closest; - let smallestDiff = Infinity; - for (const attachment of imageAttachments) { - if (attachment.timestamp !== undefined) { - const timeDiff = attachment.timestamp - targetTimestamp; - const absDiff = Math.abs(timeDiff); - if (absDiff < smallestDiff) { - smallestDiff = absDiff; - closest = { attachment, timeDifference: timeDiff }; - } - } - } - // If no attachment has a timestamp, return the first image attachment - if (!closest && imageAttachments.length > 0) { - const firstImage = imageAttachments[0]; - if (firstImage) { - return { attachment: firstImage, timeDifference: 0 }; - } - } - return closest; - } - /** - * Find video attachment by type identifier and filename extension - */ - static findVideoAttachment(attachments) { - return attachments.find(attachment => { - const typeId = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; - const filename = attachment.filename || attachment.name || ''; - // Check for video type identifiers or video extensions - return typeId.includes('mp4') || - typeId.includes('quicktime') || - typeId === 'public.mpeg-4' || - typeId === 'com.apple.quicktime-movie' || - filename.toLowerCase().endsWith('.mp4') || - filename.toLowerCase().endsWith('.mov'); - }); - } - /** - * Export screenshot attachment to temporary directory - */ - static async exportScreenshotAttachment(parser, attachment) { - const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID; - if (!attachmentId) { - throw new Error('Attachment does not have a valid ID for export'); - } - const filename = attachment.filename || attachment.name || `screenshot_${attachmentId}.png`; - return await parser.exportAttachment(attachmentId, filename); - } - /** - * Extract screenshot from video attachment using ffmpeg at specific timestamp - */ - static async extractScreenshotFromVideo(parser, attachment, testName, timestamp) { - const attachmentId = attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID; - if (!attachmentId) { - throw new Error('Video attachment does not have a valid ID for export'); - } - // Export video to temporary directory - const videoFilename = attachment.filename || attachment.name || `video_${attachmentId}.mp4`; - const videoPath = await parser.exportAttachment(attachmentId, videoFilename); - Logger.info(`Exported video to: ${videoPath}`); - // Generate screenshot path - const tempDir = join(tmpdir(), 'xcode-mcp-attachments'); - const screenshotFilename = `screenshot_${testName.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}.png`; - const screenshotPath = join(tempDir, screenshotFilename); - // Extract screenshot using ffmpeg at specific timestamp - await this.runFFmpeg(videoPath, screenshotPath, timestamp); - // Verify screenshot was created - if (!existsSync(screenshotPath)) { - throw new Error(`Failed to create screenshot at ${screenshotPath}`); - } - Logger.info(`Screenshot extracted to: ${screenshotPath}`); - return screenshotPath; - } - /** - * Run ffmpeg to extract a frame from video as PNG at specific timestamp - */ - static async runFFmpeg(videoPath, outputPath, timestamp) { - return new Promise((resolve, reject) => { - // Try common ffmpeg paths - const ffmpegPaths = [ - '/opt/homebrew/bin/ffmpeg', // Homebrew on Apple Silicon - '/usr/local/bin/ffmpeg', // Homebrew on Intel - 'ffmpeg' // System PATH - ]; - let ffmpegPath = 'ffmpeg'; - for (const path of ffmpegPaths) { - if (existsSync(path)) { - ffmpegPath = path; - break; - } - } - Logger.info(`Using ffmpeg at: ${ffmpegPath}`); - Logger.info(`Extracting frame from: ${videoPath} at ${timestamp}s`); - Logger.info(`Output path: ${outputPath}`); - // Extract a frame at the specific timestamp as PNG - const process = spawn(ffmpegPath, [ - '-i', videoPath, // Input video - '-ss', timestamp.toString(), // Seek to specific timestamp - '-frames:v', '1', // Extract only 1 frame - '-q:v', '2', // High quality - '-y', // Overwrite output file - outputPath // Output PNG file - ], { - stdio: ['pipe', 'pipe', 'pipe'] - }); - let stderr = ''; - process.stderr.on('data', (data) => { - stderr += data.toString(); - }); - process.on('close', (code) => { - if (code === 0) { - Logger.info(`ffmpeg completed successfully`); - // Add a small delay to ensure file is written - setTimeout(() => { - if (existsSync(outputPath)) { - resolve(); - } - else { - reject(new Error(`Screenshot file not found after ffmpeg completion: ${outputPath}`)); - } - }, 100); - } - else { - Logger.error(`ffmpeg failed with code ${code}`); - Logger.error(`ffmpeg stderr: ${stderr}`); - reject(new Error(`ffmpeg failed with code ${code}: ${stderr}`)); - } - }); - process.on('error', (error) => { - Logger.error(`ffmpeg execution error: ${error.message}`); - reject(new Error(`Failed to run ffmpeg: ${error.message}. Make sure ffmpeg is installed (brew install ffmpeg)`)); - }); - }); - } -} -//# sourceMappingURL=XCResultTools.js.map \ No newline at end of file diff --git a/dist/tools/XCResultTools.js.map b/dist/tools/XCResultTools.js.map deleted file mode 100644 index 628b2b9..0000000 --- a/dist/tools/XCResultTools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XCResultTools.js","sourceRoot":"","sources":["../../src/tools/XCResultTools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts deleted file mode 100644 index 58a2bce..0000000 --- a/dist/types/index.d.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -export interface McpContent { - type: 'text' | 'image' | 'resource'; - text?: string; - data?: string; - mimeType?: string; -} -export type McpResult = CallToolResult; -export interface BuildLogInfo { - path: string; - mtime: Date; -} -export interface ParsedBuildResults { - errors: string[]; - warnings: string[]; - buildStatus?: string; -} -export interface EnvironmentValidationResult { - valid: boolean; - message?: string; - recoveryInstructions?: string[]; - degradedMode?: { - available: boolean; - limitations?: string[]; - }; - metadata?: Record; -} -export interface OverallValidationResult { - valid: boolean; - canOperateInDegradedMode: boolean; - criticalFailures: string[]; - nonCriticalFailures: string[]; -} -export interface EnvironmentValidation { - overall: OverallValidationResult; - xcode?: EnvironmentValidationResult; - osascript?: EnvironmentValidationResult; - xclogparser?: EnvironmentValidationResult; - permissions?: EnvironmentValidationResult; - [key: string]: EnvironmentValidationResult | OverallValidationResult | undefined; -} -export interface ToolLimitations { - blocked: boolean; - degraded: boolean; - reason?: string; - instructions?: string[]; -} -export interface JXAScheme { - name(): string; -} -export interface JXADestination { - name(): string; -} -export interface JXAWorkspace { - schemes(): JXAScheme[]; - runDestinations(): JXADestination[]; - activeScheme: JXAScheme; - activeRunDestination: JXADestination; - build(): void; - clean(): JXAActionResult; - test(options?: { - withCommandLineArguments?: string[]; - }): JXAActionResult; - run(options?: { - withCommandLineArguments?: string[]; - }): JXAActionResult; - debug(options?: { - scheme?: string; - skipBuilding?: boolean; - }): JXAActionResult; - stop(): void; - path(): string; - name(): string; - loaded(): boolean; - close(): void; -} -export interface JXAActionResult { - id(): string; - completed(): boolean; -} -export interface JXAApplication { - activeWorkspaceDocument(): JXAWorkspace | null; - workspaceDocuments(): JXAWorkspace[]; -} -export interface OpenProjectCallback { - (projectPath: string): Promise; -} -export interface CommonErrorPattern { - pattern: RegExp; - message: string; - guidance?: string; -} -export interface NormalizedName { - original: string; - normalized: string; -} -export interface SpawnOptions { - stdio?: string | string[]; - env?: NodeJS.ProcessEnv; - cwd?: string; - uid?: number; - gid?: number; - shell?: boolean | string; -} -export interface ChildProcessResult { - stdout: string; - stderr: string; - code: number; -} -export interface TestAttachment { - payloadId?: string; - payload_uuid?: string; - payloadUUID?: string; - uniform_type_identifier?: string; - uniformTypeIdentifier?: string; - filename?: string; - name?: string; - payloadSize?: number; - payload_size?: number; - timestamp?: number; -} -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/types/index.d.ts.map b/dist/types/index.d.ts.map deleted file mode 100644 index ab0ab26..0000000 --- a/dist/types/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types/index.js b/dist/types/index.js deleted file mode 100644 index f8a711a..0000000 --- a/dist/types/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/types/index.js.map b/dist/types/index.js.map deleted file mode 100644 index 37b1eea..0000000 --- a/dist/types/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/utils/BuildLogParser.d.ts b/dist/utils/BuildLogParser.d.ts deleted file mode 100644 index 9d72b85..0000000 --- a/dist/utils/BuildLogParser.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { BuildLogInfo, ParsedBuildResults } from '../types/index.js'; -export declare class BuildLogParser { - static findProjectDerivedData(projectPath: string): Promise; - static getCustomDerivedDataLocationFromXcodePreferences(): Promise; - static getLatestBuildLog(projectPath: string): Promise; - static getRecentBuildLogs(projectPath: string, sinceTime: number): Promise; - static getLatestTestLog(projectPath: string): Promise; - static parseBuildLog(logPath: string, retryCount?: number, maxRetries?: number): Promise; - static canParseLog(logPath: string): Promise; - static parseTestResults(_xcresultPath: string): Promise; -} -//# sourceMappingURL=BuildLogParser.d.ts.map \ No newline at end of file diff --git a/dist/utils/BuildLogParser.d.ts.map b/dist/utils/BuildLogParser.d.ts.map deleted file mode 100644 index f844577..0000000 --- a/dist/utils/BuildLogParser.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BuildLogParser.d.ts","sourceRoot":"","sources":["../../src/utils/BuildLogParser.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAe1E,qBAAa,cAAc;WACL,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;WA+EnE,gDAAgD,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;WA4B1E,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;WA8BpE,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;WA4CnF,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;WA8BnE,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAI,EAAE,UAAU,SAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC;WAqJ3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;WA4B9C,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAQzF"} \ No newline at end of file diff --git a/dist/utils/BuildLogParser.js b/dist/utils/BuildLogParser.js deleted file mode 100644 index f3e5620..0000000 --- a/dist/utils/BuildLogParser.js +++ /dev/null @@ -1,358 +0,0 @@ -import { spawn } from 'child_process'; -import { readdir, stat, readFile } from 'fs/promises'; -import path from 'path'; -import os from 'os'; -import { Logger } from './Logger.js'; -export class BuildLogParser { - static async findProjectDerivedData(projectPath) { - const customDerivedDataLocation = await this.getCustomDerivedDataLocationFromXcodePreferences(); - // Extract the actual project name from .xcodeproj or .xcworkspace files - let projectName; - let actualProjectPath = projectPath; - // If projectPath points to a .xcodeproj file, get its directory - if (projectPath.endsWith('.xcodeproj') || projectPath.endsWith('.xcworkspace')) { - actualProjectPath = path.dirname(projectPath); - projectName = path.basename(projectPath, path.extname(projectPath)); - } - else { - // projectPath is a directory, find project files inside it - try { - const files = await readdir(actualProjectPath); - const projectFile = files.find(file => file.endsWith('.xcodeproj') || file.endsWith('.xcworkspace')); - if (projectFile) { - projectName = path.basename(projectFile, path.extname(projectFile)); - } - else { - // Fallback to directory name if no project files found - projectName = path.basename(actualProjectPath, path.extname(actualProjectPath)); - } - } - catch { - // Fallback to directory name if we can't read the directory - projectName = path.basename(actualProjectPath, path.extname(actualProjectPath)); - } - } - let derivedDataPath; - if (!customDerivedDataLocation) { - derivedDataPath = path.join(os.homedir(), 'Library/Developer/Xcode/DerivedData'); - } - else if (customDerivedDataLocation.startsWith('/')) { - derivedDataPath = customDerivedDataLocation; - } - else { - const localProjectPath = path.dirname(projectPath); - derivedDataPath = path.join(localProjectPath, customDerivedDataLocation); - } - try { - const dirs = await readdir(derivedDataPath); - const matches = dirs.filter(dir => dir.startsWith(`${projectName}-`)); - if (matches.length === 0) - return null; - // Find the correct DerivedData folder by verifying WorkspacePath in info.plist - for (const match of matches) { - const fullPath = path.join(derivedDataPath, match); - const infoPlistPath = path.join(fullPath, 'info.plist'); - try { - const plistContent = await readFile(infoPlistPath, 'utf8'); - const workspacePathMatch = plistContent.match(/WorkspacePath<\/key>\s*(.*?)<\/string>/); - if (workspacePathMatch && workspacePathMatch[1]) { - const workspacePath = workspacePathMatch[1]; - // Resolve both paths to absolute paths for comparison - const resolvedProjectPath = path.resolve(actualProjectPath); - const resolvedWorkspacePath = path.resolve(workspacePath); - // Check if paths match exactly, or if workspace path is inside the project directory - if (resolvedProjectPath === resolvedWorkspacePath || - resolvedWorkspacePath.startsWith(resolvedProjectPath + path.sep) || - resolvedProjectPath.startsWith(resolvedWorkspacePath + path.sep)) { - return fullPath; - } - } - } - catch (plistError) { - // Continue to next match if info.plist can't be read - continue; - } - } - return null; - } - catch (error) { - return null; - } - } - static async getCustomDerivedDataLocationFromXcodePreferences() { - return new Promise((resolve) => { - const defaults = spawn('defaults', ['read', 'com.apple.dt.Xcode', 'IDECustomDerivedDataLocation']); - let stdout = ''; - let stderr = ''; - defaults.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - defaults.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - defaults.on('close', (code) => { - if (code === 0 && stdout.trim()) { - resolve(stdout.trim()); - } - else { - resolve(null); - } - }); - defaults.on('error', () => { - resolve(null); - }); - }); - } - static async getLatestBuildLog(projectPath) { - const derivedData = await this.findProjectDerivedData(projectPath); - if (!derivedData) - return null; - const logsDir = path.join(derivedData, 'Logs', 'Build'); - try { - const files = await readdir(logsDir); - const logFiles = files.filter(file => file.endsWith('.xcactivitylog')); - if (logFiles.length === 0) - return null; - let latestLog = null; - let latestTime = 0; - for (const logFile of logFiles) { - const fullPath = path.join(logsDir, logFile); - const stats = await stat(fullPath); - if (stats.mtime.getTime() > latestTime) { - latestTime = stats.mtime.getTime(); - latestLog = { path: fullPath, mtime: stats.mtime }; - } - } - return latestLog; - } - catch (error) { - return null; - } - } - static async getRecentBuildLogs(projectPath, sinceTime) { - const derivedData = await this.findProjectDerivedData(projectPath); - if (!derivedData) - return []; - const logsDir = path.join(derivedData, 'Logs', 'Build'); - try { - const files = await readdir(logsDir); - const logFiles = files.filter(file => file.endsWith('.xcactivitylog')); - if (logFiles.length === 0) - return []; - const recentLogs = []; - for (const logFile of logFiles) { - const fullPath = path.join(logsDir, logFile); - const stats = await stat(fullPath); - // Include logs modified AFTER the test started (strict comparison, no buffer) - // This ensures we only get logs created by the current operation - if (stats.mtime.getTime() > sinceTime) { - recentLogs.push({ path: fullPath, mtime: stats.mtime }); - } - } - // Sort by modification time (newest first) - recentLogs.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); - // If no recent logs found, fallback to the single most recent log - // This handles cases where clock differences might cause issues - if (recentLogs.length === 0) { - Logger.warn('No recent build logs found, falling back to latest log'); - const latestLog = await this.getLatestBuildLog(projectPath); - if (latestLog) { - return [latestLog]; - } - } - return recentLogs; - } - catch (error) { - return []; - } - } - static async getLatestTestLog(projectPath) { - const derivedData = await this.findProjectDerivedData(projectPath); - if (!derivedData) - return null; - const logsDir = path.join(derivedData, 'Logs', 'Test'); - try { - const files = await readdir(logsDir); - const testResultDirs = files.filter(file => file.endsWith('.xcresult')); - if (testResultDirs.length === 0) - return null; - let latestLog = null; - let latestTime = 0; - for (const resultDir of testResultDirs) { - const fullPath = path.join(logsDir, resultDir); - const stats = await stat(fullPath); - if (stats.mtime.getTime() > latestTime) { - latestTime = stats.mtime.getTime(); - latestLog = { path: fullPath, mtime: stats.mtime }; - } - } - return latestLog; - } - catch (error) { - return null; - } - } - static async parseBuildLog(logPath, retryCount = 0, maxRetries = 6) { - const delays = [1000, 2000, 3000, 5000, 8000, 13000]; - return new Promise((resolve) => { - const command = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'issues']); - let stdout = ''; - let stderr = ''; - // Ensure child process cleanup on exit - const cleanup = () => { - if (command && !command.killed) { - command.kill('SIGTERM'); - } - }; - process.once('exit', cleanup); - process.once('SIGTERM', cleanup); - process.once('SIGINT', cleanup); - command.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - command.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - command.on('close', (code) => { - // Remove cleanup handlers once process closes - process.removeListener('exit', cleanup); - process.removeListener('SIGTERM', cleanup); - process.removeListener('SIGINT', cleanup); - if (code !== 0) { - const errorMessage = stderr.trim() || 'No error details available'; - if (errorMessage.includes('not a valid SLF log') || - errorMessage.includes('not a valid xcactivitylog file') || - errorMessage.includes('corrupted') || - errorMessage.includes('incomplete') || - errorMessage.includes('Error while parsing') || - errorMessage.includes('Failed to parse')) { - if (retryCount < maxRetries) { - Logger.warn(`XCLogParser failed (attempt ${retryCount + 1}/${maxRetries + 1}): ${errorMessage}`); - Logger.debug(`Retrying in ${delays[retryCount]}ms...`); - setTimeout(async () => { - const result = await this.parseBuildLog(logPath, retryCount + 1, maxRetries); - resolve(result); - }, delays[retryCount]); - return; - } - Logger.error('xclogparser failed:', stderr); - resolve({ - errors: [ - 'XCLogParser failed to parse the build log.', - '', - 'This may indicate:', - '• The log file is corrupted or incomplete', - '• An unsupported Xcode version was used', - '• XCLogParser needs to be updated', - '', - `Error details: ${errorMessage}` - ], - warnings: [] - }); - return; - } - } - try { - const result = JSON.parse(stdout); - const errors = [...new Set((result.errors || []).map(error => { - const fileName = error.documentURL ? error.documentURL.replace('file://', '') : 'Unknown file'; - const line = error.startingLineNumber; - const column = error.startingColumnNumber; - let location = fileName; - if (line && line > 0) { - location += `:${line}`; - if (column && column > 0) { - location += `:${column}`; - } - } - return `${location}: ${error.title}`; - }))]; - const warnings = [...new Set((result.warnings || []).map(warning => { - const fileName = warning.documentURL ? warning.documentURL.replace('file://', '') : 'Unknown file'; - const line = warning.startingLineNumber; - const column = warning.startingColumnNumber; - let location = fileName; - if (line && line > 0) { - location += `:${line}`; - if (column && column > 0) { - location += `:${column}`; - } - } - return `${location}: ${warning.title}`; - }))]; - const buildResult = { - errors, - warnings - }; - if (result.buildStatus) { - buildResult.buildStatus = result.buildStatus; - } - resolve(buildResult); - } - catch (parseError) { - const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); - Logger.error('Failed to parse xclogparser output:', parseError); - resolve({ - errors: [ - 'Failed to parse XCLogParser JSON output.', - '', - 'This may indicate:', - '• XCLogParser returned unexpected output format', - '• The build log contains unusual data', - '• XCLogParser version incompatibility', - '', - `Parse error: ${errorMessage}` - ], - warnings: [] - }); - } - }); - command.on('error', (err) => { - Logger.error('Failed to run xclogparser:', err); - resolve({ - errors: [ - 'XCLogParser is required to parse Xcode build logs but is not installed.', - '', - 'Please install XCLogParser using one of these methods:', - '• Homebrew: brew install xclogparser', - '• From source: https://github.com/MobileNativeFoundation/XCLogParser', - '', - 'XCLogParser is a professional tool for parsing Xcode\'s binary log format.' - ], - warnings: [] - }); - }); - }); - } - static async canParseLog(logPath) { - return new Promise((resolve) => { - const command = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'issues']); - let hasOutput = false; - command.stdout?.on('data', () => { - hasOutput = true; - }); - command.on('close', (code) => { - resolve(code === 0 && hasOutput); - }); - command.on('error', () => { - resolve(false); - }); - const timeoutId = setTimeout(() => { - command.kill(); - resolve(false); - }, 5000); - command.on('close', () => { - clearTimeout(timeoutId); - }); - }); - } - static async parseTestResults(_xcresultPath) { - // For now, return a simple result indicating tests completed - // The xcresult format is complex and the tool API has changed - return { - errors: [], - warnings: [], - }; - } -} -//# sourceMappingURL=BuildLogParser.js.map \ No newline at end of file diff --git a/dist/utils/BuildLogParser.js.map b/dist/utils/BuildLogParser.js.map deleted file mode 100644 index e75d555..0000000 --- a/dist/utils/BuildLogParser.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"BuildLogParser.js","sourceRoot":"","sources":["../../src/utils/BuildLogParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAgBrC,MAAM,OAAO,cAAc;IAClB,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,WAAmB;QAC5D,MAAM,yBAAyB,GAAG,MAAM,IAAI,CAAC,gDAAgD,EAAE,CAAC;QAEhG,wEAAwE;QACxE,IAAI,WAAmB,CAAC;QACxB,IAAI,iBAAiB,GAAG,WAAW,CAAC;QAEpC,gEAAgE;QAChE,IAAI,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/E,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC9C,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,2DAA2D;YAC3D,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;gBAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;gBACrG,IAAI,WAAW,EAAE,CAAC;oBAChB,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,uDAAuD;oBACvD,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;gBAClF,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;gBAC5D,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;QAED,IAAI,eAAuB,CAAC;QAE5B,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC/B,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,qCAAqC,CAAC,CAAC;QACnF,CAAC;aAAM,IAAI,yBAAyB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,eAAe,GAAG,yBAAyB,CAAC;QAC9C,CAAC;aAAM,CAAC;YACN,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACnD,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC;YAEtE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAEtC,+EAA+E;YAC/E,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;gBACnD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;gBAExD,IAAI,CAAC;oBACH,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;oBAC3D,MAAM,kBAAkB,GAAG,YAAY,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;oBAErG,IAAI,kBAAkB,IAAI,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;wBAChD,MAAM,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;wBAC5C,sDAAsD;wBACtD,MAAM,mBAAmB,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;wBAC5D,MAAM,qBAAqB,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;wBAE1D,qFAAqF;wBACrF,IAAI,mBAAmB,KAAK,qBAAqB;4BAC7C,qBAAqB,CAAC,UAAU,CAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC;4BAChE,mBAAmB,CAAC,UAAU,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;4BACrE,OAAO,QAAQ,CAAC;wBAClB,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,OAAO,UAAU,EAAE,CAAC;oBACpB,qDAAqD;oBACrD,SAAS;gBACX,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,gDAAgD;QAClE,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC5C,MAAM,QAAQ,GAAiB,KAAK,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,oBAAoB,EAAE,8BAA8B,CAAC,CAAC,CAAC;YACjH,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC3C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC3C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;gBAC3C,IAAI,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;oBAChC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;gBACzB,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACxB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACvD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAEvE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAEvC,IAAI,SAAS,GAAwB,IAAI,CAAC;YAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;YAEnB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC7C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACnC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;oBACvC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnC,SAAS,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;gBACrD,CAAC;YACH,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,WAAmB,EAAE,SAAiB;QAC3E,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,CAAC;QAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAEvE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAErC,MAAM,UAAU,GAAmB,EAAE,CAAC;YAEtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC7C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAEnC,8EAA8E;gBAC9E,iEAAiE;gBACjE,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC;oBACtC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC1D,CAAC;YACH,CAAC;YAED,2CAA2C;YAC3C,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAEjE,kEAAkE;YAClE,gEAAgE;YAChE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;gBACtE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;gBAC5D,IAAI,SAAS,EAAE,CAAC;oBACd,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrB,CAAC;YACH,CAAC;YAED,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,WAAmB;QACtD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YAExE,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAE7C,IAAI,SAAS,GAAwB,IAAI,CAAC;YAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;YAEnB,KAAK,MAAM,SAAS,IAAI,cAAc,EAAE,CAAC;gBACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBAC/C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACnC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;oBACvC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnC,SAAS,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;gBACrD,CAAC;YACH,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAe,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC;QAC/E,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACrD,OAAO,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;YACjD,MAAM,OAAO,GAAiB,KAAK,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;YACzG,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,uCAAuC;YACvC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;oBAC/B,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAEhC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC1C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC1C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;gBAC1C,8CAA8C;gBAC9C,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBACxC,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBAC3C,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC1C,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,4BAA4B,CAAC;oBAEnE,IAAI,YAAY,CAAC,QAAQ,CAAC,qBAAqB,CAAC;wBAC5C,YAAY,CAAC,QAAQ,CAAC,gCAAgC,CAAC;wBACvD,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC;wBAClC,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC;wBACnC,YAAY,CAAC,QAAQ,CAAC,qBAAqB,CAAC;wBAC5C,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;wBAE7C,IAAI,UAAU,GAAG,UAAU,EAAE,CAAC;4BAC5B,MAAM,CAAC,IAAI,CAAC,+BAA+B,UAAU,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,MAAM,YAAY,EAAE,CAAC,CAAC;4BACjG,MAAM,CAAC,KAAK,CAAC,eAAe,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;4BAEvD,UAAU,CAAC,KAAK,IAAI,EAAE;gCACpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;gCAC7E,OAAO,CAAC,MAAM,CAAC,CAAC;4BAClB,CAAC,EAAE,MAAM,CAAC,UAAU,CAAE,CAAC,CAAC;4BACxB,OAAO;wBACT,CAAC;wBAED,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;wBAC5C,OAAO,CAAC;4BACN,MAAM,EAAE;gCACN,4CAA4C;gCAC5C,EAAE;gCACF,oBAAoB;gCACpB,2CAA2C;gCAC3C,yCAAyC;gCACzC,mCAAmC;gCACnC,EAAE;gCACF,kBAAkB,YAAY,EAAE;6BACjC;4BACD,QAAQ,EAAE,EAAE;yBACb,CAAC,CAAC;wBACH,OAAO;oBACT,CAAC;gBACH,CAAC;gBAED,IAAI,CAAC;oBACH,MAAM,MAAM,GAAsB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBAErD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;4BAC3D,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;4BAC/F,MAAM,IAAI,GAAG,KAAK,CAAC,kBAAkB,CAAC;4BACtC,MAAM,MAAM,GAAG,KAAK,CAAC,oBAAoB,CAAC;4BAE1C,IAAI,QAAQ,GAAG,QAAQ,CAAC;4BACxB,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;gCACrB,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC;gCACvB,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oCACzB,QAAQ,IAAI,IAAI,MAAM,EAAE,CAAC;gCAC3B,CAAC;4BACH,CAAC;4BAED,OAAO,GAAG,QAAQ,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;wBACvC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAEL,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;4BACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;4BACnG,MAAM,IAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC;4BACxC,MAAM,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;4BAE5C,IAAI,QAAQ,GAAG,QAAQ,CAAC;4BACxB,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;gCACrB,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC;gCACvB,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oCACzB,QAAQ,IAAI,IAAI,MAAM,EAAE,CAAC;gCAC3B,CAAC;4BACH,CAAC;4BAED,OAAO,GAAG,QAAQ,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC;wBACzC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAEL,MAAM,WAAW,GAAuB;wBACtC,MAAM;wBACN,QAAQ;qBACT,CAAC;oBACF,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;wBACvB,WAAW,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;oBAC/C,CAAC;oBACD,OAAO,CAAC,WAAW,CAAC,CAAC;gBACvB,CAAC;gBAAC,OAAO,UAAU,EAAE,CAAC;oBACpB,MAAM,YAAY,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;oBAC3F,MAAM,CAAC,KAAK,CAAC,qCAAqC,EAAE,UAAU,CAAC,CAAC;oBAChE,OAAO,CAAC;wBACN,MAAM,EAAE;4BACN,0CAA0C;4BAC1C,EAAE;4BACF,oBAAoB;4BACpB,iDAAiD;4BACjD,uCAAuC;4BACvC,uCAAuC;4BACvC,EAAE;4BACF,gBAAgB,YAAY,EAAE;yBAC/B;wBACD,QAAQ,EAAE,EAAE;qBACb,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;gBACjC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;gBAChD,OAAO,CAAC;oBACN,MAAM,EAAE;wBACN,yEAAyE;wBACzE,EAAE;wBACF,wDAAwD;wBACxD,sCAAsC;wBACtC,sEAAsE;wBACtE,EAAE;wBACF,4EAA4E;qBAC7E;oBACD,QAAQ,EAAE,EAAE;iBACb,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,OAAe;QAC7C,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;YACtC,MAAM,OAAO,GAAiB,KAAK,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;YACzG,IAAI,SAAS,GAAG,KAAK,CAAC;YAEtB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC9B,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;gBAC1C,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC;YAEH,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;gBAChC,OAAO,CAAC,IAAI,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC,EAAE,IAAI,CAAC,CAAC;YAET,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,aAAqB;QACxD,6DAA6D;QAC7D,8DAA8D;QAC9D,OAAO;YACL,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;CACF"} \ No newline at end of file diff --git a/dist/utils/EnvironmentValidator.d.ts b/dist/utils/EnvironmentValidator.d.ts deleted file mode 100644 index 3505fd6..0000000 --- a/dist/utils/EnvironmentValidator.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { EnvironmentValidation } from '../types/index.js'; -export declare class EnvironmentValidator { - private static validationResults; - /** - * Validates the entire environment and returns detailed results - */ - static validateEnvironment(): Promise; - /** - * Validates macOS environment - */ - private static validateOS; - /** - * Validates Xcode installation - */ - private static validateXcode; - /** - * Validates XCLogParser installation - */ - private static validateXCLogParser; - /** - * Validates osascript availability - */ - private static validateOSAScript; - /** - * Get the actual Xcode application path - */ - private static getXcodePath; - /** - * Validates automation permissions - */ - private static validatePermissions; - /** - * Gets Xcode version - */ - private static getXcodeVersion; - /** - * Executes a command and returns stdout - */ - private static executeCommand; - /** - * Generates a human-readable validation summary - */ - private static generateValidationSummary; - /** - * Checks if the environment can operate in degraded mode - */ - static canOperateInDegradedMode(results?: EnvironmentValidation | null): boolean; - /** - * Gets the list of features unavailable in current environment - */ - static getUnavailableFeatures(results?: EnvironmentValidation | null): string[]; - /** - * Gets the version from package.json - */ - private static getVersion; - /** - * Creates a configuration health check report - */ - static createHealthCheckReport(): Promise; -} -//# sourceMappingURL=EnvironmentValidator.d.ts.map \ No newline at end of file diff --git a/dist/utils/EnvironmentValidator.d.ts.map b/dist/utils/EnvironmentValidator.d.ts.map deleted file mode 100644 index 04ffa0b..0000000 --- a/dist/utils/EnvironmentValidator.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"EnvironmentValidator.d.ts","sourceRoot":"","sources":["../../src/utils/EnvironmentValidator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAA+B,MAAM,mBAAmB,CAAC;AAE5F,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAE9B;IAEF;;OAEG;WACiB,mBAAmB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAyBzE;;OAEG;mBACkB,UAAU;IAoB/B;;OAEG;mBACkB,aAAa;IAmElC;;OAEG;mBACkB,mBAAmB;IAiHxC;;OAEG;mBACkB,iBAAiB;IA0BtC;;OAEG;mBACkB,YAAY;IA4BjC;;OAEG;mBACkB,mBAAmB;IAmExC;;OAEG;mBACkB,eAAe;IA0BpC;;OAEG;mBACkB,cAAc;IAmCnC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IAqCxC;;OAEG;WACW,wBAAwB,CAAC,OAAO,GAAE,qBAAqB,GAAG,IAAW,GAAG,OAAO;IAK7F;;OAEG;WACW,sBAAsB,CAAC,OAAO,GAAE,qBAAqB,GAAG,IAAW,GAAG,MAAM,EAAE;IAmB5F;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;IAYzB;;OAEG;WACiB,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;CAkD/D"} \ No newline at end of file diff --git a/dist/utils/EnvironmentValidator.js b/dist/utils/EnvironmentValidator.js deleted file mode 100644 index cdf7be5..0000000 --- a/dist/utils/EnvironmentValidator.js +++ /dev/null @@ -1,548 +0,0 @@ -import { spawn } from 'child_process'; -import { existsSync, readFileSync } from 'fs'; -import { platform } from 'os'; -import path from 'path'; -import { fileURLToPath } from 'url'; -export class EnvironmentValidator { - static validationResults = { - overall: { valid: false, canOperateInDegradedMode: false, criticalFailures: [], nonCriticalFailures: [] } - }; - /** - * Validates the entire environment and returns detailed results - */ - static async validateEnvironment() { - const results = { - os: await this.validateOS(), - xcode: await this.validateXcode(), - xclogparser: await this.validateXCLogParser(), - osascript: await this.validateOSAScript(), - permissions: await this.validatePermissions(), - overall: { valid: false, canOperateInDegradedMode: false, criticalFailures: [], nonCriticalFailures: [] } - }; - // Determine overall validity and degraded mode capability - const criticalFailures = ['os', 'osascript'].filter(key => !results[key]?.valid); - const nonCriticalFailures = ['xcode', 'xclogparser', 'permissions'].filter(key => !results[key]?.valid); - results.overall = { - valid: criticalFailures.length === 0 && nonCriticalFailures.length === 0, - canOperateInDegradedMode: criticalFailures.length === 0, - criticalFailures, - nonCriticalFailures - }; - this.validationResults = results; - return results; - } - /** - * Validates macOS environment - */ - static async validateOS() { - if (platform() !== 'darwin') { - return { - valid: false, - message: 'XcodeMCP requires macOS to operate', - recoveryInstructions: [ - 'This MCP server only works on macOS', - 'Xcode and its automation features are macOS-exclusive', - 'Consider using this server on a Mac or macOS virtual machine' - ] - }; - } - return { - valid: true, - message: 'macOS environment detected', - recoveryInstructions: [] - }; - } - /** - * Validates Xcode installation - */ - static async validateXcode() { - const possibleXcodePaths = [ - '/Applications/Xcode.app', - '/Applications/Xcode-beta.app' - ]; - // Also check for versioned Xcode installations - try { - const { readdirSync } = await import('fs'); - const appDir = readdirSync('/Applications'); - const versionedXcodes = appDir - .filter(name => name.startsWith('Xcode-') && name.endsWith('.app')) - .map(name => `/Applications/${name}`); - possibleXcodePaths.push(...versionedXcodes); - } - catch (error) { - // Ignore errors when scanning for versioned Xcodes - } - let xcodeFound = false; - let xcodePath = null; - for (const path of possibleXcodePaths) { - if (existsSync(path)) { - xcodeFound = true; - xcodePath = path; - break; - } - } - if (!xcodeFound) { - return { - valid: false, - message: 'Xcode not found in /Applications', - recoveryInstructions: [ - 'Download and install Xcode from the Mac App Store', - 'Ensure Xcode is installed in /Applications/Xcode.app', - 'Launch Xcode once to complete installation and accept license', - 'If using Xcode beta, ensure it is in /Applications/Xcode-beta.app' - ] - }; - } - // Check if Xcode can be launched and get version - try { - const version = await this.getXcodeVersion(xcodePath); - return { - valid: true, - message: `Xcode found at ${xcodePath} (version ${version})`, - recoveryInstructions: [] - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { - valid: false, - message: `Xcode found but appears to be corrupted or not properly installed: ${errorMessage}`, - recoveryInstructions: [ - 'Try launching Xcode manually to complete setup', - 'Accept the license agreement if prompted', - 'Install additional components if requested', - 'Restart Xcode if it hangs on first launch', - 'Consider reinstalling Xcode if problems persist' - ] - }; - } - } - /** - * Validates XCLogParser installation - */ - static async validateXCLogParser() { - try { - // First check if xclogparser exists in PATH - const whichResult = await this.executeCommand('which', ['xclogparser']); - const xclogparserPath = whichResult.trim(); - // Try to get version using the 'version' subcommand - let version; - try { - version = await this.executeCommand('xclogparser', ['version']); - } - catch (versionError) { - // Some versions might use different command structure, try help as fallback - try { - await this.executeCommand('xclogparser', ['--help']); - // If we get help output, xclogparser is working but version command might be different - version = 'Unknown version (tool is working)'; - } - catch (helpError) { - throw new Error(`xclogparser found at ${xclogparserPath} but cannot execute: ${versionError.message}`); - } - } - return { - valid: true, - message: `XCLogParser found (${version.trim()})`, - recoveryInstructions: [], - metadata: { - version: version.trim(), - path: xclogparserPath - } - }; - } - catch (error) { - // Add more detailed error information for debugging - const errorDetails = []; - let xclogparserLocation = null; - // Check if it's a PATH issue - try { - const whichResult = await this.executeCommand('which', ['xclogparser']); - xclogparserLocation = whichResult.trim(); - errorDetails.push(`xclogparser found at ${xclogparserLocation} but failed to execute`); - errorDetails.push(`Error: ${error.message}`); - // Check if it's a permission issue - try { - await this.executeCommand('test', ['-x', xclogparserLocation]); - } - catch (permError) { - errorDetails.push(`Permission issue: ${xclogparserLocation} is not executable`); - errorDetails.push('Try: chmod +x ' + xclogparserLocation); - } - } - catch (whichError) { - errorDetails.push('xclogparser not found in PATH'); - errorDetails.push(`Current PATH: ${process.env.PATH}`); - // Check common installation locations - const commonPaths = [ - '/usr/local/bin/xclogparser', - '/opt/homebrew/bin/xclogparser', - '/usr/bin/xclogparser', - '/opt/local/bin/xclogparser' // MacPorts - ]; - for (const checkPath of commonPaths) { - try { - await this.executeCommand('test', ['-f', checkPath]); - xclogparserLocation = checkPath; - errorDetails.push(`Found at ${checkPath} but not in PATH`); - // Check if it's executable - try { - await this.executeCommand('test', ['-x', checkPath]); - errorDetails.push('Add to PATH: export PATH="$PATH:' + path.dirname(checkPath) + '"'); - } - catch (execError) { - errorDetails.push(`File exists but not executable: chmod +x ${checkPath}`); - } - break; - } - catch (testError) { - // File doesn't exist at this path - } - } - if (!xclogparserLocation) { - // Check if Homebrew is installed and where it would put xclogparser - try { - const brewPrefix = await this.executeCommand('brew', ['--prefix']); - const brewPath = path.join(brewPrefix.trim(), 'bin/xclogparser'); - errorDetails.push(`Homebrew detected at: ${brewPrefix.trim()}`); - errorDetails.push(`Expected xclogparser location: ${brewPath}`); - } - catch (brewError) { - // Homebrew not found or not working - } - } - } - return { - valid: false, - message: 'XCLogParser not found or not executable', - recoveryInstructions: [ - 'Install XCLogParser using Homebrew: brew install xclogparser', - 'Or download from GitHub: https://github.com/MobileNativeFoundation/XCLogParser', - 'Ensure xclogparser is in your PATH', - 'Note: Build log parsing will be unavailable without XCLogParser', - '', - 'Debugging information:', - ...errorDetails.map(detail => ` • ${detail}`) - ], - degradedMode: { - available: true, - limitations: ['Build logs cannot be parsed', 'Error details from builds will be limited'] - } - }; - } - } - /** - * Validates osascript availability - */ - static async validateOSAScript() { - try { - const version = await this.executeCommand('osascript', ['-l', 'JavaScript', '-e', '"test"']); - if (version.trim() === 'test') { - return { - valid: true, - message: 'JavaScript for Automation (JXA) is available', - recoveryInstructions: [] - }; - } - else { - throw new Error('Unexpected output from osascript'); - } - } - catch (error) { - return { - valid: false, - message: 'JavaScript for Automation (JXA) not available', - recoveryInstructions: [ - 'Ensure you are running on macOS (osascript is a macOS system tool)', - 'Check if JavaScript for Automation is enabled in System Preferences', - 'Try running "osascript -l JavaScript -e \\"return \'test\'\\"" manually', - 'This is a critical component - the server cannot function without it' - ] - }; - } - } - /** - * Get the actual Xcode application path - */ - static async getXcodePath() { - try { - // First try to get from xcode-select - const developerDir = await this.executeCommand('xcode-select', ['-p']); - const xcodeAppPath = developerDir.trim().replace('/Contents/Developer', ''); - // Verify this Xcode app exists - await this.executeCommand('test', ['-d', xcodeAppPath]); - return xcodeAppPath; - } - catch { - // Fall back to searching /Applications for any Xcode app - try { - const result = await this.executeCommand('find', ['/Applications', '-name', 'Xcode*.app', '-type', 'd', '-maxdepth', '1']); - const xcodePaths = result.trim().split('\n').filter(path => path.length > 0); - if (xcodePaths.length > 0 && xcodePaths[0]) { - // Return the first Xcode found - return xcodePaths[0]; - } - } - catch { - // Last resort - try the standard path - return '/Applications/Xcode.app'; - } - } - return '/Applications/Xcode.app'; - } - /** - * Validates automation permissions - */ - static async validatePermissions() { - try { - // Get the actual Xcode path first - const xcodePath = await this.getXcodePath(); - // Try a simple Xcode automation command to test permissions using the actual path - const result = await this.executeCommand('osascript', [ - '-l', 'JavaScript', '-e', - `Application("${xcodePath}").version()` - ]); - if (result && result.trim()) { - return { - valid: true, - message: 'Xcode automation permissions are working', - recoveryInstructions: [] - }; - } - else { - throw new Error('No version returned from Xcode'); - } - } - catch (error) { - const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase(); - if (errorMessage.includes('not allowed assistive access') || - errorMessage.includes('not authorized') || - errorMessage.includes('permission')) { - return { - valid: false, - message: 'Automation permissions not granted', - recoveryInstructions: [ - 'Open System Preferences → Privacy & Security → Automation', - 'Find your terminal application (Terminal, iTerm, VS Code, etc.)', - 'Enable permission to control "Xcode"', - 'You may need to restart your terminal after granting permission', - 'If using VS Code, look for "Code" in the automation list' - ] - }; - } - else if (errorMessage.includes("application isn't running")) { - return { - valid: false, - message: 'Cannot test permissions - Xcode not running', - recoveryInstructions: [ - 'Launch Xcode to test automation permissions', - 'Permissions will be validated when Xcode operations are attempted', - 'This is not critical for server startup' - ], - degradedMode: { - available: true, - limitations: ['Permission validation deferred until Xcode operations'] - } - }; - } - else { - const errorMsg = error instanceof Error ? error.message : String(error); - return { - valid: false, - message: `Permission check failed: ${errorMsg}`, - recoveryInstructions: [ - 'Ensure Xcode is properly installed', - 'Try launching Xcode manually first', - 'Check System Preferences → Privacy & Security → Automation', - 'Grant permission for your terminal to control Xcode' - ] - }; - } - } - } - /** - * Gets Xcode version - */ - static async getXcodeVersion(xcodePath) { - const infoPlistPath = path.join(xcodePath, 'Contents/Info.plist'); - if (!existsSync(infoPlistPath)) { - throw new Error('Info.plist not found'); - } - try { - const result = await this.executeCommand('defaults', [ - 'read', infoPlistPath, 'CFBundleShortVersionString' - ]); - return result.trim(); - } - catch (error) { - // Fallback to plutil if defaults doesn't work - try { - const result = await this.executeCommand('plutil', [ - '-extract', 'CFBundleShortVersionString', 'raw', infoPlistPath - ]); - return result.trim(); - } - catch (fallbackError) { - const fallbackErrorMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError); - throw new Error(`Cannot read Xcode version: ${fallbackErrorMessage}`); - } - } - } - /** - * Executes a command and returns stdout - */ - static async executeCommand(command, args = [], timeout = 5000) { - return new Promise((resolve, reject) => { - const process = spawn(command, args); - let stdout = ''; - let stderr = ''; - const timer = setTimeout(() => { - process.kill(); - reject(new Error(`Command timed out: ${command} ${args.join(' ')}`)); - }, timeout); - process.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - process.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - process.on('close', (code) => { - clearTimeout(timer); - if (code === 0) { - resolve(stdout); - } - else { - reject(new Error(`Command failed with exit code ${code}: ${stderr || 'No error details'}`)); - } - }); - process.on('error', (error) => { - clearTimeout(timer); - reject(new Error(`Failed to start command: ${error.message}`)); - }); - }); - } - /** - * Generates a human-readable validation summary - */ - static generateValidationSummary(results) { - const summary = ['XcodeMCP Environment Validation Report', '']; - // Overall status - if (results.overall.valid) { - summary.push('✅ All systems operational'); - } - else if (results.overall.canOperateInDegradedMode) { - summary.push('⚠️ Can operate with limitations'); - } - else { - summary.push('❌ Critical failures detected - server cannot operate'); - } - summary.push(''); - // Component status - Object.entries(results).forEach(([component, result]) => { - if (component === 'overall' || !result) - return; - // Type guard to check if this is an EnvironmentValidationResult - if ('valid' in result) { - const validationResult = result; - const status = validationResult.valid ? '✅' : '❌'; - summary.push(`${status} ${component.toUpperCase()}: ${validationResult.message || 'Status unknown'}`); - if (!validationResult.valid && validationResult.recoveryInstructions && validationResult.recoveryInstructions.length > 0) { - summary.push(' Recovery instructions:'); - validationResult.recoveryInstructions.forEach((instruction) => { - summary.push(` • ${instruction}`); - }); - summary.push(''); - } - } - }); - return summary.join('\n'); - } - /** - * Checks if the environment can operate in degraded mode - */ - static canOperateInDegradedMode(results = null) { - const validationResults = results || this.validationResults; - return validationResults.overall?.canOperateInDegradedMode ?? false; - } - /** - * Gets the list of features unavailable in current environment - */ - static getUnavailableFeatures(results = null) { - const validationResults = results || this.validationResults; - const unavailable = []; - if (!validationResults.xclogparser?.valid) { - unavailable.push('Build log parsing and detailed error reporting'); - } - if (!validationResults.xcode?.valid) { - unavailable.push('All Xcode operations (build, test, run, debug)'); - } - if (!validationResults.permissions?.valid) { - unavailable.push('Xcode automation (may work after granting permissions)'); - } - return unavailable; - } - /** - * Gets the version from package.json - */ - static getVersion() { - try { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const packageJsonPath = path.join(__dirname, '../../package.json'); - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); - return packageJson.version || 'unknown'; - } - catch (error) { - return 'unknown'; - } - } - /** - * Creates a configuration health check report - */ - static async createHealthCheckReport() { - const results = await this.validateEnvironment(); - const version = this.getVersion(); - const report = [ - `XcodeMCP Configuration Health Check (v${version})`, - '='.repeat(50), - '', - this.generateValidationSummary(results), - '' - ]; - if (!results.overall.valid) { - report.push('IMMEDIATE ACTIONS REQUIRED:'); - results.overall.criticalFailures.forEach(component => { - const result = results[component]; - if (result && 'valid' in result) { - const validationResult = result; - report.push(`\n${component.toUpperCase()} FAILURE:`); - validationResult.recoveryInstructions?.forEach((instruction) => { - report.push(`• ${instruction}`); - }); - } - }); - if (results.overall.nonCriticalFailures.length > 0) { - report.push('\nOPTIONAL IMPROVEMENTS:'); - results.overall.nonCriticalFailures.forEach(component => { - const result = results[component]; - if (result && 'valid' in result) { - const validationResult = result; - report.push(`\n${component.toUpperCase()}:`); - validationResult.recoveryInstructions?.forEach((instruction) => { - report.push(`• ${instruction}`); - }); - } - }); - } - } - const unavailableFeatures = this.getUnavailableFeatures(results); - if (unavailableFeatures.length > 0) { - report.push('\nLIMITED FUNCTIONALITY:'); - unavailableFeatures.forEach(feature => { - report.push(`• ${feature}`); - }); - } - return report.join('\n'); - } -} -//# sourceMappingURL=EnvironmentValidator.js.map \ No newline at end of file diff --git a/dist/utils/EnvironmentValidator.js.map b/dist/utils/EnvironmentValidator.js.map deleted file mode 100644 index 7e50bb9..0000000 --- a/dist/utils/EnvironmentValidator.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"EnvironmentValidator.js","sourceRoot":"","sources":["../../src/utils/EnvironmentValidator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/ErrorHelper.d.ts b/dist/utils/ErrorHelper.d.ts deleted file mode 100644 index 99831f3..0000000 --- a/dist/utils/ErrorHelper.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export declare class ErrorHelper { - static createErrorWithGuidance(message: string, guidance: string): string; - static getXcodeNotFoundGuidance(): string; - static getProjectNotFoundGuidance(projectPath: string): string; - static getSchemeNotFoundGuidance(schemeName: string, availableSchemes?: string[]): string; - static getDestinationNotFoundGuidance(destination: string, availableDestinations?: string[]): string; - static getXcodeNotRunningGuidance(): string; - static getNoWorkspaceGuidance(): string; - static getBuildLogNotFoundGuidance(): string; - static getJXAPermissionGuidance(): string; - static parseCommonErrors(error: Error | { - message?: string; - }): string | null; -} -//# sourceMappingURL=ErrorHelper.d.ts.map \ No newline at end of file diff --git a/dist/utils/ErrorHelper.d.ts.map b/dist/utils/ErrorHelper.d.ts.map deleted file mode 100644 index a7f612d..0000000 --- a/dist/utils/ErrorHelper.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ErrorHelper.d.ts","sourceRoot":"","sources":["../../src/utils/ErrorHelper.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;WACR,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;WAIlE,wBAAwB,IAAI,MAAM;WASlC,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;WASvD,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,GAAE,MAAM,EAAO,GAAG,MAAM;WAkBtF,8BAA8B,CAAC,WAAW,EAAE,MAAM,EAAE,qBAAqB,GAAE,MAAM,EAAO,GAAG,MAAM;WAkBjG,0BAA0B,IAAI,MAAM;WASpC,sBAAsB,IAAI,MAAM;WAShC,2BAA2B,IAAI,MAAM;WASrC,wBAAwB,IAAI,MAAM;WASlC,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,IAAI;CAiCpF"} \ No newline at end of file diff --git a/dist/utils/ErrorHelper.js b/dist/utils/ErrorHelper.js deleted file mode 100644 index 99fa3e6..0000000 --- a/dist/utils/ErrorHelper.js +++ /dev/null @@ -1,102 +0,0 @@ -export class ErrorHelper { - static createErrorWithGuidance(message, guidance) { - return `${message}\n\n💡 To fix this:\n${guidance}`; - } - static getXcodeNotFoundGuidance() { - return [ - "• Install Xcode from the Mac App Store", - "• Make sure Xcode is in your /Applications folder", - "• Launch Xcode once to complete the installation", - "• Accept the license agreement when prompted" - ].join('\n'); - } - static getProjectNotFoundGuidance(projectPath) { - return [ - `• Check that the path is correct: ${projectPath}`, - "• Use an absolute path (starting with /)", - "• Make sure the file extension is .xcodeproj or .xcworkspace", - "• Verify the project file hasn't been moved or deleted" - ].join('\n'); - } - static getSchemeNotFoundGuidance(schemeName, availableSchemes = []) { - const guidance = [ - `• Check the scheme name spelling: '${schemeName}'`, - "• Scheme names are case-sensitive" - ]; - if (availableSchemes.length > 0) { - guidance.push("• Available schemes:"); - availableSchemes.forEach(scheme => { - guidance.push(` - ${scheme}`); - }); - } - else { - guidance.push("• Run 'Get Schemes' to see available schemes"); - } - return guidance.join('\n'); - } - static getDestinationNotFoundGuidance(destination, availableDestinations = []) { - const guidance = [ - `• Check the destination name spelling: '${destination}'`, - "• Destination names are case-sensitive" - ]; - if (availableDestinations.length > 0) { - guidance.push("• Available destinations:"); - availableDestinations.forEach(dest => { - guidance.push(` - ${dest}`); - }); - } - else { - guidance.push("• Run 'Get Run Destinations' to see available destinations"); - } - return guidance.join('\n'); - } - static getXcodeNotRunningGuidance() { - return [ - "• Launch Xcode application", - "• Make sure Xcode is not stuck on a license agreement", - "• Try restarting Xcode if it's already open", - "• Check Activity Monitor for any hanging Xcode processes" - ].join('\n'); - } - static getNoWorkspaceGuidance() { - return [ - "• Open a project in Xcode first", - "• Make sure the project has finished loading", - "• Try closing and reopening the project if it's already open", - "• Check that the project file is not corrupted" - ].join('\n'); - } - static getBuildLogNotFoundGuidance() { - return [ - "• Try building the project again", - "• Check that Xcode has permission to write to derived data", - "• Clear derived data (Product → Clean Build Folder) and rebuild", - "• Ensure XCLogParser is installed: brew install xclogparser" - ].join('\n'); - } - static getJXAPermissionGuidance() { - return [ - "• Go to System Preferences → Privacy & Security → Automation", - "• Allow your terminal app to control Xcode", - "• You may need to restart your terminal after granting permission", - "• If using VS Code, allow 'Code' to control Xcode" - ].join('\n'); - } - static parseCommonErrors(error) { - const errorMessage = error.message || error.toString(); - if (errorMessage.includes('Xcode got an error: Application isn\'t running')) { - return this.createErrorWithGuidance("Xcode is not running", this.getXcodeNotRunningGuidance()); - } - if (errorMessage.includes('No active workspace')) { - return this.createErrorWithGuidance("No active workspace found in Xcode", this.getNoWorkspaceGuidance()); - } - if (errorMessage.includes('not allowed assistive access')) { - return this.createErrorWithGuidance("Permission denied - automation access required", this.getJXAPermissionGuidance()); - } - if (errorMessage.includes('osascript: command not found')) { - return this.createErrorWithGuidance("macOS scripting tools not available", "• This MCP server requires macOS\n• Make sure you're running on a Mac with osascript available"); - } - return null; - } -} -//# sourceMappingURL=ErrorHelper.js.map \ No newline at end of file diff --git a/dist/utils/ErrorHelper.js.map b/dist/utils/ErrorHelper.js.map deleted file mode 100644 index 069e694..0000000 --- a/dist/utils/ErrorHelper.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ErrorHelper.js","sourceRoot":"","sources":["../../src/utils/ErrorHelper.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/JXAExecutor.d.ts b/dist/utils/JXAExecutor.d.ts deleted file mode 100644 index 955514a..0000000 --- a/dist/utils/JXAExecutor.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export declare class JXAExecutor { - /** - * Execute a JavaScript for Automation (JXA) script - * @param script - The JXA script to execute - * @param timeoutMs - Timeout in milliseconds (default: 30 seconds) - * @returns Promise that resolves with the script output or rejects with an error - */ - static execute(script: string, timeoutMs?: number): Promise; -} -//# sourceMappingURL=JXAExecutor.d.ts.map \ No newline at end of file diff --git a/dist/utils/JXAExecutor.d.ts.map b/dist/utils/JXAExecutor.d.ts.map deleted file mode 100644 index 9d7e153..0000000 --- a/dist/utils/JXAExecutor.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"JXAExecutor.d.ts","sourceRoot":"","sources":["../../src/utils/JXAExecutor.ts"],"names":[],"mappings":"AAEA,qBAAa,WAAW;IACtB;;;;;OAKG;WACiB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,MAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAyDxF"} \ No newline at end of file diff --git a/dist/utils/JXAExecutor.js b/dist/utils/JXAExecutor.js deleted file mode 100644 index d3563cd..0000000 --- a/dist/utils/JXAExecutor.js +++ /dev/null @@ -1,64 +0,0 @@ -import { spawn } from 'child_process'; -export class JXAExecutor { - /** - * Execute a JavaScript for Automation (JXA) script - * @param script - The JXA script to execute - * @param timeoutMs - Timeout in milliseconds (default: 30 seconds) - * @returns Promise that resolves with the script output or rejects with an error - */ - static async execute(script, timeoutMs = 30000) { - return new Promise((resolve, reject) => { - try { - const osascript = spawn('osascript', ['-l', 'JavaScript', '-e', script]); - let stdout = ''; - let stderr = ''; - // Add cleanup handlers to prevent process leaks - const cleanup = () => { - if (osascript && !osascript.killed) { - try { - osascript.kill('SIGTERM'); - } - catch (killError) { - // Ignore kill errors - } - } - }; - // Set a timeout to prevent hanging - const timeout = setTimeout(() => { - cleanup(); - reject(new Error(`JXA execution timed out after ${timeoutMs / 1000} seconds`)); - }, timeoutMs); - osascript.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - osascript.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - osascript.on('close', (code) => { - clearTimeout(timeout); - try { - if (code !== 0) { - reject(new Error(`JXA execution failed: ${stderr}`)); - } - else { - resolve(stdout.trim()); - } - } - catch (handlerError) { - // Prevent any handler errors from crashing - reject(new Error(`JXA handler error: ${handlerError}`)); - } - }); - osascript.on('error', (error) => { - clearTimeout(timeout); - cleanup(); - reject(new Error(`Failed to spawn osascript: ${error.message}`)); - }); - } - catch (spawnError) { - reject(new Error(`Failed to create JXA process: ${spawnError}`)); - } - }); - } -} -//# sourceMappingURL=JXAExecutor.js.map \ No newline at end of file diff --git a/dist/utils/JXAExecutor.js.map b/dist/utils/JXAExecutor.js.map deleted file mode 100644 index a792532..0000000 --- a/dist/utils/JXAExecutor.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"JXAExecutor.js","sourceRoot":"","sources":["../../src/utils/JXAExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AAEpD,MAAM,OAAO,WAAW;IACtB;;;;;OAKG;IACI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,YAAoB,KAAK;QACnE,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,CAAC;gBACH,MAAM,SAAS,GAAiB,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBACvF,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,MAAM,GAAG,EAAE,CAAC;gBAEhB,gDAAgD;gBAChD,MAAM,OAAO,GAAG,GAAG,EAAE;oBACnB,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;wBACnC,IAAI,CAAC;4BACH,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC5B,CAAC;wBAAC,OAAO,SAAS,EAAE,CAAC;4BACnB,qBAAqB;wBACvB,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC;gBAEF,mCAAmC;gBACnC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC9B,OAAO,EAAE,CAAC;oBACV,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,SAAS,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC;gBACjF,CAAC,EAAE,SAAS,CAAC,CAAC;gBAEd,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAC5C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,CAAC,CAAC,CAAC;gBAEH,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;oBAC5C,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,CAAC,CAAC,CAAC;gBAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;oBAC5C,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,IAAI,CAAC;wBACH,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;4BACf,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAC,CAAC;wBACvD,CAAC;6BAAM,CAAC;4BACN,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;wBACzB,CAAC;oBACH,CAAC;oBAAC,OAAO,YAAY,EAAE,CAAC;wBACtB,2CAA2C;wBAC3C,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,YAAY,EAAE,CAAC,CAAC,CAAC;oBAC1D,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;oBACrC,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,OAAO,EAAE,CAAC;oBACV,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACnE,CAAC,CAAC,CAAC;YAEL,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,UAAU,EAAE,CAAC,CAAC,CAAC;YACnE,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"} \ No newline at end of file diff --git a/dist/utils/JXAHelpers.d.ts b/dist/utils/JXAHelpers.d.ts deleted file mode 100644 index ab97a1f..0000000 --- a/dist/utils/JXAHelpers.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Helper functions for JXA workspace operations - */ -/** - * Generate JXA script to get workspace by path instead of using activeWorkspaceDocument - * This allows targeting specific workspaces when multiple are open - * - * @param projectPath - The path to the .xcodeproj or .xcworkspace file - * @returns JXA script snippet to get the workspace - */ -export declare function getWorkspaceByPathScript(projectPath: string): string; -/** - * Generate JXA script that tries to get workspace by path, falling back to active if only one is open - * This provides backward compatibility for single-workspace scenarios - * - * @param projectPath - The path to the .xcodeproj or .xcworkspace file (optional) - * @returns JXA script snippet to get the workspace - */ -export declare function getWorkspaceWithFallbackScript(projectPath?: string): string; -//# sourceMappingURL=JXAHelpers.d.ts.map \ No newline at end of file diff --git a/dist/utils/JXAHelpers.d.ts.map b/dist/utils/JXAHelpers.d.ts.map deleted file mode 100644 index 205a2b0..0000000 --- a/dist/utils/JXAHelpers.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"JXAHelpers.d.ts","sourceRoot":"","sources":["../../src/utils/JXAHelpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CA+BpE;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAqC3E"} \ No newline at end of file diff --git a/dist/utils/JXAHelpers.js b/dist/utils/JXAHelpers.js deleted file mode 100644 index b9293c6..0000000 --- a/dist/utils/JXAHelpers.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Helper functions for JXA workspace operations - */ -/** - * Generate JXA script to get workspace by path instead of using activeWorkspaceDocument - * This allows targeting specific workspaces when multiple are open - * - * @param projectPath - The path to the .xcodeproj or .xcworkspace file - * @returns JXA script snippet to get the workspace - */ -export function getWorkspaceByPathScript(projectPath) { - if (!projectPath) { - throw new Error('projectPath is required for workspace finding'); - } - const workspacePath = projectPath.replace('.xcodeproj', '.xcworkspace'); - const projectOnlyPath = projectPath.replace('.xcworkspace', '.xcodeproj'); - return ` - const app = Application('Xcode'); - const documents = app.workspaceDocuments(); - - // Find the workspace document matching the target path - let workspace = null; - for (let i = 0; i < documents.length; i++) { - const doc = documents[i]; - const docPath = doc.path(); - - // Match exact path or handle .xcodeproj vs .xcworkspace differences - if (docPath === ${JSON.stringify(projectPath)} || - docPath === ${JSON.stringify(workspacePath)} || - docPath === ${JSON.stringify(projectOnlyPath)}) { - workspace = doc; - break; - } - } - - if (!workspace) { - throw new Error('Workspace not found for path: ' + ${JSON.stringify(projectPath)} + '. Open workspaces: ' + documents.map(d => d.path()).join(', ')); - } - `; -} -/** - * Generate JXA script that tries to get workspace by path, falling back to active if only one is open - * This provides backward compatibility for single-workspace scenarios - * - * @param projectPath - The path to the .xcodeproj or .xcworkspace file (optional) - * @returns JXA script snippet to get the workspace - */ -export function getWorkspaceWithFallbackScript(projectPath) { - if (!projectPath) { - // No path provided, use active workspace (backward compatibility) - return ` - const app = Application('Xcode'); - const workspace = app.activeWorkspaceDocument(); - if (!workspace) throw new Error('No active workspace'); - `; - } - return ` - const app = Application('Xcode'); - const documents = app.workspaceDocuments(); - - // If only one workspace is open, use it (backward compatibility) - if (documents.length === 1) { - const workspace = documents[0]; - } else { - // Multiple workspaces open, find the right one - let workspace = null; - for (let i = 0; i < documents.length; i++) { - const doc = documents[i]; - const docPath = doc.path(); - - if (docPath === ${JSON.stringify(projectPath)} || - docPath === ${JSON.stringify(projectPath.replace('.xcodeproj', '.xcworkspace'))} || - docPath === ${JSON.stringify(projectPath.replace('.xcworkspace', '.xcodeproj'))}) { - workspace = doc; - break; - } - } - - if (!workspace) { - throw new Error('Workspace not found for path: ' + ${JSON.stringify(projectPath)} + '. Open workspaces: ' + documents.map(d => d.path()).join(', ')); - } - } - `; -} -//# sourceMappingURL=JXAHelpers.js.map \ No newline at end of file diff --git a/dist/utils/JXAHelpers.js.map b/dist/utils/JXAHelpers.js.map deleted file mode 100644 index d24b7a3..0000000 --- a/dist/utils/JXAHelpers.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"JXAHelpers.js","sourceRoot":"","sources":["../../src/utils/JXAHelpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IACxE,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAE1E,OAAO;;;;;;;;;;;wBAWe,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;wBAC3B,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;wBAC7B,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;;;;;;;2DAOI,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;GAEnF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,WAAoB;IACjE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,kEAAkE;QAClE,OAAO;;;;KAIN,CAAC;IACJ,CAAC;IAED,OAAO;;;;;;;;;;;;;;0BAciB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;0BAC3B,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;0BACjE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;;;;;;;6DAO9B,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;GAGrF,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/utils/Logger.d.ts b/dist/utils/Logger.d.ts deleted file mode 100644 index 2b567fa..0000000 --- a/dist/utils/Logger.d.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Configurable logging system for XcodeMCP - * Supports log levels: DEBUG, INFO, WARN, ERROR, SILENT - * Logs to stderr by default, with optional file logging - * Environment variables: - * - LOG_LEVEL: Sets the minimum log level (default: INFO) - * - XCODEMCP_LOG_FILE: Optional file path for logging - * - XCODEMCP_CONSOLE_LOGGING: Enable/disable console output (default: true) - */ -export declare class Logger { - static readonly LOG_LEVELS: { - readonly SILENT: 0; - readonly ERROR: 1; - readonly WARN: 2; - readonly INFO: 3; - readonly DEBUG: 4; - }; - static readonly LOG_LEVEL_NAMES: Record; - private static instance; - private logLevel; - private consoleLogging; - private logFile; - private fileStream; - constructor(); - /** - * Get or create the singleton logger instance - */ - static getInstance(): Logger; - /** - * Parse log level from string (case insensitive) - */ - private parseLogLevel; - /** - * Setup file logging if specified - */ - private setupFileLogging; - /** - * Format log message with timestamp and level - */ - private formatMessage; - /** - * Write log message to configured outputs - */ - private writeLog; - /** - * Log at DEBUG level - */ - debug(message: string, ...args: unknown[]): void; - /** - * Log at INFO level - */ - info(message: string, ...args: unknown[]): void; - /** - * Log at WARN level - */ - warn(message: string, ...args: unknown[]): void; - /** - * Log at ERROR level - */ - error(message: string, ...args: unknown[]): void; - /** - * Flush any pending log writes (important for process exit) - */ - flush(): Promise; - /** - * Get current log level as string - */ - getLogLevel(): string; - /** - * Check if a log level is enabled - */ - isLevelEnabled(level: number): boolean; - static debug(message: string, ...args: unknown[]): void; - static info(message: string, ...args: unknown[]): void; - static warn(message: string, ...args: unknown[]): void; - static error(message: string, ...args: unknown[]): void; - static flush(): Promise; - static getLogLevel(): string; - static isLevelEnabled(level: number): boolean; -} -//# sourceMappingURL=Logger.d.ts.map \ No newline at end of file diff --git a/dist/utils/Logger.d.ts.map b/dist/utils/Logger.d.ts.map deleted file mode 100644 index af83b19..0000000 --- a/dist/utils/Logger.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"Logger.d.ts","sourceRoot":"","sources":["../../src/utils/Logger.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,gBAAuB,UAAU;;;;;;MAMtB;IAEX,gBAAuB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM5D;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAuB;IAE9C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,UAAU,CAA4B;;IAS9C;;OAEG;WACW,WAAW,IAAI,MAAM;IAOnC;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;YACW,gBAAgB;IA4B9B;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAkBhB;;OAEG;IACI,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAIvD;;OAEG;IACI,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAItD;;OAEG;IACI,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAItD;;OAEG;IACI,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAIvD;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUnC;;OAEG;IACI,WAAW,IAAI,MAAM;IAI5B;;OAEG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;WAK/B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;WAIhD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;WAI/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;WAI/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;WAI1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;WAM5B,WAAW,IAAI,MAAM;WAIrB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGrD"} \ No newline at end of file diff --git a/dist/utils/Logger.js b/dist/utils/Logger.js deleted file mode 100644 index b20f7e8..0000000 --- a/dist/utils/Logger.js +++ /dev/null @@ -1,196 +0,0 @@ -import { createWriteStream } from 'fs'; -import { mkdir } from 'fs/promises'; -import path from 'path'; -/** - * Configurable logging system for XcodeMCP - * Supports log levels: DEBUG, INFO, WARN, ERROR, SILENT - * Logs to stderr by default, with optional file logging - * Environment variables: - * - LOG_LEVEL: Sets the minimum log level (default: INFO) - * - XCODEMCP_LOG_FILE: Optional file path for logging - * - XCODEMCP_CONSOLE_LOGGING: Enable/disable console output (default: true) - */ -export class Logger { - static LOG_LEVELS = { - SILENT: 0, - ERROR: 1, - WARN: 2, - INFO: 3, - DEBUG: 4 - }; - static LOG_LEVEL_NAMES = { - 0: 'SILENT', - 1: 'ERROR', - 2: 'WARN', - 3: 'INFO', - 4: 'DEBUG' - }; - static instance = null; - logLevel; - consoleLogging; - logFile; - fileStream = null; - constructor() { - this.logLevel = this.parseLogLevel(process.env.LOG_LEVEL || 'INFO'); - this.consoleLogging = process.env.XCODEMCP_CONSOLE_LOGGING !== 'false'; - this.logFile = process.env.XCODEMCP_LOG_FILE; - this.setupFileLogging(); - } - /** - * Get or create the singleton logger instance - */ - static getInstance() { - if (!Logger.instance) { - Logger.instance = new Logger(); - } - return Logger.instance; - } - /** - * Parse log level from string (case insensitive) - */ - parseLogLevel(levelStr) { - const level = levelStr.toUpperCase(); - const logLevelValue = Logger.LOG_LEVELS[level]; - return logLevelValue !== undefined ? logLevelValue : Logger.LOG_LEVELS.INFO; - } - /** - * Setup file logging if specified - */ - async setupFileLogging() { - if (!this.logFile) { - return; - } - try { - // Create parent directories if they don't exist - const dir = path.dirname(this.logFile); - await mkdir(dir, { recursive: true }); - // Create write stream - this.fileStream = createWriteStream(this.logFile, { flags: 'a' }); - this.fileStream.on('error', (error) => { - // Fallback to stderr if file logging fails - if (this.consoleLogging) { - process.stderr.write(`Logger: Failed to write to log file: ${error.message}\n`); - } - }); - } - catch (error) { - // Fallback to stderr if file setup fails - if (this.consoleLogging) { - const errorMessage = error instanceof Error ? error.message : String(error); - process.stderr.write(`Logger: Failed to setup log file: ${errorMessage}\n`); - } - } - } - /** - * Format log message with timestamp and level - */ - formatMessage(level, message, ...args) { - const timestamp = new Date().toISOString(); - const levelName = Logger.LOG_LEVEL_NAMES[level]; - const formattedArgs = args.length > 0 ? ' ' + args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ') : ''; - return `[${timestamp}] [${levelName}] XcodeMCP: ${message}${formattedArgs}`; - } - /** - * Write log message to configured outputs - */ - writeLog(level, message, ...args) { - if (level > this.logLevel) { - return; // Skip if below configured log level - } - const formattedMessage = this.formatMessage(level, message, ...args); - // Always write to stderr for MCP protocol compatibility (unless console logging disabled) - if (this.consoleLogging) { - process.stderr.write(formattedMessage + '\n'); - } - // Write to file if configured - if (this.fileStream && this.fileStream.writable) { - this.fileStream.write(formattedMessage + '\n'); - } - } - /** - * Log at DEBUG level - */ - debug(message, ...args) { - this.writeLog(Logger.LOG_LEVELS.DEBUG, message, ...args); - } - /** - * Log at INFO level - */ - info(message, ...args) { - this.writeLog(Logger.LOG_LEVELS.INFO, message, ...args); - } - /** - * Log at WARN level - */ - warn(message, ...args) { - this.writeLog(Logger.LOG_LEVELS.WARN, message, ...args); - } - /** - * Log at ERROR level - */ - error(message, ...args) { - this.writeLog(Logger.LOG_LEVELS.ERROR, message, ...args); - } - /** - * Flush any pending log writes (important for process exit) - */ - async flush() { - return new Promise((resolve) => { - if (this.fileStream && this.fileStream.writable) { - this.fileStream.end(resolve); - } - else { - resolve(); - } - }); - } - /** - * Get current log level as string - */ - getLogLevel() { - return Logger.LOG_LEVEL_NAMES[this.logLevel] || 'UNKNOWN'; - } - /** - * Check if a log level is enabled - */ - isLevelEnabled(level) { - return level <= this.logLevel; - } - // Static convenience methods - static debug(message, ...args) { - Logger.getInstance().debug(message, ...args); - } - static info(message, ...args) { - Logger.getInstance().info(message, ...args); - } - static warn(message, ...args) { - Logger.getInstance().warn(message, ...args); - } - static error(message, ...args) { - Logger.getInstance().error(message, ...args); - } - static async flush() { - if (Logger.instance) { - await Logger.instance.flush(); - } - } - static getLogLevel() { - return Logger.getInstance().getLogLevel(); - } - static isLevelEnabled(level) { - return Logger.getInstance().isLevelEnabled(level); - } -} -// Ensure proper cleanup on process exit -process.on('exit', async () => { - await Logger.flush(); -}); -process.on('SIGINT', async () => { - await Logger.flush(); - process.exit(0); -}); -process.on('SIGTERM', async () => { - await Logger.flush(); - process.exit(0); -}); -//# sourceMappingURL=Logger.js.map \ No newline at end of file diff --git a/dist/utils/Logger.js.map b/dist/utils/Logger.js.map deleted file mode 100644 index 5c68a63..0000000 --- a/dist/utils/Logger.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../../src/utils/Logger.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/ParameterNormalizer.d.ts b/dist/utils/ParameterNormalizer.d.ts deleted file mode 100644 index eb9f955..0000000 --- a/dist/utils/ParameterNormalizer.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export declare class ParameterNormalizer { - static normalizeDestinationName(destination: string): string; - static normalizeSchemeName(schemeName: string): string; - private static _capitalizeDeviceName; - static findBestMatch(input: string, availableOptions: string[]): string | null; - private static _calculateSimilarity; - private static _levenshteinDistance; -} -//# sourceMappingURL=ParameterNormalizer.d.ts.map \ No newline at end of file diff --git a/dist/utils/ParameterNormalizer.d.ts.map b/dist/utils/ParameterNormalizer.d.ts.map deleted file mode 100644 index dff7025..0000000 --- a/dist/utils/ParameterNormalizer.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ParameterNormalizer.d.ts","sourceRoot":"","sources":["../../src/utils/ParameterNormalizer.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAmB;WAEhB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;WAoErD,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IA2C7D,OAAO,CAAC,MAAM,CAAC,qBAAqB;WAwBtB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI;IAoCrF,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAYnC,OAAO,CAAC,MAAM,CAAC,oBAAoB;CA2BpC"} \ No newline at end of file diff --git a/dist/utils/ParameterNormalizer.js b/dist/utils/ParameterNormalizer.js deleted file mode 100644 index 3a2ea83..0000000 --- a/dist/utils/ParameterNormalizer.js +++ /dev/null @@ -1,179 +0,0 @@ -export class ParameterNormalizer { - static normalizeDestinationName(destination) { - if (!destination || typeof destination !== 'string') { - return destination; - } - // Remove extra whitespace and normalize case - let normalized = destination.trim(); - // Common destination name variations - const destinationMappings = { - // iPhone variants - 'iphone': 'iPhone', - 'iphone-15': 'iPhone 15', - 'iphone-15-pro': 'iPhone 15 Pro', - 'iphone-15-pro-max': 'iPhone 15 Pro Max', - 'iphone-16': 'iPhone 16', - 'iphone-16-pro': 'iPhone 16 Pro', - 'iphone-16-pro-max': 'iPhone 16 Pro Max', - 'iphone-14': 'iPhone 14', - 'iphone-14-pro': 'iPhone 14 Pro', - 'iphone-14-pro-max': 'iPhone 14 Pro Max', - 'iphone-13': 'iPhone 13', - 'iphone-13-pro': 'iPhone 13 Pro', - 'iphone-13-pro-max': 'iPhone 13 Pro Max', - // iPad variants - 'ipad': 'iPad', - 'ipad-air': 'iPad Air', - 'ipad-pro': 'iPad Pro', - 'ipad-mini': 'iPad mini', - // Simulator variants - 'simulator': 'Simulator', - 'sim': 'Simulator', - // Mac variants - 'mac': 'Mac', - 'my-mac': 'My Mac', - 'mymac': 'My Mac', - }; - // Try exact mapping first - const lowerNormalized = normalized.toLowerCase(); - if (destinationMappings[lowerNormalized]) { - return destinationMappings[lowerNormalized]; - } - // Try pattern matching for simulator names - if (lowerNormalized.includes('simulator')) { - // Handle "iPhone 15 Simulator" -> keep as is but normalize spacing - normalized = normalized.replace(/\s+/g, ' ').trim(); - return normalized; - } - // Handle dash/underscore to space conversion for device names - if (lowerNormalized.includes('-') || lowerNormalized.includes('_')) { - normalized = normalized - .replace(/[-_]/g, ' ') - .replace(/\s+/g, ' ') - .trim(); - // Capitalize words appropriately - normalized = this._capitalizeDeviceName(normalized); - } - return normalized; - } - static normalizeSchemeName(schemeName) { - if (!schemeName || typeof schemeName !== 'string') { - return schemeName; - } - // Remove extra whitespace - let normalized = schemeName.trim(); - // Common scheme name patterns - const schemeMappings = { - // Test scheme variants - 'test': 'Tests', - 'tests': 'Tests', - 'unit-test': 'UnitTests', - 'unit-tests': 'UnitTests', - 'unittests': 'UnitTests', - 'integration-test': 'IntegrationTests', - 'integration-tests': 'IntegrationTests', - 'integrationtests': 'IntegrationTests', - // Debug/Release variants - 'debug': 'Debug', - 'release': 'Release', - 'prod': 'Release', - 'production': 'Release', - 'dev': 'Debug', - 'development': 'Debug', - }; - const lowerNormalized = normalized.toLowerCase(); - if (schemeMappings[lowerNormalized]) { - return schemeMappings[lowerNormalized]; - } - // Handle dash/underscore to space conversion, but preserve original casing - if (normalized.includes('-') || normalized.includes('_')) { - // Don't modify case for scheme names as they're often project-specific - normalized = normalized.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim(); - } - return normalized; - } - static _capitalizeDeviceName(name) { - const words = name.split(' '); - return words.map(word => { - const lower = word.toLowerCase(); - // Special cases - if (lower === 'iphone') - return 'iPhone'; - if (lower === 'ipad') - return 'iPad'; - if (lower === 'mac') - return 'Mac'; - if (lower === 'pro') - return 'Pro'; - if (lower === 'max') - return 'Max'; - if (lower === 'mini') - return 'mini'; - if (lower === 'air') - return 'Air'; - if (lower === 'simulator') - return 'Simulator'; - // Numbers stay as-is - if (/^\d+$/.test(word)) - return word; - // Default capitalization - return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); - }).join(' '); - } - // Fuzzy matching for when exact normalization doesn't work - static findBestMatch(input, availableOptions) { - if (!input || !availableOptions || !Array.isArray(availableOptions)) { - return null; - } - const normalized = input.toLowerCase().trim(); - // Try exact match first - const exactMatch = availableOptions.find(option => option.toLowerCase() === normalized); - if (exactMatch) - return exactMatch; - // Try partial match - const partialMatches = availableOptions.filter(option => option.toLowerCase().includes(normalized) || - normalized.includes(option.toLowerCase())); - if (partialMatches.length === 1) { - return partialMatches[0] || null; - } - // Try fuzzy matching for common typos - const fuzzyMatches = availableOptions.filter(option => { - const optionLower = option.toLowerCase(); - return this._calculateSimilarity(normalized, optionLower) > 0.7; - }); - if (fuzzyMatches.length === 1) { - return fuzzyMatches[0] || null; - } - return null; - } - static _calculateSimilarity(str1, str2) { - const longer = str1.length > str2.length ? str1 : str2; - const shorter = str1.length > str2.length ? str2 : str1; - if (longer.length === 0) { - return 1.0; - } - const editDistance = this._levenshteinDistance(longer, shorter); - return (longer.length - editDistance) / longer.length; - } - static _levenshteinDistance(str1, str2) { - const matrix = []; - for (let i = 0; i <= str2.length; i++) { - matrix[i] = [i]; - } - for (let j = 0; j <= str1.length; j++) { - matrix[0][j] = j; - } - for (let i = 1; i <= str2.length; i++) { - for (let j = 1; j <= str1.length; j++) { - if (str2.charAt(i - 1) === str1.charAt(j - 1)) { - matrix[i][j] = matrix[i - 1][j - 1]; - } - else { - matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1); - } - } - } - return matrix[str2.length][str1.length]; - } -} -//# sourceMappingURL=ParameterNormalizer.js.map \ No newline at end of file diff --git a/dist/utils/ParameterNormalizer.js.map b/dist/utils/ParameterNormalizer.js.map deleted file mode 100644 index e7436ce..0000000 --- a/dist/utils/ParameterNormalizer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ParameterNormalizer.js","sourceRoot":"","sources":["../../src/utils/ParameterNormalizer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/PathValidator.d.ts b/dist/utils/PathValidator.d.ts deleted file mode 100644 index 0bd5fba..0000000 --- a/dist/utils/PathValidator.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { McpResult } from '../types/index.js'; -export declare class PathValidator { - /** - * Resolve relative paths to absolute paths and validate project path - */ - static resolveAndValidateProjectPath(projectPath: string, parameterName?: string): { - resolvedPath: string; - error: McpResult | null; - }; - static validateProjectPath(projectPath: string, parameterName?: string): McpResult | null; - static validateFilePath(filePath: string): McpResult | null; -} -//# sourceMappingURL=PathValidator.d.ts.map \ No newline at end of file diff --git a/dist/utils/PathValidator.d.ts.map b/dist/utils/PathValidator.d.ts.map deleted file mode 100644 index 9fd90ea..0000000 --- a/dist/utils/PathValidator.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PathValidator.d.ts","sourceRoot":"","sources":["../../src/utils/PathValidator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,qBAAa,aAAa;IACxB;;OAEG;WACW,6BAA6B,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,GAAE,MAAoB,GAAG;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE;WA0B1I,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,GAAE,MAAoB,GAAG,SAAS,GAAG,IAAI;WAoD/F,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAsBnE"} \ No newline at end of file diff --git a/dist/utils/PathValidator.js b/dist/utils/PathValidator.js deleted file mode 100644 index 75010df..0000000 --- a/dist/utils/PathValidator.js +++ /dev/null @@ -1,97 +0,0 @@ -import { existsSync } from 'fs'; -import path from 'path'; -import { ErrorHelper } from './ErrorHelper.js'; -export class PathValidator { - /** - * Resolve relative paths to absolute paths and validate project path - */ - static resolveAndValidateProjectPath(projectPath, parameterName = 'xcodeproj') { - if (!projectPath) { - const guidance = [ - `• Specify the path to your .xcodeproj or .xcworkspace file using the "${parameterName}" parameter`, - "• Example: /Users/username/MyApp/MyApp.xcodeproj (absolute path)", - "• Example: MyApp.xcodeproj (relative to current directory)", - "• You can drag the project file from Finder to get the path" - ].join('\n'); - return { - resolvedPath: projectPath, - error: { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Missing required parameter: ${parameterName}`, guidance) }] } - }; - } - // Resolve relative paths to absolute paths - const resolvedPath = path.isAbsolute(projectPath) ? projectPath : path.resolve(process.cwd(), projectPath); - // Validate the resolved absolute path - const validationError = this.validateProjectPath(resolvedPath, parameterName); - return { - resolvedPath, - error: validationError - }; - } - static validateProjectPath(projectPath, parameterName = 'xcodeproj') { - if (!projectPath) { - const guidance = [ - `• Specify the absolute path to your .xcodeproj or .xcworkspace file using the "${parameterName}" parameter`, - "• Example: /Users/username/MyApp/MyApp.xcodeproj", - "• You can drag the project file from Finder to get the path" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Missing required parameter: ${parameterName}`, guidance) }] }; - } - if (!path.isAbsolute(projectPath)) { - const guidance = [ - "• Use an absolute path starting with /", - "• Example: /Users/username/MyApp/MyApp.xcodeproj", - "• Avoid relative paths like ./MyApp.xcodeproj" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Project path must be absolute, got: ${projectPath}`, guidance) }] }; - } - if (!existsSync(projectPath)) { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Project file does not exist: ${projectPath}`, ErrorHelper.getProjectNotFoundGuidance(projectPath)) }] }; - } - if (projectPath.endsWith('.xcodeproj')) { - const pbxprojPath = path.join(projectPath, 'project.pbxproj'); - if (!existsSync(pbxprojPath)) { - const guidance = [ - "• The project file appears to be corrupted or incomplete", - "• Try recreating the project in Xcode", - "• Check if you have the correct permissions to access the file", - "• Make sure the project wasn't partially copied" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Project is missing project.pbxproj file: ${pbxprojPath}`, guidance) }] }; - } - } - if (projectPath.endsWith('.xcworkspace')) { - const workspaceDataPath = path.join(projectPath, 'contents.xcworkspacedata'); - if (!existsSync(workspaceDataPath)) { - const guidance = [ - "• The workspace file appears to be corrupted or incomplete", - "• Try recreating the workspace in Xcode", - "• Check if you have the correct permissions to access the file", - "• Make sure the workspace wasn't partially copied" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Workspace is missing contents.xcworkspacedata file: ${workspaceDataPath}`, guidance) }] }; - } - } - return null; - } - static validateFilePath(filePath) { - if (!path.isAbsolute(filePath)) { - const guidance = [ - "• Use an absolute path starting with /", - "• Example: /Users/username/MyApp/ViewController.swift", - "• You can drag the file from Xcode navigator to get the path" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`File path must be absolute, got: ${filePath}`, guidance) }] }; - } - if (!existsSync(filePath)) { - const guidance = [ - `• Check that the file path is correct: ${filePath}`, - "• Make sure the file hasn't been moved or deleted", - "• Verify you have permission to access the file", - "• Try refreshing the project navigator in Xcode" - ].join('\n'); - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`File does not exist: ${filePath}`, guidance) }] }; - } - return null; - } -} -//# sourceMappingURL=PathValidator.js.map \ No newline at end of file diff --git a/dist/utils/PathValidator.js.map b/dist/utils/PathValidator.js.map deleted file mode 100644 index 353fc61..0000000 --- a/dist/utils/PathValidator.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PathValidator.js","sourceRoot":"","sources":["../../src/utils/PathValidator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/XCResultParser.d.ts b/dist/utils/XCResultParser.d.ts deleted file mode 100644 index 449fd7f..0000000 --- a/dist/utils/XCResultParser.d.ts +++ /dev/null @@ -1,164 +0,0 @@ -import type { TestAttachment } from '../types/index.js'; -export interface TestResultsSummary { - devicesAndConfigurations: DeviceConfiguration[]; - environmentDescription: string; - expectedFailures: number; - failedTests: number; - finishTime: number; - passedTests: number; - result: string; - skippedTests: number; - startTime: number; - testFailures: TestFailure[]; - title: string; - totalTestCount: number; - statistics: any[]; - topInsights: any[]; -} -export interface DeviceConfiguration { - device: Device; - expectedFailures: number; - failedTests: number; - passedTests: number; - skippedTests: number; - testPlanConfiguration: TestPlanConfiguration; -} -export interface Device { - architecture: string; - deviceId: string; - deviceName: string; - modelName: string; - osBuildNumber: string; - osVersion: string; - platform: string; -} -export interface TestPlanConfiguration { - configurationId: string; - configurationName: string; -} -export interface TestFailure { - failureText: string; - targetName: string; - testIdentifier: number; - testIdentifierString: string; - testIdentifierURL: string; - testName: string; -} -export interface TestResults { - devices: Device[]; - testNodes: TestNode[]; - testPlanConfigurations: TestPlanConfiguration[]; -} -export interface TestNode { - children?: TestNode[]; - duration?: string; - durationInSeconds?: number; - name: string; - nodeIdentifier?: string; - nodeIdentifierURL?: string; - nodeType: string; - result: string; -} -export interface XCResultAnalysis { - summary: TestResultsSummary; - totalTests: number; - passedTests: number; - failedTests: number; - skippedTests: number; - passRate: number; - duration: string; -} -export declare class XCResultParser { - private xcresultPath; - constructor(xcresultPath: string); - /** - * Check if xcresult file exists and is readable - */ - static isXCResultReadable(xcresultPath: string): boolean; - /** - * Wait for xcresult to be fully written and readable with robust checks - * Uses proportional timeouts based on test duration - longer tests need more patience - */ - static waitForXCResultReadiness(xcresultPath: string, testDurationMs?: number): Promise; - /** - * Get test results summary - */ - getTestResultsSummary(): Promise; - /** - * Get detailed test results - */ - getTestResults(): Promise; - /** - * Analyze xcresult and provide comprehensive summary - */ - analyzeXCResult(): Promise; - /** - * Extract individual test details from test results - */ - extractTestDetails(): Promise<{ - failed: Array<{ - name: string; - id: string; - }>; - passed: Array<{ - name: string; - id: string; - }>; - skipped: Array<{ - name: string; - id: string; - }>; - }>; - /** - * Format test results summary with optional individual test details - */ - formatTestResultsSummary(includeIndividualTests?: boolean, maxPassedTests?: number): Promise; - /** - * Get console output for a specific test - */ - getConsoleOutput(testId?: string): Promise; - /** - * Get test activities for a specific test - */ - getTestActivities(testId: string): Promise; - /** - * Get test attachments for a specific test from activities output - */ - getTestAttachments(testId: string): Promise; - /** - * Export an attachment to a temporary directory - */ - exportAttachment(attachmentId: string, filename?: string): Promise; - /** - * Find a test node by ID or index - */ - findTestNode(testIdOrIndex: string): Promise; - /** - * Format test list with indices - */ - formatTestList(): Promise; - /** - * Format detailed test information - */ - formatTestDetails(testIdOrIndex: string, includeConsole?: boolean): Promise; - private static runXCResultTool; - private cleanJSONFloats; - private formatDuration; - private formatTestActivities; - private formatActivity; - private formatTestHierarchy; - private calculateTestCounts; - private countTestCases; - private searchTestNodeById; - private findTestNodeByIndex; - private getStatusIcon; - /** - * Extract attachments from activities JSON recursively - */ - private extractAttachmentsFromActivities; - /** - * Find the test start time from the activities JSON to enable relative timestamp calculation - */ - private findTestStartTime; -} -//# sourceMappingURL=XCResultParser.d.ts.map \ No newline at end of file diff --git a/dist/utils/XCResultParser.d.ts.map b/dist/utils/XCResultParser.d.ts.map deleted file mode 100644 index fcdf226..0000000 --- a/dist/utils/XCResultParser.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XCResultParser.d.ts","sourceRoot":"","sources":["../../src/utils/XCResultParser.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,wBAAwB,EAAE,mBAAmB,EAAE,CAAC;IAChD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,qBAAqB,CAAC;CAC9C;AAED,MAAM,WAAW,MAAM;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,EAAE,MAAM;IAIhC;;OAEG;WACW,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAU/D;;;OAGG;WACiB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,GAAE,MAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAsNtH;;OAEG;IACU,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAwBjE;;OAEG;IACU,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAgBnD;;OAEG;IACU,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA0BzD;;OAEG;IACU,kBAAkB,IAAI,OAAO,CAAC;QACzC,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC5C,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC5C,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC9C,CAAC;IAmDF;;OAEG;IACU,wBAAwB,CACnC,sBAAsB,GAAE,OAAe,EACvC,cAAc,GAAE,MAAU,GACzB,OAAO,CAAC,MAAM,CAAC;IA0ClB;;OAEG;IACU,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAc/D;;OAEG;IACU,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe/D;;OAEG;IACU,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAuC1E;;OAEG;IACU,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBvF;;OAEG;IACU,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAgB1E;;OAEG;IACU,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAyB9C;;OAEG;IACU,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,CAAC;mBAsFlF,eAAe;IAkHpC,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,cAAc;IA+BtB,OAAO,CAAC,mBAAmB;IAkC3B,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,OAAO,CAAC,gCAAgC;IA+DxC;;OAEG;IACH,OAAO,CAAC,iBAAiB;CA6B1B"} \ No newline at end of file diff --git a/dist/utils/XCResultParser.js b/dist/utils/XCResultParser.js deleted file mode 100644 index 1379372..0000000 --- a/dist/utils/XCResultParser.js +++ /dev/null @@ -1,959 +0,0 @@ -import { spawn } from 'child_process'; -import { existsSync, mkdirSync } from 'fs'; -import { stat } from 'fs/promises'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { Logger } from './Logger.js'; -export class XCResultParser { - xcresultPath; - constructor(xcresultPath) { - this.xcresultPath = xcresultPath; - } - /** - * Check if xcresult file exists and is readable - */ - static isXCResultReadable(xcresultPath) { - if (!existsSync(xcresultPath)) { - return false; - } - // Check if we can at least access the Info.plist which should be immediately available - const infoPlistPath = `${xcresultPath}/Info.plist`; - return existsSync(infoPlistPath); - } - /** - * Wait for xcresult to be fully written and readable with robust checks - * Uses proportional timeouts based on test duration - longer tests need more patience - */ - static async waitForXCResultReadiness(xcresultPath, testDurationMs = 1200000) { - // Use test duration as the timeout - longer tests likely produce larger XCResults that need more time - const timeoutMs = Math.max(testDurationMs, 300000); // Minimum 5 minutes, but scale with test duration - const startTime = Date.now(); - Logger.info(`Starting robust XCResult readiness check for: ${xcresultPath}`); - Logger.info(`Total timeout: ${timeoutMs / 60000} minutes`); - // Phase 1: Wait for staging folder to disappear - this is CRITICAL - // We must not try to read the file while Xcode is still writing to it - // Based on user insight: we contribute to corruption by reading too early - Logger.info('Phase 1: Waiting for staging folder to disappear (this indicates Xcode is done writing)...'); - const stagingPath = `${xcresultPath}/Staging`; - // Use 90% of timeout for staging folder wait since this is the most critical phase - const stagingTimeout = Math.min(timeoutMs * 0.9, 300000); // 90% of total, max 5 minutes for staging - Logger.info(`Will wait up to ${stagingTimeout / 60000} minutes for staging folder to disappear`); - let lastLogTime = Date.now(); - while (Date.now() - startTime < stagingTimeout) { - if (!existsSync(stagingPath)) { - Logger.info('Staging folder has disappeared - Xcode finished writing XCResult'); - break; - } - // Log every 30 seconds to show progress - if (Date.now() - lastLogTime >= 30000) { - const elapsed = Math.round((Date.now() - startTime) / 1000); - Logger.info(`Staging folder still exists after ${elapsed}s - Xcode is still writing XCResult...`); - lastLogTime = Date.now(); - } - await new Promise(resolve => setTimeout(resolve, 5000)); // Check every 5 seconds for less CPU usage - } - if (existsSync(stagingPath)) { - const elapsed = Math.round((Date.now() - startTime) / 60000); - Logger.warn(`Staging folder still exists after ${elapsed} minutes - Xcode may still be writing`); - Logger.warn('This might indicate an Xcode issue or very large test results'); - Logger.warn('Proceeding anyway but file may not be complete'); - } - else { - const elapsed = Math.round((Date.now() - startTime) / 1000); - Logger.info(`Staging folder disappeared after ${elapsed} seconds - ready to proceed`); - } - // Phase 2: Wait patiently for essential files to appear - // Based on user insight: we must not touch xcresulttool until files are completely ready - Logger.info('Phase 2: Waiting patiently for essential XCResult files to appear...'); - const infoPlistPath = `${xcresultPath}/Info.plist`; - const databasePath = `${xcresultPath}/database.sqlite3`; - const dataPath = `${xcresultPath}/Data`; - let lastProgressTime = Date.now(); - while (Date.now() - startTime < timeoutMs) { - const hasInfoPlist = existsSync(infoPlistPath); - const hasDatabase = existsSync(databasePath); - const hasData = existsSync(dataPath); - if (hasInfoPlist && hasDatabase && hasData) { - Logger.info('All essential XCResult files are present - ready for stabilization check'); - // Fast-path for small XCResult files - skip extensive waiting for files < 5MB - const xcresultStats = await stat(xcresultPath); - const xcresultSizeMB = xcresultStats.size / (1024 * 1024); - if (xcresultSizeMB < 5) { - Logger.info(`Fast-path: XCResult is small (${xcresultSizeMB.toFixed(1)}MB) - minimal waiting required`); - // Quick stability check for small files - await new Promise(resolve => setTimeout(resolve, 2000)); // Just 2 seconds - return true; - } - break; - } - // Log progress every 30 seconds - if (Date.now() - lastProgressTime >= 30000) { - Logger.info(`Still waiting for XCResult files - Info.plist: ${hasInfoPlist ? '✓' : '✗'}, database.sqlite3: ${hasDatabase ? '✓' : '✗'}, Data: ${hasData ? '✓' : '✗'}`); - lastProgressTime = Date.now(); - } - await new Promise(resolve => setTimeout(resolve, 3000)); // Check every 3 seconds - } - if (!existsSync(infoPlistPath) || !existsSync(databasePath) || !existsSync(dataPath)) { - const elapsed = Math.round((Date.now() - startTime) / 60000); - Logger.error(`Essential XCResult files did not appear after ${elapsed} minutes`); - Logger.error(`This suggests Xcode encountered a serious issue writing the XCResult`); - return false; - } - // Phase 3: Critical stabilization check - wait until sizes haven't changed for N seconds - // This implements user insight: "wait until its size hasnt changed for 10 seconds before trying to read it" - // But scale the wait time based on XCResult size for faster completion on small test results - // Calculate dynamic stability requirement based on XCResult size - const xcresultStats = await stat(xcresultPath); - const xcresultSizeMB = xcresultStats.size / (1024 * 1024); - // Scale stability time: 2s for small files (<10MB), up to 12s for large files (>100MB) - const requiredStabilitySeconds = Math.min(12, Math.max(2, Math.ceil(xcresultSizeMB / 10))); - Logger.info(`Phase 3: Waiting for file sizes to stabilize (${requiredStabilitySeconds}+ seconds unchanged)...`); - Logger.info(`XCResult size: ${xcresultSizeMB.toFixed(1)}MB - using ${requiredStabilitySeconds}s stability requirement`); - Logger.info('This is critical - we must not touch xcresulttool until files are completely stable'); - let previousSizes = {}; - let stableStartTime = null; - while (Date.now() - startTime < timeoutMs) { - try { - const infoPlistStats = await stat(infoPlistPath); - const databaseStats = await stat(databasePath); - const dataStats = await stat(dataPath); - const currentSizes = { - infoPlist: infoPlistStats.size, - database: databaseStats.size, - data: dataStats.size - }; - const sizesMatch = (previousSizes.infoPlist === currentSizes.infoPlist && - previousSizes.database === currentSizes.database && - previousSizes.data === currentSizes.data); - if (sizesMatch && Object.keys(previousSizes).length > 0) { - // Sizes are stable - if (stableStartTime === null) { - stableStartTime = Date.now(); - Logger.info(`File sizes stabilized - starting ${requiredStabilitySeconds}s countdown`); - } - const stableForSeconds = (Date.now() - stableStartTime) / 1000; - if (stableForSeconds >= requiredStabilitySeconds) { - Logger.info(`File sizes have been stable for ${Math.round(stableForSeconds)} seconds - ready for xcresulttool`); - break; - } - else { - Logger.debug(`File sizes stable for ${Math.round(stableForSeconds)}/${requiredStabilitySeconds} seconds`); - } - } - else { - // Sizes changed - reset stability timer - if (stableStartTime !== null) { - Logger.info(`File sizes changed - restarting stability check`); - Logger.debug(`New sizes - Info.plist: ${currentSizes.infoPlist}, database: ${currentSizes.database}, Data: ${currentSizes.data}`); - } - stableStartTime = null; - } - previousSizes = currentSizes; - await new Promise(resolve => setTimeout(resolve, 2000)); // Check every 2 seconds - } - catch (error) { - Logger.debug(`Error checking file sizes: ${error}`); - stableStartTime = null; // Reset on error - await new Promise(resolve => setTimeout(resolve, 2000)); - } - } - if (stableStartTime === null || (Date.now() - stableStartTime) / 1000 < requiredStabilitySeconds) { - const elapsed = Math.round((Date.now() - startTime) / 60000); - Logger.error(`File sizes did not stabilize within ${elapsed} minutes`); - Logger.error(`This suggests Xcode is still writing to the XCResult file`); - return false; - } - // Add safety delay scaled to file size - const safetyDelayMs = Math.min(5000, Math.max(1000, xcresultSizeMB * 500)); // 1-5s based on size - Logger.info(`Adding ${safetyDelayMs / 1000}s safety delay before touching xcresulttool...`); - await new Promise(resolve => setTimeout(resolve, safetyDelayMs)); - // Phase 4: Attempt to read with retries - Logger.info('Phase 4: Attempting to read XCResult with retries...'); - const maxRetries = 14; // Up to 14 retries (total 15 attempts) - // Scale retry delay: 3s for small files, up to 15s for large files - const retryDelay = Math.min(15000, Math.max(3000, xcresultSizeMB * 1500)); - for (let attempt = 0; attempt < maxRetries + 1; attempt++) { - try { - Logger.info(`Reading attempt ${attempt + 1}/${maxRetries + 1}...`); - const output = await XCResultParser.runXCResultTool(['get', 'test-results', 'summary', '--path', xcresultPath, '--compact'], 20000); - // Verify we got actual JSON data, not just empty output - if (output.trim().length < 10) { - throw new Error('xcresulttool returned insufficient data'); - } - // Try to parse the JSON to make sure it's valid - const parsed = JSON.parse(output); - if (!parsed.totalTestCount && parsed.totalTestCount !== 0) { - throw new Error('XCResult data is incomplete - missing totalTestCount'); - } - Logger.info(`XCResult is ready after ${attempt + 1} attempts: ${xcresultPath}`); - return true; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.warn(`Reading attempt ${attempt + 1} failed: ${errorMessage}`); - if (attempt < maxRetries) { - const timeRemaining = timeoutMs - (Date.now() - startTime); - if (timeRemaining < retryDelay + 5000) { // Need 5 extra seconds for the actual command - Logger.warn('Not enough time remaining for another retry'); - break; - } - Logger.info(`Waiting ${retryDelay / 1000} seconds before retry...`); - await new Promise(resolve => setTimeout(resolve, retryDelay)); - } - } - } - const totalTimeSeconds = (Date.now() - startTime) / 1000; - Logger.error(`XCResult file failed to become readable after ${maxRetries + 1} attempts over ${totalTimeSeconds} seconds`); - Logger.error(`This is likely a genuine Xcode bug where the XCResult file remains corrupt after ${Math.round(totalTimeSeconds / 60)} minutes of waiting`); - Logger.error(`XCResult path: ${xcresultPath}`); - return false; - } - /** - * Get test results summary - */ - async getTestResultsSummary() { - Logger.debug(`getTestResultsSummary called for path: ${this.xcresultPath}`); - Logger.debug(`File exists check: ${existsSync(this.xcresultPath)}`); - try { - Logger.debug('About to call runXCResultTool...'); - const output = await XCResultParser.runXCResultTool([ - 'get', 'test-results', 'summary', - '--path', this.xcresultPath, - '--compact' - ]); - Logger.debug(`xcresulttool output length: ${output.length} characters`); - Logger.debug(`xcresulttool output first 200 chars: ${output.substring(0, 200)}`); - const cleanedOutput = this.cleanJSONFloats(output); - const parsed = JSON.parse(cleanedOutput); - Logger.debug(`Successfully parsed JSON with keys: ${Object.keys(parsed).join(', ')}`); - return parsed; - } - catch (error) { - Logger.error(`Failed to get test results summary from ${this.xcresultPath}: ${error instanceof Error ? error.message : String(error)}`); - throw new Error(`Cannot read XCResult test summary: ${error instanceof Error ? error.message : String(error)}`); - } - } - /** - * Get detailed test results - */ - async getTestResults() { - try { - const output = await XCResultParser.runXCResultTool([ - 'get', 'test-results', 'tests', - '--path', this.xcresultPath, - '--compact' - ]); - const cleanedOutput = this.cleanJSONFloats(output); - return JSON.parse(cleanedOutput); - } - catch (error) { - Logger.error(`Failed to get detailed test results from ${this.xcresultPath}: ${error instanceof Error ? error.message : String(error)}`); - throw new Error(`Cannot read XCResult test details: ${error instanceof Error ? error.message : String(error)}`); - } - } - /** - * Analyze xcresult and provide comprehensive summary - */ - async analyzeXCResult() { - try { - const summary = await this.getTestResultsSummary(); - const totalPassRate = summary.totalTestCount > 0 - ? (summary.passedTests / summary.totalTestCount) * 100 - : 0; - const duration = this.formatDuration(summary.finishTime - summary.startTime); - return { - summary, - totalTests: summary.totalTestCount, - passedTests: summary.passedTests, - failedTests: summary.failedTests, - skippedTests: summary.skippedTests, - passRate: totalPassRate, - duration - }; - } - catch (error) { - Logger.error(`Failed to analyze XCResult: ${error instanceof Error ? error.message : String(error)}`); - // Return a safe fallback analysis - throw new Error(`XCResult analysis failed: ${error instanceof Error ? error.message : String(error)}`); - } - } - /** - * Extract individual test details from test results - */ - async extractTestDetails() { - try { - const testResults = await this.getTestResults(); - const failedTests = []; - const passedTests = []; - const skippedTests = []; - const extractTests = (nodes, depth = 0) => { - for (const node of nodes) { - // Only include actual test methods (not test classes/suites) - if (node.nodeType === 'Test Case' && node.name && node.result) { - const testInfo = { - name: node.name, - id: node.nodeIdentifier || 'unknown' - }; - const result = node.result.toLowerCase(); - if (result === 'failed') { - failedTests.push(testInfo); - } - else if (result === 'passed') { - passedTests.push(testInfo); - } - else if (result === 'skipped') { - skippedTests.push(testInfo); - } - } - // Recursively process children - if (node.children) { - extractTests(node.children, depth + 1); - } - } - }; - extractTests(testResults.testNodes || []); - return { - failed: failedTests, - passed: passedTests, - skipped: skippedTests - }; - } - catch (error) { - Logger.warn(`Failed to extract test details: ${error}`); - return { - failed: [], - passed: [], - skipped: [] - }; - } - } - /** - * Format test results summary with optional individual test details - */ - async formatTestResultsSummary(includeIndividualTests = false, maxPassedTests = 5) { - const analysis = await this.analyzeXCResult(); - let message = `📊 Test Results Summary:\n`; - message += `Result: ${analysis.summary.result === 'Failed' ? '❌' : '✅'} ${analysis.summary.result}\n`; - message += `Total: ${analysis.totalTests} | Passed: ${analysis.passedTests} ✅ | Failed: ${analysis.failedTests} ❌ | Skipped: ${analysis.skippedTests} ⏭️\n`; - message += `Pass Rate: ${analysis.passRate.toFixed(1)}%\n`; - message += `Duration: ${analysis.duration}\n`; - if (includeIndividualTests) { - const testDetails = await this.extractTestDetails(); - if (testDetails.failed.length > 0) { - message += `\n❌ Failed Tests (${testDetails.failed.length}):\n`; - testDetails.failed.forEach((test, index) => { - message += ` ${index + 1}. ${test.name} (ID: ${test.id})\n`; - }); - } - if (testDetails.skipped.length > 0) { - message += `\n⏭️ Skipped Tests (${testDetails.skipped.length}):\n`; - testDetails.skipped.forEach((test, index) => { - message += ` ${index + 1}. ${test.name} (ID: ${test.id})\n`; - }); - } - // Only show passed tests if there are failures (to keep output manageable) - if (testDetails.failed.length > 0 && testDetails.passed.length > 0) { - const showingText = testDetails.passed.length > maxPassedTests ? ` - showing first ${maxPassedTests}` : ''; - message += `\n✅ Passed Tests (${testDetails.passed.length})${showingText}:\n`; - testDetails.passed.slice(0, maxPassedTests).forEach((test, index) => { - message += ` ${index + 1}. ${test.name} (ID: ${test.id})\n`; - }); - if (testDetails.passed.length > maxPassedTests) { - message += ` ... and ${testDetails.passed.length - maxPassedTests} more passed tests\n`; - } - } - } - return message; - } - /** - * Get console output for a specific test - */ - async getConsoleOutput(testId) { - try { - const args = ['get', 'log', '--path', this.xcresultPath, '--type', 'console']; - if (testId) { - args.push('--test-id', testId); - } - const output = await XCResultParser.runXCResultTool(args, 30000); - return output || 'No console output available'; - } - catch (error) { - return `Error retrieving console output: ${error instanceof Error ? error.message : String(error)}`; - } - } - /** - * Get test activities for a specific test - */ - async getTestActivities(testId) { - try { - const output = await XCResultParser.runXCResultTool([ - 'get', 'test-results', 'activities', - '--test-id', testId, - '--path', this.xcresultPath, - '--compact' - ], 30000); - return this.formatTestActivities(output); - } - catch (error) { - return `Error retrieving test activities: ${error instanceof Error ? error.message : String(error)}`; - } - } - /** - * Get test attachments for a specific test from activities output - */ - async getTestAttachments(testId) { - try { - Logger.info(`Attempting to get test attachments for test: ${testId}`); - // Give xcresulttool plenty of time to process large files - const output = await XCResultParser.runXCResultTool([ - 'get', 'test-results', 'activities', - '--test-id', testId, - '--path', this.xcresultPath, - '--compact' - ], 600000); // 10 minutes timeout - Logger.info(`Successfully retrieved activities data for test: ${testId}`); - const cleanedOutput = this.cleanJSONFloats(output); - const json = JSON.parse(cleanedOutput); - const attachments = []; - // Find test start time for relative timestamp conversion - const testStartTime = this.findTestStartTime(json); - // Parse attachments from activities - this.extractAttachmentsFromActivities(json, attachments, undefined, testStartTime); - Logger.info(`Found ${attachments.length} attachments for test: ${testId}`); - return attachments; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - Logger.error(`Failed to get test attachments for ${testId}: ${errorMessage}`); - // If it's a timeout, provide a more specific error - if (errorMessage.includes('timed out')) { - throw new Error(`xcresulttool timed out when trying to get attachments for test '${testId}'. This xcresult file may be corrupted, incomplete, or too large. Try with a different test or xcresult file.`); - } - throw error; - } - } - /** - * Export an attachment to a temporary directory - */ - async exportAttachment(attachmentId, filename) { - // Create temporary directory for attachments - const tempDir = join(tmpdir(), 'xcode-mcp-attachments'); - if (!existsSync(tempDir)) { - mkdirSync(tempDir, { recursive: true }); - } - // Generate output path - const outputFilename = filename || `attachment_${attachmentId}`; - const outputPath = join(tempDir, outputFilename); - // Export attachment using xcresulttool - await XCResultParser.runXCResultTool([ - 'export', 'object', - '--legacy', - '--path', this.xcresultPath, - '--id', attachmentId, - '--type', 'file', - '--output-path', outputPath - ], 30000); - return outputPath; - } - /** - * Find a test node by ID or index - */ - async findTestNode(testIdOrIndex) { - const tests = await this.getTestResults(); - // Try to find by ID first - const byId = this.searchTestNodeById(tests.testNodes, testIdOrIndex); - if (byId) - return byId; - // Try to find by index - const index = parseInt(testIdOrIndex); - if (!isNaN(index)) { - return this.findTestNodeByIndex(tests.testNodes, index); - } - return null; - } - /** - * Format test list with indices - */ - async formatTestList() { - const analysis = await this.analyzeXCResult(); - const tests = await this.getTestResults(); - let output = `🔍 XCResult Analysis - ${this.xcresultPath}\n`; - output += '='.repeat(80) + '\n\n'; - output += `📊 Test Summary\n`; - output += `Result: ${analysis.summary.result === 'Failed' ? '❌' : '✅'} ${analysis.summary.result}\n`; - output += `Total: ${analysis.totalTests} | Passed: ${analysis.passedTests} ✅ | Failed: ${analysis.failedTests} ❌ | Skipped: ${analysis.skippedTests} ⏭️\n`; - output += `Pass Rate: ${analysis.passRate.toFixed(1)}%\n`; - output += `Duration: ${analysis.duration}\n\n`; - output += `📋 All Tests:\n`; - output += '-'.repeat(80) + '\n'; - let testIndex = 1; - for (const testNode of tests.testNodes) { - output += this.formatTestHierarchy(testNode, '', testIndex); - testIndex = this.countTestCases(testNode) + testIndex; - } - return output; - } - /** - * Format detailed test information - */ - async formatTestDetails(testIdOrIndex, includeConsole = false) { - const analysis = await this.analyzeXCResult(); - const testNode = await this.findTestNode(testIdOrIndex); - if (!testNode) { - return `❌ Test '${testIdOrIndex}' not found\n\nRun xcresult_browse without parameters to see all available tests`; - } - let output = `🔍 Test Details\n`; - output += '='.repeat(80) + '\n'; - output += `Name: ${testNode.name}\n`; - output += `ID: ${testNode.nodeIdentifier || 'unknown'}\n`; - output += `Type: ${testNode.nodeType}\n`; - output += `Result: ${this.getStatusIcon(testNode.result)} ${testNode.result}\n`; - if (testNode.duration) { - output += `Duration: ${testNode.duration}\n`; - } - output += '\n'; - // Show failure details if test failed - if (testNode.result.toLowerCase().includes('fail')) { - const failure = analysis.summary.testFailures.find(f => f.testIdentifierString === testNode.nodeIdentifier); - if (failure) { - output += `❌ Failure Details:\n`; - output += `Target: ${failure.targetName}\n`; - output += `Error: ${failure.failureText}\n\n`; - } - // Show detailed failure info from test node - if (testNode.children) { - output += `📍 Detailed Failure Information:\n`; - for (const child of testNode.children) { - if (child.nodeType === 'Failure Message') { - const parts = child.name.split(': '); - if (parts.length >= 2) { - output += `Location: ${parts[0]}\n`; - output += `Message: ${parts.slice(1).join(': ')}\n`; - } - else { - output += `Details: ${child.name}\n`; - } - output += '\n'; - } - } - } - } - if (includeConsole && testNode.nodeIdentifier) { - // Get console output and activities - const consoleOutput = await this.getConsoleOutput(testNode.nodeIdentifier); - const activities = await this.getTestActivities(testNode.nodeIdentifier); - let consoleSection = `📟 Console Output:\n${consoleOutput}\n\n🔬 Test Activities:\n${activities}\n`; - // Check if console output is very long and should be saved to a file - const lineCount = consoleSection.split('\n').length; - const charCount = consoleSection.length; - // If output is longer than 20 lines or 2KB, save to file - if (lineCount > 20 || charCount > 2000) { - const { writeFile } = await import('fs/promises'); - const { tmpdir } = await import('os'); - const { join } = await import('path'); - // Create a unique filename - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const safeTestName = testNode.name.replace(/[^a-zA-Z0-9]/g, '_'); - const filename = `console_output_${safeTestName}_${timestamp}.txt`; - const filePath = join(tmpdir(), filename); - await writeFile(filePath, consoleSection, 'utf-8'); - const fileSizeKB = Math.round(charCount / 1024); - output += `📟 Console Output:\n`; - output += `📄 Output saved to file (${lineCount} lines, ${fileSizeKB} KB): ${filePath}\n\n`; - output += `💡 The console output was too large to display directly. `; - output += `You can read the file to access the complete console log and test activities.\n`; - } - else { - output += consoleSection; - } - } - return output; - } - static async runXCResultTool(args, timeoutMs = 15000) { - Logger.debug(`Running xcresulttool with args: ${JSON.stringify(['xcresulttool', ...args])}`); - Logger.debug(`Process environment PATH: ${process.env.PATH?.substring(0, 200)}...`); - return new Promise((resolve, reject) => { - let isResolved = false; - let timeoutHandle = null; - const childProcess = spawn('xcrun', ['xcresulttool', ...args], { - stdio: ['pipe', 'pipe', 'pipe'], - detached: false, // Ensure process is killed with parent - env: { - ...process.env, - PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Xcode-16.4.0.app/Contents/Developer/usr/bin:' + (process.env.PATH || '') - } - }); - let stdout = ''; - let stderr = ''; - // Helper function to safely resolve/reject only once - const safeResolve = (value) => { - if (isResolved) - return; - isResolved = true; - cleanup(); - resolve(value); - }; - const safeReject = (error) => { - if (isResolved) - return; - isResolved = true; - cleanup(); - reject(error); - }; - // Helper function to cleanup resources - const cleanup = () => { - if (timeoutHandle) { - clearTimeout(timeoutHandle); - timeoutHandle = null; - } - // Forcefully kill the process if it's still running - if (!childProcess.killed) { - try { - // Try graceful termination first - childProcess.kill('SIGTERM'); - // Force kill after 2 seconds if still running - setTimeout(() => { - if (!childProcess.killed) { - try { - childProcess.kill('SIGKILL'); - } - catch (killError) { - Logger.warn('Failed to force kill xcresulttool process:', killError); - } - } - }, 2000); - } - catch (killError) { - Logger.warn('Failed to kill xcresulttool process:', killError); - } - } - // Remove all listeners to prevent memory leaks - try { - childProcess.removeAllListeners(); - if (childProcess.stdout) - childProcess.stdout.removeAllListeners(); - if (childProcess.stderr) - childProcess.stderr.removeAllListeners(); - } - catch (listenerError) { - Logger.warn('Failed to remove process listeners:', listenerError); - } - }; - // Set up data collection - childProcess.stdout?.on('data', (data) => { - stdout += data.toString(); - }); - childProcess.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - // Set up timeout with proper cleanup - timeoutHandle = setTimeout(() => { - safeReject(new Error(`xcresulttool command timed out after ${timeoutMs}ms`)); - }, timeoutMs); - // Handle process completion - childProcess.on('close', (code, signal) => { - Logger.debug(`Process closed: code=${code}, signal=${signal}, stdout.length=${stdout.length}, stderr.length=${stderr.length}`); - if (code === 0) { - if (stdout.trim() === '') { - Logger.warn(`xcresulttool succeeded but returned empty output. stderr: ${stderr}`); - safeReject(new Error(`xcresulttool exited with code 0 but returned no output. stderr: ${stderr}`)); - } - else { - Logger.debug(`Process succeeded with ${stdout.length} chars of output`); - safeResolve(stdout); - } - } - else { - const errorMsg = signal - ? `xcresulttool was killed with signal ${signal}: ${stderr}` - : `xcresulttool failed with code ${code}: ${stderr}`; - safeReject(new Error(errorMsg)); - } - }); - // Handle process errors - childProcess.on('error', (error) => { - safeReject(new Error(`xcresulttool process error: ${error.message}`)); - }); - // Note: Removed exit handler as it fires before close handler and prevents proper output processing - }); - } - cleanJSONFloats(json) { - // Replace extremely precise floating point numbers with rounded versions - const pattern = /(\d+\.\d{10,})/g; - return json.replace(pattern, (match) => { - const number = parseFloat(match); - return number.toFixed(6); - }); - } - formatDuration(seconds) { - const minutes = Math.floor(seconds / 60); - const remainingSeconds = Math.floor(seconds % 60); - if (minutes > 0) { - return `${minutes}m ${remainingSeconds}s`; - } - else { - return `${remainingSeconds}s`; - } - } - formatTestActivities(jsonString) { - try { - const json = JSON.parse(jsonString); - const activities = []; - let testStartTime; - if (json.testRuns && Array.isArray(json.testRuns)) { - for (const testRun of json.testRuns) { - if (testRun.activities && Array.isArray(testRun.activities)) { - // Find start time - for (const activity of testRun.activities) { - if (activity.title && activity.title.includes('Start Test at') && activity.startTime) { - testStartTime = activity.startTime; - break; - } - } - // Format all activities - for (const activity of testRun.activities) { - this.formatActivity(activity, testStartTime, '', activities); - } - } - } - } - return activities.length > 0 ? activities.join('\n') : 'No test activities found'; - } - catch (error) { - return `Error parsing test activities: ${error instanceof Error ? error.message : String(error)}`; - } - } - formatActivity(activity, baseTime, indent, activities) { - if (!activity.title) - return; - let formattedLine = indent; - // Add timestamp if available - if (activity.startTime && baseTime) { - const relativeTime = activity.startTime - baseTime; - formattedLine += `t = ${relativeTime.toFixed(2).padStart(8)}s `; - } - else { - formattedLine += ' '; - } - // Add failure indicator - if (activity.isAssociatedWithFailure) { - formattedLine += '❌ '; - } - else { - formattedLine += ' '; - } - formattedLine += activity.title; - activities.push(formattedLine); - // Recursively format child activities - if (activity.childActivities && Array.isArray(activity.childActivities)) { - for (const child of activity.childActivities) { - this.formatActivity(child, baseTime, indent + ' ', activities); - } - } - } - formatTestHierarchy(node, prefix, startIndex) { - let output = ''; - let currentIndex = startIndex; - const status = this.getStatusIcon(node.result); - const duration = node.duration ? ` (${node.duration})` : ''; - const testId = node.nodeIdentifier || 'unknown'; - if (node.nodeType === 'Test Case') { - output += `${prefix}[${currentIndex}] ${status} ${node.name}${duration}\n`; - output += `${prefix} ID: ${testId}\n`; - currentIndex++; - } - else if (node.nodeType === 'Test Suite' || node.nodeType === 'Test Target') { - const counts = this.calculateTestCounts(node); - const passRate = counts.total > 0 ? (counts.passed / counts.total * 100) : 0; - const passRateText = counts.total > 0 ? ` - ${passRate.toFixed(1)}% pass rate (${counts.passed}/${counts.total})` : ''; - output += `${prefix}📁 ${node.name}${passRateText}\n`; - } - if (node.children) { - const newPrefix = prefix + (node.nodeType === 'Test Case' ? ' ' : ' '); - for (const child of node.children) { - output += this.formatTestHierarchy(child, newPrefix, currentIndex); - if (child.nodeType === 'Test Case') { - currentIndex++; - } - else { - currentIndex += this.countTestCases(child); - } - } - } - return output; - } - calculateTestCounts(node) { - let passed = 0; - let failed = 0; - let total = 0; - if (node.nodeType === 'Test Case') { - total = 1; - if (node.result.toLowerCase().includes('pass') || node.result.toLowerCase().includes('success')) { - passed = 1; - } - else if (node.result.toLowerCase().includes('fail')) { - failed = 1; - } - } - else if (node.children) { - for (const child of node.children) { - const childCounts = this.calculateTestCounts(child); - passed += childCounts.passed; - failed += childCounts.failed; - total += childCounts.total; - } - } - return { passed, failed, total }; - } - countTestCases(node) { - if (node.nodeType === 'Test Case') { - return 1; - } - let count = 0; - if (node.children) { - for (const child of node.children) { - count += this.countTestCases(child); - } - } - return count; - } - searchTestNodeById(nodes, id) { - for (const node of nodes) { - if (node.nodeIdentifier === id) { - return node; - } - if (node.children) { - const found = this.searchTestNodeById(node.children, id); - if (found) - return found; - } - } - return null; - } - findTestNodeByIndex(nodes, targetIndex) { - let currentIndex = 1; - const search = (nodes) => { - for (const node of nodes) { - if (node.nodeType === 'Test Case') { - if (currentIndex === targetIndex) { - return node; - } - currentIndex++; - } - if (node.children) { - const found = search(node.children); - if (found) - return found; - } - } - return null; - }; - return search(nodes); - } - getStatusIcon(result) { - const lowerResult = result.toLowerCase(); - if (lowerResult.includes('pass') || lowerResult.includes('success')) { - return '✅'; - } - else if (lowerResult.includes('fail')) { - return '❌'; - } - else if (lowerResult.includes('skip')) { - return '⏭️'; - } - else { - return '❓'; - } - } - /** - * Extract attachments from activities JSON recursively - */ - extractAttachmentsFromActivities(json, attachments, parentTimestamp, testStartTime) { - if (!json) - return; - // Extract timestamp from current activity if available - let currentTimestamp = parentTimestamp; - if (json.timestamp && !isNaN(json.timestamp)) { - currentTimestamp = json.timestamp; - } - // Check if current object has attachments - if (json.attachments && Array.isArray(json.attachments)) { - for (const attachment of json.attachments) { - // Handle various property name variations - const testAttachment = { - payloadId: attachment.payloadId || attachment.payload_uuid || attachment.payloadUUID, - payload_uuid: attachment.payload_uuid || attachment.payloadId || attachment.payloadUUID, - payloadUUID: attachment.payloadUUID || attachment.payloadId || attachment.payload_uuid, - uniform_type_identifier: attachment.uniform_type_identifier || attachment.uniformTypeIdentifier, - uniformTypeIdentifier: attachment.uniformTypeIdentifier || attachment.uniform_type_identifier, - filename: attachment.filename || attachment.name, - name: attachment.name || attachment.filename, - payloadSize: attachment.payloadSize || attachment.payload_size, - payload_size: attachment.payload_size || attachment.payloadSize - }; - // Add timestamp if available - prefer attachment's own timestamp, fallback to current activity timestamp - if (attachment.timestamp !== undefined && !isNaN(attachment.timestamp)) { - // Convert absolute timestamp to relative timestamp from test start - if (testStartTime !== undefined) { - testAttachment.timestamp = attachment.timestamp - testStartTime; - } - else { - testAttachment.timestamp = attachment.timestamp; - } - } - else if (currentTimestamp !== undefined) { - testAttachment.timestamp = currentTimestamp; - } - attachments.push(testAttachment); - } - } - // Recursively check testRuns - if (json.testRuns && Array.isArray(json.testRuns)) { - for (const testRun of json.testRuns) { - this.extractAttachmentsFromActivities(testRun, attachments, currentTimestamp, testStartTime); - } - } - // Recursively check activities - if (json.activities && Array.isArray(json.activities)) { - for (const activity of json.activities) { - this.extractAttachmentsFromActivities(activity, attachments, currentTimestamp, testStartTime); - } - } - // Recursively check childActivities - if (json.childActivities && Array.isArray(json.childActivities)) { - for (const childActivity of json.childActivities) { - this.extractAttachmentsFromActivities(childActivity, attachments, currentTimestamp, testStartTime); - } - } - } - /** - * Find the test start time from the activities JSON to enable relative timestamp calculation - */ - findTestStartTime(json) { - // Look for the earliest startTime in the test activities - let earliestStartTime; - const findEarliestTime = (obj) => { - if (!obj) - return; - // Check current object for startTime - if (obj.startTime !== undefined && !isNaN(obj.startTime)) { - if (earliestStartTime === undefined || obj.startTime < earliestStartTime) { - earliestStartTime = obj.startTime; - } - } - // Recursively check nested structures - if (obj.testRuns && Array.isArray(obj.testRuns)) { - obj.testRuns.forEach(findEarliestTime); - } - if (obj.activities && Array.isArray(obj.activities)) { - obj.activities.forEach(findEarliestTime); - } - if (obj.childActivities && Array.isArray(obj.childActivities)) { - obj.childActivities.forEach(findEarliestTime); - } - }; - findEarliestTime(json); - return earliestStartTime; - } -} -//# sourceMappingURL=XCResultParser.js.map \ No newline at end of file diff --git a/dist/utils/XCResultParser.js.map b/dist/utils/XCResultParser.js.map deleted file mode 100644 index 8e0bae9..0000000 --- a/dist/utils/XCResultParser.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"XCResultParser.js","sourceRoot":"","sources":["../../src/utils/XCResultParser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 479cc2a..4be758d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xcodemcp", - "version": "1.8.0", + "version": "2.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "xcodemcp", - "version": "1.8.0", + "version": "2.1.4", "license": "MIT", "os": [ "darwin" @@ -15,16 +15,22 @@ "@modelcontextprotocol/sdk": "^0.6.0", "commander": "^12.1.0", "eventsource": "^4.0.0", - "node-fetch": "^3.3.2" + "execa": "^9.6.0", + "get-port": "^7.0.0", + "node-fetch": "^3.3.2", + "tree-kill": "^1.2.2", + "undici": "^6.19.8", + "ws": "^8.17.1" }, "bin": { + "xcodecontrol": "dist/cli.js", "xcodemcp": "dist/index.js" }, "devDependencies": { "@types/node": "^20.0.0", + "@types/ws": "^8.5.10", "@vitest/coverage-v8": "^3.2.3", "@vitest/ui": "^3.2.3", - "execa": "^9.6.0", "tsx": "^4.7.0", "typescript": "^5.3.0", "vitest": "^3.2.3" @@ -926,14 +932,12 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -976,6 +980,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitest/coverage-v8": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.3.tgz", @@ -1300,7 +1314,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1454,7 +1467,6 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", - "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", @@ -1521,7 +1533,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" @@ -1584,11 +1595,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, "license": "MIT", "dependencies": { "@sec-ant/readable-stream": "^0.4.1", @@ -1672,7 +1694,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -1710,7 +1731,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1723,7 +1743,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1736,7 +1755,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1749,7 +1767,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -1985,7 +2002,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^4.0.0", @@ -2002,7 +2018,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2022,7 +2037,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2035,7 +2049,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2115,7 +2128,6 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", - "dev": true, "license": "MIT", "dependencies": { "parse-ms": "^4.0.0" @@ -2228,7 +2240,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -2241,7 +2252,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2258,7 +2268,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -2423,7 +2432,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2581,6 +2589,15 @@ "node": ">=6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/tsx": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", @@ -2615,6 +2632,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -2626,7 +2652,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2869,7 +2894,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -2996,11 +3020,31 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yoctocolors": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index b2c2f69..6cb7d26 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ ], "scripts": { "build": "tsc", + "postbuild": "chmod +x dist/cli.js dist/index.js", "build:watch": "tsc --watch", "clean": "rm -rf dist", "start": "node dist/index.js", @@ -33,13 +34,18 @@ "@modelcontextprotocol/sdk": "^0.6.0", "commander": "^12.1.0", "eventsource": "^4.0.0", - "node-fetch": "^3.3.2" + "execa": "^9.6.0", + "get-port": "^7.0.0", + "node-fetch": "^3.3.2", + "tree-kill": "^1.2.2", + "undici": "^6.19.8", + "ws": "^8.17.1" }, "devDependencies": { "@types/node": "^20.0.0", + "@types/ws": "^8.5.10", "@vitest/coverage-v8": "^3.2.3", "@vitest/ui": "^3.2.3", - "execa": "^9.6.0", "tsx": "^4.7.0", "typescript": "^5.3.0", "vitest": "^3.2.3" diff --git a/src/XcodeServer.ts b/src/XcodeServer.ts index 781af11..91b14de 100644 --- a/src/XcodeServer.ts +++ b/src/XcodeServer.ts @@ -6,20 +6,49 @@ import { McpError, CallToolResult, } from '@modelcontextprotocol/sdk/types.js'; -import { BuildTools } from './tools/BuildTools.js'; -import { ProjectTools } from './tools/ProjectTools.js'; -import { InfoTools } from './tools/InfoTools.js'; -import { XCResultTools } from './tools/XCResultTools.js'; -import { PathValidator } from './utils/PathValidator.js'; +import { readFile, stat } from 'fs/promises'; +import { execFile } from 'child_process'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { promisify } from 'util'; +import BuildTools from './tools/BuildTools.js'; +import ProjectTools from './tools/ProjectTools.js'; +import InfoTools from './tools/InfoTools.js'; +import XCResultTools from './tools/XCResultTools.js'; +import SimulatorTools from './tools/SimulatorTools.js'; +import SimulatorUiTools from './tools/SimulatorUiTools.js'; +import LogTools from './tools/LogTools.js'; +import LockTools from './tools/LockTools.js'; +import PathValidator from './utils/PathValidator.js'; import { EnvironmentValidator } from './utils/EnvironmentValidator.js'; -import { Logger } from './utils/Logger.js'; -import type { - EnvironmentValidation, - ToolLimitations, - McpResult +import Logger from './utils/Logger.js'; +import LockManager from './utils/LockManager.js'; +import type { + EnvironmentValidation, + ToolLimitations, + McpResult, + OpenProjectCallback, } from './types/index.js'; import { getToolDefinitions } from './shared/toolDefinitions.js'; +type TestRunOptions = { + testPlanPath?: string; + selectedTests?: string[]; + selectedTestClasses?: string[]; + testTargetIdentifier?: string; + testTargetName?: string; + schemeName?: string; + deviceType?: string; + osVersion?: string; +}; + +type TestRunRequest = { + projectPath: string; + destination: string | null; + commandLineArguments: string[]; + options?: TestRunOptions; +}; + export class XcodeServer { public server: Server; @@ -39,7 +68,6 @@ export class XcodeServer { this.includeClean = options.includeClean ?? true; this.preferredScheme = options.preferredScheme; this.preferredXcodeproj = options.preferredXcodeproj; - // Log preferred values if set if (this.preferredScheme) { Logger.info(`Using preferred scheme: ${this.preferredScheme}`); @@ -167,6 +195,35 @@ export class XcodeServer { return null; // Operation can proceed } + private parseNumericArg(value: unknown, name: string): number { + const numeric = + typeof value === 'number' + ? value + : typeof value === 'string' + ? Number(value) + : Number.NaN; + if (!Number.isFinite(numeric)) { + throw new McpError(ErrorCode.InvalidParams, `Parameter '${name}' must be a number`); + } + return numeric; + } + + private parseOptionalNumericArg(value: unknown, name: string): number | undefined { + if (value === undefined || value === null) { + return undefined; + } + const numeric = + typeof value === 'number' + ? value + : typeof value === 'string' + ? Number(value) + : Number.NaN; + if (!Number.isFinite(numeric)) { + throw new McpError(ErrorCode.InvalidParams, `Parameter '${name}' must be numeric when provided`); + } + return numeric; + } + /** * Determines tool limitations based on environment validation */ @@ -176,10 +233,33 @@ export class XcodeServer { return { blocked: false, degraded: false }; } - const buildTools = ['xcode_build', 'xcode_test', 'xcode_build_and_run', 'xcode_debug', 'xcode_clean']; - const xcodeTools = [...buildTools, 'xcode_open_project', 'xcode_get_schemes', 'xcode_set_active_scheme', - 'xcode_get_run_destinations', 'xcode_get_workspace_info', 'xcode_get_projects']; - const xcresultTools = ['xcresult_browse', 'xcresult_browser_get_console', 'xcresult_summary', 'xcresult_get_screenshot', 'xcresult_get_ui_hierarchy', 'xcresult_get_ui_element', 'xcresult_list_attachments', 'xcresult_export_attachment']; + const buildTools = ['xcode_build', 'xcode_test', 'xcode_build_and_run', 'xcode_clean']; + const xcodeTools = [...buildTools, 'xcode_get_schemes', 'xcode_set_active_scheme', + 'xcode_get_run_destinations', 'xcode_get_workspace_info', 'xcode_get_projects', 'xcode_view_build_log', 'xcode_view_run_log']; + const simulatorTools = [ + 'xcode_list_sims', + 'xcode_boot_sim', + 'xcode_shutdown_sim', + 'xcode_open_sim', + 'xcode_screenshot', + 'xcode_describe_ui', + 'xcode_tap', + 'xcode_type_text', + 'xcode_swipe', + ]; + const xcresultTools = ['xcode_xcresult_browse', 'xcode_xcresult_browser_get_console', 'xcode_xcresult_summary', 'xcode_xcresult_get_screenshot', 'xcode_xcresult_get_ui_hierarchy', 'xcode_xcresult_get_ui_element', 'xcode_xcresult_list_attachments', 'xcode_xcresult_export_attachment']; + + if (simulatorTools.includes(toolName) && !validation.xcode?.valid) { + return { + blocked: true, + degraded: false, + reason: 'Xcode Command Line Tools are required for simulator operations', + instructions: [ + 'Install Xcode Command Line Tools: xcode-select --install', + 'Ensure the iOS Simulator is installed from Xcode', + ], + }; + } // Check Xcode availability if (xcodeTools.includes(toolName) && !validation.xcode?.valid) { @@ -349,7 +429,13 @@ export class XcodeServer { // Handle health check tool first (no environment validation needed) if (name === 'xcode_health_check') { const report = await EnvironmentValidator.createHealthCheckReport(); - return { content: [{ type: 'text', text: report }] }; + const versionInfo = await this.getVersionInfo(); + return { + content: [ + { type: 'text', text: report }, + ...(versionInfo.content ?? []), + ], + }; } // Validate environment for all other tools @@ -359,42 +445,6 @@ export class XcodeServer { } switch (name) { - case 'xcode_open_project': - if (!args.xcodeproj) { - throw new McpError( - ErrorCode.InvalidParams, - this.preferredXcodeproj - ? `Missing required parameter: xcodeproj (no preferred value was applied)\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file` - : `Missing required parameter: xcodeproj\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file` - ); - } - const result = await ProjectTools.openProject(args.xcodeproj as string); - if (result && 'content' in result && result.content?.[0] && 'text' in result.content[0]) { - const textContent = result.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - if (!textContent.text.includes('Error') && !textContent.text.includes('does not exist')) { - this.currentProjectPath = args.xcodeproj as string; - } - } - } - return result; - case 'xcode_close_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - try { - const validationError = PathValidator.validateProjectPath(args.xcodeproj as string); - if (validationError) return validationError; - - const closeResult = await ProjectTools.closeProject(args.xcodeproj as string); - this.currentProjectPath = null; - return closeResult; - } catch (closeError) { - // Ensure close project never crashes the server - Logger.error('Close project error (handled):', closeError); - this.currentProjectPath = null; - return { content: [{ type: 'text', text: 'Project close attempted - may have completed with dialogs' }] }; - } case 'xcode_build': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); @@ -402,12 +452,16 @@ export class XcodeServer { if (!args.scheme) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); } - return await BuildTools.build( - args.xcodeproj as string, - args.scheme as string, - (args.destination as string) || null, - this.openProject.bind(this) - ); + { + const reason = this.normalizeLockReason(args.reason, 'xcode_build'); + return await BuildTools.build( + args.xcodeproj as string, + args.scheme as string, + reason, + (args.destination as string) || null, + this.openProject.bind(this), + ); + } case 'xcode_clean': if (!this.includeClean) { throw new McpError(ErrorCode.MethodNotFound, `Clean tool is disabled`); @@ -416,40 +470,11 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } return await BuildTools.clean(args.xcodeproj as string, this.openProject.bind(this)); - case 'xcode_test': - if (!args.xcodeproj) { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: xcodeproj\n\n💡 To fix this:\n• Specify the absolute path to your .xcodeproj or .xcworkspace file using the "xcodeproj" parameter\n• Example: /Users/username/MyApp/MyApp.xcodeproj\n• You can drag the project file from Finder to get the path` - ); - } - if (!args.destination) { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: destination\n\n💡 To fix this:\n• Specify the test destination (e.g., "iPhone 15 Pro Simulator")\n• Use 'get-run-destinations' to see available destinations\n• Example: "iPad Air Simulator" or "iPhone 16 Pro"` - ); - } - const testOptions: { - testPlanPath?: string; - selectedTests?: string[]; - selectedTestClasses?: string[]; - testTargetIdentifier?: string; - testTargetName?: string; - } = {}; - - if (args.test_plan_path) testOptions.testPlanPath = args.test_plan_path as string; - if (args.selected_tests) testOptions.selectedTests = args.selected_tests as string[]; - if (args.selected_test_classes) testOptions.selectedTestClasses = args.selected_test_classes as string[]; - if (args.test_target_identifier) testOptions.testTargetIdentifier = args.test_target_identifier as string; - if (args.test_target_name) testOptions.testTargetName = args.test_target_name as string; - - return await BuildTools.test( - args.xcodeproj as string, - args.destination as string, - (args.command_line_arguments as string[]) || [], - this.openProject.bind(this), - Object.keys(testOptions).length > 0 ? testOptions : undefined - ); + case 'xcode_test': { + const request = this.prepareTestRequest(args); + const runOptions = this.cloneTestOptions(request.options); + return await this.executeTestRun(request, this.openProject.bind(this), runOptions); + } case 'xcode_build_and_run': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); @@ -457,31 +482,27 @@ export class XcodeServer { if (!args.scheme) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); } - return await BuildTools.run( - args.xcodeproj as string, - args.scheme as string, - (args.command_line_arguments as string[]) || [], - this.openProject.bind(this) - ); - case 'xcode_debug': + { + const reason = this.normalizeLockReason(args.reason, 'xcode_build_and_run'); + return await BuildTools.run( + args.xcodeproj as string, + args.scheme as string, + reason, + (args.command_line_arguments as string[]) || [], + this.openProject.bind(this), + ); + } + case 'xcode_release_lock': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.debug( - args.xcodeproj as string, - args.scheme as string, - args.skip_building as boolean, - this.openProject.bind(this) - ); + return await LockTools.release(args.xcodeproj as string); case 'xcode_stop': if (!args.xcodeproj) { return { content: [{ type: 'text', text: 'Error: xcodeproj parameter is required' }] }; } return await BuildTools.stop(args.xcodeproj as string); - case 'find_xcresults': + case 'xcode_find_xcresults': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } @@ -523,7 +544,170 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: file_path`); } return await InfoTools.openFile(args.file_path as string, args.line_number as number); - case 'xcresult_browse': + case 'xcode_view_build_log': { + const parseBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') { + return value.toLowerCase() === 'true'; + } + return false; + }; + const rawMaxLines = + typeof args.max_lines === 'number' + ? args.max_lines + : typeof args.max_lines === 'string' + ? Number.parseInt(args.max_lines, 10) + : undefined; + const normalizedMaxLines = + typeof rawMaxLines === 'number' && Number.isFinite(rawMaxLines) && rawMaxLines > 0 + ? rawMaxLines + : undefined; + const logArgs: { + log_id?: string; + xcodeproj?: string; + filter_regex: boolean; + case_sensitive: boolean; + filter?: string; + max_lines?: number; + filter_globs?: string[]; + cursor?: string; + } = { + filter_regex: parseBoolean(args.filter_regex), + case_sensitive: parseBoolean(args.case_sensitive), + }; + const logId = (args.log_id as string) ?? (args.logId as string) ?? undefined; + if (logId) { + logArgs.log_id = logId; + } + const projectPath = (args.xcodeproj as string) ?? undefined; + if (projectPath) { + logArgs.xcodeproj = projectPath; + } + const filterGlobsArg = + (args.filter_globs as string[] | string | undefined) ?? + (args.filterGlobs as string[] | string | undefined); + if (filterGlobsArg) { + if (Array.isArray(filterGlobsArg)) { + const sanitized = filterGlobsArg + .map(item => (typeof item === 'string' ? item.trim() : '')) + .filter(item => item.length > 0); + if (sanitized.length > 0) { + logArgs.filter_globs = sanitized; + } + } else if (typeof filterGlobsArg === 'string' && filterGlobsArg.trim().length > 0) { + logArgs.filter_globs = filterGlobsArg + .split(',') + .map(item => item.trim()) + .filter(item => item.length > 0); + } + } + const cursorArg = (args.cursor as string) ?? undefined; + if (cursorArg) { + logArgs.cursor = cursorArg; + } + if (typeof args.filter === 'string' && args.filter.length > 0) { + logArgs.filter = args.filter; + } + if (normalizedMaxLines !== undefined) { + logArgs.max_lines = normalizedMaxLines; + } + return await LogTools.getBuildLog(logArgs); + } + case 'xcode_list_sims': + return await SimulatorTools.listSimulators(); + case 'xcode_boot_sim': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorTools.bootSimulator(simulatorUuid); + } + case 'xcode_open_sim': + return await SimulatorTools.openSimulator(); + case 'xcode_shutdown_sim': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorTools.shutdownSimulator(simulatorUuid); + } + case 'xcode_screenshot': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid) || + undefined; + const savePath = typeof args.save_path === 'string' ? args.save_path : undefined; + return await SimulatorTools.captureScreenshot(simulatorUuid, savePath); + } + case 'xcode_describe_ui': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorUiTools.describeUI(simulatorUuid); + } + case 'xcode_tap': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + const x = this.parseNumericArg(args.x, 'x'); + const y = this.parseNumericArg(args.y, 'y'); + const preDelay = this.parseOptionalNumericArg(args.pre_delay ?? args.preDelay, 'pre_delay'); + const postDelay = this.parseOptionalNumericArg(args.post_delay ?? args.postDelay, 'post_delay'); + const tapOptions: { preDelay?: number; postDelay?: number } = {}; + if (preDelay !== undefined) tapOptions.preDelay = preDelay; + if (postDelay !== undefined) tapOptions.postDelay = postDelay; + return await SimulatorUiTools.tap(simulatorUuid, x, y, tapOptions); + } + case 'xcode_type_text': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + if (typeof args.text !== 'string' || args.text.length === 0) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: text`); + } + return await SimulatorUiTools.typeText(simulatorUuid, args.text); + } + case 'xcode_swipe': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + const x1 = this.parseNumericArg(args.x1, 'x1'); + const y1 = this.parseNumericArg(args.y1, 'y1'); + const x2 = this.parseNumericArg(args.x2, 'x2'); + const y2 = this.parseNumericArg(args.y2, 'y2'); + const duration = this.parseOptionalNumericArg(args.duration, 'duration'); + const delta = this.parseOptionalNumericArg(args.delta, 'delta'); + const preDelay = this.parseOptionalNumericArg(args.pre_delay ?? args.preDelay, 'pre_delay'); + const postDelay = this.parseOptionalNumericArg(args.post_delay ?? args.postDelay, 'post_delay'); + const swipeOptions: { duration?: number; delta?: number; preDelay?: number; postDelay?: number } = {}; + if (duration !== undefined) swipeOptions.duration = duration; + if (delta !== undefined) swipeOptions.delta = delta; + if (preDelay !== undefined) swipeOptions.preDelay = preDelay; + if (postDelay !== undefined) swipeOptions.postDelay = postDelay; + return await SimulatorUiTools.swipe( + simulatorUuid, + { x: x1, y: y1 }, + { x: x2, y: y2 }, + swipeOptions, + ); + } + case 'xcode_xcresult_browse': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -532,7 +716,7 @@ export class XcodeServer { args.test_id as string | undefined, args.include_console as boolean || false ); - case 'xcresult_browser_get_console': + case 'xcode_xcresult_browser_get_console': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -543,12 +727,12 @@ export class XcodeServer { args.xcresult_path as string, args.test_id as string ); - case 'xcresult_summary': + case 'xcode_xcresult_summary': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } return await XCResultTools.xcresultSummary(args.xcresult_path as string); - case 'xcresult_get_screenshot': + case 'xcode_xcresult_get_screenshot': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -563,7 +747,7 @@ export class XcodeServer { args.test_id as string, args.timestamp as number ); - case 'xcresult_get_ui_hierarchy': + case 'xcode_xcresult_get_ui_hierarchy': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -577,7 +761,7 @@ export class XcodeServer { args.full_hierarchy as boolean | undefined, args.raw_format as boolean | undefined ); - case 'xcresult_get_ui_element': + case 'xcode_xcresult_get_ui_element': if (!args.hierarchy_json_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: hierarchy_json_path`); } @@ -589,7 +773,7 @@ export class XcodeServer { args.element_index as number, args.include_children as boolean | undefined ); - case 'xcresult_list_attachments': + case 'xcode_xcresult_list_attachments': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -600,7 +784,7 @@ export class XcodeServer { args.xcresult_path as string, args.test_id as string ); - case 'xcresult_export_attachment': + case 'xcode_xcresult_export_attachment': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -621,19 +805,6 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } return await ProjectTools.getTestTargets(args.xcodeproj as string); - case 'xcode_refresh_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - // Close and reopen the project to refresh it - await ProjectTools.closeProject(args.xcodeproj as string); - const refreshResult = await ProjectTools.openProjectAndWaitForLoad(args.xcodeproj as string); - return { - content: [{ - type: 'text', - text: `Project refreshed: ${refreshResult.content?.[0]?.type === 'text' ? refreshResult.content[0].text : 'Completed'}` - }] - }; default: throw new McpError( ErrorCode.MethodNotFound, @@ -687,6 +858,37 @@ export class XcodeServer { return PathValidator.validateProjectPath(projectPath); } + private normalizeLockReason(rawReason: unknown, toolName: string): string { + if (typeof rawReason !== 'string') { + throw new McpError( + ErrorCode.InvalidParams, + `Missing required parameter: reason (briefly describe what part of the app you're touching for ${toolName}).`, + ); + } + const trimmed = rawReason.trim(); + if (!trimmed) { + throw new McpError( + ErrorCode.InvalidParams, + 'reason must be a short one-line summary (e.g., "Working on onboarding modals").', + ); + } + if (/[\r\n]/.test(trimmed)) { + throw new McpError( + ErrorCode.InvalidParams, + 'reason must fit on a single line—please remove newlines.', + ); + } + const normalized = trimmed.replace(/\s{2,}/g, ' '); + const maxLength = LockManager.getMaxReasonLength(); + if (normalized.length > maxLength) { + throw new McpError( + ErrorCode.InvalidParams, + `reason is too long (${normalized.length} characters). Keep it under ${maxLength} characters so teammates can scan it quickly.`, + ); + } + return normalized; + } + public async findProjectDerivedData(projectPath: string): Promise { const { BuildLogParser } = await import('./utils/BuildLogParser.js'); return BuildLogParser.findProjectDerivedData(projectPath); @@ -698,9 +900,15 @@ export class XcodeServer { } // Direct method interfaces for testing/CLI compatibility - public async build(projectPath: string, schemeName = 'Debug', destination: string | null = null): Promise { + public async build( + projectPath: string, + schemeName = 'Debug', + destination: string | null = null, + reason?: string, + ): Promise { const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.build(projectPath, schemeName, destination, this.openProject.bind(this)); + const normalizedReason = this.normalizeLockReason(reason ?? '', 'xcode_build'); + return BuildTools.build(projectPath, schemeName, normalizedReason, destination, this.openProject.bind(this)); } public async clean(projectPath: string): Promise { @@ -710,12 +918,18 @@ export class XcodeServer { public async test(projectPath: string, destination: string, commandLineArguments: string[] = []): Promise { const { BuildTools } = await import('./tools/BuildTools.js'); + Logger.debug(`Direct XcodeServer.test invoked with destination '${destination}' and args length ${commandLineArguments.length}`); return BuildTools.test(projectPath, destination, commandLineArguments, this.openProject.bind(this)); } - public async run(projectPath: string, commandLineArguments: string[] = []): Promise { + public async run( + projectPath: string, + commandLineArguments: string[] = [], + reason?: string, + ): Promise { const { BuildTools } = await import('./tools/BuildTools.js'); - return BuildTools.run(projectPath, 'Debug', commandLineArguments, this.openProject.bind(this)); + const normalizedReason = this.normalizeLockReason(reason ?? '', 'xcode_build_and_run'); + return BuildTools.run(projectPath, 'Debug', normalizedReason, commandLineArguments, this.openProject.bind(this)); } public async debug(projectPath: string, scheme: string, skipBuilding = false): Promise { @@ -801,10 +1015,18 @@ export class XcodeServer { try { // Handle health check tool first (no environment validation needed) - if (name === 'xcode_health_check') { - const report = await EnvironmentValidator.createHealthCheckReport(); - return { content: [{ type: 'text', text: report }] }; - } + if (name === 'xcode_health_check') { + const report = await EnvironmentValidator.createHealthCheckReport(); + const versionInfo = await this.getVersionInfo(); + return { + content: [ + { type: 'text', text: report }, + ...(versionInfo.content ?? []), + ], + }; + } + + Logger.debug(`callToolDirect: ${name} args = ${JSON.stringify(args)}`); // Validate environment for all other tools const validationError = await this.validateToolOperation(name); @@ -813,40 +1035,6 @@ export class XcodeServer { } switch (name) { - case 'xcode_open_project': - if (!args.xcodeproj) { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: xcodeproj\n\n💡 Expected: absolute path to .xcodeproj or .xcworkspace file` - ); - } - const result = await ProjectTools.openProject(args.xcodeproj as string); - if (result && 'content' in result && result.content?.[0] && 'text' in result.content[0]) { - const textContent = result.content[0]; - if (textContent.type === 'text' && typeof textContent.text === 'string') { - if (!textContent.text.includes('Error') && !textContent.text.includes('does not exist')) { - this.currentProjectPath = args.xcodeproj as string; - } - } - } - return result; - case 'xcode_close_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - try { - const validationError = PathValidator.validateProjectPath(args.xcodeproj as string); - if (validationError) return validationError; - - const closeResult = await ProjectTools.closeProject(args.xcodeproj as string); - this.currentProjectPath = null; - return closeResult; - } catch (closeError) { - // Ensure close project never crashes the server - Logger.error('Close project error (handled):', closeError); - this.currentProjectPath = null; - return { content: [{ type: 'text', text: 'Project close attempted - may have completed with dialogs' }] }; - } case 'xcode_build': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); @@ -854,12 +1042,16 @@ export class XcodeServer { if (!args.scheme) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); } - return await BuildTools.build( - args.xcodeproj as string, - args.scheme as string, - (args.destination as string) || null, - this.openProject.bind(this) - ); + { + const reason = this.normalizeLockReason(args.reason, 'xcode_build'); + return await BuildTools.build( + args.xcodeproj as string, + args.scheme as string, + reason, + (args.destination as string) || null, + this.openProject.bind(this), + ); + } case 'xcode_clean': if (!this.includeClean) { throw new McpError(ErrorCode.MethodNotFound, `Clean tool is disabled`); @@ -868,25 +1060,11 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } return await BuildTools.clean(args.xcodeproj as string, this.openProject.bind(this)); - case 'xcode_test': - if (!args.xcodeproj) { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: xcodeproj\n\n💡 To fix this:\n• Specify the absolute path to your .xcodeproj or .xcworkspace file using the "xcodeproj" parameter\n• Example: /Users/username/MyApp/MyApp.xcodeproj\n• You can drag the project file from Finder to get the path` - ); - } - if (!args.destination) { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: destination\n\n💡 To fix this:\n• Specify the test destination (e.g., "iPhone 15 Pro Simulator")\n• Use 'get-run-destinations' to see available destinations\n• Example: "iPad Air Simulator" or "iPhone 16 Pro"` - ); - } - return await BuildTools.test( - args.xcodeproj as string, - args.destination as string, - (args.command_line_arguments as string[]) || [], - this.openProject.bind(this) - ); + case 'xcode_test': { + const request = this.prepareTestRequest(args); + const runOptions = this.cloneTestOptions(request.options); + return await this.executeTestRun(request, this.openProject.bind(this), runOptions); + } case 'xcode_build_and_run': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); @@ -894,31 +1072,27 @@ export class XcodeServer { if (!args.scheme) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); } - return await BuildTools.run( - args.xcodeproj as string, - args.scheme as string, - (args.command_line_arguments as string[]) || [], - this.openProject.bind(this) - ); - case 'xcode_debug': + { + const reason = this.normalizeLockReason(args.reason, 'xcode_build_and_run'); + return await BuildTools.run( + args.xcodeproj as string, + args.scheme as string, + reason, + (args.command_line_arguments as string[]) || [], + this.openProject.bind(this), + ); + } + case 'xcode_release_lock': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } - if (!args.scheme) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: scheme`); - } - return await BuildTools.debug( - args.xcodeproj as string, - args.scheme as string, - args.skip_building as boolean, - this.openProject.bind(this) - ); + return await LockTools.release(args.xcodeproj as string); case 'xcode_stop': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } return await BuildTools.stop(args.xcodeproj as string); - case 'find_xcresults': + case 'xcode_find_xcresults': if (!args.xcodeproj) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } @@ -960,7 +1134,241 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: file_path`); } return await InfoTools.openFile(args.file_path as string, args.line_number as number); - case 'xcresult_browse': + case 'xcode_view_build_log': { + const parseBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') { + return value.toLowerCase() === 'true'; + } + return false; + }; + const rawMaxLines = + typeof args.max_lines === 'number' + ? args.max_lines + : typeof args.max_lines === 'string' + ? Number.parseInt(args.max_lines, 10) + : undefined; + const normalizedMaxLines = + typeof rawMaxLines === 'number' && Number.isFinite(rawMaxLines) && rawMaxLines > 0 + ? rawMaxLines + : undefined; + const logArgs: { + log_id?: string; + xcodeproj?: string; + filter_regex: boolean; + case_sensitive: boolean; + filter?: string; + max_lines?: number; + filter_globs?: string[]; + cursor?: string; + } = { + filter_regex: parseBoolean(args.filter_regex), + case_sensitive: parseBoolean(args.case_sensitive), + }; + const logId = (args.log_id as string) ?? (args.logId as string) ?? undefined; + if (logId) { + logArgs.log_id = logId; + } + const projectPath = (args.xcodeproj as string) ?? undefined; + if (projectPath) { + logArgs.xcodeproj = projectPath; + } + const filterGlobsArg = + (args.filter_globs as string[] | string | undefined) ?? + (args.filterGlobs as string[] | string | undefined); + if (filterGlobsArg) { + if (Array.isArray(filterGlobsArg)) { + const sanitized = filterGlobsArg + .map(item => (typeof item === 'string' ? item.trim() : '')) + .filter(item => item.length > 0); + if (sanitized.length > 0) { + logArgs.filter_globs = sanitized; + } + } else if (typeof filterGlobsArg === 'string' && filterGlobsArg.trim().length > 0) { + logArgs.filter_globs = filterGlobsArg + .split(',') + .map(item => item.trim()) + .filter(item => item.length > 0); + } + } + const cursorArg = (args.cursor as string) ?? undefined; + if (cursorArg) { + logArgs.cursor = cursorArg; + } + if (typeof args.filter === 'string' && args.filter.length > 0) { + logArgs.filter = args.filter; + } + if (normalizedMaxLines !== undefined) { + logArgs.max_lines = normalizedMaxLines; + } + return await LogTools.getBuildLog(logArgs); + } + case 'xcode_view_run_log': { + const parseBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') { + return value.toLowerCase() === 'true'; + } + return false; + }; + const rawMaxLines = + typeof args.max_lines === 'number' + ? args.max_lines + : typeof args.max_lines === 'string' + ? Number.parseInt(args.max_lines, 10) + : undefined; + const normalizedMaxLines = + typeof rawMaxLines === 'number' && Number.isFinite(rawMaxLines) && rawMaxLines > 0 + ? rawMaxLines + : undefined; + const logArgs: { + log_id?: string; + xcodeproj?: string; + filter_regex: boolean; + case_sensitive: boolean; + filter?: string; + max_lines?: number; + filter_globs?: string[]; + cursor?: string; + log_type: 'build' | 'run'; + } = { + filter_regex: parseBoolean(args.filter_regex), + case_sensitive: parseBoolean(args.case_sensitive), + log_type: 'run', + }; + const logId = (args.log_id as string) ?? (args.logId as string) ?? undefined; + if (logId) { + logArgs.log_id = logId; + } + const projectPath = (args.xcodeproj as string) ?? undefined; + if (projectPath) { + logArgs.xcodeproj = projectPath; + } + const filterGlobsArg = + (args.filter_globs as string[] | string | undefined) ?? + (args.filterGlobs as string[] | string | undefined); + if (filterGlobsArg) { + if (Array.isArray(filterGlobsArg)) { + const sanitized = filterGlobsArg + .map(item => (typeof item === 'string' ? item.trim() : '')) + .filter(item => item.length > 0); + if (sanitized.length > 0) { + logArgs.filter_globs = sanitized; + } + } else if (typeof filterGlobsArg === 'string' && filterGlobsArg.trim().length > 0) { + logArgs.filter_globs = filterGlobsArg + .split(',') + .map(item => item.trim()) + .filter(item => item.length > 0); + } + } + const cursorArg = (args.cursor as string) ?? undefined; + if (cursorArg) { + logArgs.cursor = cursorArg; + } + if (typeof args.filter === 'string' && args.filter.length > 0) { + logArgs.filter = args.filter; + } + if (normalizedMaxLines !== undefined) { + logArgs.max_lines = normalizedMaxLines; + } + return await LogTools.getBuildLog(logArgs); + } + case 'xcode_list_sims': + return await SimulatorTools.listSimulators(); + case 'xcode_boot_sim': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorTools.bootSimulator(simulatorUuid); + } + case 'xcode_open_sim': + return await SimulatorTools.openSimulator(); + case 'xcode_shutdown_sim': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorTools.shutdownSimulator(simulatorUuid); + } + case 'xcode_screenshot': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid) || + undefined; + const savePath = typeof args.save_path === 'string' ? args.save_path : undefined; + return await SimulatorTools.captureScreenshot(simulatorUuid, savePath); + } + case 'xcode_describe_ui': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + return await SimulatorUiTools.describeUI(simulatorUuid); + } + case 'xcode_tap': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + const x = this.parseNumericArg(args.x, 'x'); + const y = this.parseNumericArg(args.y, 'y'); + const preDelay = this.parseOptionalNumericArg(args.pre_delay ?? args.preDelay, 'pre_delay'); + const postDelay = this.parseOptionalNumericArg(args.post_delay ?? args.postDelay, 'post_delay'); + const tapOptions: { preDelay?: number; postDelay?: number } = {}; + if (preDelay !== undefined) tapOptions.preDelay = preDelay; + if (postDelay !== undefined) tapOptions.postDelay = postDelay; + return await SimulatorUiTools.tap(simulatorUuid, x, y, tapOptions); + } + case 'xcode_type_text': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + if (typeof args.text !== 'string' || args.text.length === 0) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: text`); + } + return await SimulatorUiTools.typeText(simulatorUuid, args.text); + } + case 'xcode_swipe': { + const simulatorUuid = + (typeof args.simulator_uuid === 'string' && args.simulator_uuid) || + (typeof args.simulatorUuid === 'string' && args.simulatorUuid); + if (!simulatorUuid) { + throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: simulator_uuid`); + } + const x1 = this.parseNumericArg(args.x1, 'x1'); + const y1 = this.parseNumericArg(args.y1, 'y1'); + const x2 = this.parseNumericArg(args.x2, 'x2'); + const y2 = this.parseNumericArg(args.y2, 'y2'); + const duration = this.parseOptionalNumericArg(args.duration, 'duration'); + const delta = this.parseOptionalNumericArg(args.delta, 'delta'); + const preDelay = this.parseOptionalNumericArg(args.pre_delay ?? args.preDelay, 'pre_delay'); + const postDelay = this.parseOptionalNumericArg(args.post_delay ?? args.postDelay, 'post_delay'); + const swipeOptions: { duration?: number; delta?: number; preDelay?: number; postDelay?: number } = {}; + if (duration !== undefined) swipeOptions.duration = duration; + if (delta !== undefined) swipeOptions.delta = delta; + if (preDelay !== undefined) swipeOptions.preDelay = preDelay; + if (postDelay !== undefined) swipeOptions.postDelay = postDelay; + return await SimulatorUiTools.swipe( + simulatorUuid, + { x: x1, y: y1 }, + { x: x2, y: y2 }, + swipeOptions, + ); + } + case 'xcode_xcresult_browse': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -969,7 +1377,7 @@ export class XcodeServer { args.test_id as string | undefined, args.include_console as boolean || false ); - case 'xcresult_browser_get_console': + case 'xcode_xcresult_browser_get_console': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -980,12 +1388,12 @@ export class XcodeServer { args.xcresult_path as string, args.test_id as string ); - case 'xcresult_summary': + case 'xcode_xcresult_summary': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } return await XCResultTools.xcresultSummary(args.xcresult_path as string); - case 'xcresult_get_screenshot': + case 'xcode_xcresult_get_screenshot': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -1000,7 +1408,7 @@ export class XcodeServer { args.test_id as string, args.timestamp as number ); - case 'xcresult_get_ui_hierarchy': + case 'xcode_xcresult_get_ui_hierarchy': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -1014,7 +1422,7 @@ export class XcodeServer { args.full_hierarchy as boolean | undefined, args.raw_format as boolean | undefined ); - case 'xcresult_get_ui_element': + case 'xcode_xcresult_get_ui_element': if (!args.hierarchy_json_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: hierarchy_json_path`); } @@ -1026,7 +1434,7 @@ export class XcodeServer { args.element_index as number, args.include_children as boolean | undefined ); - case 'xcresult_list_attachments': + case 'xcode_xcresult_list_attachments': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -1037,7 +1445,7 @@ export class XcodeServer { args.xcresult_path as string, args.test_id as string ); - case 'xcresult_export_attachment': + case 'xcode_xcresult_export_attachment': if (!args.xcresult_path) { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcresult_path`); } @@ -1058,19 +1466,6 @@ export class XcodeServer { throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); } return await ProjectTools.getTestTargets(args.xcodeproj as string); - case 'xcode_refresh_project': - if (!args.xcodeproj) { - throw new McpError(ErrorCode.InvalidParams, `Missing required parameter: xcodeproj`); - } - // Close and reopen the project to refresh it - await ProjectTools.closeProject(args.xcodeproj as string); - const refreshResult = await ProjectTools.openProjectAndWaitForLoad(args.xcodeproj as string); - return { - content: [{ - type: 'text', - text: `Project refreshed: ${refreshResult.content?.[0]?.type === 'text' ? refreshResult.content[0].text : 'Completed'}` - }] - }; default: throw new McpError( ErrorCode.MethodNotFound, @@ -1100,4 +1495,214 @@ export class XcodeServer { }; } } -} \ No newline at end of file + + private cloneTestOptions(source?: TestRunOptions): TestRunOptions | undefined { + if (!source) { + return undefined; + } + + const cloned: TestRunOptions = {}; + + if (source.schemeName) cloned.schemeName = source.schemeName; + if (source.testPlanPath) cloned.testPlanPath = source.testPlanPath; + if (source.testTargetIdentifier) cloned.testTargetIdentifier = source.testTargetIdentifier; + if (source.testTargetName) cloned.testTargetName = source.testTargetName; + if (source.deviceType) cloned.deviceType = source.deviceType; + if (source.osVersion) cloned.osVersion = source.osVersion; + if (source.selectedTests) cloned.selectedTests = [...source.selectedTests]; + if (source.selectedTestClasses) cloned.selectedTestClasses = [...source.selectedTestClasses]; + + return cloned; + } + + private prepareTestRequest(args: Record): TestRunRequest { + if (!args.xcodeproj || typeof args.xcodeproj !== 'string') { + throw new McpError( + ErrorCode.InvalidParams, + `Missing required parameter: xcodeproj\n\n💡 Provide the absolute path to your .xcodeproj or .xcworkspace file.`, + ); + } + + const projectPath = args.xcodeproj as string; + + const schemeFromArgs = typeof args.scheme === 'string' ? (args.scheme as string).trim() : ''; + const schemeName = schemeFromArgs || (this.preferredScheme ? this.preferredScheme.trim() : ''); + if (!schemeName) { + throw new McpError( + ErrorCode.InvalidParams, + `Missing required parameter: scheme\n\n💡 Pass --scheme or set XCODE_MCP_PREFERRED_SCHEME.`, + ); + } + + const hasDestination = typeof args.destination === 'string' && (args.destination as string).trim().length > 0; + const hasDeviceType = typeof args.device_type === 'string' && (args.device_type as string).trim().length > 0; + if (!hasDestination && !hasDeviceType) { + throw new McpError( + ErrorCode.InvalidParams, + `Missing required parameters. Provide either:\n• destination (e.g., "platform=iOS Simulator,name=iPhone 16")\n• or device_type with os_version (e.g., device_type="iphone" os_version="18.0").`, + ); + } + + const destination = hasDestination ? (args.destination as string).trim() : null; + + const commandLineArguments: string[] = []; + if (Array.isArray(args.command_line_arguments)) { + (args.command_line_arguments as unknown[]).forEach(value => { + const str = typeof value === 'string' ? value.trim() : String(value); + if (str.length > 0) { + commandLineArguments.push(str); + } + }); + } else if (typeof args.command_line_arguments === 'string') { + const trimmed = (args.command_line_arguments as string).trim(); + if (trimmed.length > 0) { + commandLineArguments.push(trimmed); + } + } + + const options: TestRunOptions = { schemeName }; + + if (hasDeviceType) { + options.deviceType = (args.device_type as string).trim(); + } + if (typeof args.os_version === 'string' && (args.os_version as string).trim().length > 0) { + options.osVersion = (args.os_version as string).trim(); + } + if (typeof args.test_plan_path === 'string' && (args.test_plan_path as string).trim().length > 0) { + options.testPlanPath = (args.test_plan_path as string).trim(); + } + if (typeof args.test_target_identifier === 'string' && (args.test_target_identifier as string).trim().length > 0) { + options.testTargetIdentifier = (args.test_target_identifier as string).trim(); + } + if (typeof args.test_target_name === 'string' && (args.test_target_name as string).trim().length > 0) { + options.testTargetName = (args.test_target_name as string).trim(); + } + + const normalizeStringArray = (value: unknown): string[] | undefined => { + if (Array.isArray(value)) { + const mapped = (value as unknown[]) + .map(entry => (typeof entry === 'string' ? entry.trim() : String(entry).trim())) + .filter(str => str.length > 0); + return mapped.length > 0 ? mapped : undefined; + } + if (typeof value === 'string') { + const trimmed = value.trim(); + return trimmed.length > 0 ? [trimmed] : undefined; + } + return undefined; + }; + + const selectedTests = normalizeStringArray(args.selected_tests); + if (selectedTests) { + options.selectedTests = selectedTests; + } + + const selectedClasses = normalizeStringArray(args.selected_test_classes); + if (selectedClasses) { + options.selectedTestClasses = selectedClasses; + } + + const request: TestRunRequest = { + projectPath, + destination, + commandLineArguments, + options, + }; + return request; + } + + private async getVersionInfo(): Promise { + const moduleDir = dirname(fileURLToPath(import.meta.url)); + const projectRoot = join(moduleDir, '..'); + + let packageVersion = 'unknown'; + try { + const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8')); + if (packageJson && typeof packageJson.version === 'string') { + packageVersion = packageJson.version; + } + } catch (error) { + Logger.warn(`Unable to read package.json for version info: ${error instanceof Error ? error.message : String(error)}`); + } + + let gitDescription: string | null = null; + try { + const execFileAsync = promisify(execFile); + const { stdout } = await execFileAsync('git', ['describe', '--tags', '--dirty', '--always'], { + cwd: projectRoot, + timeout: 2000, + }); + const trimmed = stdout.trim(); + if (trimmed.length > 0) { + gitDescription = trimmed; + } + } catch (error) { + Logger.debug(`git describe unavailable for version info: ${error instanceof Error ? error.message : String(error)}`); + } + + const keyArtifacts = [ + join(projectRoot, 'dist', 'XcodeServer.js'), + join(projectRoot, 'dist', 'tools', 'BuildTools.js'), + join(projectRoot, 'dist', 'tools', 'XCResultTools.js'), + ]; + + let latestModified: Date | null = null; + for (const artifact of keyArtifacts) { + try { + const stats = await stat(artifact); + if (!latestModified || stats.mtime > latestModified) { + latestModified = stats.mtime; + } + } catch (error) { + Logger.debug(`Version info: could not stat ${artifact}: ${error instanceof Error ? error.message : String(error)}`); + } + } + + return { + content: [ + { + type: 'text', + text: [ + '📦 XcodeMCP Version Information', + '==============================' + ].join('\n'), + }, + { + type: 'text', + text: `Package version: ${packageVersion}`, + }, + ...(gitDescription + ? [{ type: 'text' as const, text: `Git describe: ${gitDescription}` }] + : []), + ...(latestModified + ? [{ + type: 'text' as const, + text: `Latest build artifact modified: ${latestModified.toLocaleString()}`, + }] + : []), + { + type: 'text', + text: `Server root: ${projectRoot}`, + }, + ], + }; + } + + private async executeTestRun( + request: TestRunRequest, + openProject: OpenProjectCallback, + runOptions?: TestRunOptions, + ): Promise { + if (runOptions && Object.keys(runOptions).length > 0) { + BuildTools.setPendingTestOptions(runOptions); + } + + return await BuildTools.test( + request.projectPath, + request.destination, + [...request.commandLineArguments], + openProject, + runOptions, + ); + } +} diff --git a/src/cli.ts b/src/cli.ts index a76ae76..0523fec 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -5,14 +5,10 @@ import { readFile } from 'fs/promises'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { XcodeServer } from './XcodeServer.js'; -import { Logger } from './utils/Logger.js'; -import { getToolDefinitions } from './shared/toolDefinitions.js'; - -interface ToolDefinition { - name: string; - description: string; - inputSchema: any; -} +import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import Logger from './utils/Logger.js'; +import { getToolDefinitions, type ToolDefinition } from './shared/toolDefinitions.js'; +import LockManager from './utils/LockManager.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -38,7 +34,7 @@ function schemaPropertyToOption(name: string, property: any): { flags: string; d const dashName = name.replace(/_/g, '-'); const flags = property.type === 'boolean' ? `--${dashName}` : `--${dashName} `; const description = property.description || `${name} parameter`; - + const option = { flags, description }; if (property.default !== undefined) { (option as any).defaultValue = property.default; @@ -47,6 +43,83 @@ function schemaPropertyToOption(name: string, property: any): { flags: string; d return option; } +function getArgValue(flag: string): string | undefined { + const equalsMatch = process.argv.find(arg => arg.startsWith(`${flag}=`)); + if (equalsMatch) { + const [, value = ''] = equalsMatch.split('='); + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : undefined; + } + + const index = process.argv.indexOf(flag); + if (index !== -1 && process.argv.length > index + 1) { + const next = process.argv[index + 1]; + if (next && !next.startsWith('-')) { + const trimmed = next.trim(); + return trimmed.length > 0 ? trimmed : undefined; + } + } + return undefined; +} + +function hasFlag(flag: string): boolean { + return process.argv.includes(flag); +} + +function detectError(toolName: string, result: CallToolResult | undefined): boolean { + if (!result) return false; + if (result.isError) return true; + + if (!result.content || !Array.isArray(result.content)) { + return false; + } + + for (const item of result.content) { + if (item?.type === 'text' && typeof item.text === 'string') { + const text = item.text; + + if (toolName === 'xcode_health_check') { + if (text.includes('⚠️ CRITICAL ERRORS DETECTED') || text.includes('❌ OS:') || text.includes('❌ OSASCRIPT:')) { + return true; + } + continue; + } + + if (toolName === 'xcode_test') { + if (text.includes('✅ All tests passed!')) { + continue; + } + if ( + text.includes('❌ TEST BUILD FAILED') || + text.includes('❌ TESTS FAILED') || + text.includes('⏹️ TEST BUILD INTERRUPTED') || + (/Failed:\s*(?!0)/.test(text) && !text.includes('Failed: 0')) + ) { + return true; + } + continue; + } + + if ( + text.includes('❌') || + text.includes('does not exist') || + text.includes('failed') || + text.includes('error') || + text.includes('Error') || + text.includes('missing required parameter') || + text.includes('cannot find') || + text.includes('not found') || + text.includes('invalid') || + text.includes('Invalid') + ) { + return true; + } + } + } + + return false; +} + /** * Parse command line arguments into tool arguments */ @@ -126,53 +199,7 @@ async function main(): Promise { try { const pkg = await loadPackageJson(); - // Check for --no-clean argument early to configure both server and tools - const noCleanArg = process.argv.includes('--no-clean'); - const includeClean = !noCleanArg; - - // Parse preferred values from command-line or environment - const preferredScheme = process.env.XCODE_MCP_PREFERRED_SCHEME || - process.argv.find(arg => arg.startsWith('--preferred-scheme='))?.split('=')[1]; - const preferredXcodeproj = process.env.XCODE_MCP_PREFERRED_XCODEPROJ || - process.argv.find(arg => arg.startsWith('--preferred-xcodeproj='))?.split('=')[1]; - - const serverOptions: { - includeClean: boolean; - preferredScheme?: string; - preferredXcodeproj?: string; - } = { includeClean }; - - if (preferredScheme) serverOptions.preferredScheme = preferredScheme; - if (preferredXcodeproj) serverOptions.preferredXcodeproj = preferredXcodeproj; - - const server = new XcodeServer(serverOptions); - - // Get tool definitions from shared source to ensure CLI is always in sync with MCP - const toolOptions: { - includeClean: boolean; - preferredScheme?: string; - preferredXcodeproj?: string; - } = { includeClean }; - - if (preferredScheme) toolOptions.preferredScheme = preferredScheme; - if (preferredXcodeproj) toolOptions.preferredXcodeproj = preferredXcodeproj; - - const tools = getToolDefinitions(toolOptions); - - // Build description with preferred values if set - let description = `Command-line interface for Xcode automation and control`; - - if (preferredScheme || preferredXcodeproj) { - description += '\n\n📌 Preferred Values:'; - if (preferredScheme) { - description += `\n • Scheme: ${preferredScheme}`; - } - if (preferredXcodeproj) { - description += `\n • Project: ${preferredXcodeproj}`; - } - } - - description += ` + let description = `Command-line interface for Xcode automation and control 📁 Command Categories: • Project Management - Open/close projects, manage schemes and workspaces @@ -200,6 +227,67 @@ async function main(): Promise { .option('--no-clean', 'Disable the clean tool', false) .option('--preferred-scheme ', 'Set a preferred scheme to use as default') .option('--preferred-xcodeproj ', 'Set a preferred xcodeproj/xcworkspace to use as default'); + + let includeClean = true; + let cliPreferredScheme: string | undefined; + let cliPreferredXcodeproj: string | undefined; + + if (typeof (program as any).parseOptions === 'function') { + program.parseOptions(process.argv); + const globalOptions = program.opts(); + includeClean = globalOptions.clean !== false; + if (typeof globalOptions.preferredScheme === 'string') { + const trimmed = globalOptions.preferredScheme.trim(); + cliPreferredScheme = trimmed.length > 0 ? trimmed : undefined; + } + if (typeof globalOptions.preferredXcodeproj === 'string') { + const trimmed = globalOptions.preferredXcodeproj.trim(); + cliPreferredXcodeproj = trimmed.length > 0 ? trimmed : undefined; + } + } else { + includeClean = !hasFlag('--no-clean'); + cliPreferredScheme = getArgValue('--preferred-scheme'); + cliPreferredXcodeproj = getArgValue('--preferred-xcodeproj'); + } + + const envPreferredScheme = process.env.XCODE_MCP_PREFERRED_SCHEME?.trim(); + const envPreferredXcodeproj = process.env.XCODE_MCP_PREFERRED_XCODEPROJ?.trim(); + + const preferredScheme = cliPreferredScheme || envPreferredScheme; + const preferredXcodeproj = cliPreferredXcodeproj || envPreferredXcodeproj; + + if (preferredScheme || preferredXcodeproj) { + description += '\n\n📌 Preferred Values:'; + if (preferredScheme) { + description += `\n • Scheme: ${preferredScheme}`; + } + if (preferredXcodeproj) { + description += `\n • Project: ${preferredXcodeproj}`; + } + program.description(description); + } + + const serverOptions: { + includeClean: boolean; + preferredScheme?: string; + preferredXcodeproj?: string; + } = { includeClean }; + + if (preferredScheme) serverOptions.preferredScheme = preferredScheme; + if (preferredXcodeproj) serverOptions.preferredXcodeproj = preferredXcodeproj; + + const server = new XcodeServer(serverOptions); + + const toolOptions: { + includeClean: boolean; + preferredScheme?: string; + preferredXcodeproj?: string; + } = { includeClean }; + + if (preferredScheme) toolOptions.preferredScheme = preferredScheme; + if (preferredXcodeproj) toolOptions.preferredXcodeproj = preferredXcodeproj; + + const tools = getToolDefinitions(toolOptions); // Add global help command program @@ -219,8 +307,14 @@ async function main(): Promise { // Define command categories const buildAndRunCommands = [ - 'build', 'build-and-run', 'debug', 'stop', - 'get-run-destinations' + 'build', + 'build-and-run', + 'release-lock', + 'view-build-log', + 'view-run-log', + 'stop', + 'get-run-destinations', + 'release-all-locks', ]; // Add clean only if not disabled @@ -230,7 +324,6 @@ async function main(): Promise { const categories = { 'Project Management': [ - 'open-project', 'close-project', 'refresh-project', 'get-schemes', 'set-active-scheme', 'get-projects', 'get-workspace-info', 'open-file' ], @@ -252,13 +345,23 @@ async function main(): Promise { // Create a map of command name to tool for quick lookup const toolMap = new Map(); for (const tool of tools) { - const commandName = tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); + const defaultName = tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); + const commandName = tool.cliName ?? defaultName; toolMap.set(commandName, tool); + if (tool.cliAliases) { + for (const alias of tool.cliAliases) { + toolMap.set(alias, tool); + } + } + if (!toolMap.has(defaultName)) { + toolMap.set(defaultName, tool); + } } // Add non-tool commands toolMap.set('help', { description: 'Show help information' }); toolMap.set('list-tools', { description: 'List all available tools' }); + toolMap.set('release-all-locks', { description: 'CLI-only: Force release every outstanding build/run lock' }); // Display categorized commands for (const [category, commands] of Object.entries(categories)) { @@ -279,15 +382,41 @@ async function main(): Promise { console.log('⏱️ Note: Build, test, and run commands can take minutes to hours.'); console.log(' The CLI handles long operations automatically - do not timeout.'); }); - + + program + .command('release-all-locks') + .description('Force release every outstanding build/run lock (CLI-only safety valve)') + .action(async () => { + const { released, details } = await LockManager.releaseAllLocks(); + if (released === 0) { + console.log('No active locks detected.'); + return; + } + console.log(`Released ${released} lock${released === 1 ? '' : 's'}:`); + for (const detail of details) { + const reason = detail.reason ? `reason: "${detail.reason}"` : 'reason: n/a'; + const waiting = Math.max(0, detail.queueDepth - 1); + const waitingMsg = waiting > 0 ? `, ${waiting} worker${waiting === 1 ? '' : 's'} were waiting` : ''; + console.log(` • ${detail.path} (${reason}${waitingMsg})`); + } + }); // Dynamically create subcommands for each tool for (const tool of tools) { + if (tool.cliHidden) { + continue; + } // Convert tool name: remove "xcode_" prefix and replace underscores with dashes - const commandName = tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); + const commandName = tool.cliName ?? tool.name.replace(/^xcode_/, '').replace(/_/g, '-'); const cmd = program .command(commandName) .description(tool.description); + if (tool.cliAliases) { + for (const alias of tool.cliAliases) { + cmd.alias(alias); + } + } + // Add options based on the tool's input schema if (tool.inputSchema?.properties) { for (const [propName, propSchema] of Object.entries(tool.inputSchema.properties)) { @@ -326,7 +455,8 @@ async function main(): Promise { toolArgs = JSON.parse(cliArgs.jsonInput); } catch (error) { console.error('❌ Invalid JSON input:', error); - process.exit(1); + process.exitCode = 1; + return; } } else { toolArgs = parseToolArgs(tool, cliArgs); @@ -342,7 +472,8 @@ async function main(): Promise { if (error) { const output = formatResult(error, program.opts().json); console.error(output); - process.exit(1); + process.exitCode = 1; + return; } toolArgs.xcodeproj = resolvedPath; @@ -367,67 +498,25 @@ async function main(): Promise { } // Call the tool directly on server - const result = await server.callToolDirect(tool.name, toolArgs); - - // Check if the result indicates an error - let hasError = false; - if (result?.content && Array.isArray(result.content)) { - for (const item of result.content) { - if (item.type === 'text' && item.text) { - const text = item.text; - - // Special case for health-check: don't treat degraded mode as error - if (tool.name === 'xcode_health_check') { - // Only treat as error if there are critical failures - hasError = text.includes('⚠️ CRITICAL ERRORS DETECTED') || - text.includes('❌ OS:') || - text.includes('❌ OSASCRIPT:'); - } else if (tool.name === 'xcode_test') { - // Special case for test results: check if tests actually failed - if (text.includes('✅ All tests passed!')) { - hasError = false; - } else { - // Look for actual test failures or build errors - hasError = text.includes('❌ TEST BUILD FAILED') || - text.includes('❌ TESTS FAILED') || - text.includes('⏹️ TEST BUILD INTERRUPTED') || - (text.includes('Failed:') && !text.includes('Failed: 0')); - } - } else { - // Check for common error patterns - if (text.includes('❌') || - text.includes('does not exist') || - text.includes('failed') || - text.includes('error') || - text.includes('Error') || - text.includes('missing required parameter') || - text.includes('cannot find') || - text.includes('not found') || - text.includes('invalid') || - text.includes('Invalid')) { - hasError = true; - break; - } - } - } - } - } - + let result = await server.callToolDirect(tool.name, toolArgs); + // Output the result const output = formatResult(result, program.opts().json); + const hasError = detectError(tool.name, result); if (hasError) { console.error(output); } else { console.log(output); } - // Exit with appropriate code - process.exit(hasError ? 1 : 0); + // Exit with appropriate code without forcing an immediate shutdown (allows stdout flush) + process.exitCode = hasError ? 1 : 0; + return; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); console.error(`❌ ${tool.name} failed:`, errorMsg); - process.exit(1); + process.exitCode = 1; } }); } @@ -450,8 +539,8 @@ if (process.env.NODE_ENV !== 'test' || process.argv[1]?.includes('cli.js')) { main().catch((error) => { Logger.error('CLI execution failed:', error); console.error('❌ CLI execution failed:', error); - process.exit(1); + process.exitCode = 1; }); } -export { main }; \ No newline at end of file +export { main }; diff --git a/src/index.ts b/src/index.ts index dec6d59..5a8a727 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,21 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { createServer } from 'http'; import { XcodeServer } from './XcodeServer.js'; -import { Logger } from './utils/Logger.js'; +import Logger from './utils/Logger.js'; import type { EnvironmentValidation } from './types/index.js'; +const ALLOWED_EXACT_ARGS = new Set(['--no-clean']); +const ALLOWED_PREFIX_ARGS = ['--preferred-scheme=', '--preferred-xcodeproj=', '--port=']; + +export function findUnsupportedServerArgs(args: string[]): string[] { + return args.filter((arg) => { + if (ALLOWED_EXACT_ARGS.has(arg)) { + return false; + } + return !ALLOWED_PREFIX_ARGS.some((prefix) => arg.startsWith(prefix)); + }); +} + // Handle uncaught exceptions and unhandled promise rejections process.on('uncaughtException', (error) => { Logger.error('🚨 UNCAUGHT EXCEPTION - This may indicate a bug that needs fixing:', error); @@ -200,6 +212,17 @@ export class XcodeMCPServer extends XcodeServer { // Only run if not in test environment if (process.env.NODE_ENV !== 'test') { + const unsupportedArgs = findUnsupportedServerArgs(process.argv.slice(2)); + if (unsupportedArgs.length > 0) { + const formattedArgs = unsupportedArgs.join(', '); + Logger.error('Unsupported arguments provided to xcodemcp CLI:', formattedArgs); + Logger.error('The xcodemcp binary only starts the MCP server and does not run tools directly.'); + console.error('❌ xcodemcp only launches the MCP server.'); + console.error(`Unsupported arguments detected: ${formattedArgs}`); + console.error('Run individual build/test commands via your MCP client (e.g., call the xcode_build or xcode_test tools, or use the xcodecontrol CLI).'); + await Logger.flush(); + process.exit(1); + } // Check for --no-clean argument const noCleanArg = process.argv.includes('--no-clean'); const includeClean = !noCleanArg; @@ -233,4 +256,4 @@ if (process.env.NODE_ENV !== 'test') { await Logger.flush(); process.exit(1); }); -} \ No newline at end of file +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 610afd2..e651e37 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -1,5 +1,5 @@ import { spawn } from 'child_process'; -import { Logger } from '../utils/Logger.js'; +import Logger from '../utils/Logger.js'; import { EventEmitter } from 'events'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; @@ -83,34 +83,6 @@ export class McpLibrary extends EventEmitter { // Fallback to hardcoded tool definitions return [ - { - name: 'xcode_open_project', - description: 'Open an Xcode project or workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, - { - name: 'xcode_close_project', - description: 'Close the currently active Xcode project or workspace (automatically stops any running actions first)', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: ['xcodeproj'], - }, - }, { name: 'xcode_build', description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination.', @@ -221,28 +193,6 @@ export class McpLibrary extends EventEmitter { required: ['xcodeproj', 'scheme'], }, }, - { - name: 'xcode_debug', - description: 'Start debugging session for a specific project', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: 'Scheme name (optional)', - }, - skip_building: { - type: 'boolean', - description: 'Whether to skip building', - }, - }, - required: ['xcodeproj'], - }, - }, { name: 'xcode_stop', description: 'Stop the current scheme action', @@ -252,7 +202,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'find_xcresults', + name: 'xcode_find_xcresults', description: 'Find all XCResult files for a specific project with timestamps and file information', inputSchema: { type: 'object', @@ -334,7 +284,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_browse', + name: 'xcode_xcresult_browse', description: 'Browse XCResult files - list all tests or show details for a specific test. Returns comprehensive test results including pass/fail status, failure details, and browsing instructions. Large console output (>20 lines or >2KB) is automatically saved to a temporary file.', inputSchema: { type: 'object', @@ -357,7 +307,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_browser_get_console', + name: 'xcode_xcresult_browser_get_console', description: 'Get console output and test activities for a specific test in an XCResult file. Large output (>20 lines or >2KB) is automatically saved to a temporary file.', inputSchema: { type: 'object', @@ -375,7 +325,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_summary', + name: 'xcode_xcresult_summary', description: 'Get a quick summary of test results from an XCResult file', inputSchema: { type: 'object', @@ -389,7 +339,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_get_screenshot', + name: 'xcode_xcresult_get_screenshot', description: 'Get screenshot from a failed test at specific timestamp - extracts frame from video attachment using ffmpeg', inputSchema: { type: 'object', @@ -411,7 +361,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_get_ui_hierarchy', + name: 'xcode_xcresult_get_ui_hierarchy', description: 'Get UI hierarchy attachment from test. Returns raw accessibility tree (best for AI), slim AI-readable JSON (default), or full JSON.', inputSchema: { type: 'object', @@ -441,7 +391,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_get_ui_element', + name: 'xcode_xcresult_get_ui_element', description: 'Get full details of a specific UI element by index from a previously exported UI hierarchy JSON file', inputSchema: { type: 'object', @@ -463,7 +413,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_list_attachments', + name: 'xcode_xcresult_list_attachments', description: 'List all attachments for a specific test - shows attachment names, types, and indices for export', inputSchema: { type: 'object', @@ -481,7 +431,7 @@ export class McpLibrary extends EventEmitter { }, }, { - name: 'xcresult_export_attachment', + name: 'xcode_xcresult_export_attachment', description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON', inputSchema: { type: 'object', @@ -642,4 +592,4 @@ export async function callTool( export async function getTools(): Promise { const library = getDefaultLibrary(); return library.getTools(); -} \ No newline at end of file +} diff --git a/src/shared/toolDefinitions.ts b/src/shared/toolDefinitions.ts index c5e0f5d..b7ccda0 100644 --- a/src/shared/toolDefinitions.ts +++ b/src/shared/toolDefinitions.ts @@ -2,6 +2,9 @@ export interface ToolDefinition { name: string; description: string; inputSchema: any; + cliName?: string; + cliAliases?: string[]; + cliHidden?: boolean; } /** @@ -14,38 +17,6 @@ export function getToolDefinitions(options: { } = { includeClean: true }): ToolDefinition[] { const { includeClean = true, preferredScheme, preferredXcodeproj } = options; const tools: ToolDefinition[] = [ - { - name: 'xcode_open_project', - description: 'Open an Xcode project or workspace', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, - { - name: 'xcode_close_project', - description: 'Close the currently active Xcode project or workspace (automatically stops any running actions first)', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - }, - required: preferredXcodeproj ? [] : ['xcodeproj'], - }, - }, { name: 'xcode_build', description: 'Build a specific Xcode project or workspace with the specified scheme. If destination is not provided, uses the currently active destination. ⏱️ Can take minutes to hours - do not timeout.', @@ -68,8 +39,13 @@ export function getToolDefinitions(options: { type: 'string', description: 'Build destination (optional - uses active destination if not provided)', }, + reason: { + type: 'string', + description: 'One-line reason for holding the build lock (e.g., "Working on onboarding flow").', + }, }, required: [ + 'reason', ...(!preferredXcodeproj ? ['xcodeproj'] : []), ...(!preferredScheme ? ['scheme'] : []) ], @@ -125,7 +101,15 @@ export function getToolDefinitions(options: { }, destination: { type: 'string', - description: 'Test destination (required for predictable test environments) - e.g., "iPhone 15 Pro Simulator", "iPad Air Simulator"', + description: 'Explicit xcodebuild destination string (e.g., "platform=iOS Simulator,name=iPhone 16"). Optional when device_type/os_version are supplied.', + }, + device_type: { + type: 'string', + description: 'High-level device family to target (e.g., iphone, ipad, mac, watch, tv, vision). When provided, os_version is recommended.', + }, + os_version: { + type: 'string', + description: 'Desired OS version for the selected device family (e.g., 18.0, 26.0). Used with device_type.', }, command_line_arguments: { type: 'array', @@ -154,8 +138,17 @@ export function getToolDefinitions(options: { type: 'string', description: 'Optional: Target name for the test target (alternative to test_target_identifier). Example: "TestAppTests".', }, + scheme: { + type: 'string', + description: preferredScheme + ? `Name of the test scheme - defaults to ${preferredScheme}` + : 'Name of the test scheme to run', + }, }, - required: preferredXcodeproj ? ['destination'] : ['xcodeproj', 'destination'], + required: [ + ...(!preferredXcodeproj ? ['xcodeproj'] : []), + ...(!preferredScheme ? ['scheme'] : []) + ], }, }, { @@ -181,34 +174,30 @@ export function getToolDefinitions(options: { items: { type: 'string' }, description: 'Additional command line arguments', }, + reason: { + type: 'string', + description: 'One-line reason for holding the build & run lock (helps other workers coordinate).', + }, }, required: [ + 'reason', ...(!preferredXcodeproj ? ['xcodeproj'] : []), ...(!preferredScheme ? ['scheme'] : []) ], }, }, { - name: 'xcode_debug', - description: 'Start debugging session for a specific project. ⏱️ Can run indefinitely - do not timeout.', + name: 'xcode_release_lock', + cliName: 'release-lock', + description: 'Release the exclusive build/run lock for a project or workspace once you are finished inspecting the results.', inputSchema: { type: 'object', properties: { xcodeproj: { type: 'string', - description: preferredXcodeproj - ? `Absolute path to the .xcodeproj file (or .xcworkspace if available) - defaults to ${preferredXcodeproj}` - : 'Absolute path to the .xcodeproj file (or .xcworkspace if available) - e.g., /path/to/project.xcodeproj', - }, - scheme: { - type: 'string', - description: preferredScheme - ? `Scheme name (optional) - defaults to ${preferredScheme}` - : 'Scheme name (optional)', - }, - skip_building: { - type: 'boolean', - description: 'Whether to skip building', + description: preferredXcodeproj + ? `Absolute path to the .xcodeproj or .xcworkspace whose lock you want to release - defaults to ${preferredXcodeproj}` + : 'Absolute path to the .xcodeproj or .xcworkspace whose lock you want to release.', }, }, required: preferredXcodeproj ? [] : ['xcodeproj'], @@ -231,7 +220,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'find_xcresults', + name: 'xcode_find_xcresults', description: 'Find all XCResult files for a specific project with timestamps and file information', inputSchema: { type: 'object', @@ -312,6 +301,268 @@ export function getToolDefinitions(options: { required: ['file_path'], }, }, + { + name: 'xcode_view_build_log', + cliName: 'view-build-log', + cliAliases: ['get-build-log'], + description: + 'Display the build/run log captured for a project. Supply a log_id returned by build/run commands or a project path to view the latest log.', + inputSchema: { + type: 'object', + properties: { + log_id: { + type: 'string', + description: 'Log ID returned by xcode_build/xcode_build_and_run when the log was registered.', + }, + xcodeproj: { + type: 'string', + description: 'Absolute path to the project/workspace. Used to locate the most recent log when log_id is not provided.', + }, + filter: { + type: 'string', + description: 'Optional filter string. Matches are case-insensitive unless case_sensitive=true.', + }, + filter_regex: { + type: 'boolean', + description: 'Treat filter as a JavaScript regex pattern.', + }, + case_sensitive: { + type: 'boolean', + description: 'Make the filter match case-sensitive (default false).', + }, + max_lines: { + type: 'number', + description: 'Maximum number of log lines to return (default 400).', + }, + filter_globs: { + type: 'array', + items: { type: 'string' }, + description: 'Optional glob patterns (e.g., "*error*", "??-warning") to match multiple expressions. Separate with commas when using the CLI.', + }, + cursor: { + type: 'string', + description: 'Cursor returned by a previous call. When supplied, only shows new log output written since that cursor.', + }, + }, + }, + }, + { + name: 'xcode_view_run_log', + cliName: 'view-run-log', + cliAliases: ['get-run-log'], + description: + 'Display the runtime console log captured for the most recent run. Supply a log_id returned by build-and-run or use a project path to view the latest log.', + inputSchema: { + type: 'object', + properties: { + log_id: { + type: 'string', + description: 'Log ID returned by xcode_build_and_run when the run log was registered.', + }, + xcodeproj: { + type: 'string', + description: 'Absolute path to the project/workspace. Used to locate the most recent run log when log_id is not provided.', + }, + filter: { + type: 'string', + description: 'Optional filter string. Matches are case-insensitive unless case_sensitive=true.', + }, + filter_regex: { + type: 'boolean', + description: 'Treat filter as a JavaScript regex pattern.', + }, + filter_globs: { + type: 'array', + items: { type: 'string' }, + description: 'Optional glob patterns (e.g., "*error*", "??-warning") to match multiple expressions. Separate with commas when using the CLI.', + }, + case_sensitive: { + type: 'boolean', + description: 'Make the filter match case-sensitive (default false).', + }, + max_lines: { + type: 'number', + description: 'Maximum number of log lines to return (default 400).', + }, + cursor: { + type: 'string', + description: 'Cursor returned by a previous call. When supplied, only shows new log output written since that cursor.', + }, + }, + }, + }, + { + name: 'xcode_list_sims', + description: 'List available iOS simulators with their states and runtime information.', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + { + name: 'xcode_boot_sim', + description: 'Boot an iOS simulator using its UUID.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to boot (use list_sims to discover).', + }, + }, + required: ['simulator_uuid'], + }, + }, + { + name: 'xcode_open_sim', + description: 'Open the Simulator application.', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + { + name: 'xcode_shutdown_sim', + description: 'Shut down a running simulator.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to shut down.', + }, + }, + required: ['simulator_uuid'], + }, + }, + { + name: 'xcode_screenshot', + description: 'Capture a PNG screenshot from a simulator. Returns the image as base64 data.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'Optional simulator UUID (defaults to the booted simulator).', + }, + save_path: { + type: 'string', + description: 'Optional path to save the screenshot on disk.', + }, + }, + }, + }, + { + name: 'xcode_describe_ui', + description: + 'Return the accessibility hierarchy for the running simulator using AXe. Provides coordinates for automation.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to inspect.', + }, + }, + required: ['simulator_uuid'], + }, + }, + { + name: 'xcode_tap', + description: + 'Tap at specific coordinates using AXe. Use describe_ui first to gather accurate positions.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to interact with.', + }, + x: { + type: 'number', + description: 'X coordinate in simulator points.', + }, + y: { + type: 'number', + description: 'Y coordinate in simulator points.', + }, + pre_delay: { + type: 'number', + description: 'Optional delay before performing the tap (seconds).', + }, + post_delay: { + type: 'number', + description: 'Optional delay after performing the tap (seconds).', + }, + }, + required: ['simulator_uuid', 'x', 'y'], + }, + }, + { + name: 'xcode_type_text', + description: 'Type text into the simulator using AXe keyboard events. Focus the target field first.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to interact with.', + }, + text: { + type: 'string', + description: 'Text to type (standard US keyboard characters).', + }, + }, + required: ['simulator_uuid', 'text'], + }, + }, + { + name: 'xcode_swipe', + description: + 'Swipe from one coordinate to another using AXe. Coordinates are provided in simulator points.', + inputSchema: { + type: 'object', + properties: { + simulator_uuid: { + type: 'string', + description: 'UUID of the simulator to interact with.', + }, + x1: { + type: 'number', + description: 'Start X coordinate.', + }, + y1: { + type: 'number', + description: 'Start Y coordinate.', + }, + x2: { + type: 'number', + description: 'End X coordinate.', + }, + y2: { + type: 'number', + description: 'End Y coordinate.', + }, + duration: { + type: 'number', + description: 'Optional swipe duration in seconds.', + }, + delta: { + type: 'number', + description: 'Optional sampling delta for the gesture.', + }, + pre_delay: { + type: 'number', + description: 'Optional delay before performing the swipe.', + }, + post_delay: { + type: 'number', + description: 'Optional delay after performing the swipe.', + }, + }, + required: ['simulator_uuid', 'x1', 'y1', 'x2', 'y2'], + }, + }, { name: 'xcode_health_check', description: 'Perform a comprehensive health check of the XcodeMCP environment and configuration', @@ -321,7 +572,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_browse', + name: 'xcode_xcresult_browse', description: 'Browse XCResult files - list all tests or show details for a specific test. Returns comprehensive test results including pass/fail status, failure details, and browsing instructions. Large console output (>20 lines or >2KB) is automatically saved to a temporary file.', inputSchema: { type: 'object', @@ -344,7 +595,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_browser_get_console', + name: 'xcode_xcresult_browser_get_console', description: 'Get console output and test activities for a specific test in an XCResult file. Large output (>20 lines or >2KB) is automatically saved to a temporary file.', inputSchema: { type: 'object', @@ -362,7 +613,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_summary', + name: 'xcode_xcresult_summary', description: 'Get a quick summary of test results from an XCResult file', inputSchema: { type: 'object', @@ -376,7 +627,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_get_screenshot', + name: 'xcode_xcresult_get_screenshot', description: 'Get screenshot from a failed test at specific timestamp - extracts frame from video attachment using ffmpeg', inputSchema: { type: 'object', @@ -398,7 +649,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_get_ui_hierarchy', + name: 'xcode_xcresult_get_ui_hierarchy', description: 'Get UI hierarchy attachment from test. Returns raw accessibility tree (best for AI), slim AI-readable JSON (default), or full JSON.', inputSchema: { type: 'object', @@ -428,7 +679,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_get_ui_element', + name: 'xcode_xcresult_get_ui_element', description: 'Get full details of a specific UI element by index from a previously exported UI hierarchy JSON file', inputSchema: { type: 'object', @@ -450,7 +701,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_list_attachments', + name: 'xcode_xcresult_list_attachments', description: 'List all attachments for a specific test - shows attachment names, types, and indices for export', inputSchema: { type: 'object', @@ -468,7 +719,7 @@ export function getToolDefinitions(options: { }, }, { - name: 'xcresult_export_attachment', + name: 'xcode_xcresult_export_attachment', description: 'Export a specific attachment by index - can convert App UI hierarchy attachments to JSON', inputSchema: { type: 'object', @@ -493,20 +744,6 @@ export function getToolDefinitions(options: { required: ['xcresult_path', 'test_id', 'attachment_index'], }, }, - { - name: 'xcode_refresh_project', - description: 'Refresh/reload an Xcode project by closing and reopening it to pick up external changes like modified .xctestplan files', - inputSchema: { - type: 'object', - properties: { - xcodeproj: { - type: 'string', - description: 'Absolute path to the .xcodeproj file (or .xcworkspace if available) to refresh', - }, - }, - required: ['xcodeproj'], - }, - }, { name: 'xcode_get_test_targets', description: 'Get information about test targets in a project, including names and identifiers', @@ -520,7 +757,7 @@ export function getToolDefinitions(options: { }, required: ['xcodeproj'], }, - }, + } ]; // Conditionally add the clean tool @@ -544,4 +781,4 @@ export function getToolDefinitions(options: { } return tools; -} \ No newline at end of file +} diff --git a/src/tools/BuildTools.ts b/src/tools/BuildTools.ts index e122681..ab5abc8 100644 --- a/src/tools/BuildTools.ts +++ b/src/tools/BuildTools.ts @@ -1,22 +1,53 @@ -import { stat } from 'fs/promises'; -import { readdir } from 'fs/promises'; -import { join } from 'path'; +import { stat, readdir, rm, mkdir, readFile, writeFile } from 'fs/promises'; +import { basename, dirname, join } from 'path'; +import { spawn, execFile } from 'child_process'; +import { tmpdir, homedir } from 'os'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { JXAExecutor } from '../utils/JXAExecutor.js'; import { BuildLogParser } from '../utils/BuildLogParser.js'; -import { PathValidator } from '../utils/PathValidator.js'; -import { ErrorHelper } from '../utils/ErrorHelper.js'; +import PathValidator from '../utils/PathValidator.js'; +import ErrorHelper from '../utils/ErrorHelper.js'; import { ParameterNormalizer } from '../utils/ParameterNormalizer.js'; -import { Logger } from '../utils/Logger.js'; +import Logger from '../utils/Logger.js'; import { XCResultParser } from '../utils/XCResultParser.js'; import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; -import type { McpResult, OpenProjectCallback } from '../types/index.js'; +import type { BuildLogInfo, McpResult, OpenProjectCallback, ParsedBuildResults } from '../types/index.js'; +import BuildLogStore, { BuildLogRecord } from '../utils/BuildLogStore.js'; +import LockManager from '../utils/LockManager.js'; + +const FAILURE_STATUS_TOKENS = ['fail', 'error', 'cancel', 'terminate', 'abort']; export class BuildTools { + private static pendingTestOptions: { + testPlanPath?: string; + selectedTests?: string[]; + selectedTestClasses?: string[]; + testTargetIdentifier?: string; + testTargetName?: string; + schemeName?: string; + deviceType?: string; + osVersion?: string; + } | null = null; + + public static setPendingTestOptions(options: { + testPlanPath?: string; + selectedTests?: string[]; + selectedTestClasses?: string[]; + testTargetIdentifier?: string; + testTargetName?: string; + schemeName?: string; + deviceType?: string; + osVersion?: string; + }): void { + Logger.debug(`Pending test options set: ${JSON.stringify(options)}`); + this.pendingTestOptions = options; + } + public static async build( - projectPath: string, - schemeName: string, - destination: string | null = null, + projectPath: string, + schemeName: string, + reason: string, + destination: string | null = null, openProject: OpenProjectCallback ): Promise { const validationError = PathValidator.validateProjectPath(projectPath); @@ -24,6 +55,42 @@ export class BuildTools { await openProject(projectPath); + let logRecord: BuildLogRecord | null = null; + let lockFooterText: string | null = null; + let autoReleaseNote: string | null = null; + const applyLockMessaging = (message: string): string => { + if (autoReleaseNote) { + const note = `🔓 Lock automatically released: ${autoReleaseNote}\nNo manual release command is required for this attempt.`; + return `${message}\n\n${note}`; + } + return LockManager.appendFooter(message, lockFooterText); + }; + const attachLogHint = (message: string): string => { + if (!logRecord) return applyLockMessaging(message); + const hintLines = [ + '🪵 Build Log Metadata', + ` • Log ID: ${logRecord.id}`, + ` • Path: ${logRecord.logPath}`, + ]; + if (logRecord.schemeName) { + hintLines.splice(1, 0, ` • Scheme: ${logRecord.schemeName}`); + } + if (logRecord.destination) { + hintLines.splice(hintLines.length - 1, 0, ` • Destination: ${logRecord.destination}`); + } + hintLines.push(` • View: xcodecontrol view-build-log --log-id ${logRecord.id}`); + return applyLockMessaging(`${message}\n\n${hintLines.join('\n')}`); + }; + const finalizeLogStatus = (status: 'active' | 'completed' | 'failed', buildStatus?: string | null) => { + const extras = + buildStatus === undefined + ? undefined + : { + buildStatus, + }; + BuildLogStore.updateStatus(logRecord?.id, status, extras); + }; + // Normalize the scheme name for better matching const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); @@ -87,8 +154,8 @@ export class BuildTools { } if (destination) { - // Normalize the destination name for better matching - const normalizedDestination = ParameterNormalizer.normalizeDestinationName(destination); + // Pre-compute destination name candidates for flexible matching + const destinationCandidates = ParameterNormalizer.getDestinationNameCandidates(destination); const setDestinationScript = ` (function() { @@ -96,14 +163,23 @@ export class BuildTools { const destinations = workspace.runDestinations(); const destinationNames = destinations.map(dest => dest.name()); + const candidateNames = ${JSON.stringify(destinationCandidates)}; - // Try exact match first - let targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(normalizedDestination)}); + const findMatch = (names) => { + if (!names || !names.length) { + return null; + } + for (const name of names) { + const match = destinations.find(dest => dest.name() === name); + if (match) { + return match; + } + } + return null; + }; - // If not found, try original name - if (!targetDestination) { - targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(destination)}); - } + // Try exact match first + let targetDestination = findMatch(candidateNames); if (!targetDestination) { throw new Error('Destination not found. Available: ' + JSON.stringify(destinationNames)); @@ -160,6 +236,30 @@ export class BuildTools { } } + const { footerText: buildLockFooter } = await LockManager.acquireLock(projectPath, reason, 'xcode_build'); + lockFooterText = buildLockFooter; + let lockReleased = false; + const releaseLockNow = async (): Promise => { + if (lockReleased) { + return; + } + try { + await LockManager.releaseLock(projectPath); + } catch (error) { + Logger.warn( + `Failed to release build lock for ${projectPath}: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } finally { + lockReleased = true; + } + }; + const markAutoRelease = async (reasonText: string): Promise => { + autoReleaseNote = reasonText; + await releaseLockNow(); + }; + const buildScript = ` (function() { ${getWorkspaceByPathScript(projectPath)} @@ -180,16 +280,22 @@ export class BuildTools { } catch (error) { const enhancedError = ErrorHelper.parseCommonErrors(error as Error); if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; + await markAutoRelease('Failed to start the build in Xcode'); + return { + content: [{ type: 'text', text: applyLockMessaging(enhancedError) }], + }; } const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Failed to start build: ${errorMessage}` }] }; + await markAutoRelease('Failed to start the build in Xcode'); + return { + content: [{ type: 'text', text: applyLockMessaging(`Failed to start build: ${errorMessage}`) }], + }; } Logger.info('Waiting for new build log to appear after build start...'); let attempts = 0; - let newLog = null; + let newLog: BuildLogInfo | null = null; const initialWaitAttempts = 3600; // 1 hour max to wait for build log while (attempts < initialWaitAttempts) { @@ -214,7 +320,31 @@ export class BuildTools { } if (!newLog) { - return { content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Build started but no new build log appeared within ${initialWaitAttempts} seconds`, ErrorHelper.getBuildLogNotFoundGuidance()) }] }; + await markAutoRelease('Build log never appeared'); + return { + content: [ + { + type: 'text', + text: applyLockMessaging( + ErrorHelper.createErrorWithGuidance( + `Build started but no new build log appeared within ${initialWaitAttempts} seconds`, + ErrorHelper.getBuildLogNotFoundGuidance(), + ), + ), + }, + ], + }; + } + + if (!logRecord) { + logRecord = BuildLogStore.registerLog({ + projectPath, + logPath: newLog.path, + schemeName, + destination, + action: 'build', + logKind: 'build', + }); } Logger.info(`Monitoring build completion for log: ${newLog.path}`); @@ -262,10 +392,31 @@ export class BuildTools { } if (attempts >= maxAttempts) { - return { content: [{ type: 'text', text: `Build timed out after ${maxAttempts} seconds` }] }; + finalizeLogStatus('failed', 'timeout'); + await markAutoRelease('Build timed out'); + return { + content: [{ type: 'text', text: attachLogHint(`Build timed out after ${maxAttempts} seconds`) }], + }; } - const results = await BuildLogParser.parseBuildLog(newLog.path); + const results = await BuildLogParser.parseBuildLog(newLog.path, 0, 6, { timeoutMs: 45000 }); + + const normalizedStatus = results.buildStatus ? results.buildStatus.toLowerCase() : null; + const statusIndicatesFailure = normalizedStatus + ? normalizedStatus !== 'stopped' && + normalizedStatus !== 'interrupted' && + FAILURE_STATUS_TOKENS.some(token => normalizedStatus.includes(token)) + : false; + const summaryIndicatesFailure = typeof results.errorCount === 'number' && results.errorCount > 0; + + if (results.errors.length === 0 && (statusIndicatesFailure || summaryIndicatesFailure)) { + const descriptor = results.buildStatus + ? `Xcode reported build status '${results.buildStatus}'` + : 'Xcode reported build errors in the log summary'; + results.errors = [ + `${descriptor} for log ${newLog.path}. Open the log in Xcode for full details.`, + ]; + } let message = ''; const schemeInfo = schemeName ? ` for scheme '${schemeName}'` : ''; @@ -276,7 +427,9 @@ export class BuildTools { // Handle stopped/interrupted builds if (results.buildStatus === 'stopped') { message = `⏹️ BUILD INTERRUPTED${schemeInfo}${destInfo}\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the build again to complete it.`; - return { content: [{ type: 'text', text: message }] }; + finalizeLogStatus('failed', results.buildStatus); + await markAutoRelease('Build was interrupted'); + return { content: [{ type: 'text', text: attachLogHint(message) }] }; } if (results.errors.length > 0) { @@ -285,9 +438,11 @@ export class BuildTools { message += ` • ${error}\n`; Logger.error('Build error:', error); }); + finalizeLogStatus('failed', results.buildStatus ?? 'failed'); + await markAutoRelease('Build failed with errors'); throw new McpError( ErrorCode.InternalError, - message + attachLogHint(message) ); } else if (results.warnings.length > 0) { message = `⚠️ BUILD COMPLETED WITH WARNINGS${schemeInfo}${destInfo} (${results.warnings.length} warnings)\n\nWARNINGS:\n`; @@ -299,7 +454,8 @@ export class BuildTools { message = `✅ BUILD SUCCESSFUL${schemeInfo}${destInfo}`; } - return { content: [{ type: 'text', text: message }] }; + finalizeLogStatus('completed', results.buildStatus ?? null); + return { content: [{ type: 'text', text: attachLogHint(message) }] }; } public static async clean(projectPath: string, openProject: OpenProjectCallback): Promise { @@ -330,640 +486,443 @@ export class BuildTools { } public static async test( - projectPath: string, - destination: string, - commandLineArguments: string[] = [], - openProject: OpenProjectCallback, + projectPath: string, + destination: string | null, + commandLineArguments: string[] = [], + _openProject: OpenProjectCallback, options?: { testPlanPath?: string; selectedTests?: string[]; selectedTestClasses?: string[]; testTargetIdentifier?: string; testTargetName?: string; + schemeName?: string; + deviceType?: string; + osVersion?: string; } ): Promise { + if ((!options || Object.keys(options).length === 0) && this.pendingTestOptions) { + Logger.debug(`Using pending test options fallback: ${JSON.stringify(this.pendingTestOptions)}`); + options = this.pendingTestOptions; + this.pendingTestOptions = null; + } else { + this.pendingTestOptions = null; + } + const validationError = PathValidator.validateProjectPath(projectPath); if (validationError) return validationError; - await openProject(projectPath); + const requestedScheme = options?.schemeName; + if (!requestedScheme || requestedScheme.trim().length === 0) { + return { + content: [{ + type: 'text', + text: `Error: scheme parameter is required when running tests with xcodebuild.\n\n💡 Pass --scheme or set XCODE_MCP_PREFERRED_SCHEME.` + }] + }; + } - // Set the destination for testing - { - // Normalize the destination name for better matching - const normalizedDestination = ParameterNormalizer.normalizeDestinationName(destination); - - const setDestinationScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - const destinations = workspace.runDestinations(); - const destinationNames = destinations.map(dest => dest.name()); - - // Try exact match first - let targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(normalizedDestination)}); - - // If not found, try original name - if (!targetDestination) { - targetDestination = destinations.find(dest => dest.name() === ${JSON.stringify(destination)}); - } - - if (!targetDestination) { - throw new Error('Destination not found. Available: ' + JSON.stringify(destinationNames)); + const requestedDeviceType = options?.deviceType ? options.deviceType.trim() : ''; + const requestedOsVersion = options?.osVersion ? options.osVersion.trim() : ''; + + let destinationArgs: string[] | null = null; + let destinationLabel = destination ?? ''; + + if (requestedDeviceType) { + const deviceSelection = await this._buildDestinationArgsForDevice(requestedDeviceType, requestedOsVersion || null); + if (!deviceSelection) { + return { + content: [{ + type: 'text', + text: `Error: Unable to find a simulator for device '${requestedDeviceType}'${requestedOsVersion ? ` with OS ${requestedOsVersion}` : ''}.\n\n💡 Open Simulator.app to download the desired runtime, or supply an explicit destination string.` + }] + }; + } + destinationArgs = deviceSelection.args; + destinationLabel = deviceSelection.label; + } else if (destination && destination.trim().length > 0) { + destinationArgs = await this._buildDestinationArgs(destination); + if (!destinationArgs) { + return { + content: [{ + type: 'text', + text: `Error: Could not determine destination from '${destination}'.\n\n💡 Provide a full xcodebuild destination string (e.g., platform=iOS Simulator,name iPhone 16) or a recognizable simulator name.` + }] + }; + } + if (destinationArgs.length > 1) { + destinationLabel = destinationArgs[1] ?? destinationArgs[0] ?? (destination ?? 'unspecified destination'); + } else { + destinationLabel = destination ?? (destinationArgs[0] ?? 'unspecified destination'); + } + } else { + return { + content: [{ + type: 'text', + text: 'Error: destination or device_type is required.\n\nSupply an explicit destination string or provide device_type (iphone, ipad, mac, etc.) and os_version.' + }] + }; + } + + if (!destinationArgs) { + return { + content: [{ type: 'text', text: 'Error: Unable to compute a destination for the requested device.' }], + }; + } + + const finalDestinationArgs = destinationArgs; + + let buildContainerPath = projectPath; + let projectFlag: '-workspace' | '-project' | null = null; + + if (projectPath.endsWith('.xcworkspace')) { + projectFlag = '-workspace'; + } else if (projectPath.endsWith('.xcodeproj')) { + const workspaceCandidate = join(dirname(projectPath), `${basename(projectPath, '.xcodeproj')}.xcworkspace`); + if (await this._pathExists(workspaceCandidate)) { + projectFlag = '-workspace'; + buildContainerPath = workspaceCandidate; + Logger.info(`Detected workspace at ${workspaceCandidate} – using it for xcodebuild to ensure shared schemes load correctly`); + } else { + projectFlag = '-project'; + } + } + + if (!projectFlag) { + return { + content: [{ + type: 'text', + text: `Error: Unsupported project type. Expected .xcodeproj or .xcworkspace, received: ${projectPath}` + }] + }; + } + + const schemeResolution = await this._resolveSchemeName(projectFlag, buildContainerPath, requestedScheme); + if (!schemeResolution.ok) { + return schemeResolution.result; + } + + const schemeName = schemeResolution.schemeName; + if (options) { + options.schemeName = schemeName; + } + + if (options?.testPlanPath) { + Logger.info(`Ignoring test plan path '${options.testPlanPath}' when invoking xcodebuild. Using -only-testing to target specific tests.`); + } + + const testStartTime = Date.now(); + + const sanitizedArgs: string[] = []; + const onlyTestingIdentifiers = new Set(); + + if (commandLineArguments && commandLineArguments.length > 0) { + for (let i = 0; i < commandLineArguments.length; i += 1) { + const rawArg = commandLineArguments[i]; + if (!rawArg || typeof rawArg !== 'string') { + continue; + } + + const arg = rawArg.trim(); + if (arg.length === 0) { + continue; + } + + if (arg === '-only-testing') { + const next = commandLineArguments[i + 1]; + if (typeof next === 'string' && next.trim().length > 0) { + onlyTestingIdentifiers.add(next.trim()); + i += 1; } - - workspace.activeRunDestination = targetDestination; - return 'Destination set to ' + targetDestination.name(); - })() - `; - - try { - await JXAExecutor.execute(setDestinationScript); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('Destination not found')) { - // Extract available destinations from error message - try { - const availableMatch = errorMessage.match(/Available: (\[.*\])/); - if (availableMatch) { - const availableDestinations = JSON.parse(availableMatch[1]!); - const bestMatch = ParameterNormalizer.findBestMatch(destination, availableDestinations); - - let message = `❌ Destination '${destination}' not found\n\nAvailable destinations:\n`; - availableDestinations.forEach((dest: string) => { - if (dest === bestMatch) { - message += ` • ${dest} ← Did you mean this?\n`; - } else { - message += ` • ${dest}\n`; - } - }); - - return { content: [{ type: 'text', text: message }] }; - } - } catch { - // Fall through to generic error + continue; + } + + if (arg.startsWith('-only-testing:')) { + const identifier = arg.slice('-only-testing:'.length).trim(); + if (identifier.length > 0) { + onlyTestingIdentifiers.add(identifier); } + continue; } - - return { content: [{ type: 'text', text: `Failed to set destination '${destination}': ${errorMessage}` }] }; + + sanitizedArgs.push(arg); } } - // Handle test plan modification if selective tests are requested - let originalTestPlan: string | null = null; - let shouldRestoreTestPlan = false; - - if (options?.testPlanPath && (options?.selectedTests?.length || options?.selectedTestClasses?.length)) { - if (!options.testTargetIdentifier && !options.testTargetName) { - return { - content: [{ - type: 'text', - text: 'Error: either test_target_identifier or test_target_name is required when using test filtering' - }] - }; + if (options?.selectedTests?.length) { + for (const testIdentifier of options.selectedTests) { + if (typeof testIdentifier === 'string' && testIdentifier.trim().length > 0) { + onlyTestingIdentifiers.add(testIdentifier.trim()); + } } + } - // If target name is provided but no identifier, look up the identifier - let targetIdentifier = options.testTargetIdentifier; - let targetName = options.testTargetName; - - if (options.testTargetName && !options.testTargetIdentifier) { - try { - const { ProjectTools } = await import('./ProjectTools.js'); - const targetInfo = await ProjectTools.getTestTargets(projectPath); - - // Parse the target info to find the identifier - const targetText = targetInfo.content?.[0]?.type === 'text' ? targetInfo.content[0].text : ''; - const namePattern = new RegExp(`\\*\\*${options.testTargetName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\*\\*\\s*\\n\\s*•\\s*Identifier:\\s*([A-F0-9]{24})`, 'i'); - const match = targetText.match(namePattern); - - if (match && match[1]) { - targetIdentifier = match[1]; - targetName = options.testTargetName; - } else { - return { - content: [{ - type: 'text', - text: `Error: Test target '${options.testTargetName}' not found. Use 'xcode_get_test_targets' to see available targets.` - }] - }; + if (options?.selectedTestClasses?.length) { + let targetPrefix: string | null = options.testTargetName ?? null; + + if (!targetPrefix && options.testTargetIdentifier) { + targetPrefix = options.testTargetIdentifier; + } + + if (!targetPrefix && options.selectedTests?.length) { + for (const identifier of options.selectedTests) { + if (typeof identifier === 'string' && identifier.includes('/')) { + targetPrefix = identifier.split('/')[0] ?? null; + if (targetPrefix) { + break; + } } - } catch (lookupError) { - return { - content: [{ - type: 'text', - text: `Error: Failed to lookup test target '${options.testTargetName}': ${lookupError instanceof Error ? lookupError.message : String(lookupError)}` - }] - }; } } - try { - // Import filesystem operations - const { promises: fs } = await import('fs'); - - // Backup original test plan - originalTestPlan = await fs.readFile(options.testPlanPath, 'utf8'); - shouldRestoreTestPlan = true; - - // Build selected tests array - let selectedTests: string[] = []; - - // Add individual tests - if (options.selectedTests?.length) { - selectedTests.push(...options.selectedTests); + for (const className of options.selectedTestClasses) { + if (typeof className !== 'string' || className.trim().length === 0) { + continue; } - - // Add all tests from selected test classes - if (options.selectedTestClasses?.length) { - // For now, add the class names - we'd need to scan for specific test methods later - selectedTests.push(...options.selectedTestClasses); + + const trimmed = className.trim(); + const identifier = trimmed.includes('/') + ? trimmed + : targetPrefix + ? `${targetPrefix}/${trimmed}` + : trimmed; + + if (identifier.length > 0) { + onlyTestingIdentifiers.add(identifier); } - - // Get project name from path for container reference - const { basename } = await import('path'); - const projectName = basename(projectPath, '.xcodeproj'); - - // Create test target configuration - const testTargets = [{ - target: { - containerPath: `container:${projectName}.xcodeproj`, - identifier: targetIdentifier!, - name: targetName || targetIdentifier! - }, - selectedTests: selectedTests - }]; - - // Update test plan temporarily - const { TestPlanTools } = await import('./TestPlanTools.js'); - await TestPlanTools.updateTestPlanAndReload( - options.testPlanPath, - projectPath, - testTargets - ); - - Logger.info(`Temporarily modified test plan to run ${selectedTests.length} selected tests`); - - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - return { - content: [{ - type: 'text', - text: `Failed to modify test plan: ${errorMsg}` - }] - }; } } - // Get initial xcresult files to detect new ones - const initialXCResults = await this._findXCResultFiles(projectPath); - const testStartTime = Date.now(); - Logger.info(`Test start time: ${new Date(testStartTime).toISOString()}, found ${initialXCResults.length} initial XCResult files`); + if (onlyTestingIdentifiers.size > 0) { + Logger.info(`Applying -only-testing filter for ${onlyTestingIdentifiers.size} test identifier(s).`); + } - // Start the test action + const spawnEnv = { + ...process.env, + NSUnbufferedIO: 'YES' + } as NodeJS.ProcessEnv; - const hasArgs = commandLineArguments && commandLineArguments.length > 0; - const script = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - - ${hasArgs - ? `const actionResult = workspace.test({withCommandLineArguments: ${JSON.stringify(commandLineArguments)}});` - : `const actionResult = workspace.test();` - } - - // Return immediately - we'll monitor the build separately - return JSON.stringify({ - actionId: actionResult.id(), - message: 'Test started' - }); - })() - `; - - try { - const startResult = await JXAExecutor.execute(script); - const { actionId, message } = JSON.parse(startResult); - - Logger.info(`${message} with action ID: ${actionId}`); - - // Check for and handle "replace existing build" alert - await this._handleReplaceExistingBuildAlert(); - - // Check for build errors with polling approach - Logger.info('Monitoring for build logs...'); - - // Poll for build logs for up to 30 seconds - let foundLogs = false; - for (let i = 0; i < 6; i++) { - await new Promise(resolve => setTimeout(resolve, 5000)); - const logs = await BuildLogParser.getRecentBuildLogs(projectPath, testStartTime); - if (logs.length > 0) { - Logger.info(`Found ${logs.length} build logs after ${(i + 1) * 5} seconds`); - foundLogs = true; - break; - } - Logger.info(`No logs found after ${(i + 1) * 5} seconds, continuing to wait...`); + if (!('SIMCTL_CHILD_wait_for_debugger' in spawnEnv) || !spawnEnv.SIMCTL_CHILD_wait_for_debugger) { + spawnEnv.SIMCTL_CHILD_wait_for_debugger = '0'; + } + if (!('SIMCTL_CHILD_WAIT_FOR_DEBUGGER' in spawnEnv) || !spawnEnv.SIMCTL_CHILD_WAIT_FOR_DEBUGGER) { + spawnEnv.SIMCTL_CHILD_WAIT_FOR_DEBUGGER = '0'; + } + + const buildWorkingDirectory = dirname(buildContainerPath); + const fallbackNotices: string[] = []; + let finalAttempt: { + exitCode: number; + stdoutBuffer: string; + stderrBuffer: string; + resultBundlePath: string; + } | null = null; + let disableParallel = false; + let attempt = 0; + + while (attempt < 2) { + attempt += 1; + const resultBundlePath = await this._createTemporaryResultBundlePath( + `test-${disableParallel ? 'serial' : 'parallel'}`, + ); + + const xcodebuildArgs: string[] = [ + 'test', + projectFlag, + buildContainerPath, + '-scheme', + schemeName, + ...finalDestinationArgs, + '-resultBundlePath', + resultBundlePath + ]; + + if (sanitizedArgs.length > 0) { + xcodebuildArgs.push(...sanitizedArgs); } - - if (!foundLogs) { - Logger.info('No build logs found after 30 seconds - build may not have started yet'); + + if (onlyTestingIdentifiers.size > 0) { + for (const identifier of onlyTestingIdentifiers) { + xcodebuildArgs.push(`-only-testing:${identifier}`); + } } - - Logger.info('Build monitoring complete, proceeding to analysis...'); - - // Get ALL recent build logs for analysis (test might create multiple logs) - Logger.info(`DEBUG: testStartTime = ${testStartTime} (${new Date(testStartTime)})`); - Logger.info(`DEBUG: projectPath = ${projectPath}`); - - // First check if we can find DerivedData - const derivedData = await BuildLogParser.findProjectDerivedData(projectPath); - Logger.info(`DEBUG: derivedData = ${derivedData}`); - - const recentLogs = await BuildLogParser.getRecentBuildLogs(projectPath, testStartTime); - Logger.info(`DEBUG: recentLogs.length = ${recentLogs.length}`); - if (recentLogs.length > 0) { - Logger.info(`Analyzing ${recentLogs.length} recent build logs created during test...`); - - let totalErrors: string[] = []; - let totalWarnings: string[] = []; - let hasStoppedBuild = false; - - // Analyze each recent log to catch build errors in any of them - for (const log of recentLogs) { - try { - Logger.info(`Analyzing build log: ${log.path}`); - const results = await BuildLogParser.parseBuildLog(log.path); - Logger.info(`Log analysis: ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); - - // Check for stopped builds - if (results.buildStatus === 'stopped') { - hasStoppedBuild = true; - } - - // Accumulate errors and warnings from all logs - totalErrors.push(...results.errors); - totalWarnings.push(...results.warnings); - - } catch (error) { - Logger.warn(`Failed to parse build log ${log.path}: ${error instanceof Error ? error.message : error}`); - } + + if (disableParallel) { + if (!this._hasArgument(xcodebuildArgs, '-parallel-testing-enabled')) { + xcodebuildArgs.push('-parallel-testing-enabled', 'NO'); } - - Logger.info(`Total build analysis: ${totalErrors.length} errors, ${totalWarnings.length} warnings, stopped builds: ${hasStoppedBuild}`); - - Logger.info(`DEBUG: totalErrors = ${JSON.stringify(totalErrors)}`); - Logger.info(`DEBUG: totalErrors.length = ${totalErrors.length}`); - Logger.info(`DEBUG: totalErrors.length > 0 = ${totalErrors.length > 0}`); - Logger.info(`DEBUG: hasStoppedBuild = ${hasStoppedBuild}`); - - // Handle stopped builds first - if (hasStoppedBuild && totalErrors.length === 0) { - let message = `⏹️ TEST BUILD INTERRUPTED${hasArgs ? ` (test with arguments ${JSON.stringify(commandLineArguments)})` : ''}\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the test again to complete it.`; - return { content: [{ type: 'text', text: message }] }; + if (!this._hasArgument(xcodebuildArgs, '-maximum-concurrent-test-simulator-destinations')) { + xcodebuildArgs.push('-maximum-concurrent-test-simulator-destinations', '1'); } - - if (totalErrors.length > 0) { - let message = `❌ TEST BUILD FAILED (${totalErrors.length} errors)\n\nERRORS:\n`; - totalErrors.forEach(error => { - message += ` • ${error}\n`; - Logger.error('Test build error:', error); - }); - - if (totalWarnings.length > 0) { - message += `\n⚠️ WARNINGS (${totalWarnings.length}):\n`; - totalWarnings.slice(0, 10).forEach(warning => { - message += ` • ${warning}\n`; - Logger.warn('Test build warning:', warning); - }); - if (totalWarnings.length > 10) { - message += ` ... and ${totalWarnings.length - 10} more warnings\n`; - } - } - - Logger.error('ABOUT TO THROW McpError for test build failure'); - throw new McpError(ErrorCode.InternalError, message); - } else if (totalWarnings.length > 0) { - Logger.warn(`Test build completed with ${totalWarnings.length} warnings`); - totalWarnings.slice(0, 10).forEach(warning => { - Logger.warn('Test build warning:', warning); - }); - if (totalWarnings.length > 10) { - Logger.warn(`... and ${totalWarnings.length - 10} more warnings`); - } + if (!this._hasArgument(xcodebuildArgs, '-disable-concurrent-testing')) { + xcodebuildArgs.push('-disable-concurrent-testing'); } - } else { - Logger.info(`DEBUG: No recent build logs found since ${new Date(testStartTime)}`); } - // Since build passed, now wait for test execution to complete - Logger.info('Build succeeded, waiting for test execution to complete...'); - - // Monitor test completion with proper AppleScript checking and 6-hour safety timeout - const maxTestTime = 21600000; // 6 hours safety timeout - let testCompleted = false; - let monitoringSeconds = 0; - - Logger.info('Monitoring test completion with 6-hour safety timeout...'); - - while (!testCompleted && (Date.now() - testStartTime) < maxTestTime) { - try { - // Check test completion via AppleScript every 30 seconds - const checkScript = ` - (function() { - ${getWorkspaceByPathScript(projectPath)} - if (!workspace) return 'No workspace'; - - const actions = workspace.schemeActionResults(); - for (let i = 0; i < actions.length; i++) { - const action = actions[i]; - if (action.id() === "${actionId}") { - const status = action.status(); - const completed = action.completed(); - return status + ':' + completed; - } - } - return 'Action not found'; - })() - `; - - const result = await JXAExecutor.execute(checkScript, 15000); - const [status, completed] = result.split(':'); - - // Log progress every 2 minutes - if (monitoringSeconds % 120 === 0) { - Logger.info(`Test monitoring: ${Math.floor(monitoringSeconds/60)}min - Action ${actionId}: status=${status}, completed=${completed}`); - } - - // Check if test is complete - if (completed === 'true' && (status === 'succeeded' || status === 'failed' || status === 'cancelled' || status === 'error occurred')) { - testCompleted = true; - Logger.info(`Test completed after ${Math.floor(monitoringSeconds/60)} minutes: status=${status}`); - break; - } - - } catch (error) { - Logger.warn(`Test monitoring error at ${Math.floor(monitoringSeconds/60)}min: ${error instanceof Error ? error.message : error}`); + Logger.info(`Starting xcodebuild test attempt #${attempt} for scheme '${schemeName}' with destination '${destinationLabel}'${disableParallel ? ' (parallel testing disabled)' : ''}`); + + let stdoutBuffer = ''; + let stderrBuffer = ''; + + const child = spawn('xcodebuild', xcodebuildArgs, { + cwd: buildWorkingDirectory, + env: spawnEnv + }); + + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + + child.stdout.on('data', data => { + stdoutBuffer += data; + data.split(/\r?\n/).filter(Boolean).forEach((line: string) => Logger.info(`[xcodebuild] ${line}`)); + }); + + child.stderr.on('data', data => { + stderrBuffer += data; + data.split(/\r?\n/).filter(Boolean).forEach((line: string) => Logger.warn(`[xcodebuild] ${line}`)); + }); + + const exitCode: number = await new Promise(resolve => { + child.on('close', code => resolve(code ?? 0)); + child.on('error', err => { + Logger.error(`xcodebuild failed to start: ${err instanceof Error ? err.message : String(err)}`); + resolve(1); + }); + }); + + Logger.info(`xcodebuild attempt #${attempt} completed with exit code ${exitCode}`); + + if (!disableParallel) { + const cloneFailure = this._detectSimulatorCloneFailure(`${stdoutBuffer}\n${stderrBuffer}`); + if (cloneFailure.matched) { + const notice = `ℹ️ Detected simulator clone failure for ${cloneFailure.deviceName ?? 'the requested simulator'}; retrying with parallel testing disabled.`; + fallbackNotices.push(notice); + Logger.warn(`Simulator clone failure detected for ${cloneFailure.deviceName ?? 'unknown simulator'} – retrying with parallel testing disabled.`); + disableParallel = true; + continue; } - - // Wait 30 seconds before next check - await new Promise(resolve => setTimeout(resolve, 30000)); - monitoringSeconds += 30; - } - - if (!testCompleted) { - Logger.warn('Test monitoring reached 6-hour timeout - proceeding anyway'); } - - Logger.info('Test monitoring result: Test completion detected or timeout reached'); - - // Only AFTER test completion is confirmed, look for the xcresult file - Logger.info('Test execution completed, now looking for XCResult file...'); - let newXCResult = await this._findNewXCResultFile(projectPath, initialXCResults, testStartTime); - - // If no xcresult found yet, wait for it to appear (should be quick now that tests are done) - if (!newXCResult) { - Logger.info('No xcresult file found yet, waiting for it to appear...'); - let attempts = 0; - const maxWaitAttempts = 15; // 15 seconds to find the file after test completion - - while (attempts < maxWaitAttempts && !newXCResult) { - await new Promise(resolve => setTimeout(resolve, 1000)); - newXCResult = await this._findNewXCResultFile(projectPath, initialXCResults, testStartTime); - attempts++; + + finalAttempt = { + exitCode, + stdoutBuffer, + stderrBuffer, + resultBundlePath + }; + break; + } + + if (!finalAttempt) { + throw new McpError( + ErrorCode.InternalError, + 'xcodebuild did not complete successfully after retrying with parallel testing disabled.' + ); + } + + const { exitCode, stdoutBuffer, stderrBuffer, resultBundlePath } = finalAttempt; + + const testDurationMs = Date.now() - testStartTime; + const xcresultExists = await this._pathExists(resultBundlePath); + const derivedDataPath = + (await BuildLogParser.findProjectDerivedData(buildContainerPath)) ?? + join(tmpdir(), 'xcodemcp-derived-data', 'unknown'); + + try { + if (!xcresultExists) { + const header = exitCode === 0 + ? '✅ TESTS COMPLETED' + : exitCode === 65 + ? '❌ TESTS FAILED' + : `❌ xcodebuild exited with code ${exitCode}`; + let message = `${header}\n\n`; + message += `xcodebuild did not produce a result bundle at:\n${resultBundlePath}\n\n`; + if (stdoutBuffer.trim().length > 0) { + message += `xcodebuild output:\n${stdoutBuffer.trim()}\n\n`; } - - // If still no XCResult found, the test likely didn't run at all - if (!newXCResult) { - Logger.warn('No XCResult file found - test may not have run or current scheme has no tests'); - return { - content: [{ - type: 'text', - text: `⚠️ TEST EXECUTION UNCLEAR\n\nNo XCResult file was created, which suggests:\n• The current scheme may not have test targets configured\n• Tests may have been skipped\n• There may be configuration issues\n\n💡 Try:\n• Use a scheme with test targets (look for schemes ending in '-Tests')\n• Check that the project has test targets configured\n• Run tests manually in Xcode first to verify setup\n\nAvailable schemes: Use 'xcode_get_schemes' to see all schemes` - }] - }; + if (stderrBuffer.trim().length > 0) { + message += `stderr:\n${stderrBuffer.trim()}\n`; } - } - - let testResult: { status: string, error: string | undefined } = { status: 'completed', error: undefined }; - - if (newXCResult) { - Logger.info(`Found xcresult file: ${newXCResult}, waiting for it to be fully written...`); - - // Calculate how long the test took - const testEndTime = Date.now(); - const testDurationMs = testEndTime - testStartTime; - const testDurationMinutes = Math.round(testDurationMs / 60000); - - // Wait 8% of test duration before even attempting to read XCResult - // This gives Xcode plenty of time to finish writing everything - const proportionalWaitMs = Math.round(testDurationMs * 0.08); - const proportionalWaitSeconds = Math.round(proportionalWaitMs / 1000); - - Logger.info(`Test ran for ${testDurationMinutes} minutes`); - Logger.info(`Applying 8% wait time: ${proportionalWaitSeconds} seconds before checking XCResult`); - Logger.info(`This prevents premature reads that could contribute to file corruption`); - - await new Promise(resolve => setTimeout(resolve, proportionalWaitMs)); - - // Now use the robust waiting method with the test duration for context - const isReady = await XCResultParser.waitForXCResultReadiness(newXCResult, testDurationMs); // Pass test duration for proportional timeouts - - if (isReady) { - // File is ready, verify analysis works - try { - Logger.info('XCResult file is ready, performing final verification...'); - const parser = new XCResultParser(newXCResult); - const analysis = await parser.analyzeXCResult(); - - if (analysis && analysis.totalTests >= 0) { - Logger.info(`XCResult parsing successful! Found ${analysis.totalTests} tests`); - testResult = { status: 'completed', error: undefined }; - } else { - Logger.error('XCResult parsed but incomplete test data found'); - testResult = { - status: 'failed', - error: `XCResult file exists but contains incomplete test data. This may indicate an Xcode bug.` - }; - } - } catch (parseError) { - Logger.error(`XCResult file appears to be corrupt: ${parseError instanceof Error ? parseError.message : parseError}`); - testResult = { - status: 'failed', - error: `XCResult file is corrupt or unreadable. This is likely an Xcode bug. Parse error: ${parseError instanceof Error ? parseError.message : parseError}` - }; - } - } else { - Logger.error('XCResult file failed to become ready within 3 minutes'); - testResult = { - status: 'failed', - error: `XCResult file failed to become readable within 3 minutes despite multiple verification attempts. This indicates an Xcode bug where the file remains corrupt or incomplete.` - }; + if (fallbackNotices.length > 0) { + message += `\n${fallbackNotices.join('\n')}`; } - } else { - Logger.warn('No xcresult file found after test completion'); - testResult = { status: 'completed', error: 'No XCResult file found' }; + return { content: [{ type: 'text', text: message }] }; } - - if (newXCResult) { - Logger.info(`Found xcresult: ${newXCResult}`); - - // Check if the xcresult file is corrupt - if (testResult.status === 'failed' && testResult.error) { - // XCResult file is corrupt - let message = `❌ XCODE BUG DETECTED${hasArgs ? ` (test with arguments ${JSON.stringify(commandLineArguments)})` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n\n`; - message += `⚠️ ${testResult.error}\n\n`; - message += `This is a known Xcode issue where the .xcresult file becomes corrupt even though Xcode reports test completion.\n\n`; - message += `💡 Troubleshooting steps:\n`; - message += ` 1. Restart Xcode and retry\n`; - message += ` 2. Delete DerivedData and retry\n\n`; - message += `The corrupt XCResult file is at:\n${newXCResult}`; - - return { content: [{ type: 'text', text: message }] }; + + const ready = await XCResultParser.waitForXCResultReadiness(resultBundlePath, testDurationMs); + if (!ready) { + let message = `❌ XCODE BUG DETECTED\n\n`; + message += `XCResult Path: ${resultBundlePath}\n\n`; + message += `The result bundle was created but never became readable.\n`; + message += `Try deleting DerivedData (${derivedDataPath}) and re-running the tests.\n`; + if (fallbackNotices.length > 0) { + message += `\n${fallbackNotices.join('\n')}`; } - - // We already confirmed the xcresult is readable in our completion detection loop - // No need to wait again - proceed directly to analysis - if (testResult.status === 'completed') { - try { - // Use shared utility to format test results with individual test details - const parser = new XCResultParser(newXCResult); - const testSummary = await parser.formatTestResultsSummary(true, 5); - - let message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += testSummary + `\n\n`; - - const analysis = await parser.analyzeXCResult(); - if (analysis.failedTests > 0) { - message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult-browse --xcresult-path \n`; - message += ` • Get console output: xcresult-browser-get-console --xcresult-path --test-id \n`; - message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path --test-id --timestamp \n`; - message += ` • Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path --test-id --timestamp \n`; - message += ` • Get element details: xcresult-get-ui-element --hierarchy-json --index \n`; - message += ` • List attachments: xcresult-list-attachments --xcresult-path --test-id \n`; - message += ` • Export attachments: xcresult-export-attachment --xcresult-path --test-id --index \n`; - message += ` • Quick summary: xcresult-summary --xcresult-path \n`; - message += `\n💡 Tip: Use console output to find failure timestamps for screenshots and UI hierarchies`; - } else { - message += `✅ All tests passed!\n\n`; - message += `💡 Explore test results:\n`; - message += ` • Browse results: xcresult-browse --xcresult-path \n`; - message += ` • Get console output: xcresult-browser-get-console --xcresult-path --test-id \n`; - message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path --test-id --timestamp \n`; - message += ` • Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path --test-id --timestamp \n`; - message += ` • Get element details: xcresult-get-ui-element --hierarchy-json --index \n`; - message += ` • List attachments: xcresult-list-attachments --xcresult-path --test-id \n`; - message += ` • Export attachments: xcresult-export-attachment --xcresult-path --test-id --index \n`; - message += ` • Quick summary: xcresult-summary --xcresult-path `; - } - - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } catch (parseError) { - Logger.warn(`Failed to parse xcresult: ${parseError}`); - // Fall back to basic result - let message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += `Status: ${testResult.status}\n\n`; - message += `Note: XCResult parsing failed, but test file is available for manual inspection.\n\n`; - message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult_browse \n`; - message += ` • Get console output: xcresult_browser_get_console \n`; - message += ` • Get screenshots: xcresult_get_screenshot \n`; - message += ` • Get UI hierarchy: xcresult_get_ui_hierarchy \n`; - message += ` • Get element details: xcresult_get_ui_element \n`; - message += ` • List attachments: xcresult_list_attachments \n`; - message += ` • Export attachments: xcresult_export_attachment \n`; - message += ` • Quick summary: xcresult_summary `; - - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - } else { - // Test completion detection timed out - let message = `🧪 TESTS ${testResult.status.toUpperCase()}${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\n`; - message += `XCResult Path: ${newXCResult}\n`; - message += `Status: ${testResult.status}\n\n`; - message += `⚠️ Test completion detection timed out, but XCResult file is available.\n\n`; + return { content: [{ type: 'text', text: message }] }; + } + + try { + const parser = new XCResultParser(resultBundlePath); + const testSummary = await parser.formatTestResultsSummary(true, 5); + const analysis = await parser.analyzeXCResult(); + + const header = analysis.failedTests > 0 + ? `❌ TESTS FAILED (${analysis.failedTests} test${analysis.failedTests === 1 ? '' : 's'} failed)` + : '✅ All tests passed'; + + let message = `🧪 TESTS COMPLETED (xcodebuild exit code ${exitCode})\n\n`; + message += `${header}\n`; + message += `XCResult Path: ${resultBundlePath}\n\n`; + message += `${testSummary}\n\n`; + + if (analysis.failedTests > 0) { message += `💡 Inspect test results:\n`; - message += ` • Browse results: xcresult_browse \n`; - message += ` • Get console output: xcresult_browser_get_console \n`; - message += ` • Get screenshots: xcresult_get_screenshot \n`; - message += ` • Get UI hierarchy: xcresult_get_ui_hierarchy \n`; - message += ` • Get element details: xcresult_get_ui_element \n`; - message += ` • List attachments: xcresult_list_attachments \n`; - message += ` • Export attachments: xcresult_export_attachment \n`; - message += ` • Quick summary: xcresult_summary `; - - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); + message += ` • Browse results: xcresult-browse --xcresult-path "${resultBundlePath}"\n`; + message += ` • Get console output: xcresult-browser-get-console --xcresult-path "${resultBundlePath}" --test-id \n`; + message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path "${resultBundlePath}" --test-id --timestamp \n`; + message += ` • Get UI hierarchy: xcresult-get-ui-hierarchy --xcresult-path "${resultBundlePath}" --test-id \n`; + message += ` • Export attachments: xcresult-export-attachment --xcresult-path "${resultBundlePath}" --test-id --index \n`; + message += ` • Quick summary: xcresult-summary --xcresult-path "${resultBundlePath}"\n`; + } else { + message += `💡 Explore test results:\n`; + message += ` • Browse results: xcresult-browse --xcresult-path "${resultBundlePath}"\n`; + message += ` • Get console output: xcresult-browser-get-console --xcresult-path "${resultBundlePath}" --test-id \n`; + message += ` • Get screenshots: xcresult-get-screenshot --xcresult-path "${resultBundlePath}" --test-id --timestamp \n`; + message += ` • Quick summary: xcresult-summary --xcresult-path "${resultBundlePath}"\n`; } - } else { - // No xcresult found - fall back to basic result - if (testResult.status === 'failed') { - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: `❌ TEST FAILED\n\n${testResult.error || 'Test execution failed'}\n\nNote: No XCResult file found for detailed analysis.` }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); + + if (analysis.failedTests === 0 && exitCode !== 0 && stdoutBuffer.trim().length > 0) { + message += `\n⚠️ xcodebuild exit code ${exitCode} despite passing tests.\n`; + message += `xcodebuild output:\n${stdoutBuffer.trim()}\n`; } - - const message = `🧪 TESTS COMPLETED${hasArgs ? ` with arguments ${JSON.stringify(commandLineArguments)}` : ''}\n\nStatus: ${testResult.status}\n\nNote: No XCResult file found for detailed analysis.`; - return await this._restoreTestPlanAndReturn({ content: [{ type: 'text', text: message }] }, shouldRestoreTestPlan, originalTestPlan, options?.testPlanPath); - } - } catch (error) { - // Restore test plan even on error - if (shouldRestoreTestPlan && originalTestPlan && options?.testPlanPath) { - try { - const { promises: fs } = await import('fs'); - await fs.writeFile(options.testPlanPath, originalTestPlan, 'utf8'); - Logger.info('Restored original test plan after error'); - } catch (restoreError) { - Logger.error(`Failed to restore test plan: ${restoreError}`); + + if (fallbackNotices.length > 0) { + message += `\n${fallbackNotices.join('\n')}`; } - } - - // Re-throw McpErrors to properly signal build failures - if (error instanceof McpError) { - throw error; - } - - const enhancedError = ErrorHelper.parseCommonErrors(error as Error); - if (enhancedError) { - return { content: [{ type: 'text', text: enhancedError }] }; - } - const errorMessage = error instanceof Error ? error.message : String(error); - return { content: [{ type: 'text', text: `Failed to run tests: ${errorMessage}` }] }; - } - } - /** - * Helper method to restore test plan and return result - */ - private static async _restoreTestPlanAndReturn( - result: McpResult, - shouldRestoreTestPlan: boolean, - originalTestPlan: string | null, - testPlanPath?: string - ): Promise { - if (shouldRestoreTestPlan && originalTestPlan && testPlanPath) { - try { - const { promises: fs } = await import('fs'); - await fs.writeFile(testPlanPath, originalTestPlan, 'utf8'); - Logger.info('Restored original test plan'); - - // Trigger reload after restoration - const { TestPlanTools } = await import('./TestPlanTools.js'); - await TestPlanTools.triggerTestPlanReload(testPlanPath, testPlanPath); - } catch (restoreError) { - Logger.error(`Failed to restore test plan: ${restoreError}`); - // Append restoration error to result - if (result.content?.[0]?.type === 'text') { - result.content[0].text += `\n\n⚠️ Warning: Failed to restore original test plan: ${restoreError}`; + return { content: [{ type: 'text', text: message }] }; + } catch (error) { + Logger.warn(`Failed to parse xcresult: ${error instanceof Error ? error.message : String(error)}`); + let message = `🧪 TESTS COMPLETED (xcodebuild exit code ${exitCode})\n\n`; + message += `XCResult Path: ${resultBundlePath}\n\n`; + message += `Result bundle is available but could not be parsed automatically.`; + if (stdoutBuffer.trim().length > 0) { + message += `\n\nxcodebuild output:\n${stdoutBuffer.trim()}`; + } + if (fallbackNotices.length > 0) { + message += `\n\n${fallbackNotices.join('\n')}`; } + return { content: [{ type: 'text', text: message }] }; } + } finally { + // keep result bundles available for inspection; no cleanup required } - return result; } public static async run( - projectPath: string, + projectPath: string, schemeName: string, - commandLineArguments: string[] = [], + reason: string, + commandLineArguments: string[] = [], openProject: OpenProjectCallback ): Promise { const validationError = PathValidator.validateProjectPath(projectPath); @@ -971,6 +930,54 @@ export class BuildTools { await openProject(projectPath); + let logRecord: BuildLogRecord | null = null; + let runLogRecord: BuildLogRecord | null = null; + let lockFooterText: string | null = null; + let autoReleaseNote: string | null = null; + const applyLockFooter = (message: string): string => { + if (autoReleaseNote) { + const note = `🔓 Lock automatically released: ${autoReleaseNote}\nNo manual release command is required for this attempt.`; + return `${message}\n\n${note}`; + } + return LockManager.appendFooter(message, lockFooterText); + }; + const attachLogHint = (message: string): string => { + if (!logRecord) return applyLockFooter(message); + const hintLines = [ + '🪵 Build Log Metadata', + ` • Log ID: ${logRecord.id}`, + ` • Path: ${logRecord.logPath}`, + ]; + if (logRecord.schemeName) { + hintLines.splice(1, 0, ` • Scheme: ${logRecord.schemeName}`); + } + hintLines.push(` • View: xcodecontrol view-build-log --log-id ${logRecord.id}`); + return applyLockFooter(`${message}\n\n${hintLines.join('\n')}`); + }; + const attachRunLogHint = (message: string): string => { + if (!runLogRecord) return applyLockFooter(message); + const hintLines = [ + '🪵 Run Log Metadata', + ` • Log ID: ${runLogRecord.id}`, + ` • Path: ${runLogRecord.logPath}`, + ` • View: xcodecontrol view-run-log --log-id ${runLogRecord.id}`, + ]; + if (runLogRecord.schemeName) { + hintLines.splice(1, 0, ` • Scheme: ${runLogRecord.schemeName}`); + } + return applyLockFooter(`${message}\n\n${hintLines.join('\n')}`); + }; + const attachAllHints = (message: string): string => attachRunLogHint(attachLogHint(message)); + const finalizeLogStatus = (status: 'active' | 'completed' | 'failed', buildStatus?: string | null) => { + const extras = + buildStatus === undefined + ? undefined + : { + buildStatus, + }; + BuildLogStore.updateStatus(logRecord?.id, status, extras); + }; + // Set the scheme const normalizedSchemeName = ParameterNormalizer.normalizeSchemeName(schemeName); @@ -1034,6 +1041,29 @@ export class BuildTools { } // Note: No longer need to track initial log since we use AppleScript completion detection + const { footerText: runLockFooter } = await LockManager.acquireLock(projectPath, reason, 'xcode_build_and_run'); + lockFooterText = runLockFooter; + let runLockReleased = false; + const releaseRunLock = async (): Promise => { + if (runLockReleased) { + return; + } + try { + await LockManager.releaseLock(projectPath); + } catch (error) { + Logger.warn( + `Failed to release run lock for ${projectPath}: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + } finally { + runLockReleased = true; + } + }; + const markRunAutoRelease = async (reasonText: string): Promise => { + autoReleaseNote = reasonText; + await releaseRunLock(); + }; const hasArgs = commandLineArguments && commandLineArguments.length > 0; const script = ` @@ -1055,7 +1085,15 @@ export class BuildTools { const actionId = actionIdMatch ? actionIdMatch[1] : null; if (!actionId) { - return { content: [{ type: 'text', text: `${runResult}\n\nError: Could not extract action ID from run result` }] }; + await markRunAutoRelease('Failed to read the run identifier from Xcode'); + return { + content: [ + { + type: 'text', + text: applyLockFooter(`${runResult}\n\nError: Could not extract action ID from run result`), + }, + ], + }; } Logger.info(`Run started with action ID: ${actionId}`); @@ -1064,15 +1102,26 @@ export class BuildTools { await this._handleReplaceExistingBuildAlert(); // Monitor run completion using AppleScript instead of build log detection - Logger.info(`Monitoring run completion using AppleScript for action ID: ${actionId}`); const maxRunTime = 3600000; // 1 hour safety timeout + const runMonitorTimeoutMs = 50000; // exit early to avoid MCP call timeout const runStartTime = Date.now(); + const checkIntervalMs = 3000; + const runningConfirmationMs = 6000; + Logger.info( + `Monitoring run completion using AppleScript for action ID: ${actionId} (interval=${checkIntervalMs}ms, running confirmation=${runningConfirmationMs}ms, guardrail=${runMonitorTimeoutMs}ms)`, + ); let runCompleted = false; - let monitoringSeconds = 0; + let monitoringElapsedMs = 0; + let runningObservedAt: number | null = null; + let monitoringAbortedForTimeout = false; + let lastStatus: string | null = null; + let lastCompleted: string | null = null; + let iteration = 0; while (!runCompleted && (Date.now() - runStartTime) < maxRunTime) { try { - // Check run completion via AppleScript every 10 seconds + iteration += 1; + // Check run completion via AppleScript at the configured interval const checkScript = ` (function() { ${getWorkspaceByPathScript(projectPath)} @@ -1091,12 +1140,27 @@ export class BuildTools { })() `; - const result = await JXAExecutor.execute(checkScript, 15000); - const [status, completed] = result.split(':'); + const result = await JXAExecutor.execute(checkScript, 10000); + const parts = result.split(':'); + const status = parts[0] ?? 'unknown'; + const completed: string | null = parts.length > 1 ? (parts[1] ?? '') : null; + if (result === 'No workspace') { + Logger.warn(`Run monitoring iteration ${iteration}: workspace not available yet.`); + } else if (result === 'Action not found') { + Logger.debug(`Run monitoring iteration ${iteration}: action ${actionId} not visible yet.`); + } - // Log progress every 2 minutes - if (monitoringSeconds % 120 === 0) { - Logger.info(`Run monitoring: ${Math.floor(monitoringSeconds/60)}min - Action ${actionId}: status=${status}, completed=${completed}`); + // Emit a log whenever status changes or every 2 minutes + if (status !== lastStatus || completed !== lastCompleted) { + Logger.info( + `Run status update after ${Math.round((Date.now() - runStartTime) / 1000)}s: action=${actionId}, status=${status}, completed=${completed ?? 'n/a'}`, + ); + lastStatus = status; + lastCompleted = completed; + } else if ((monitoringElapsedMs % 120000) === 0) { + Logger.info( + `Run status heartbeat at ${Math.floor(monitoringElapsedMs / 60000)}min: action=${actionId}, status=${status}, completed=${completed ?? 'n/a'}`, + ); } // For run actions, we need different completion logic than build/test @@ -1104,48 +1168,223 @@ export class BuildTools { if (completed === 'true' && (status === 'failed' || status === 'cancelled' || status === 'error occurred')) { // Run failed/cancelled - this is a true completion runCompleted = true; - Logger.info(`Run completed after ${Math.floor(monitoringSeconds/60)} minutes: status=${status}`); - break; - } else if (status === 'running' && monitoringSeconds >= 60) { - // If still running after 60 seconds, assume the app launched successfully - // We'll check for build errors in the log parsing step - runCompleted = true; - Logger.info(`Run appears successful after ${Math.floor(monitoringSeconds/60)} minutes (app likely launched)`); + Logger.info( + `Run completed after ${Math.floor(monitoringElapsedMs / 60000)} minutes: status=${status}`, + ); break; + } else if (status === 'running') { + if (!runningObservedAt) { + runningObservedAt = Date.now(); + Logger.info('Run reported status "running"; waiting briefly to confirm app launch.'); + } else if ((Date.now() - runningObservedAt) >= runningConfirmationMs) { + // Assume the app launched successfully once we've seen "running" for long enough + runCompleted = true; + const elapsedMs = Date.now() - runStartTime; + Logger.info(`Run appears successful after ${Math.round(elapsedMs / 1000)}s (status stayed 'running' for ~${Math.round((Date.now() - runningObservedAt) / 1000)}s)`); + break; + } } else if (status === 'succeeded') { // This might happen briefly during transition, wait a bit more Logger.info(`Run status shows 'succeeded', waiting to see if it transitions to 'running'...`); + } else { + runningObservedAt = null; } } catch (error) { - Logger.warn(`Run monitoring error at ${Math.floor(monitoringSeconds/60)}min: ${error instanceof Error ? error.message : error}`); + Logger.warn( + `Run monitoring error at ${Math.floor(monitoringElapsedMs / 60000)}min: ${ + error instanceof Error ? error.message : error + }`, + ); + } + + if (!runCompleted && (Date.now() - runStartTime) >= runMonitorTimeoutMs) { + monitoringAbortedForTimeout = true; + Logger.warn( + `Run monitoring exceeded 50s guardrail – returning early to avoid MCP timeout (last status: ${lastStatus ?? 'unknown'}, completed=${lastCompleted ?? 'n/a'})`, + ); + break; } - // Wait 10 seconds before next check - await new Promise(resolve => setTimeout(resolve, 10000)); - monitoringSeconds += 10; + // Wait before next check + await new Promise(resolve => setTimeout(resolve, checkIntervalMs)); + monitoringElapsedMs += checkIntervalMs; } if (!runCompleted) { + if (monitoringAbortedForTimeout) { + Logger.warn( + `Run monitoring exited early after ${Math.round((Date.now() - runStartTime) / 1000)}s (status=${lastStatus ?? 'unknown'}, completed=${lastCompleted ?? 'n/a'})`, + ); + return { + content: [ + { + type: 'text', + text: attachRunLogHint(`${runResult}\n\n⏳ Run is still in progress after approximately ${ + Math.round((Date.now() - runStartTime) / 1000) + } seconds (last known status: ${lastStatus ?? 'unknown'}, completed=${lastCompleted ?? 'n/a'}).\n\nThe app should keep launching in Xcode/Simulator. Check Xcode's report navigator for the final build status or rerun this command once the launch completes.`), + }, + ], + }; + } Logger.warn('Run monitoring reached 1-hour timeout - proceeding anyway'); } - // Now find the build log that was created during this run - const newLog = await BuildLogParser.getLatestBuildLog(projectPath); + Logger.info('Searching for build logs created during run...'); + const logSearchStart = Date.now(); + const logWaitTimeoutMs = 3600 * 1000; // 1 hour + let newLog = null; + while (Date.now() - logSearchStart < logWaitTimeoutMs) { + const recentLogs = await BuildLogParser.getRecentBuildLogs(projectPath, runStartTime); + const newestLog = recentLogs[0]; + if (newestLog) { + Logger.info( + `Found run build log created after start: ${newestLog.path} (mtime=${newestLog.mtime.toISOString()})`, + ); + newLog = newestLog; + break; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + if (!newLog) { - return { content: [{ type: 'text', text: `${runResult}\n\nNote: Run completed but no build log found (app may have launched without building)` }] }; + Logger.warn('Run completed but no new build log appeared; falling back to latest log.'); + newLog = await BuildLogParser.getLatestBuildLog(projectPath); + if (!newLog) { + return { + content: [ + { + type: 'text', + text: attachRunLogHint(`${runResult}\n\nNote: Run completed but no build log was found (app may have launched without building).`), + }, + ], + }; + } + } + + if (!logRecord) { + logRecord = BuildLogStore.registerLog({ + projectPath, + logPath: newLog.path, + schemeName, + action: 'run', + logKind: 'build', + }); + } + + const runConsoleLog = await BuildLogParser.getLatestRunLog(projectPath, runStartTime); + if (runConsoleLog) { + runLogRecord = BuildLogStore.registerLog({ + projectPath, + logPath: runConsoleLog.path, + schemeName, + action: 'run', + logKind: 'run', + }); + BuildLogStore.updateStatus(runLogRecord.id, 'completed', { buildStatus: null }); + } else { + Logger.warn('Unable to locate run console log for this session.'); } - + Logger.info(`Run completed, parsing build log: ${newLog.path}`); - const results = await BuildLogParser.parseBuildLog(newLog.path); + + const parseStart = Date.now(); + const parseTimeoutMs = 20000; + let parseTimedOut = false; + let parseError: unknown = null; + let results: ParsedBuildResults | null = null; + + const parsePromise = BuildLogParser.parseBuildLog(newLog.path); + + parsePromise.catch(error => { + parseError = error; + Logger.error(`Build log parsing failed: ${error instanceof Error ? error.message : error}`); + }); + + try { + results = await Promise.race([ + parsePromise, + new Promise(resolve => { + setTimeout(() => { + parseTimedOut = true; + resolve(null); + }, parseTimeoutMs); + }), + ]); + } catch (error) { + parseError = error; + Logger.warn( + `Build log parsing threw an error after ${Math.round((Date.now() - parseStart) / 1000)}s: ${ + error instanceof Error ? error.message : error + }`, + ); + } + + if (parseTimedOut) { + Logger.warn( + `Build log parsing exceeded ${parseTimeoutMs / 1000}s guardrail for ${newLog.path}; returning early while parsing continues in background.`, + ); + void parsePromise + .then(finalResult => { + Logger.info( + `Deferred build log parse completed after ${Math.round((Date.now() - parseStart) / 1000)}s - errors=${finalResult.errors.length}, warnings=${finalResult.warnings.length}`, + ); + }) + .catch(err => { + Logger.warn( + `Deferred build log parse failed: ${err instanceof Error ? err.message : err}`, + ); + }); + } else if (results) { + Logger.info( + `Build log parsed in ${Math.round((Date.now() - parseStart) / 1000)}s - errors=${results.errors.length}, warnings=${results.warnings.length}`, + ); + } let message = `${runResult}\n\n`; - Logger.info(`Run build completed - ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`); + if (!results) { + if (parseTimedOut) { + message += '⏳ Build log parsing is still in progress (took longer than 20 seconds).\n\n'; + message += `Open the log in Xcode for full details:\n • ${newLog.path}`; + } else { + message += '⚠️ Unable to summarize the build log automatically.\n\n'; + if (parseError instanceof Error) { + message += `Reason: ${parseError.message}\n\n`; + } + message += `You can open the log manually in Xcode:\n • ${newLog.path}`; + } + finalizeLogStatus('completed', null); + return { content: [{ type: 'text', text: attachAllHints(message) }] }; + } + + Logger.info( + `Run build completed - ${results.errors.length} errors, ${results.warnings.length} warnings, status: ${results.buildStatus || 'unknown'}`, + ); + + const normalizedStatus = results.buildStatus ? results.buildStatus.toLowerCase() : null; + const statusIndicatesFailure = normalizedStatus + ? normalizedStatus !== 'stopped' && + normalizedStatus !== 'interrupted' && + FAILURE_STATUS_TOKENS.some(token => normalizedStatus.includes(token)) + : false; + const summaryIndicatesFailure = + typeof results.errorCount === 'number' && results.errorCount > 0; + + if (results.errors.length === 0 && (statusIndicatesFailure || summaryIndicatesFailure)) { + const descriptor = results.buildStatus + ? `Xcode reported build status '${results.buildStatus}'` + : 'Xcode reported build errors in the log summary'; + results.errors = [ + `${descriptor} for log ${newLog.path}. Open the log in Xcode for full details.`, + ]; + } // Handle stopped/interrupted builds if (results.buildStatus === 'stopped') { message += `⏹️ BUILD INTERRUPTED\n\nThe build was stopped or interrupted before completion.\n\n💡 This may happen when:\n • The build was cancelled manually\n • Xcode was closed during the build\n • System resources were exhausted\n\nTry running the build again to complete it.`; - return { content: [{ type: 'text', text: message }] }; + finalizeLogStatus('failed', results.buildStatus); + await markRunAutoRelease('Run was interrupted'); + return { content: [{ type: 'text', text: attachLogHint(message) }] }; } if (results.errors.length > 0) { @@ -1153,9 +1392,11 @@ export class BuildTools { results.errors.forEach(error => { message += ` • ${error}\n`; }); + finalizeLogStatus('failed', results.buildStatus ?? 'failed'); + await markRunAutoRelease('Run build failed with errors'); throw new McpError( ErrorCode.InternalError, - message + attachAllHints(message) ); } else if (results.warnings.length > 0) { message += `⚠️ BUILD COMPLETED WITH WARNINGS (${results.warnings.length} warnings)\n\nWARNINGS:\n`; @@ -1166,7 +1407,8 @@ export class BuildTools { message += '✅ BUILD SUCCESSFUL - App should be launching'; } - return { content: [{ type: 'text', text: message }] }; + finalizeLogStatus('completed', results.buildStatus ?? null); + return { content: [{ type: 'text', text: attachAllHints(message) }] }; } public static async debug( @@ -1299,60 +1541,6 @@ export class BuildTools { } - private static async _findNewXCResultFile( - projectPath: string, - initialFiles: { path: string; mtime: number }[], - testStartTime: number - ): Promise { - const maxAttempts = 30; // 30 seconds - let attempts = 0; - - while (attempts < maxAttempts) { - const currentFiles = await this._findXCResultFiles(projectPath); - - // Look for new files created after test start - for (const file of currentFiles) { - const wasInitialFile = initialFiles.some(initial => - initial.path === file.path && initial.mtime === file.mtime - ); - - if (!wasInitialFile && file.mtime >= testStartTime - 5000) { // 5s buffer - Logger.info(`Found new xcresult file: ${file.path}, mtime: ${new Date(file.mtime)}, test start: ${new Date(testStartTime)}`); - return file.path; - } else if (!wasInitialFile) { - Logger.warn(`Found xcresult file but too old: ${file.path}, mtime: ${new Date(file.mtime)}, test start: ${new Date(testStartTime)}, diff: ${file.mtime - testStartTime}ms`); - } else { - Logger.debug(`Skipping initial file: ${file.path}, mtime: ${new Date(file.mtime)}`); - } - } - - await new Promise(resolve => setTimeout(resolve, 1000)); - attempts++; - } - - // If no new file found, look for files created AFTER test start time - const allFiles = await this._findXCResultFiles(projectPath); - - // Find files created after the test started (not just within the timeframe) - const filesAfterTestStart = allFiles.filter(file => file.mtime > testStartTime); - - if (filesAfterTestStart.length > 0) { - // Return the newest file that was created after the test started - const mostRecentAfterTest = filesAfterTestStart[0]; // Already sorted newest first - if (mostRecentAfterTest) { - Logger.warn(`Using most recent xcresult file created after test start: ${mostRecentAfterTest.path}`); - return mostRecentAfterTest.path; - } - } else if (allFiles.length > 0) { - const mostRecent = allFiles[0]; - if (mostRecent) { - Logger.debug(`Most recent file too old: ${mostRecent.path}, mtime: ${new Date(mostRecent.mtime)}, test start: ${new Date(testStartTime)}`); - } - } - - return null; - } - /** * Find XCResult files for a given project */ @@ -1417,14 +1605,616 @@ export class BuildTools { private static _formatFileSize(bytes: number): string { if (bytes === 0) return '0 bytes'; - + const k = 1024; const sizes = ['bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); - + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; } + private static async _pathExists(targetPath: string): Promise { + try { + await stat(targetPath); + return true; + } catch { + return false; + } + } + + private static async _buildDestinationArgs(destination: string): Promise { + if (!destination || typeof destination !== 'string') { + return null; + } + + const trimmed = destination.trim(); + if (trimmed.length === 0) { + return null; + } + + if (trimmed.includes('=')) { + return ['-destination', trimmed]; + } + + let name = trimmed; + let osVersion: string | null = null; + const parenMatch = trimmed.match(/^(.*)\(([^)]+)\)$/); + if (parenMatch) { + name = parenMatch[1]?.trim() ?? trimmed; + osVersion = parenMatch[2]?.trim() ?? null; + } + + const lowerName = name.toLowerCase(); + let platform = 'iOS Simulator'; + if (lowerName.includes('watch')) { + platform = 'watchOS Simulator'; + } else if (lowerName.includes('tv')) { + platform = 'tvOS Simulator'; + } else if (lowerName.includes('mac')) { + platform = 'macOS'; + } + + if (platform === 'macOS') { + let destinationValue = `platform=${platform}`; + if (osVersion) { + destinationValue += `,OS=${osVersion}`; + } + return ['-destination', destinationValue]; + } + + const originalInput = trimmed; + let resolved = await this._findBestSimulatorId(name, osVersion, platform); + if (!resolved && osVersion) { + resolved = await this._findBestSimulatorId(name, null, platform); + } + if (!resolved && originalInput !== name) { + resolved = await this._findBestSimulatorId(originalInput, null, platform); + } + if (resolved) { + let destinationValue = `platform=${platform},id=${resolved.id}`; + if (resolved.osVersion) { + destinationValue += `,OS=${resolved.osVersion}`; + } else if (osVersion) { + destinationValue += `,OS=${osVersion}`; + } + return ['-destination', destinationValue]; + } + + let destinationValue = `platform=${platform},name=${name}`; + if (osVersion) { + destinationValue += `,OS=${osVersion}`; + } + return ['-destination', destinationValue]; + } + + private static async _buildDestinationArgsForDevice( + deviceType: string, + osVersion: string | null, + ): Promise<{ args: string[]; label: string } | null> { + const normalizedType = deviceType.trim().toLowerCase(); + if (normalizedType.length === 0) { + return null; + } + + const normalizedOs = osVersion && osVersion.trim().length > 0 ? osVersion.trim() : null; + + if (normalizedType.startsWith('mac')) { + let destinationValue = 'platform=macOS'; + if (normalizedOs) { + destinationValue += `,OS=${normalizedOs}`; + } + return { args: ['-destination', destinationValue], label: destinationValue }; + } + + let platform: 'iOS Simulator' | 'watchOS Simulator' | 'tvOS Simulator' | 'visionOS Simulator'; + let familyMatcher: (name: string) => boolean; + + if (normalizedType.startsWith('iphone') || normalizedType === 'ios' || normalizedType === 'phone') { + platform = 'iOS Simulator'; + familyMatcher = name => name.toLowerCase().startsWith('iphone'); + } else if (normalizedType.startsWith('ipad')) { + platform = 'iOS Simulator'; + familyMatcher = name => name.toLowerCase().startsWith('ipad'); + } else if (normalizedType.includes('watch')) { + platform = 'watchOS Simulator'; + familyMatcher = name => name.toLowerCase().includes('apple watch'); + } else if (normalizedType.includes('tv')) { + platform = 'tvOS Simulator'; + familyMatcher = name => name.toLowerCase().includes('apple tv'); + } else if (normalizedType.includes('vision')) { + platform = 'visionOS Simulator'; + familyMatcher = name => name.toLowerCase().includes('vision'); + } else { + throw new McpError( + ErrorCode.InvalidParams, + `Unsupported device_type '${deviceType}'. Expected values include iphone, ipad, mac, watch, tv, or vision.`, + ); + } + + const inventory = await this._getSimulatorInventory(); + if (inventory.length === 0) { + return null; + } + + const preferenceKey = this._buildSimulatorPreferenceKey(platform, normalizedType, normalizedOs); + const preferences = await this._loadSimulatorPreferences(); + const remembered = preferences[preferenceKey]; + + const candidates = inventory + .filter(device => device.platform === platform && familyMatcher(device.name) && device.isAvailable) + .map(device => ({ + ...device, + versionMatch: normalizedOs && device.runtimeVersion + ? this._runtimeMatchesRequested(device.runtimeVersion, normalizedOs) + : normalizedOs === null, + })); + + if (candidates.length === 0) { + return null; + } + + const versionMatches = normalizedOs + ? candidates.filter(candidate => candidate.versionMatch) + : candidates; + const candidatePool = versionMatches.length > 0 ? versionMatches : candidates; + const allBooted = candidatePool.every(candidate => candidate.state.toLowerCase() === 'booted'); + + let selected = null as (typeof candidates)[number] | null; + if (remembered) { + selected = candidatePool.find(candidate => candidate.udid === remembered.udid) ?? null; + if (selected && selected.state.toLowerCase() === 'booted' && !allBooted) { + selected = null; + } + } + + if (!selected) { + const idleCandidates = candidatePool.filter(candidate => candidate.state.toLowerCase() !== 'booted'); + const rankingPool = idleCandidates.length > 0 ? idleCandidates : candidatePool; + rankingPool.sort((a, b) => this._scoreSimulatorCandidate(b, normalizedOs) - this._scoreSimulatorCandidate(a, normalizedOs)); + selected = rankingPool[0] ?? null; + } + + if (!selected) { + return null; + } + + const destinationOs = normalizedOs || selected.runtimeVersion || undefined; + let destinationValue = `platform=${platform},id=${selected.udid}`; + if (destinationOs) { + destinationValue += `,OS=${destinationOs}`; + } + + const rememberPayload: { udid: string; name?: string; runtimeVersion?: string } = { + udid: selected.udid, + }; + if (selected.name) { + rememberPayload.name = selected.name; + } + if (selected.runtimeVersion) { + rememberPayload.runtimeVersion = selected.runtimeVersion; + } + await this._rememberSimulatorSelection(preferenceKey, rememberPayload); + + const label = `${selected.name}${selected.runtimeVersion ? ` (${selected.runtimeVersion})` : ''}`; + return { args: ['-destination', destinationValue], label }; + } + + private static async _findBestSimulatorId( + name: string, + osVersion: string | null, + platform: string + ): Promise<{ id: string; osVersion?: string } | null> { + try { + const { stdout } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + execFile('xcrun', ['simctl', 'list', 'devices', '--json'], (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve({ stdout, stderr }); + } + }); + }); + + const parsed = JSON.parse(stdout) as { + devices?: Record>; + }; + + if (parsed.devices) { + const normalizedName = name.toLowerCase(); + let bestMatch: { id: string; osVersion?: string } | null = null; + + const desiredPrefix = platform.startsWith('watchOS') + ? 'com.apple.CoreSimulator.SimRuntime.watchOS' + : platform.startsWith('tvOS') + ? 'com.apple.CoreSimulator.SimRuntime.tvOS' + : 'com.apple.CoreSimulator.SimRuntime.iOS'; + + for (const [runtime, devices] of Object.entries(parsed.devices)) { + if (!runtime.startsWith(desiredPrefix)) { + continue; + } + + const runtimeVersionMatch = runtime.match(/-(\d+)-(\d+)/); + const runtimeVersion = runtimeVersionMatch ? `${runtimeVersionMatch[1]}.${runtimeVersionMatch[2]}` : null; + + for (const device of devices ?? []) { + if (!device || typeof device.name !== 'string' || typeof device.udid !== 'string') { + continue; + } + + if (device.isAvailable === false) { + continue; + } + + if (device.state && typeof device.state === 'string' && device.state.toLowerCase() === 'creating') { + continue; + } + + if (device.name.toLowerCase() !== normalizedName) { + continue; + } + + if (osVersion && runtimeVersion) { + if (osVersion === runtimeVersion) { + return { id: device.udid, osVersion: runtimeVersion }; + } + + if (!bestMatch) { + bestMatch = { id: device.udid, osVersion: runtimeVersion }; + } + continue; + } + + if (!bestMatch) { + bestMatch = runtimeVersion + ? { id: device.udid, osVersion: runtimeVersion } + : { id: device.udid }; + } + } + } + + if (bestMatch) { + return bestMatch; + } + } + } catch (error) { + Logger.warn(`Failed to query simulator list: ${error instanceof Error ? error.message : String(error)}`); + } + + try { + const { stdout } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + execFile('xcrun', ['simctl', 'list', 'devices'], (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve({ stdout, stderr }); + } + }); + }); + + const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`^\\s*${escapedName} \\(([0-9A-F-]+)\\)`, 'mi'); + const match = stdout.match(regex); + if (match && match[1]) { + return { id: match[1] }; + } + } catch (error) { + Logger.warn(`Failed to parse textual simulator list: ${error instanceof Error ? error.message : String(error)}`); + } + + return null; + } + + private static async _getSchemesViaXcodebuild( + projectFlag: '-workspace' | '-project', + containerPath: string + ): Promise { + return await new Promise(resolve => { + const args = ['-list', projectFlag, containerPath]; + const child = spawn('xcodebuild', args); + let stdoutBuffer = ''; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', chunk => { + stdoutBuffer += chunk; + }); + child.on('close', () => { + const sections = stdoutBuffer.split('Schemes:'); + if (sections.length < 2) { + resolve([]); + return; + } + const schemesSection = sections[1] ?? ''; + const lines = schemesSection + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0); + resolve(lines); + }); + child.on('error', () => resolve([])); + }); + } + + private static async _resolveSchemeName( + projectFlag: '-workspace' | '-project', + containerPath: string, + requestedScheme: string + ): Promise<{ ok: true; schemeName: string } | { ok: false; result: McpResult }> { + const availableSchemes = await this._getSchemesViaXcodebuild(projectFlag, containerPath); + + if (availableSchemes.length === 0) { + return { + ok: false, + result: { + content: [{ + type: 'text', + text: `❌ No shared schemes found when inspecting ${basename(containerPath)}.\n\nMake sure the scheme is shared (Product → Scheme → Manage Schemes… → "Shared") and try again.` + }] + } + }; + } + + const exact = availableSchemes.find(name => name === requestedScheme); + if (exact) { + return { ok: true, schemeName: exact }; + } + + const caseInsensitive = availableSchemes.find(name => name.toLowerCase() === requestedScheme.toLowerCase()); + if (caseInsensitive) { + Logger.debug(`Resolved scheme '${requestedScheme}' to '${caseInsensitive}' (case-insensitive match)`); + return { ok: true, schemeName: caseInsensitive }; + } + + const bestMatch = ParameterNormalizer.findBestMatch(requestedScheme, availableSchemes); + let message = `❌ Scheme '${requestedScheme}' not found.`; + message += '\n\nAvailable schemes:\n'; + for (const scheme of availableSchemes) { + if (scheme === bestMatch) { + message += ` • ${scheme} ← Did you mean this?\n`; + } else { + message += ` • ${scheme}\n`; + } + } + + return { + ok: false, + result: { + content: [{ type: 'text', text: message }] + } + }; + } + + private static _hasArgument(args: string[], flag: string): boolean { + return args.some(entry => { + if (entry === flag) { + return true; + } + if (entry.startsWith(`${flag}=`) || entry.startsWith(`${flag} `) || entry.startsWith(`${flag}:`)) { + return true; + } + return false; + }); + } + + private static async _createTemporaryResultBundlePath(prefix: string): Promise { + const root = join(tmpdir(), 'xcodemcp-test-results'); + await mkdir(root, { recursive: true }); + const uniqueSuffix = `${Date.now()}-${Math.random().toString(16).slice(2)}`; + const bundlePath = join(root, `${prefix}-${uniqueSuffix}.xcresult`); + try { + await rm(bundlePath, { recursive: true, force: true }); + } catch { + // ignore cleanup errors; the file will be overwritten by xcodebuild + } + return bundlePath; + } + + private static simulatorPreferenceCache: { + loaded: boolean; + data: Record; + } = { loaded: false, data: {} }; + + private static _buildSimulatorPreferenceKey(platform: string, deviceType: string, osVersion: string | null): string { + const normalizedPlatform = platform.toLowerCase().replace(/\s+/g, '-'); + const normalizedDevice = deviceType.toLowerCase().replace(/\s+/g, '-'); + const normalizedOs = osVersion ? osVersion.toLowerCase() : 'any'; + return `${normalizedPlatform}:${normalizedDevice}:${normalizedOs}`; + } + + private static _getSimulatorPreferenceFile(): { dir: string; file: string } { + const dir = join(homedir(), 'Library', 'Application Support', 'XcodeMCP'); + const file = join(dir, 'simulator-preferences.json'); + return { dir, file }; + } + + private static async _loadSimulatorPreferences(): Promise> { + if (this.simulatorPreferenceCache.loaded) { + return this.simulatorPreferenceCache.data; + } + + const { file } = this._getSimulatorPreferenceFile(); + try { + const content = await readFile(file, 'utf8'); + const parsed = JSON.parse(content); + if (parsed && typeof parsed === 'object') { + this.simulatorPreferenceCache = { + loaded: true, + data: parsed as Record, + }; + return this.simulatorPreferenceCache.data; + } + } catch { + // Ignore missing or invalid preference files + } + + this.simulatorPreferenceCache = { loaded: true, data: {} }; + return this.simulatorPreferenceCache.data; + } + + private static async _rememberSimulatorSelection( + key: string, + value: { udid: string; name?: string; runtimeVersion?: string }, + ): Promise { + const preferences = await this._loadSimulatorPreferences(); + const entry: { udid: string; name?: string; runtimeVersion?: string; updatedAt: number } = { + udid: value.udid, + updatedAt: Date.now(), + }; + if (value.name) { + entry.name = value.name; + } + if (value.runtimeVersion) { + entry.runtimeVersion = value.runtimeVersion; + } + preferences[key] = entry; + + const { dir, file } = this._getSimulatorPreferenceFile(); + await mkdir(dir, { recursive: true }); + await writeFile(file, JSON.stringify(preferences, null, 2), 'utf8'); + this.simulatorPreferenceCache = { loaded: true, data: preferences }; + } + + private static _versionScore(version: string): number { + const parts = version.split('.').map(part => parseInt(part, 10)).filter(n => !Number.isNaN(n)); + const [major = 0, minor = 0, patch = 0] = parts; + return major * 10000 + minor * 100 + patch; + } + + private static _runtimeMatchesRequested(runtime: string, requested: string): boolean { + const normalizedRequested = requested.trim().toLowerCase(); + const normalizedRuntime = runtime.trim().toLowerCase(); + if (normalizedRuntime === normalizedRequested) { + return true; + } + return normalizedRuntime.startsWith(`${normalizedRequested}.`); + } + + private static _scoreSimulatorCandidate( + candidate: { runtimeVersion: string | null; name: string; state: string }, + requestedVersion: string | null, + ): number { + const base = candidate.runtimeVersion ? this._versionScore(candidate.runtimeVersion) : 0; + const idleBonus = candidate.state.toLowerCase() === 'booted' ? -50 : 10; + const matchBonus = requestedVersion && candidate.runtimeVersion + ? (this._runtimeMatchesRequested(candidate.runtimeVersion, requestedVersion) ? 100 : 0) + : 0; + const deviceBonus = candidate.name.toLowerCase().includes('pro') ? 1 : 0; + return base + idleBonus + matchBonus + deviceBonus; + } + + private static _extractRuntimeVersion(runtimeIdentifier: string): string | null { + const match = runtimeIdentifier.match(/-(\d+)(?:-(\d+))?(?:-(\d+))?/); + if (!match) { + return null; + } + const major = match[1] ?? '0'; + const minor = match[2] ?? '0'; + const patch = match[3]; + const components = [major, minor, patch].filter((component): component is string => typeof component === 'string' && component.length > 0); + return components.map(component => component.replace(/^0+/, '') || '0').join('.'); + } + + private static _platformForRuntime(runtimeIdentifier: string): + | 'iOS Simulator' + | 'watchOS Simulator' + | 'tvOS Simulator' + | 'visionOS Simulator' + | null { + if (runtimeIdentifier.includes('iOS')) { + return 'iOS Simulator'; + } + if (runtimeIdentifier.includes('watchOS')) { + return 'watchOS Simulator'; + } + if (runtimeIdentifier.includes('tvOS')) { + return 'tvOS Simulator'; + } + if (runtimeIdentifier.includes('visionOS')) { + return 'visionOS Simulator'; + } + return null; + } + + private static async _getSimulatorInventory(): Promise< + Array<{ + name: string; + udid: string; + platform: 'iOS Simulator' | 'watchOS Simulator' | 'tvOS Simulator' | 'visionOS Simulator'; + runtimeIdentifier: string; + runtimeVersion: string | null; + state: string; + isAvailable: boolean; + }> + > { + try { + const { stdout } = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { + execFile('xcrun', ['simctl', 'list', 'devices', '--json'], (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve({ stdout, stderr }); + } + }); + }); + + const parsed = JSON.parse(stdout) as { + devices?: Record>; + }; + + const inventory: Array<{ + name: string; + udid: string; + platform: 'iOS Simulator' | 'watchOS Simulator' | 'tvOS Simulator' | 'visionOS Simulator'; + runtimeIdentifier: string; + runtimeVersion: string | null; + state: string; + isAvailable: boolean; + }> = []; + + for (const [runtimeIdentifier, devices] of Object.entries(parsed.devices ?? {})) { + const platform = this._platformForRuntime(runtimeIdentifier); + if (!platform) { + continue; + } + + const runtimeVersion = this._extractRuntimeVersion(runtimeIdentifier); + + for (const device of devices ?? []) { + if (!device || typeof device.name !== 'string' || typeof device.udid !== 'string') { + continue; + } + const isAvailable = device.isAvailable !== false && (!device.availability || !device.availability.includes('unavailable')); + inventory.push({ + name: device.name, + udid: device.udid, + platform, + runtimeIdentifier, + runtimeVersion, + state: typeof device.state === 'string' ? device.state : 'Unknown', + isAvailable, + }); + } + } + + return inventory; + } catch (error) { + Logger.warn(`Failed to query simulator list: ${error instanceof Error ? error.message : String(error)}`); + return []; + } + } + + private static _detectSimulatorCloneFailure(output: string): { matched: boolean; deviceName?: string } { + if (!output || output.trim().length === 0) { + return { matched: false }; + } + + const match = output.match(/Failed to clone device named '([^']+)'/); + if (match && match[1]) { + return { matched: true, deviceName: match[1] }; + } + + return { matched: false }; + } + /** * Handle alerts that appear when starting builds/tests while another operation is in progress. * This includes "replace existing build" alerts and similar dialog overlays. @@ -1600,4 +2390,6 @@ export class BuildTools { Logger.info(`Alert handling failed: ${error instanceof Error ? error.message : String(error)}`); } } -} \ No newline at end of file +} + +export default BuildTools; diff --git a/src/tools/InfoTools.ts b/src/tools/InfoTools.ts index 1221dc2..9b46cfb 100644 --- a/src/tools/InfoTools.ts +++ b/src/tools/InfoTools.ts @@ -1,5 +1,5 @@ import { JXAExecutor } from '../utils/JXAExecutor.js'; -import { PathValidator } from '../utils/PathValidator.js'; +import PathValidator from '../utils/PathValidator.js'; import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; import type { McpResult, OpenProjectCallback } from '../types/index.js'; @@ -77,4 +77,6 @@ export class InfoTools { const result = await JXAExecutor.execute(script); return { content: [{ type: 'text', text: result }] }; } -} \ No newline at end of file +} + +export default InfoTools; diff --git a/src/tools/LockTools.ts b/src/tools/LockTools.ts new file mode 100644 index 0000000..80d6910 --- /dev/null +++ b/src/tools/LockTools.ts @@ -0,0 +1,54 @@ +import path from 'path'; +import type { McpResult } from '../types/index.js'; +import LockManager from '../utils/LockManager.js'; +import ErrorHelper from '../utils/ErrorHelper.js'; + +export class LockTools { + private static validateTarget(projectPath: string): McpResult | null { + if (!projectPath || typeof projectPath !== 'string') { + return { + content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance('Missing required parameter: xcodeproj', '• Supply the absolute path to the project/workspace whose lock you want to release.\n• Example: /Users/name/MyApp/MyApp.xcodeproj') }], + }; + } + if (!path.isAbsolute(projectPath)) { + return { + content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance(`Project path must be absolute, got: ${projectPath}`, '• Provide a path starting with /\n• Example: /Users/name/MyApp/MyApp.xcodeproj') }], + }; + } + if (!projectPath.endsWith('.xcodeproj') && !projectPath.endsWith('.xcworkspace')) { + return { + content: [{ type: 'text', text: ErrorHelper.createErrorWithGuidance('Lock release path must end in .xcodeproj or .xcworkspace', '• Example: /Users/name/MyApp/MyApp.xcodeproj\n• Example: /Users/name/MyApp/MyApp.xcworkspace') }], + }; + } + return null; + } + + public static async release(projectPath: string): Promise { + const validationError = this.validateTarget(projectPath); + if (validationError) { + return validationError; + } + + const { released, info } = await LockManager.releaseLock(projectPath); + if (!released) { + return { + content: [{ type: 'text', text: `No active lock found for ${projectPath}. It may have already been released.` }], + }; + } + + const previousReason = info?.reason ? ` Previous reason: "${info.reason}".` : ''; + const lockId = info?.lockId ? ` (Lock ID: ${info.lockId})` : ''; + const waitingCount = info ? Math.max(0, info.queueDepth - 1) : 0; + const waitingText = waitingCount > 0 ? ` ${waitingCount} worker${waitingCount === 1 ? '' : 's'} can proceed now.` : ' No other workers were waiting.'; + return { + content: [ + { + type: 'text', + text: `Released lock for ${projectPath}${lockId}.${previousReason}${waitingText}`, + }, + ], + }; + } +} + +export default LockTools; diff --git a/src/tools/LogTools.ts b/src/tools/LogTools.ts new file mode 100644 index 0000000..0ae6264 --- /dev/null +++ b/src/tools/LogTools.ts @@ -0,0 +1,372 @@ +import { readFile, stat } from 'fs/promises'; +import { basename } from 'path'; +import type { McpResult } from '../types/index.js'; +import BuildLogStore, { BuildLogRecord, type BuildLogKind } from '../utils/BuildLogStore.js'; +import { BuildLogParser } from '../utils/BuildLogParser.js'; +import PathValidator from '../utils/PathValidator.js'; +import Logger from '../utils/Logger.js'; + +interface BuildLogViewParams { + logPath: string; + projectPath: string; + logRecord?: BuildLogRecord; + hint?: string; + filter?: string; + filter_globs?: string[]; + filter_regex?: boolean; + case_sensitive?: boolean; + max_lines?: number; + cursor?: string; + logType?: BuildLogKind; +} + +interface LogCursorPayload { + v: 1; + logPath: string; + logId?: string; + byteOffset: number; + mtimeMs: number; +} + +export class LogTools { + public static async getBuildLog(params: { + log_id?: string; + xcodeproj?: string; + filter?: string; + filter_regex?: boolean; + case_sensitive?: boolean; + max_lines?: number; + cursor?: string; + filter_globs?: string[]; + log_type?: 'build' | 'run'; + }): Promise { + const { log_id, xcodeproj, filter, filter_regex = false, case_sensitive = false } = params; + const logType: BuildLogKind = params.log_type === 'run' ? 'run' : 'build'; + let { max_lines } = params; + const filterGlobs = Array.isArray(params.filter_globs) + ? params.filter_globs.filter(pattern => typeof pattern === 'string' && pattern.trim().length > 0) + : undefined; + const cursor = typeof params.cursor === 'string' && params.cursor.length > 0 ? params.cursor : undefined; + + if (!log_id && !xcodeproj) { + return { + content: [ + { + type: 'text', + text: '❌ Provide either log_id (preferred) or xcodeproj to identify which build/run log to show.', + }, + ], + isError: true, + }; + } + + let record = log_id ? BuildLogStore.getLog(log_id) : undefined; + if (record && record.logKind !== logType) { + return { + content: [ + { + type: 'text', + text: `❌ Log ID ${log_id} refers to a ${record.logKind} log, but this command expects a ${logType} log.`, + }, + ], + isError: true, + }; + } + + if (!record && xcodeproj) { + const { resolvedPath, error } = PathValidator.resolveAndValidateProjectPath(xcodeproj, 'xcodeproj'); + if (error) { + return error; + } + record = BuildLogStore.getLatestLogForProject(resolvedPath, logType); + if (!record) { + const latestLog = + logType === 'run' + ? await BuildLogParser.getLatestRunLog(resolvedPath) + : await BuildLogParser.getLatestBuildLog(resolvedPath); + if (latestLog) { + const responseParams: BuildLogViewParams = { + logPath: latestLog.path, + projectPath: resolvedPath, + hint: + logType === 'run' + ? 'Latest run log from DerivedData (not tracked by MCP log store)' + : 'Latest build log from DerivedData (not tracked by MCP log store)', + filter_regex, + case_sensitive, + logType, + }; + if (typeof filter === 'string') { + responseParams.filter = filter; + } + if (filterGlobs && filterGlobs.length > 0) { + responseParams.filter_globs = filterGlobs; + } + if (typeof max_lines === 'number' && Number.isFinite(max_lines) && max_lines > 0) { + responseParams.max_lines = max_lines; + } + if (cursor) { + responseParams.cursor = cursor; + } + return this.buildResponseFromPath(responseParams); + } + } + } + + if (!record) { + const scope = log_id ? `log ID ${log_id}` : `project ${basename(xcodeproj ?? 'unknown')}`; + return { + content: [{ type: 'text', text: `❌ Could not find a ${logType} log associated with ${scope}.` }], + isError: true, + }; + } + + const responseParams: BuildLogViewParams = { + logPath: record.logPath, + projectPath: record.projectPath, + logRecord: record, + filter_regex, + case_sensitive, + logType, + }; + if (typeof filter === 'string') { + responseParams.filter = filter; + } + if (filterGlobs && filterGlobs.length > 0) { + responseParams.filter_globs = filterGlobs; + } + if (typeof max_lines === 'number' && Number.isFinite(max_lines) && max_lines > 0) { + responseParams.max_lines = max_lines; + } + if (cursor) { + responseParams.cursor = cursor; + } + return this.buildResponseFromPath(responseParams); + } + + private static async buildResponseFromPath(params: BuildLogViewParams): Promise { + const { + logPath, + projectPath, + logRecord, + hint, + filter, + filter_regex = false, + case_sensitive = false, + } = params; + let { max_lines } = params; + + max_lines = typeof max_lines === 'number' && max_lines > 0 ? Math.floor(max_lines) : 400; + + try { + const fileStats = await stat(logPath); + const fileBuffer = await readFile(logPath); + + let cursorPayload: LogCursorPayload | null = null; + let startOffset = 0; + if (params.cursor) { + cursorPayload = this.decodeCursor(params.cursor); + if (!cursorPayload) { + return { + content: [{ type: 'text', text: '❌ Invalid cursor format. Please re-run without --cursor.' }], + isError: true, + }; + } + if (cursorPayload.logPath !== logPath) { + return { + content: [ + { + type: 'text', + text: '❌ Cursor belongs to a different log file. Use the cursor that was returned with this log.', + }, + ], + isError: true, + }; + } + if (cursorPayload.logId && logRecord?.id && cursorPayload.logId !== logRecord.id) { + return { + content: [ + { + type: 'text', + text: '❌ Cursor is tied to a different build/run session. Please use the most recent cursor for this log.', + }, + ], + isError: true, + }; + } + if (cursorPayload.byteOffset > fileBuffer.length) { + return { + content: [ + { + type: 'text', + text: '❌ Cursor is no longer valid because the log file rotated or shrank. Re-run without --cursor to start over.', + }, + ], + isError: true, + }; + } + startOffset = cursorPayload.byteOffset; + } + + const slicedBuffer = startOffset > 0 ? fileBuffer.slice(startOffset) : fileBuffer; + const slicedText = slicedBuffer.toString('utf8'); + let lines = + slicedText.length === 0 + ? [] + : slicedText.split(/\r?\n/).filter((line, idx, arr) => !(line === '' && idx === arr.length - 1)); + + const matchers: Array<(line: string) => boolean> = []; + const globFilters = params.filter_globs; + if (filter && filter.length > 0) { + if (filter_regex) { + try { + const flags = case_sensitive ? '' : 'i'; + const regex = new RegExp(filter, flags); + matchers.push((line: string) => regex.test(line)); + } catch (error) { + Logger.warn(`Invalid regex filter "${filter}": ${error instanceof Error ? error.message : error}`); + return { + content: [ + { + type: 'text', + text: `❌ Invalid filter regex "${filter}": ${error instanceof Error ? error.message : error}`, + }, + ], + isError: true, + }; + } + } else { + const needle = case_sensitive ? filter : filter.toLowerCase(); + matchers.push((line: string) => { + const haystack = case_sensitive ? line : line.toLowerCase(); + return haystack.includes(needle); + }); + } + } + if (globFilters && globFilters.length > 0) { + for (const glob of globFilters) { + const regex = this.globToRegex(glob, case_sensitive); + if (!regex) { + return { + content: [ + { + type: 'text', + text: `❌ Invalid filter glob "${glob}". Use * and ? wildcards only.`, + }, + ], + isError: true, + }; + } + matchers.push((line: string) => regex.test(line)); + } + } + + let filteredLines = + matchers.length > 0 ? lines.filter(line => matchers.some(matcher => matcher(line))) : lines; + const truncated = filteredLines.length > max_lines; + if (truncated) { + filteredLines = filteredLines.slice(filteredLines.length - max_lines); + } + + if (filteredLines.length === 0) { + filteredLines = ['(no matching lines in this range)']; + } + + const logLabel = logRecord?.logKind ?? params.logType ?? 'build'; + const headerLines = [ + `🪵 ${logLabel === 'run' ? 'Run' : 'Build/Run'} Log: ${basename(projectPath)}`, + ` • Path: ${logPath}`, + ` • Type: ${logLabel === 'run' ? 'Run Log' : 'Build Log'}`, + ]; + if (logRecord?.id) { + headerLines.push(` • Log ID: ${logRecord.id}`); + } + if (logRecord?.status) { + headerLines.push(` • Status: ${logRecord.status}${logRecord.buildStatus ? ` (${logRecord.buildStatus})` : ''}`); + } else if (hint) { + headerLines.push(` • Source: ${hint}`); + } + if (cursorPayload) { + headerLines.push( + ` • Showing lines appended after cursor offset ${cursorPayload.byteOffset.toLocaleString('en-US')}`, + ); + } + if (filter) { + headerLines.push(` • Filter: ${filter_regex ? `regex /${filter}/` : `"${filter}"`} (${case_sensitive ? 'case-sensitive' : 'case-insensitive'})`); + } + if (globFilters && globFilters.length > 0) { + headerLines.push(` • Filter Globs: ${globFilters.join(', ')}`); + } + if (truncated) { + headerLines.push(` • Showing last ${max_lines} matching line${max_lines === 1 ? '' : 's'} (truncated)`); + } else { + headerLines.push(` • Showing ${filteredLines.length} line${filteredLines.length === 1 ? '' : 's'}`); + } + + const newCursorPayload: LogCursorPayload = { + v: 1, + logPath, + byteOffset: fileBuffer.length, + mtimeMs: fileStats.mtimeMs, + }; + if (logRecord?.id) { + newCursorPayload.logId = logRecord.id; + } + const newCursor = this.encodeCursor(newCursorPayload); + headerLines.push(` • Cursor: ${newCursor}`); + headerLines.push(' (Supply this cursor next time to stream only new log output.)'); + + const message = `${headerLines.join('\n')}\n\n${filteredLines.join('\n')}`; + return { content: [{ type: 'text', text: message }] }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: 'text', + text: `❌ Failed to read log file (${logPath}): ${errorMessage}`, + }, + ], + isError: true, + }; + } + } + + private static encodeCursor(payload: LogCursorPayload): string { + return Buffer.from(JSON.stringify(payload)).toString('base64url'); + } + + private static decodeCursor(cursor: string): LogCursorPayload | null { + try { + const json = Buffer.from(cursor, 'base64url').toString('utf8'); + const parsed = JSON.parse(json); + if ( + !parsed || + typeof parsed !== 'object' || + parsed.v !== 1 || + typeof parsed.logPath !== 'string' || + typeof parsed.byteOffset !== 'number' || + parsed.byteOffset < 0 + ) { + return null; + } + return parsed as LogCursorPayload; + } catch { + return null; + } + } + + private static globToRegex(pattern: string, caseSensitive: boolean): RegExp | null { + if (typeof pattern !== 'string' || pattern.length === 0) { + return null; + } + const escaped = pattern.replace(/([.+^${}()|[\]\\])/g, '\\$1').replace(/\*/g, '.*').replace(/\?/g, '.'); + try { + return new RegExp(escaped, caseSensitive ? '' : 'i'); + } catch { + return null; + } + } +} + +export default LogTools; diff --git a/src/tools/ProjectTools.ts b/src/tools/ProjectTools.ts index f0e0f88..4bea5c6 100644 --- a/src/tools/ProjectTools.ts +++ b/src/tools/ProjectTools.ts @@ -1,7 +1,7 @@ import { JXAExecutor } from '../utils/JXAExecutor.js'; -import { PathValidator } from '../utils/PathValidator.js'; +import PathValidator from '../utils/PathValidator.js'; import { ParameterNormalizer } from '../utils/ParameterNormalizer.js'; -import { ErrorHelper } from '../utils/ErrorHelper.js'; +import ErrorHelper from '../utils/ErrorHelper.js'; import { getWorkspaceByPathScript } from '../utils/JXAHelpers.js'; import type { McpResult, OpenProjectCallback } from '../types/index.js'; @@ -546,3 +546,5 @@ export class ProjectTools { } } } + +export default ProjectTools; diff --git a/src/tools/SimulatorTools.ts b/src/tools/SimulatorTools.ts new file mode 100644 index 0000000..7cc2e7c --- /dev/null +++ b/src/tools/SimulatorTools.ts @@ -0,0 +1,214 @@ +import { execFile, spawn } from 'child_process'; +import { promisify } from 'util'; +import { promises as fsPromises } from 'fs'; +import type { McpResult } from '../types/index.js'; +import Logger from '../utils/Logger.js'; + +const execFileAsync = promisify(execFile); + +interface SimulatorDevice { + name: string; + udid: string; + state: string; + isAvailable?: boolean; + runtime?: string; +} + +export class SimulatorTools { + public static async listSimulators(): Promise { + try { + const { stdout } = await execFileAsync('xcrun', ['simctl', 'list', 'devices', '--json']); + const parsed = JSON.parse(stdout) as { devices?: Record }; + const deviceMap = parsed.devices ?? {}; + + let response = 'Available iOS Simulators:\n\n'; + const lines: string[] = []; + + for (const [runtime, devices] of Object.entries(deviceMap)) { + const available = devices.filter((device) => device.isAvailable ?? true); + if (!available.length) continue; + lines.push(`${runtime}:`); + for (const device of available) { + const status = device.state === 'Booted' ? ' [Booted]' : ''; + lines.push(`- ${device.name} (${device.udid})${status}`); + } + lines.push(''); + } + + if (!lines.length) { + response += 'No available simulators were returned by simctl.'; + } else { + response += `${lines.join('\n')}\nNext Steps:\n- Boot a simulator: xcode_boot_sim({ simulator_uuid: 'UUID_FROM_ABOVE' })\n- Open the simulator UI: xcode_open_sim({})`; + } + + return { + content: [ + { + type: 'text', + text: response, + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Logger.error('Failed to list simulators', error); + return { + content: [ + { + type: 'text', + text: `Failed to list simulators: ${message}`, + }, + ], + isError: true, + }; + } + + } + public static async bootSimulator(simulatorUuid: string): Promise { + try { + await execFileAsync('xcrun', ['simctl', 'boot', simulatorUuid]); + return { + content: [ + { + type: 'text', + text: `✅ Simulator ${simulatorUuid} booted successfully.\n\nNext Steps:\n- Make sure the Simulator UI is visible: xcode_open_sim({})\n- Launch your app with xcode_build_and_run or install it manually.`, + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Logger.error(`Failed to boot simulator ${simulatorUuid}`, error); + return { + content: [ + { + type: 'text', + text: `Failed to boot simulator ${simulatorUuid}: ${message}`, + }, + ], + isError: true, + }; + } + } + + public static async openSimulator(): Promise { + try { + await execFileAsync('open', ['-a', 'Simulator']); + return { + content: [ + { + type: 'text', + text: 'Simulator app opened successfully.', + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Logger.error('Failed to open Simulator app', error); + return { + content: [ + { + type: 'text', + text: `Failed to open Simulator app: ${message}`, + }, + ], + isError: true, + }; + } + } + + public static async captureScreenshot( + simulatorUuid?: string, + savePath?: string, + ): Promise { + const target = simulatorUuid ?? 'booted'; + const args = ['simctl', 'io', target, 'screenshot', '--type=png', '-']; + + return new Promise((resolve) => { + const child = spawn('xcrun', args); + const chunks: Buffer[] = []; + const errorChunks: Buffer[] = []; + + child.stdout.on('data', (chunk: Buffer) => chunks.push(chunk)); + child.stderr.on('data', (chunk: Buffer) => errorChunks.push(chunk)); + + child.on('close', async (code) => { + if (code !== 0 || chunks.length === 0) { + const errorMessage = + errorChunks.length > 0 + ? Buffer.concat(errorChunks).toString('utf8') + : `simctl exited with code ${code}`; + Logger.error(`Screenshot capture failed for ${target}: ${errorMessage}`); + resolve({ + content: [ + { + type: 'text', + text: `Failed to capture screenshot: ${errorMessage.trim()}`, + }, + ], + isError: true, + }); + return; + } + + const pngBuffer = Buffer.concat(chunks); + let savedTo: string | undefined; + + if (savePath) { + try { + await fsPromises.writeFile(savePath, pngBuffer); + savedTo = savePath; + } catch (writeError) { + const message = writeError instanceof Error ? writeError.message : String(writeError); + Logger.warn(`Failed to write screenshot to ${savePath}: ${message}`); + } + } + + resolve({ + content: [ + { + type: 'image', + data: pngBuffer.toString('base64'), + mimeType: 'image/png', + }, + ...(savedTo + ? [ + { + type: 'text' as const, + text: `Screenshot saved to ${savedTo}`, + }, + ] + : []), + ], + }); + }); + }); + } + + public static async shutdownSimulator(simulatorUuid: string): Promise { + try { + await execFileAsync('xcrun', ['simctl', 'shutdown', simulatorUuid]); + return { + content: [ + { + type: 'text', + text: `Simulator ${simulatorUuid} has been shut down.`, + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Logger.error(`Failed to shutdown simulator ${simulatorUuid}`, error); + return { + content: [ + { + type: 'text', + text: `Failed to shutdown simulator ${simulatorUuid}: ${message}`, + }, + ], + isError: true, + }; + } + } +} + +export default SimulatorTools; diff --git a/src/tools/SimulatorUiTools.ts b/src/tools/SimulatorUiTools.ts new file mode 100644 index 0000000..80163f8 --- /dev/null +++ b/src/tools/SimulatorUiTools.ts @@ -0,0 +1,306 @@ +import { execFile } from 'child_process'; +import { constants } from 'fs'; +import { access } from 'fs/promises'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { promisify } from 'util'; +import type { McpResult } from '../types/index.js'; +import Logger from '../utils/Logger.js'; + +const execFileAsync = promisify(execFile); + +interface AxeCommandOptions { + simulatorUuid: string; + args: string[]; +} + +interface AxeCommandResult { + success: boolean; + stdout?: string; + stderr?: string; + errorMessage?: string; +} + +class AxeInvoker { + private static cachedPath: string | null | undefined; + + private static async resolveBinary(): Promise { + if (this.cachedPath !== undefined) { + return this.cachedPath; + } + + const envOverride = process.env.XCODEMCP_AXE_PATH ?? process.env.AXE_PATH; + if (envOverride) { + try { + await access(envOverride, constants.X_OK); + this.cachedPath = envOverride; + return envOverride; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + Logger.warn(`AXe path specified but not executable (${envOverride}): ${message}`); + } + } + + // Try bundled binary if it exists (for future packaging) + try { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const candidate = join(__dirname, '..', 'bundled', 'axe'); + await access(candidate, constants.X_OK); + this.cachedPath = candidate; + return candidate; + } catch { + // ignore - bundled binary not present + } + + // Fall back to expecting axe in PATH + this.cachedPath = 'axe'; + return 'axe'; + } + + public static async run(options: AxeCommandOptions): Promise { + const axeBinary = await this.resolveBinary(); + if (!axeBinary) { + return { + success: false, + errorMessage: + "AXe binary not found. Install it with `brew install cameroncooke/axe/axe` or set XCODEMCP_AXE_PATH to the executable's location.", + }; + } + + const fullArgs = [...options.args, '--udid', options.simulatorUuid]; + + try { + const { stdout, stderr } = await execFileAsync(axeBinary, fullArgs, { + env: process.env, + }); + const result: AxeCommandResult = { success: true }; + const trimmedStdout = stdout?.trim(); + const trimmedStderr = stderr?.trim(); + if (trimmedStdout) { + result.stdout = trimmedStdout; + } + if (trimmedStderr) { + result.stderr = trimmedStderr; + } + return result; + } catch (error) { + if (error && typeof error === 'object' && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') { + return { + success: false, + errorMessage: + "AXe binary could not be executed. Install it with `brew install cameroncooke/axe/axe` or set XCODEMCP_AXE_PATH to the executable's location.", + }; + } + + const execError = error as { stderr?: string; stdout?: string; message?: string }; + const payload: AxeCommandResult = { + success: false, + errorMessage: execError.message ?? 'AXe command failed', + }; + const trimmedStdout = execError.stdout?.toString().trim(); + const trimmedStderr = execError.stderr?.toString().trim(); + if (trimmedStdout) { + payload.stdout = trimmedStdout; + } + if (trimmedStderr) { + payload.stderr = trimmedStderr; + } + return payload; + } + } +} + +const describeUiTracker = new Map(); +const DESCRIBE_UI_STALENESS_MS = 60_000; + +function formatCoordinateWarning(simulatorUuid: string): string | null { + const last = describeUiTracker.get(simulatorUuid); + if (!last) { + return 'Tip: call describe_ui first to capture precise coordinates instead of guessing from screenshots.'; + } + + const age = Date.now() - last; + if (age > DESCRIBE_UI_STALENESS_MS) { + const seconds = Math.round(age / 1000); + return `Tip: describe_ui was last run ${seconds}s ago. Run it again if the UI has changed.`; + } + return null; +} + +export class SimulatorUiTools { + public static async describeUI(simulatorUuid: string): Promise { + const result = await AxeInvoker.run({ + simulatorUuid, + args: ['describe-ui'], + }); + + if (!result.success) { + const message = + result.errorMessage ?? + result.stderr ?? + 'AXe could not describe the UI. Ensure the simulator is booted and your app is running.'; + return { + content: [ + { + type: 'text', + text: `Failed to describe UI: ${message}`, + }, + ], + isError: true, + }; + } + + describeUiTracker.set(simulatorUuid, Date.now()); + + const body = result.stdout ?? ''; + return { + content: [ + { + type: 'text', + text: `Accessibility hierarchy for simulator ${simulatorUuid}:\n\`\`\`json\n${body}\n\`\`\`\nUse these frames to drive tap/swipe commands.`, + }, + ], + }; + } + + public static async tap( + simulatorUuid: string, + x: number, + y: number, + options: { preDelay?: number; postDelay?: number } = {}, + ): Promise { + const args = ['xcode_tap', '-x', String(Math.round(x)), '-y', String(Math.round(y))]; + if (options.preDelay !== undefined) { + args.push('--pre-delay', String(options.preDelay)); + } + if (options.postDelay !== undefined) { + args.push('--post-delay', String(options.postDelay)); + } + + const result = await AxeInvoker.run({ simulatorUuid, args }); + if (!result.success) { + const message = + result.errorMessage ?? + result.stderr ?? + 'Tap command failed. Ensure AXe has Accessibility permissions (System Settings > Privacy & Security > Accessibility).'; + return { + content: [ + { + type: 'text', + text: `Failed to tap at (${x}, ${y}): ${message}`, + }, + ], + isError: true, + }; + } + + const warning = formatCoordinateWarning(simulatorUuid); + return { + content: [ + { + type: 'text', + text: `Tap at (${x}, ${y}) executed successfully.${warning ? `\n\n${warning}` : ''}`, + }, + ], + }; + } + + public static async typeText(simulatorUuid: string, text: string): Promise { + const result = await AxeInvoker.run({ + simulatorUuid, + args: ['type', text], + }); + + if (!result.success) { + const message = + result.errorMessage ?? + result.stderr ?? + 'Type command failed. Make sure the target text field is focused (use tap first).'; + return { + content: [ + { + type: 'text', + text: `Failed to type text: ${message}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: 'text', + text: `Typed text "${text}" on simulator ${simulatorUuid}.`, + }, + ], + }; + } + + public static async swipe( + simulatorUuid: string, + start: { x: number; y: number }, + end: { x: number; y: number }, + options: { duration?: number; delta?: number; preDelay?: number; postDelay?: number } = {}, + ): Promise { + const args = [ + 'xcode_swipe', + '--start-x', + String(Math.round(start.x)), + '--start-y', + String(Math.round(start.y)), + '--end-x', + String(Math.round(end.x)), + '--end-y', + String(Math.round(end.y)), + ]; + + if (options.duration !== undefined) { + args.push('--duration', String(options.duration)); + } + if (options.delta !== undefined) { + args.push('--delta', String(options.delta)); + } + if (options.preDelay !== undefined) { + args.push('--pre-delay', String(options.preDelay)); + } + if (options.postDelay !== undefined) { + args.push('--post-delay', String(options.postDelay)); + } + + const result = await AxeInvoker.run({ simulatorUuid, args }); + if (!result.success) { + const message = + result.errorMessage ?? + result.stderr ?? + 'Swipe command failed. Verify coordinates using describe_ui.'; + return { + content: [ + { + type: 'text', + text: `Failed to swipe from (${start.x}, ${start.y}) to (${end.x}, ${end.y}): ${message}`, + }, + ], + isError: true, + }; + } + + const warning = formatCoordinateWarning(simulatorUuid); + return { + content: [ + { + type: 'text', + text: `Swipe from (${start.x}, ${start.y}) to (${end.x}, ${end.y}) executed successfully.${ + warning ? `\n\n${warning}` : '' + }`, + }, + ], + }; + } +} + +export function resetAxeCacheForTesting(): void { + (AxeInvoker as unknown as { cachedPath?: string | null | undefined }).cachedPath = undefined; +} + +export default SimulatorUiTools; diff --git a/src/tools/TestPlanTools.ts b/src/tools/TestPlanTools.ts index 88117b2..db1f293 100644 --- a/src/tools/TestPlanTools.ts +++ b/src/tools/TestPlanTools.ts @@ -1,7 +1,8 @@ import { promises as fs } from 'fs'; +import { dirname, join, basename } from 'path'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import type { McpResult } from '../types/index.js'; -import { Logger } from '../utils/Logger.js'; +import Logger from '../utils/Logger.js'; interface TestTarget { containerPath: string; @@ -60,16 +61,136 @@ export class TestPlanTools { ); } + const splitIdentifier = ( + identifier: string, + targetName: string | undefined + ): { classQualified: string; targetQualified: string } => { + const safeIdentifier = typeof identifier === 'string' ? identifier : ''; + const fallback: { classQualified: string; targetQualified: string } = { + classQualified: String(safeIdentifier), + targetQualified: String(safeIdentifier) + }; + if (!safeIdentifier) return fallback; + const trimmed = safeIdentifier.trim(); + if (!trimmed) return fallback; + + const parts = trimmed.split('/').filter(Boolean); + if (parts.length >= 2) { + const rawMethodName = parts[parts.length - 1] ?? ''; + const methodName = rawMethodName.replace(/\(\)$/,''); + const className = parts[parts.length - 2] ?? ''; + const classQualified = className && methodName ? `${className}/${methodName}` : trimmed; + const targetPart = parts.length === 3 ? parts[0] : targetName; + const targetQualified = targetPart ? `${targetPart}/${classQualified}` : classQualified; + return { + classQualified: String(classQualified), + targetQualified: String(targetQualified) + }; + } + + if (parts.length === 1) { + const classQualified = parts[0]; + const targetQualified = targetName ? `${targetName}/${classQualified}` : classQualified; + return { + classQualified: String(classQualified), + targetQualified: String(targetQualified) + }; + } + + return fallback; + }; + + const normalizedTargets = testTargets.map(config => { + const targetName = config.target?.name; + const normalizedSelected = (config.selectedTests || []).map(identifier => + splitIdentifier(identifier, targetName) + ); + const normalizedSkipped = (config.skippedTests || []).map(identifier => + splitIdentifier(identifier, targetName) + ); + return { + config, + normalizedSelected, + normalizedSkipped + }; + }); + // Update test targets - testPlan.testTargets = testTargets.map(config => ({ - target: { - containerPath: config.target.containerPath, - identifier: config.target.identifier, - name: config.target.name - }, - ...(config.selectedTests && config.selectedTests.length > 0 && { selectedTests: config.selectedTests }), - ...(config.skippedTests && config.skippedTests.length > 0 && { skippedTests: config.skippedTests }) - })); + testPlan.testTargets = normalizedTargets.map(({ config, normalizedSelected, normalizedSkipped }) => { + const selectedClassQualified = normalizedSelected + .map(item => item.classQualified) + .filter((value): value is string => typeof value === 'string' && value.length > 0); + const skippedClassQualified = normalizedSkipped + .map(item => item.classQualified) + .filter((value): value is string => typeof value === 'string' && value.length > 0); + + const targetConfig: any = { + target: { + containerPath: config.target.containerPath, + identifier: config.target.identifier, + name: config.target.name + }, + isEnabled: true + }; + + if (selectedClassQualified.length > 0) { + targetConfig.selectedTests = selectedClassQualified; + targetConfig.enabledTests = selectedClassQualified; + targetConfig.onlyTestIdentifiers = selectedClassQualified; + targetConfig.testSelectionMode = 'selectTests'; + } + + if (skippedClassQualified.length > 0) { + targetConfig.skippedTests = skippedClassQualified; + } + + return targetConfig; + }); + + // If we have explicit selected tests, propagate them to onlyTestIdentifiers + const combinedEnabledTests = normalizedTargets + .flatMap(target => target.normalizedSelected.map(item => item.targetQualified)) + .filter((value): value is string => typeof value === 'string' && value.length > 0) + .filter((value, index, self) => self.indexOf(value) === index); + const combinedOnlyTestIdentifiers = normalizedTargets + .flatMap(target => target.normalizedSelected.map(item => item.targetQualified)) + .filter((value): value is string => typeof value === 'string' && value.length > 0) + .filter((value, index, self) => self.indexOf(value) === index); + + if (!testPlan.defaultOptions) { + testPlan.defaultOptions = {}; + } + + testPlan.defaultOptions.automaticallyIncludeNewTests = combinedOnlyTestIdentifiers.length === 0; + if (combinedOnlyTestIdentifiers.length > 0) { + testPlan.defaultOptions.onlyTestIdentifiers = combinedOnlyTestIdentifiers; + testPlan.defaultOptions.enabledTests = combinedEnabledTests; + testPlan.defaultOptions.testSelectionMode = 'selectTests'; + } else { + delete testPlan.defaultOptions.onlyTestIdentifiers; + delete testPlan.defaultOptions.enabledTests; + delete testPlan.defaultOptions.testSelectionMode; + } + + if (Array.isArray(testPlan.configurations)) { + testPlan.configurations = testPlan.configurations.map(configuration => { + const options = configuration.options || {}; + options.automaticallyIncludeNewTests = combinedOnlyTestIdentifiers.length === 0; + if (combinedOnlyTestIdentifiers.length > 0) { + options.onlyTestIdentifiers = combinedOnlyTestIdentifiers; + options.enabledTests = combinedEnabledTests; + options.testSelectionMode = 'selectTests'; + } else { + delete options.onlyTestIdentifiers; + delete options.enabledTests; + delete options.testSelectionMode; + } + return { + ...configuration, + options + }; + }); + } // Ensure version is set if (!testPlan.version) { @@ -79,6 +200,18 @@ export class TestPlanTools { // Write updated test plan try { await fs.writeFile(testPlanPath, JSON.stringify(testPlan, null, 2), 'utf8'); + Logger.debug(`Test plan '${testPlanPath}' updated with ${combinedEnabledTests.length} selected test identifiers: ${combinedEnabledTests.join(', ')}`); + if ((process.env.LOG_LEVEL || '').toUpperCase() === 'DEBUG') { + const serializedPlan = JSON.stringify(testPlan, null, 2); + await fs.writeFile(`${testPlanPath}.mcp-debug.json`, serializedPlan, 'utf8'); + await fs.writeFile(`${testPlanPath}.mcp-last.json`, serializedPlan, 'utf8'); + try { + const snapshotPath = join(dirname(testPlanPath), `${basename(testPlanPath, '.xctestplan')}.mcp-snapshot.json`); + await fs.writeFile(snapshotPath, serializedPlan, 'utf8'); + } catch (snapshotError) { + Logger.debug(`Failed to write snapshot debug plan: ${snapshotError}`); + } + } } catch (error) { throw new McpError( ErrorCode.InternalError, @@ -202,7 +335,7 @@ export class TestPlanTools { return { content: [{ type: 'text', - text: `Test plan reload triggered:\n${results.join('\n')}\n\nNote: If Xcode doesn't reload the test plan automatically, you may need to use xcode_refresh_project as a fallback.` + text: `Test plan reload triggered:\n${results.join('\n')}\n\nNote: If Xcode doesn't reload the test plan automatically, close and reopen the project manually in Xcode.` }] }; @@ -213,7 +346,7 @@ export class TestPlanTools { return { content: [{ type: 'text', - text: `Failed to trigger test plan reload: ${errorMessage}\n\nRecommendation: Use xcode_refresh_project tool to ensure test plan changes are loaded.` + text: `Failed to trigger test plan reload: ${errorMessage}\n\nRecommendation: Close and reopen the project manually to ensure test plan changes are loaded.` }] }; } @@ -382,4 +515,4 @@ export class TestPlanTools { }; } } -} \ No newline at end of file +} diff --git a/src/tools/XCResultTools.ts b/src/tools/XCResultTools.ts index f85e421..3d7a1d2 100644 --- a/src/tools/XCResultTools.ts +++ b/src/tools/XCResultTools.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'os'; import { join } from 'path'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { XCResultParser } from '../utils/XCResultParser.js'; -import { Logger } from '../utils/Logger.js'; +import Logger from '../utils/Logger.js'; import type { McpResult, TestAttachment } from '../types/index.js'; export class XCResultTools { @@ -331,22 +331,24 @@ export class XCResultTools { output += 'No attachments found for this test.\n'; } else { attachments.forEach((att, index) => { - const filename = att.name || att.filename || 'unnamed'; + const rawFilename = att.name ?? att.filename ?? 'unnamed'; + const filename = typeof rawFilename === 'string' ? rawFilename : String(rawFilename); output += `[${index + 1}] ${filename}\n`; // Determine type from identifier or filename let type = att.uniform_type_identifier || att.uniformTypeIdentifier || ''; if (!type || type === 'unknown') { // Infer type from filename extension or special patterns - const ext = filename.toLowerCase().split('.').pop(); + const lowered = filename.toLowerCase(); + const ext = lowered.split('.').pop(); if (ext === 'jpeg' || ext === 'jpg') type = 'public.jpeg'; else if (ext === 'png') type = 'public.png'; else if (ext === 'mp4') type = 'public.mpeg-4'; else if (ext === 'mov') type = 'com.apple.quicktime-movie'; else if (ext === 'txt') type = 'public.plain-text'; - else if (filename.toLowerCase().includes('app ui hierarchy')) type = 'ui-hierarchy'; - else if (filename.toLowerCase().includes('ui snapshot')) type = 'ui-snapshot'; - else if (filename.toLowerCase().includes('synthesized event')) type = 'synthesized-event'; + else if (lowered.includes('app ui hierarchy')) type = 'ui-hierarchy'; + else if (lowered.includes('ui snapshot')) type = 'ui-snapshot'; + else if (lowered.includes('synthesized event')) type = 'synthesized-event'; else type = 'unknown'; } @@ -482,21 +484,23 @@ export class XCResultTools { ); } - const filename = attachment.filename || attachment.name || `attachment_${attachmentIndex}`; + const rawFilename = attachment.filename ?? attachment.name ?? `attachment_${attachmentIndex}`; + const filename = typeof rawFilename === 'string' ? rawFilename : String(rawFilename); // Determine type from identifier or filename first let type = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; if (!type || type === 'unknown') { // Infer type from filename extension or special patterns - const ext = filename.toLowerCase().split('.').pop(); + const lowered = filename.toLowerCase(); + const ext = lowered.split('.').pop(); if (ext === 'jpeg' || ext === 'jpg') type = 'public.jpeg'; else if (ext === 'png') type = 'public.png'; else if (ext === 'mp4') type = 'public.mpeg-4'; else if (ext === 'mov') type = 'com.apple.quicktime-movie'; else if (ext === 'txt') type = 'public.plain-text'; - else if (filename.toLowerCase().includes('app ui hierarchy')) type = 'ui-hierarchy'; - else if (filename.toLowerCase().includes('ui snapshot')) type = 'ui-snapshot'; - else if (filename.toLowerCase().includes('synthesized event')) type = 'synthesized-event'; + else if (lowered.includes('app ui hierarchy')) type = 'ui-hierarchy'; + else if (lowered.includes('ui snapshot')) type = 'ui-snapshot'; + else if (lowered.includes('synthesized event')) type = 'synthesized-event'; else type = 'unknown'; } @@ -1502,7 +1506,8 @@ export class XCResultTools { // Filter to only image attachments const imageAttachments = attachments.filter(attachment => { const typeId = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; - const filename = attachment.filename || attachment.name || ''; + const rawFilename = attachment.filename ?? attachment.name ?? ''; + const filename = typeof rawFilename === 'string' ? rawFilename : String(rawFilename); return typeId.includes('png') || typeId === 'public.png' || @@ -1552,7 +1557,8 @@ export class XCResultTools { private static findVideoAttachment(attachments: TestAttachment[]): TestAttachment | undefined { return attachments.find(attachment => { const typeId = attachment.uniform_type_identifier || attachment.uniformTypeIdentifier || ''; - const filename = attachment.filename || attachment.name || ''; + const rawFilename = attachment.filename ?? attachment.name ?? ''; + const filename = typeof rawFilename === 'string' ? rawFilename : String(rawFilename); // Check for video type identifiers or video extensions return typeId.includes('mp4') || @@ -1676,4 +1682,6 @@ export class XCResultTools { }); }); } -} \ No newline at end of file +} + +export default XCResultTools; diff --git a/src/types/index.ts b/src/types/index.ts index 2e5f831..b846e67 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -19,6 +19,8 @@ export interface ParsedBuildResults { errors: string[]; warnings: string[]; buildStatus?: string; + errorCount?: number; + warningCount?: number; } export interface EnvironmentValidationResult { @@ -133,4 +135,4 @@ export interface TestAttachment { payloadSize?: number; payload_size?: number; timestamp?: number; -} \ No newline at end of file +} diff --git a/src/utils/BuildLogParser.ts b/src/utils/BuildLogParser.ts index b00a267..4f6895a 100644 --- a/src/utils/BuildLogParser.ts +++ b/src/utils/BuildLogParser.ts @@ -2,7 +2,7 @@ import { spawn, ChildProcess } from 'child_process'; import { readdir, stat, readFile } from 'fs/promises'; import path from 'path'; import os from 'os'; -import { Logger } from './Logger.js'; +import Logger from './Logger.js'; import type { BuildLogInfo, ParsedBuildResults } from '../types/index.js'; interface XCLogParserIssue { @@ -10,6 +10,9 @@ interface XCLogParserIssue { startingLineNumber?: number; startingColumnNumber?: number; title: string; + detail?: string; + severity?: number; + type?: string; } interface XCLogParserResult { @@ -18,6 +21,62 @@ interface XCLogParserResult { buildStatus?: string; } +interface XCLogParserSummaryResult { + buildStatus?: string; + warningCount?: number; + errorCount?: number; + warnings?: XCLogParserIssue[]; + errors?: XCLogParserIssue[]; + notes?: XCLogParserIssue[]; +} + +const FAILURE_STATUS_TOKENS = ['fail', 'error', 'cancel', 'terminate', 'abort']; + +function formatIssue(issue: XCLogParserIssue): string { + const fileName = issue.documentURL ? issue.documentURL.replace('file://', '') : 'Unknown file'; + const line = issue.startingLineNumber; + const column = issue.startingColumnNumber; + const detail = typeof issue.detail === 'string' ? issue.detail.replace(/\r/g, '\n').trim() : ''; + + let location = fileName; + if (line && line > 0) { + location += `:${line}`; + if (column && column > 0) { + location += `:${column}`; + } + } + const header = `${location}: ${issue.title}`; + if (detail) { + return `${header}\n${detail}`; + } + return header; +} + +function dedupeIssues(values: string[]): string[] { + return Array.from(new Set(values.filter(value => typeof value === 'string' && value.trim().length > 0))); +} + +function isFailureStatus(status?: string): boolean { + if (!status) return false; + const normalized = status.toLowerCase(); + if (normalized === 'stopped' || normalized === 'interrupted') { + return false; + } + return FAILURE_STATUS_TOKENS.some(token => normalized.includes(token)); +} + +function classifyNote(note: XCLogParserIssue): 'error' | 'warning' | null { + const severity = note.severity ?? 0; + const type = typeof note.type === 'string' ? note.type.toLowerCase() : ''; + if (severity >= 2 || type.includes('error')) { + return 'error'; + } + if (severity >= 1 || type.includes('warning')) { + return 'warning'; + } + return null; +} + export class BuildLogParser { public static async findProjectDerivedData(projectPath: string): Promise { const customDerivedDataLocation = await this.getCustomDerivedDataLocationFromXcodePreferences(); @@ -230,22 +289,94 @@ export class BuildLogParser { } } - public static async parseBuildLog(logPath: string, retryCount = 0, maxRetries = 6): Promise { + public static async getLatestRunLog(projectPath: string, sinceTime?: number): Promise { + const derivedData = await this.findProjectDerivedData(projectPath); + if (!derivedData) return null; + const logsDir = path.join(derivedData, 'Logs', 'Run'); + try { + const files = await readdir(logsDir); + const logFiles = files.filter(file => file.endsWith('.log')); + if (logFiles.length === 0) return null; + let latestLog: BuildLogInfo | null = null; + let latestTime = 0; + for (const logFile of logFiles) { + const fullPath = path.join(logsDir, logFile); + const stats = await stat(fullPath); + const logTime = stats.mtime.getTime(); + if (sinceTime && logTime <= sinceTime) { + continue; + } + if (logTime > latestTime) { + latestTime = logTime; + latestLog = { path: fullPath, mtime: stats.mtime }; + } + } + if (!latestLog && sinceTime) { + return this.getLatestRunLog(projectPath); + } + return latestLog; + } catch { + return null; + } + } + + public static async parseBuildLog( + logPath: string, + retryCount = 0, + maxRetries = 6, + options: { timeoutMs?: number } = {}, + ): Promise { const delays = [1000, 2000, 3000, 5000, 8000, 13000]; return new Promise((resolve) => { const command: ChildProcess = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'issues']); let stdout = ''; let stderr = ''; - - // Ensure child process cleanup on exit - const cleanup = () => { + + const handleProcessExit = () => { if (command && !command.killed) { command.kill('SIGTERM'); } }; - process.once('exit', cleanup); - process.once('SIGTERM', cleanup); - process.once('SIGINT', cleanup); + process.once('exit', handleProcessExit); + process.once('SIGTERM', handleProcessExit); + process.once('SIGINT', handleProcessExit); + + const cleanup = () => { + process.removeListener('exit', handleProcessExit); + process.removeListener('SIGTERM', handleProcessExit); + process.removeListener('SIGINT', handleProcessExit); + }; + + let resolved = false; + const resolveOnce = (result: ParsedBuildResults) => { + if (resolved) { + return; + } + resolved = true; + if (timeoutId) { + clearTimeout(timeoutId); + } + cleanup(); + resolve(result); + }; + + const timeoutMs = options.timeoutMs ?? 0; + const timeoutId = + timeoutMs > 0 + ? setTimeout(() => { + Logger.warn( + `XCLogParser issues reporter exceeded ${timeoutMs}ms, aborting parse for ${logPath}`, + ); + handleProcessExit(); + resolveOnce({ + errors: [ + `XCLogParser did not finish within ${Math.round(timeoutMs / 1000)}s while parsing ${logPath}.`, + 'Open the log in Xcode for full details.', + ], + warnings: [], + }); + }, timeoutMs) + : undefined; command.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); @@ -255,11 +386,14 @@ export class BuildLogParser { stderr += data.toString(); }); - command.on('close', (code: number | null) => { - // Remove cleanup handlers once process closes - process.removeListener('exit', cleanup); - process.removeListener('SIGTERM', cleanup); - process.removeListener('SIGINT', cleanup); + command.on('close', async (code: number | null) => { + if (resolved) { + return; + } + cleanup(); + if (timeoutId) { + clearTimeout(timeoutId); + } if (code !== 0) { const errorMessage = stderr.trim() || 'No error details available'; @@ -275,14 +409,14 @@ export class BuildLogParser { Logger.debug(`Retrying in ${delays[retryCount]}ms...`); setTimeout(async () => { - const result = await this.parseBuildLog(logPath, retryCount + 1, maxRetries); - resolve(result); + const result = await this.parseBuildLog(logPath, retryCount + 1, maxRetries, options); + resolveOnce(result); }, delays[retryCount]!); return; } Logger.error('xclogparser failed:', stderr); - resolve({ + resolveOnce({ errors: [ 'XCLogParser failed to parse the build log.', '', @@ -302,50 +436,114 @@ export class BuildLogParser { try { const result: XCLogParserResult = JSON.parse(stdout); - const errors = [...new Set((result.errors || []).map(error => { - const fileName = error.documentURL ? error.documentURL.replace('file://', '') : 'Unknown file'; - const line = error.startingLineNumber; - const column = error.startingColumnNumber; + let errors = dedupeIssues((result.errors || []).map(formatIssue)); + let warnings = dedupeIssues((result.warnings || []).map(formatIssue)); + + const summary = await this.parseBuildSummary(logPath); + let buildStatus: string | undefined; + let summaryErrorCount: number | undefined; + let summaryWarningCount: number | undefined; + const deferredFallbackMessages: string[] = []; + + if (summary) { + buildStatus = summary.buildStatus; + summaryErrorCount = summary.errorCount; + summaryWarningCount = summary.warningCount; - let location = fileName; - if (line && line > 0) { - location += `:${line}`; - if (column && column > 0) { - location += `:${column}`; - } + if (summary.errors && summary.errors.length > 0) { + errors = dedupeIssues([...errors, ...summary.errors.map(formatIssue)]); } - return `${location}: ${error.title}`; - }))]; - - const warnings = [...new Set((result.warnings || []).map(warning => { - const fileName = warning.documentURL ? warning.documentURL.replace('file://', '') : 'Unknown file'; - const line = warning.startingLineNumber; - const column = warning.startingColumnNumber; + if (summary.warnings && summary.warnings.length > 0) { + warnings = dedupeIssues([...warnings, ...summary.warnings.map(formatIssue)]); + } + + const summaryNotes = summary.notes ?? []; - let location = fileName; - if (line && line > 0) { - location += `:${line}`; - if (column && column > 0) { - location += `:${column}`; + if (errors.length === 0) { + const failureStatus = isFailureStatus(summary.buildStatus); + const noteFailure = summaryNotes.find(note => classifyNote(note) === 'error'); + const reportedErrorCount = summary.errorCount ?? summary.errors?.length ?? 0; + + if (failureStatus || (reportedErrorCount > 0 && (!summary.errors || summary.errors.length === 0))) { + const errorDescriptor = summary.buildStatus + ? `Xcode reported build status '${summary.buildStatus}'` + : `Xcode reported ${reportedErrorCount} build error${reportedErrorCount === 1 ? '' : 's'} in the log summary`; + + const fallbackMessage = `${errorDescriptor} for log ${logPath}, but detailed issues were not available. Open the log in Xcode for full context.`; + Logger.warn(`XCLogParser summary indicates a failure without detailed issues for ${logPath} (status=${summary.buildStatus || 'unknown'}, errors=${reportedErrorCount}).`); + deferredFallbackMessages.push(fallbackMessage); + } else if (noteFailure) { + errors = dedupeIssues([...errors, formatIssue(noteFailure)]); } } - - return `${location}: ${warning.title}`; - }))]; + + if (warnings.length === 0 && summaryWarningCount && summaryWarningCount > 0 && summaryNotes.length > 0) { + const warningNotes = summaryNotes + .filter(note => classifyNote(note) === 'warning') + .map(formatIssue); + if (warningNotes.length > 0) { + warnings = dedupeIssues([...warnings, ...warningNotes]); + } + } + } const buildResult: ParsedBuildResults = { errors, warnings }; - if (result.buildStatus) { + if (buildStatus) { + buildResult.buildStatus = buildStatus; + } else if (result.buildStatus) { buildResult.buildStatus = result.buildStatus; } - resolve(buildResult); + if (typeof summaryErrorCount === 'number') { + buildResult.errorCount = summaryErrorCount; + } + if (typeof summaryWarningCount === 'number') { + buildResult.warningCount = summaryWarningCount; + } + + const summaryIndicatesFailure = summary + ? isFailureStatus(summary.buildStatus) || (summary.errorCount ?? 0) > 0 + : false; + const issuesIndicateFailure = isFailureStatus(result.buildStatus); + + if (buildResult.errors.length === 0 && (summaryIndicatesFailure || issuesIndicateFailure)) { + const fallbackIssues = await this.parseDetailedIssues(logPath); + if (fallbackIssues.errors.length > 0) { + buildResult.errors = dedupeIssues([ + ...buildResult.errors, + ...fallbackIssues.errors.map(formatIssue), + ]); + const currentErrorCount = buildResult.errorCount ?? 0; + buildResult.errorCount = Math.max(currentErrorCount, fallbackIssues.errorCount); + } + if (fallbackIssues.warnings.length > 0) { + buildResult.warnings = dedupeIssues([ + ...buildResult.warnings, + ...fallbackIssues.warnings.map(formatIssue), + ]); + const currentWarningCount = buildResult.warningCount ?? 0; + buildResult.warningCount = Math.max(currentWarningCount, fallbackIssues.warningCount); + } + if (buildResult.errors.length === 0 && fallbackIssues.notes.length > 0) { + buildResult.errors = dedupeIssues([ + ...buildResult.errors, + ...fallbackIssues.notes.map(formatIssue), + ]); + } + } + + if (buildResult.errors.length === 0 && deferredFallbackMessages.length > 0) { + buildResult.errors = dedupeIssues(deferredFallbackMessages); + } + + resolveOnce(buildResult); } catch (parseError) { const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); Logger.error('Failed to parse xclogparser output:', parseError); - resolve({ + resolveOnce({ errors: [ 'Failed to parse XCLogParser JSON output.', '', @@ -362,8 +560,15 @@ export class BuildLogParser { }); command.on('error', (err: Error) => { + if (resolved) { + return; + } + cleanup(); + if (timeoutId) { + clearTimeout(timeoutId); + } Logger.error('Failed to run xclogparser:', err); - resolve({ + resolveOnce({ errors: [ 'XCLogParser is required to parse Xcode build logs but is not installed.', '', @@ -379,6 +584,195 @@ export class BuildLogParser { }); } + private static async parseBuildSummary(logPath: string): Promise { + return new Promise((resolve) => { + const command: ChildProcess = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'summaryJson']); + let stdout = ''; + let stderr = ''; + + const cleanup = () => { + if (command && !command.killed) { + command.kill('SIGTERM'); + } + }; + process.once('exit', cleanup); + process.once('SIGTERM', cleanup); + process.once('SIGINT', cleanup); + + command.stdout?.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + + command.stderr?.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + + command.on('close', (code: number | null) => { + process.removeListener('exit', cleanup); + process.removeListener('SIGTERM', cleanup); + process.removeListener('SIGINT', cleanup); + + if (code !== 0) { + if (stderr.trim()) { + Logger.warn(`XCLogParser summary reporter exited with code ${code}: ${stderr.trim()}`); + } + resolve(null); + return; + } + + try { + const summary: XCLogParserSummaryResult = JSON.parse(stdout); + resolve(summary); + } catch (error) { + Logger.warn('Failed to parse XCLogParser summary output:', error); + resolve(null); + } + }); + + command.on('error', (err: Error) => { + process.removeListener('exit', cleanup); + process.removeListener('SIGTERM', cleanup); + process.removeListener('SIGINT', cleanup); + Logger.warn('Failed to run xclogparser summary reporter:', err); + resolve(null); + }); + }); + } + + private static async parseDetailedIssues(logPath: string): Promise<{ + errors: XCLogParserIssue[]; + warnings: XCLogParserIssue[]; + notes: XCLogParserIssue[]; + errorCount: number; + warningCount: number; + }> { + return new Promise((resolve) => { + const command: ChildProcess = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'json']); + let stdout = ''; + let stderr = ''; + + const cleanup = () => { + if (command && !command.killed) { + command.kill('SIGTERM'); + } + }; + process.once('exit', cleanup); + process.once('SIGTERM', cleanup); + process.once('SIGINT', cleanup); + + command.stdout?.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + + command.stderr?.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + + const finish = () => { + process.removeListener('exit', cleanup); + process.removeListener('SIGTERM', cleanup); + process.removeListener('SIGINT', cleanup); + }; + + command.on('close', (code: number | null) => { + finish(); + if (code !== 0) { + if (stderr.trim()) { + Logger.warn(`XCLogParser JSON reporter exited with code ${code}: ${stderr.trim()}`); + } + resolve({ + errors: [], + warnings: [], + notes: [], + errorCount: 0, + warningCount: 0, + }); + return; + } + + try { + const root = JSON.parse(stdout); + + const aggregated = { + errors: [] as XCLogParserIssue[], + warnings: [] as XCLogParserIssue[], + notes: [] as XCLogParserIssue[], + }; + let maxErrorCount = 0; + let maxWarningCount = 0; + + const visit = (node: any): void => { + if (!node || typeof node !== 'object') { + return; + } + + if (typeof node.errorCount === 'number') { + maxErrorCount = Math.max(maxErrorCount, node.errorCount); + } + if (typeof node.warningCount === 'number') { + maxWarningCount = Math.max(maxWarningCount, node.warningCount); + } + + if (Array.isArray(node.errors)) { + node.errors.forEach((issue: XCLogParserIssue) => aggregated.errors.push(issue)); + } + + if (Array.isArray(node.warnings)) { + node.warnings.forEach((issue: XCLogParserIssue) => aggregated.warnings.push(issue)); + } + + if (Array.isArray(node.notes)) { + node.notes.forEach((issue: XCLogParserIssue) => { + aggregated.notes.push(issue); + const classification = classifyNote(issue); + if (classification === 'error') { + aggregated.errors.push(issue); + } else if (classification === 'warning') { + aggregated.warnings.push(issue); + } + }); + } + + if (Array.isArray(node.subSteps)) { + node.subSteps.forEach((sub: any) => visit(sub)); + } + }; + + visit(root); + + resolve({ + errors: aggregated.errors, + warnings: aggregated.warnings, + notes: aggregated.notes, + errorCount: maxErrorCount, + warningCount: maxWarningCount, + }); + } catch (error) { + Logger.warn('Failed to parse XCLogParser JSON output for detailed issues:', error); + resolve({ + errors: [], + warnings: [], + notes: [], + errorCount: 0, + warningCount: 0, + }); + } + }); + + command.on('error', (err: Error) => { + finish(); + Logger.warn('Failed to run xclogparser JSON reporter:', err); + resolve({ + errors: [], + warnings: [], + notes: [], + errorCount: 0, + warningCount: 0, + }); + }); + }); + } + public static async canParseLog(logPath: string): Promise { return new Promise((resolve) => { const command: ChildProcess = spawn('xclogparser', ['parse', '--file', logPath, '--reporter', 'issues']); @@ -415,4 +809,4 @@ export class BuildLogParser { warnings: [], }; } -} \ No newline at end of file +} diff --git a/src/utils/BuildLogStore.ts b/src/utils/BuildLogStore.ts new file mode 100644 index 0000000..3431f20 --- /dev/null +++ b/src/utils/BuildLogStore.ts @@ -0,0 +1,119 @@ +import { randomUUID } from 'crypto'; +import Path from 'path'; +import Logger from './Logger.js'; + +export type BuildLogStatus = 'active' | 'completed' | 'failed'; +export type BuildLogAction = 'build' | 'run' | 'test'; +export type BuildLogKind = 'build' | 'run'; + +export interface BuildLogRecord { + id: string; + projectPath: string; + logPath: string; + schemeName?: string | null; + destination?: string | null; + action: BuildLogAction; + logKind: BuildLogKind; + status: BuildLogStatus; + createdAt: number; + updatedAt: number; + completedAt?: number; + buildStatus?: string | null; +} + +export class BuildLogStore { + private static readonly MAX_LOGS_PER_PROJECT = 10; + private static logs = new Map(); + private static projectIndex = new Map(); + + public static registerLog(params: { + projectPath: string; + logPath: string; + schemeName?: string | null; + destination?: string | null; + action?: BuildLogAction; + logKind?: BuildLogKind; + }): BuildLogRecord { + const id = randomUUID(); + const now = Date.now(); + const record: BuildLogRecord = { + id, + projectPath: params.projectPath, + logPath: params.logPath, + schemeName: params.schemeName ?? null, + destination: params.destination ?? null, + action: params.action ?? 'build', + logKind: params.logKind ?? 'build', + status: 'active', + createdAt: now, + updatedAt: now, + }; + + this.logs.set(id, record); + this.indexProjectLog(record.projectPath, id); + Logger.debug( + `Registered ${record.logKind} log ${id} for ${Path.basename(record.projectPath)} (${record.action}) -> ${record.logPath}`, + ); + return record; + } + + public static updateStatus( + logId: string | null | undefined, + status: BuildLogStatus, + extras?: Partial>, + ): void { + if (!logId) return; + const record = this.logs.get(logId); + if (!record) return; + record.status = status; + record.updatedAt = Date.now(); + if (status !== 'active') { + record.completedAt = record.updatedAt; + } + if (extras?.buildStatus !== undefined) { + record.buildStatus = extras.buildStatus; + } + Logger.debug( + `Updated log ${logId}: status=${status}${record.buildStatus ? `, buildStatus=${record.buildStatus}` : ''}`, + ); + } + + public static getLog(logId: string): BuildLogRecord | undefined { + return this.logs.get(logId); + } + + public static getLatestLogForProject(projectPath: string, logKind?: BuildLogKind): BuildLogRecord | undefined { + const ids = this.projectIndex.get(projectPath); + if (!ids) return undefined; + for (const id of ids) { + const record = this.logs.get(id); + if (record && (!logKind || record.logKind === logKind)) { + return record; + } + } + return undefined; + } + + public static listLogsForProject(projectPath: string, logKind?: BuildLogKind): BuildLogRecord[] { + const ids = this.projectIndex.get(projectPath); + if (!ids) return []; + const records = ids + .map(id => this.logs.get(id)) + .filter((record): record is BuildLogRecord => Boolean(record)); + return logKind ? records.filter(record => record.logKind === logKind) : records; + } + + private static indexProjectLog(projectPath: string, logId: string): void { + const existing = this.projectIndex.get(projectPath) ?? []; + existing.unshift(logId); + while (existing.length > this.MAX_LOGS_PER_PROJECT) { + const removedId = existing.pop(); + if (removedId) { + this.logs.delete(removedId); + } + } + this.projectIndex.set(projectPath, existing); + } +} + +export default BuildLogStore; diff --git a/src/utils/EnvironmentValidator.ts b/src/utils/EnvironmentValidator.ts index 1642579..4960e97 100644 --- a/src/utils/EnvironmentValidator.ts +++ b/src/utils/EnvironmentValidator.ts @@ -1,5 +1,6 @@ import { spawn, ChildProcess } from 'child_process'; import { existsSync, readFileSync } from 'fs'; +import { readdir, stat } from 'fs/promises'; import { platform } from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -535,10 +536,22 @@ export class EnvironmentValidator { public static async createHealthCheckReport(): Promise { const results = await this.validateEnvironment(); const version = this.getVersion(); + const metadata = await this.getBuildMetadata(); + const metadataLines: string[] = []; + if (metadata.buildTimestamp) { + metadataLines.push(`Build artifacts last updated: ${metadata.buildTimestamp.toISOString()}`); + } + if (metadata.latestSourceChange) { + metadataLines.push(`Latest TypeScript source change: ${metadata.latestSourceChange.toISOString()}`); + } + if (metadataLines.length > 0) { + metadataLines.push(''); + } const report = [ `XcodeMCP Configuration Health Check (v${version})`, '='.repeat(50), '', + ...metadataLines, this.generateValidationSummary(results), '' ]; @@ -582,4 +595,66 @@ export class EnvironmentValidator { return report.join('\n'); } -} \ No newline at end of file + + private static async getBuildMetadata(): Promise<{ + buildTimestamp?: Date; + latestSourceChange?: Date; + }> { + try { + const __filename = fileURLToPath(import.meta.url); + const rootDir = path.resolve(path.dirname(__filename), '..', '..'); + const distDir = path.join(rootDir, 'dist'); + const srcDir = path.join(rootDir, 'src'); + + const buildTimestamp = await this.getLatestFileMTime(distDir); + const latestSourceChange = await this.getLatestFileMTime( + srcDir, + filePath => filePath.endsWith('.ts') || filePath.endsWith('.tsx'), + ); + + const metadata: { buildTimestamp?: Date; latestSourceChange?: Date } = {}; + if (buildTimestamp) { + metadata.buildTimestamp = buildTimestamp; + } + if (latestSourceChange) { + metadata.latestSourceChange = latestSourceChange; + } + return metadata; + } catch { + return {}; + } + } + + private static async getLatestFileMTime( + directory: string, + predicate?: (filePath: string) => boolean, + ): Promise { + try { + const entries = await readdir(directory, { withFileTypes: true }); + let latest: Date | undefined; + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + const childLatest = await this.getLatestFileMTime(fullPath, predicate); + if (childLatest && (!latest || childLatest > latest)) { + latest = childLatest; + } + } else if (!predicate || predicate(fullPath)) { + try { + const stats = await stat(fullPath); + if (!latest || stats.mtime > latest) { + latest = stats.mtime; + } + } catch { + // Ignore files we can't stat + } + } + } + + return latest; + } catch { + return undefined; + } + } +} diff --git a/src/utils/ErrorHelper.ts b/src/utils/ErrorHelper.ts index 939f78a..3f14634 100644 --- a/src/utils/ErrorHelper.ts +++ b/src/utils/ErrorHelper.ts @@ -126,4 +126,6 @@ export class ErrorHelper { return null; } -} \ No newline at end of file +} + +export default ErrorHelper; diff --git a/src/utils/LockManager.ts b/src/utils/LockManager.ts new file mode 100644 index 0000000..18b60cd --- /dev/null +++ b/src/utils/LockManager.ts @@ -0,0 +1,679 @@ +import { watch } from 'fs'; +import type { FSWatcher } from 'fs'; +import { + mkdir, + readFile, + writeFile, + rm, + readdir, + stat, + open, + rename, +} from 'fs/promises'; +import { basename, dirname, join, resolve } from 'path'; +import { homedir } from 'os'; +import { randomUUID, createHash } from 'crypto'; +import Logger from './Logger.js'; + +export interface LockFileData { + filePath: string; + fileName: string; + path: string; + reason: string; + command: string; + lockedAt: string; + lockId: string; + version: number; + queueDepth: number; + queuePosition: number; +} + +export interface BlockedLockInfo extends LockFileData { + waitedMs: number; +} + +export interface LockAcquisition { + lock: LockFileData; + blockedBy?: BlockedLockInfo; + footerText: string; +} + +export interface ReleaseResult { + released: boolean; + info?: LockFileData | null; +} + +export interface ReleaseAllResult { + released: number; + details: LockFileData[]; +} + +interface LockQueueEntry { + id: string; + reason: string; + command: string; + createdAt: string; + lockedAt: string | null; +} + +interface LockFileState { + version: number; + path: string; + queue: LockQueueEntry[]; +} + +interface FooterContext { + projectPath: string; + lock: LockFileData; + blockedBy?: BlockedLockInfo; + commandName: string; +} + +export default class LockManager { + private static lockDirPromise: Promise | null = null; + private static customLockDir: string | null = null; + private static readonly LOCK_VERSION = 2; + private static readonly MAX_REASON_LENGTH = 160; + private static readonly MUTEX_SUFFIX = '.mutex'; + + public static getMaxReasonLength(): number { + return this.MAX_REASON_LENGTH; + } + + /** + * Override lock directory (tests only) + */ + public static setCustomLockDirectory(dir: string | null): void { + this.customLockDir = dir ? resolve(dir) : null; + this.lockDirPromise = null; + } + + private static resolveLockDir(): string { + if (this.customLockDir) { + return this.customLockDir; + } + const override = process.env.XCODE_MCP_LOCK_DIR?.trim(); + if (override) { + return resolve(override); + } + const home = homedir(); + if (process.platform === 'darwin') { + return join(home, 'Library', 'Application Support', 'XcodeMCP', 'locks'); + } + return join(home, '.xcodemcp', 'locks'); + } + + private static async ensureLockDir(): Promise { + if (!this.lockDirPromise) { + this.lockDirPromise = (async () => { + const dir = this.resolveLockDir(); + await mkdir(dir, { recursive: true, mode: 0o755 }); + return dir; + })().catch(error => { + this.lockDirPromise = null; + throw error; + }); + } + return this.lockDirPromise; + } + + private static slugify(projectPath: string): string { + const baseName = basename(projectPath) + .replace(/\.(xcworkspace|xcodeproj)$/i, '') + .trim(); + const slug = baseName + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + return slug || 'xcode-project'; + } + + private static deriveDeterministicUuid(projectPath: string): string { + const hash = createHash('sha1').update(projectPath).digest(); + const bytes = Buffer.alloc(16); + hash.copy(bytes, 0, 0, 16); + bytes[6] = (((bytes[6] ?? 0) & 0x0f) | 0x50) as number; // version 5 + bytes[8] = (((bytes[8] ?? 0) & 0x3f) | 0x80) as number; // variant + const hex = bytes.toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; + } + + private static async getLockFilePath(projectPath: string): Promise { + const dir = await this.ensureLockDir(); + const slug = this.slugify(projectPath); + const deterministic = this.deriveDeterministicUuid(projectPath); + return join(dir, `${slug}-${deterministic}.yaml`); + } + + private static async fileExists(filePath: string): Promise { + try { + await stat(filePath); + return true; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return false; + } + throw error; + } + } + + private static toSnapshot( + lockFilePath: string, + entry: LockQueueEntry, + queuePosition: number, + queueDepth: number, + projectPath: string, + ): LockFileData { + return { + filePath: lockFilePath, + fileName: basename(lockFilePath), + path: projectPath, + reason: entry.reason, + command: entry.command, + lockedAt: entry.lockedAt ?? entry.createdAt, + lockId: entry.id, + version: this.LOCK_VERSION, + queueDepth, + queuePosition, + }; + } + + private static buildSnapshot(lockFilePath: string, state: LockFileState, queuePosition: number): LockFileData { + const entry = state.queue[queuePosition]; + if (!entry) { + throw new Error(`Invalid queue position ${queuePosition} for lock file ${lockFilePath}`); + } + return this.toSnapshot(lockFilePath, entry, queuePosition, state.queue.length, state.path); + } + + private static parseLockFile(raw: string, filePath: string): LockFileState | null { + const lines = raw.split(/\r?\n/); + const state: LockFileState = { + version: this.LOCK_VERSION, + path: '', + queue: [], + }; + + let inQueue = false; + let currentEntry: Partial | null = null; + + const flushEntry = () => { + if (!currentEntry) return; + if (currentEntry.id && currentEntry.reason && currentEntry.command && currentEntry.createdAt) { + state.queue.push({ + id: currentEntry.id, + reason: currentEntry.reason, + command: currentEntry.command, + createdAt: currentEntry.createdAt, + lockedAt: currentEntry.lockedAt ?? null, + }); + } + currentEntry = null; + }; + + const normalizeKey = (key: string): keyof LockQueueEntry => { + switch (key) { + case 'created-at': + return 'createdAt'; + case 'locked-at': + return 'lockedAt'; + default: + return key as keyof LockQueueEntry; + } + }; + + for (const line of lines) { + if (!line.trim()) continue; + if (line.startsWith('lock-version')) { + const [, value = ''] = line.split(':'); + state.version = Number(value.trim()) || this.LOCK_VERSION; + continue; + } + if (line.startsWith('path:')) { + const [, value = ''] = line.split(':'); + state.path = this.parseScalar(value.trim()); + continue; + } + if (line.startsWith('queue:')) { + inQueue = true; + continue; + } + if (!inQueue) { + continue; + } + if (line.startsWith(' - ')) { + flushEntry(); + const remainder = line.slice(4); + const [rawKey, ...rest] = remainder.split(':'); + if (!rawKey) { + currentEntry = null; + continue; + } + currentEntry = {}; + const normalizedKey = normalizeKey(rawKey.trim()); + currentEntry[normalizedKey] = this.parseScalar(rest.join(':').trim()); + continue; + } + if (currentEntry && line.startsWith(' ')) { + const trimmed = line.trim(); + const [rawKey, ...rest] = trimmed.split(':'); + if (!rawKey) { + continue; + } + const normalizedKey = normalizeKey(rawKey.trim()); + currentEntry[normalizedKey] = this.parseScalar(rest.join(':').trim()); + } + } + + flushEntry(); + + if (!state.path) { + Logger.warn(`Lock file ${filePath} missing path metadata`); + return null; + } + + return state; + } + + private static parseScalar(raw: string): any { + if (raw === 'null') return null; + if (raw === 'true') return true; + if (raw === 'false') return false; + if (!raw) return ''; + if (raw.startsWith('"')) { + try { + return JSON.parse(raw); + } catch { + return raw.slice(1, -1); + } + } + if (raw.startsWith("'")) { + return raw.slice(1, -1); + } + return raw; + } + + private static serializeState(state: LockFileState): string { + const lines = [ + `lock-version: ${state.version}`, + `path: ${JSON.stringify(state.path)}`, + 'queue:', + ]; + for (const entry of state.queue) { + lines.push(` - id: ${JSON.stringify(entry.id)}`); + lines.push(` command: ${JSON.stringify(entry.command)}`); + lines.push(` reason: ${JSON.stringify(entry.reason)}`); + lines.push(` created-at: ${JSON.stringify(entry.createdAt)}`); + lines.push(` locked-at: ${entry.lockedAt ? JSON.stringify(entry.lockedAt) : 'null'}`); + } + return `${lines.join('\n')}\n`; + } + + private static async readState(lockFilePath: string): Promise { + try { + const raw = await readFile(lockFilePath, 'utf8'); + return this.parseLockFile(raw, lockFilePath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + Logger.warn(`Failed to read lock file ${lockFilePath}: ${(error as Error).message}`); + } + return null; + } + } + + private static async writeState(lockFilePath: string, state: LockFileState): Promise { + if (state.queue.length === 0) { + await rm(lockFilePath, { force: true }); + return; + } + const tempPath = `${lockFilePath}.${randomUUID()}.tmp`; + await writeFile(tempPath, this.serializeState(state), 'utf8'); + await rename(tempPath, lockFilePath); + } + + private static async obtainMutex(lockFilePath: string): Promise<() => Promise> { + const mutexPath = `${lockFilePath}${this.MUTEX_SUFFIX}`; + while (true) { + try { + const handle = await open(mutexPath, 'wx'); + return async () => { + await handle.close(); + await rm(mutexPath, { force: true }); + }; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'EEXIST') { + await new Promise(resolve => setTimeout(resolve, 50)); + continue; + } + throw error; + } + } + } + + private static async withFileMutex(lockFilePath: string, fn: () => Promise): Promise { + const release = await this.obtainMutex(lockFilePath); + try { + return await fn(); + } finally { + await release(); + } + } + + private static async appendEntry( + lockFilePath: string, + projectPath: string, + entry: LockQueueEntry, + ): Promise<{ position: number; state: LockFileState }> { + return this.withFileMutex(lockFilePath, async () => { + let state = await this.readState(lockFilePath); + if (!state) { + state = { + version: this.LOCK_VERSION, + path: projectPath, + queue: [], + }; + } else if (state.path !== projectPath) { + Logger.warn(`Lock file ${lockFilePath} path mismatch (${state.path} vs ${projectPath})`); + state.path = projectPath; + } + + let index = state.queue.findIndex(item => item.id === entry.id); + if (index === -1) { + state.queue.push({ ...entry }); + index = state.queue.length - 1; + } else { + state.queue[index] = { ...state.queue[index], ...entry }; + } + + const head = state.queue[0]; + if (head && !head.lockedAt) { + head.lockedAt = new Date().toISOString(); + } + + await this.writeState(lockFilePath, state); + return { position: index, state }; + }); + } + + private static async waitForTurn(lockFilePath: string, lockId: string): Promise { + const dir = dirname(lockFilePath); + const target = basename(lockFilePath); + + await new Promise((resolve, reject) => { + let settled = false; + let watcher: FSWatcher | null = null; + let safetyTimer: NodeJS.Timeout | null = null; + const cleanup = (error?: Error) => { + if (settled) return; + settled = true; + if (safetyTimer) { + clearInterval(safetyTimer); + } + if (watcher) { + watcher.close(); + } + if (error) { + reject(error); + } else { + resolve(); + } + }; + + const check = async () => { + try { + const state = await this.readState(lockFilePath); + if (!state) { + cleanup(new Error(`Lock file ${lockFilePath} disappeared while waiting`)); + return; + } + const idx = state.queue.findIndex(entry => entry.id === lockId); + if (idx === -1) { + cleanup(new Error(`Lock entry ${lockId} missing from queue`)); + return; + } + if (idx === 0) { + cleanup(); + } + } catch (error) { + cleanup(error as Error); + } + }; + + watcher = watch(dir, { persistent: true, encoding: 'utf8' }, (_eventType, filename) => { + if (!filename) return; + const name = filename; + if (name === target || name === `${target}${this.MUTEX_SUFFIX}`) { + check(); + } + }); + + safetyTimer = setInterval(() => check(), 10000); + + watcher.on('error', err => cleanup(err as Error)); + + check().catch(error => cleanup(error as Error)); + }); + } + + public static async acquireLock(projectPath: string, reason: string, commandName: string): Promise { + const lockFilePath = await this.getLockFilePath(projectPath); + const entry: LockQueueEntry = { + id: randomUUID(), + reason, + command: commandName, + createdAt: new Date().toISOString(), + lockedAt: null, + }; + + let blockedBy: BlockedLockInfo | undefined; + let waitStart: number | null = null; + + while (true) { + const { position, state } = await this.appendEntry(lockFilePath, projectPath, entry); + if (position === 0) { + const snapshot = this.buildSnapshot(lockFilePath, state, 0); + let finalBlocked: BlockedLockInfo | undefined; + if (blockedBy && waitStart) { + finalBlocked = { ...blockedBy, waitedMs: Date.now() - waitStart }; + } else if (blockedBy) { + finalBlocked = blockedBy; + } + + const footerContext: FooterContext = { + projectPath, + lock: snapshot, + commandName, + }; + if (finalBlocked) { + footerContext.blockedBy = finalBlocked; + } + const footerText = this.buildFooterText(footerContext); + const acquisition: LockAcquisition = { + lock: snapshot, + footerText, + }; + if (finalBlocked) { + acquisition.blockedBy = finalBlocked; + } + return acquisition; + } + + if (!blockedBy) { + if (state.queue.length === 0) { + continue; + } + const headSnapshot = this.buildSnapshot(lockFilePath, state, 0); + blockedBy = { + ...headSnapshot, + waitedMs: 0, + }; + waitStart = Date.now(); + Logger.info( + `Queueing for lock on ${projectPath}. Current owner "${blockedBy.reason}" (lock ${blockedBy.lockId}) has priority.`, + ); + } + + await this.waitForTurn(lockFilePath, entry.id); + } + } + + private static buildFooterText(context: FooterContext): string { + const { projectPath, lock, blockedBy, commandName } = context; + const title = + commandName === 'xcode_build_and_run' + ? '🔐 Exclusive Build & Run Lock' + : '🔐 Exclusive Build Lock'; + + const lines = [ + title, + ` • Project: ${projectPath}`, + ` • Reason: ${lock.reason}`, + ` • Command: ${commandName}`, + ` • Queue depth: ${lock.queueDepth} (${lock.queueDepth === 1 ? 'no additional workers' : `${lock.queueDepth - 1} waiting`})`, + ` • Lock ID: ${lock.lockId}`, + ` • Locked at: ${lock.lockedAt}`, + ` • Release via MCP: ${this.buildToolReleaseCommand(projectPath)}`, + ` • Release via CLI: ${this.buildCliReleaseCommand(projectPath)}`, + ' • Release immediately if you are only reviewing logs or build artifacts—idle locks block other workers.', + 'Release this lock after you finish inspecting logs or simulator state so other workers can continue.', + ]; + + if (blockedBy && blockedBy.waitedMs > 0) { + const blockerReason = blockedBy.reason || 'unspecified work'; + lines.push( + `⏳ Waited ${this.formatDuration(blockedBy.waitedMs)} for lock held by "${blockerReason}" (locked ${blockedBy.lockedAt}).`, + ); + } + + return lines.join('\n'); + } + + private static formatDuration(ms: number): string { + if (ms < 1000) { + return `${ms}ms`; + } + const seconds = Math.round(ms / 1000); + if (seconds < 60) { + return `${seconds}s`; + } + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + if (minutes >= 60) { + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`.trim(); + } + return `${minutes}m ${remainingSeconds}s`.trim(); + } + + public static buildToolReleaseCommand(projectPath: string): string { + return `xcode_release_lock({ \"xcodeproj\": ${JSON.stringify(projectPath)} })`; + } + + public static buildCliReleaseCommand(projectPath: string): string { + return `xcodecontrol release-lock --xcodeproj \"${projectPath}\"`; + } + + public static appendFooter(message: string, footerText?: string | null): string { + if (!footerText) { + return message; + } + if (message.includes(footerText)) { + return message; + } + return `${message}\n\n${footerText}`; + } + + public static async releaseLock(projectPath: string): Promise { + const lockFilePath = await this.getLockFilePath(projectPath); + if (!(await this.fileExists(lockFilePath))) { + return { released: false, info: null }; + } + + return this.withFileMutex(lockFilePath, async () => { + const state = await this.readState(lockFilePath); + if (!state || state.queue.length === 0) { + await rm(lockFilePath, { force: true }); + return { released: false, info: null }; + } + + const originalDepth = state.queue.length; + const releasedEntry = state.queue.shift()!; + const info = this.toSnapshot(lockFilePath, releasedEntry, 0, originalDepth, state.path); + + if (state.queue.length === 0) { + await rm(lockFilePath, { force: true }); + return { released: true, info }; + } + + const nextLock = state.queue[0]; + if (nextLock) { + nextLock.lockedAt = new Date().toISOString(); + await this.writeState(lockFilePath, state); + } else { + await rm(lockFilePath, { force: true }); + } + return { released: true, info }; + }); + } + + public static async listLocks(): Promise { + const lockDir = await this.ensureLockDir(); + let entries: string[] = []; + try { + entries = await readdir(lockDir); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return []; + } + throw error; + } + + const details: LockFileData[] = []; + for (const entry of entries) { + if (!entry.endsWith('.yaml')) { + continue; + } + const filePath = join(lockDir, entry); + const state = await this.readState(filePath); + if (!state || state.queue.length === 0) { + continue; + } + details.push(this.buildSnapshot(filePath, state, 0)); + } + return details; + } + + public static async releaseAllLocks(): Promise { + const lockDir = await this.ensureLockDir(); + let entries: string[] = []; + try { + entries = await readdir(lockDir); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return { released: 0, details: [] }; + } + throw error; + } + + const details: LockFileData[] = []; + for (const entry of entries) { + if (!entry.endsWith('.yaml')) { + continue; + } + const filePath = join(lockDir, entry); + const state = await this.readState(filePath); + if (state && state.queue.length > 0) { + details.push(this.buildSnapshot(filePath, state, 0)); + } + await rm(filePath, { force: true }); + await rm(`${filePath}${this.MUTEX_SUFFIX}`, { force: true }).catch(() => {}); + } + + if (details.length > 0) { + Logger.warn(`Force released ${details.length} lock(s) via CLI emergency command.`); + } + + return { released: details.length, details }; + } +} diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index b4dd98e..a805dc4 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -213,6 +213,8 @@ export class Logger { } } +export default Logger; + // Ensure proper cleanup on process exit process.on('exit', async () => { await Logger.flush(); @@ -226,4 +228,4 @@ process.on('SIGINT', async () => { process.on('SIGTERM', async () => { await Logger.flush(); process.exit(0); -}); \ No newline at end of file +}); diff --git a/src/utils/ParameterNormalizer.ts b/src/utils/ParameterNormalizer.ts index 71dbb61..31ad50b 100644 --- a/src/utils/ParameterNormalizer.ts +++ b/src/utils/ParameterNormalizer.ts @@ -8,6 +8,15 @@ export class ParameterNormalizer { // Remove extra whitespace and normalize case let normalized = destination.trim(); + const parsedParams = this._parseDestinationParameters(normalized); + if (parsedParams?.name) { + const capitalizedName = this._capitalizeDeviceName(parsedParams.name); + if (parsedParams.osVersion) { + return `${capitalizedName} (${parsedParams.osVersion})`; + } + return capitalizedName; + } + // Common destination name variations const destinationMappings: Record = { // iPhone variants @@ -68,6 +77,37 @@ export class ParameterNormalizer { return normalized; } + public static getDestinationNameCandidates(destination: string): string[] { + if (!destination || typeof destination !== 'string') { + return []; + } + + const candidates = new Set(); + const trimmed = destination.trim(); + if (trimmed) { + candidates.add(trimmed); + } + + const normalized = this.normalizeDestinationName(destination); + if (normalized) { + candidates.add(normalized); + } + + const parsedParams = this._parseDestinationParameters(destination); + if (parsedParams?.name) { + const baseName = this._capitalizeDeviceName(parsedParams.name); + if (baseName) { + candidates.add(baseName); + + if (parsedParams.osVersion) { + candidates.add(`${baseName} (${parsedParams.osVersion})`); + } + } + } + + return Array.from(candidates); + } + public static normalizeSchemeName(schemeName: string): string { if (!schemeName || typeof schemeName !== 'string') { return schemeName; @@ -111,6 +151,41 @@ export class ParameterNormalizer { return normalized; } + private static _parseDestinationParameters(destination: string): { + name?: string; + osVersion?: string; + } | null { + if (!destination || !destination.includes('=')) { + return null; + } + + const params = destination.split(',') + .map(part => part.trim()) + .map(part => part.split('=').map(token => token.trim())) + .filter(pair => pair.length === 2) as [string, string][]; + + if (!params.length) { + return null; + } + + const parsed: { name?: string; osVersion?: string } = {}; + for (const [rawKey, rawValue] of params) { + const key = rawKey.toLowerCase(); + const value = rawValue; + if (!value) { + continue; + } + + if (key === 'name') { + parsed.name = value; + } else if (key === 'os' || key === 'osversion') { + parsed.osVersion = value.replace(/^iOS\s*/i, '').replace(/^OS\s*/i, '').trim() || value; + } + } + + return Object.keys(parsed).length ? parsed : null; + } + private static _capitalizeDeviceName(name: string): string { const words = name.split(' '); return words.map(word => { @@ -210,4 +285,4 @@ export class ParameterNormalizer { return matrix[str2.length]![str1.length]!; } -} \ No newline at end of file +} diff --git a/src/utils/PathValidator.ts b/src/utils/PathValidator.ts index 0d65c0d..a8941bc 100644 --- a/src/utils/PathValidator.ts +++ b/src/utils/PathValidator.ts @@ -1,6 +1,6 @@ import { existsSync } from 'fs'; import path from 'path'; -import { ErrorHelper } from './ErrorHelper.js'; +import ErrorHelper from './ErrorHelper.js'; import type { McpResult } from '../types/index.js'; export class PathValidator { @@ -107,4 +107,6 @@ export class PathValidator { return null; } -} \ No newline at end of file +} + +export default PathValidator; diff --git a/src/utils/XCResultParser.ts b/src/utils/XCResultParser.ts index 67ef3e3..f8148fa 100644 --- a/src/utils/XCResultParser.ts +++ b/src/utils/XCResultParser.ts @@ -3,7 +3,7 @@ import { existsSync, mkdirSync } from 'fs'; import { stat } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; -import { Logger } from './Logger.js'; +import Logger from './Logger.js'; import type { TestAttachment } from '../types/index.js'; // Data models based on XCResultExplorer @@ -164,8 +164,16 @@ export class XCResultParser { const hasInfoPlist = existsSync(infoPlistPath); const hasDatabase = existsSync(databasePath); const hasData = existsSync(dataPath); + + // Xcode 16+ no longer writes database.sqlite3 for lightweight xcresult bundles. + const essentialFilesReady = hasInfoPlist && hasData; - if (hasInfoPlist && hasDatabase && hasData) { + if (essentialFilesReady) { + if (!hasDatabase) { + Logger.info('database.sqlite3 not present – continuing (new XCResult format)'); + } else { + Logger.info('database.sqlite3 detected'); + } Logger.info('All essential XCResult files are present - ready for stabilization check'); // Fast-path for small XCResult files - skip extensive waiting for files < 5MB @@ -184,19 +192,27 @@ export class XCResultParser { // Log progress every 30 seconds if (Date.now() - lastProgressTime >= 30000) { - Logger.info(`Still waiting for XCResult files - Info.plist: ${hasInfoPlist ? '✓' : '✗'}, database.sqlite3: ${hasDatabase ? '✓' : '✗'}, Data: ${hasData ? '✓' : '✗'}`); + Logger.info(`Still waiting for XCResult files - Info.plist: ${hasInfoPlist ? '✓' : '✗'}, Data: ${hasData ? '✓' : '✗'}, database.sqlite3: ${hasDatabase ? '✓' : 'optional'}`); lastProgressTime = Date.now(); } await new Promise(resolve => setTimeout(resolve, 3000)); // Check every 3 seconds } - if (!existsSync(infoPlistPath) || !existsSync(databasePath) || !existsSync(dataPath)) { + const hasInfoPlistFinal = existsSync(infoPlistPath); + const hasDataFinal = existsSync(dataPath); + const hasDatabaseFinal = existsSync(databasePath); + + if (!hasInfoPlistFinal || !hasDataFinal) { const elapsed = Math.round((Date.now() - startTime) / 60000); Logger.error(`Essential XCResult files did not appear after ${elapsed} minutes`); Logger.error(`This suggests Xcode encountered a serious issue writing the XCResult`); return false; } + + if (!hasDatabaseFinal) { + Logger.info('Proceeding without database.sqlite3 (not present in this XCResult bundle)'); + } // Phase 3: Critical stabilization check - wait until sizes haven't changed for N seconds // This implements user insight: "wait until its size hasnt changed for 10 seconds before trying to read it" @@ -215,23 +231,37 @@ export class XCResultParser { let previousSizes: Record = {}; let stableStartTime: number | null = null; + const includeDatabase = existsSync(databasePath); while (Date.now() - startTime < timeoutMs) { try { const infoPlistStats = await stat(infoPlistPath); - const databaseStats = await stat(databasePath); const dataStats = await stat(dataPath); + let databaseSize = 0; + if (includeDatabase) { + try { + const databaseStats = await stat(databasePath); + databaseSize = databaseStats.size; + } catch (dbError) { + Logger.debug(`Error checking database size: ${dbError}`); + // Treat missing/locked database as instability + stableStartTime = null; + previousSizes = {}; + await new Promise(resolve => setTimeout(resolve, 2000)); + continue; + } + } const currentSizes = { infoPlist: infoPlistStats.size, - database: databaseStats.size, - data: dataStats.size + data: dataStats.size, + ...(includeDatabase ? { database: databaseSize } : {}) }; const sizesMatch = ( previousSizes.infoPlist === currentSizes.infoPlist && - previousSizes.database === currentSizes.database && - previousSizes.data === currentSizes.data + previousSizes.data === currentSizes.data && + (!includeDatabase || previousSizes.database === currentSizes.database) ); if (sizesMatch && Object.keys(previousSizes).length > 0) { @@ -252,7 +282,8 @@ export class XCResultParser { // Sizes changed - reset stability timer if (stableStartTime !== null) { Logger.info(`File sizes changed - restarting stability check`); - Logger.debug(`New sizes - Info.plist: ${currentSizes.infoPlist}, database: ${currentSizes.database}, Data: ${currentSizes.data}`); + const databaseLog = includeDatabase ? `, database: ${currentSizes.database}` : ''; + Logger.debug(`New sizes - Info.plist: ${currentSizes.infoPlist}${databaseLog}, Data: ${currentSizes.data}`); } stableStartTime = null; } @@ -415,13 +446,14 @@ export class XCResultParser { const extractTests = (nodes: any[], depth = 0) => { for (const node of nodes) { // Only include actual test methods (not test classes/suites) - if (node.nodeType === 'Test Case' && node.name && node.result) { + const normalizedResult = this.normalizeResult(node.result); + if (node.nodeType === 'Test Case' && node.name && normalizedResult) { const testInfo = { name: node.name, id: node.nodeIdentifier || 'unknown' }; - - const result = node.result.toLowerCase(); + + const result = normalizedResult.toLowerCase(); if (result === 'failed') { failedTests.push(testInfo); } else if (result === 'passed') { @@ -670,7 +702,8 @@ export class XCResultParser { output += `Name: ${testNode.name}\n`; output += `ID: ${testNode.nodeIdentifier || 'unknown'}\n`; output += `Type: ${testNode.nodeType}\n`; - output += `Result: ${this.getStatusIcon(testNode.result)} ${testNode.result}\n`; + const testResultDisplay = this.normalizeResult(testNode.result) || 'Unknown'; + output += `Result: ${this.getStatusIcon(testNode.result)} ${testResultDisplay}\n`; if (testNode.duration) { output += `Duration: ${testNode.duration}\n`; @@ -678,7 +711,7 @@ export class XCResultParser { output += '\n'; // Show failure details if test failed - if (testNode.result.toLowerCase().includes('fail')) { + if (this.normalizeResult(testNode.result).toLowerCase().includes('fail')) { const failure = analysis.summary.testFailures.find(f => f.testIdentifierString === testNode.nodeIdentifier); if (failure) { output += `❌ Failure Details:\n`; @@ -979,9 +1012,10 @@ export class XCResultParser { if (node.nodeType === 'Test Case') { total = 1; - if (node.result.toLowerCase().includes('pass') || node.result.toLowerCase().includes('success')) { + const lowerResult = this.normalizeResult(node.result).toLowerCase(); + if (lowerResult.includes('pass') || lowerResult.includes('success')) { passed = 1; - } else if (node.result.toLowerCase().includes('fail')) { + } else if (lowerResult.includes('fail')) { failed = 1; } } else if (node.children) { @@ -1047,8 +1081,15 @@ export class XCResultParser { return search(nodes); } - private getStatusIcon(result: string): string { - const lowerResult = result.toLowerCase(); + private normalizeResult(result: string | null | undefined): string { + if (typeof result === 'string') { + return result; + } + return ''; + } + + private getStatusIcon(result?: string | null): string { + const lowerResult = this.normalizeResult(result).toLowerCase(); if (lowerResult.includes('pass') || lowerResult.includes('success')) { return '✅'; } else if (lowerResult.includes('fail')) { @@ -1158,4 +1199,4 @@ export class XCResultParser { findEarliestTime(json); return earliestStartTime; } -} \ No newline at end of file +}