From 80f29cc0f17f568b053c93db6d873c9d795f8833 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 6 Jan 2026 22:22:35 +0000 Subject: [PATCH] Refactor: Pass project directory as cwd to executor Co-authored-by: web --- .../device/__tests__/build_device.test.ts | 24 +++--- .../macos/__tests__/build_run_macos.test.ts | 18 ++-- src/test-utils/mock-executors.ts | 28 ++++-- src/utils/__tests__/build-utils.test.ts | 85 +++++++++++++++++++ src/utils/build-utils.ts | 6 +- 5 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/mcp/tools/device/__tests__/build_device.test.ts b/src/mcp/tools/device/__tests__/build_device.test.ts index b7205691..4d70cada 100644 --- a/src/mcp/tools/device/__tests__/build_device.test.ts +++ b/src/mcp/tools/device/__tests__/build_device.test.ts @@ -132,16 +132,16 @@ describe('build_device plugin', () => { args: string[]; logPrefix: string; silent: boolean; - timeout: number | undefined; + opts: { cwd?: string } | undefined; }> = []; const stubExecutor = async ( args: string[], logPrefix: string, silent: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { - commandCalls.push({ args, logPrefix, silent, timeout }); + commandCalls.push({ args, logPrefix, silent, opts }); return { success: true, output: 'Build succeeded', @@ -175,7 +175,7 @@ describe('build_device plugin', () => { ], logPrefix: 'iOS Device Build', silent: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); }); @@ -184,16 +184,16 @@ describe('build_device plugin', () => { args: string[]; logPrefix: string; silent: boolean; - timeout: number | undefined; + opts: { cwd?: string } | undefined; }> = []; const stubExecutor = async ( args: string[], logPrefix: string, silent: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { - commandCalls.push({ args, logPrefix, silent, timeout }); + commandCalls.push({ args, logPrefix, silent, opts }); return { success: true, output: 'Build succeeded', @@ -227,7 +227,7 @@ describe('build_device plugin', () => { ], logPrefix: 'iOS Device Build', silent: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); }); @@ -293,16 +293,16 @@ describe('build_device plugin', () => { args: string[]; logPrefix: string; silent: boolean; - timeout: number | undefined; + opts: { cwd?: string } | undefined; }> = []; const stubExecutor = async ( args: string[], logPrefix: string, silent: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { - commandCalls.push({ args, logPrefix, silent, timeout }); + commandCalls.push({ args, logPrefix, silent, opts }); return { success: true, output: 'Build succeeded', @@ -342,7 +342,7 @@ describe('build_device plugin', () => { ], logPrefix: 'iOS Device Build', silent: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); }); }); diff --git a/src/mcp/tools/macos/__tests__/build_run_macos.test.ts b/src/mcp/tools/macos/__tests__/build_run_macos.test.ts index bff9a478..529079c0 100644 --- a/src/mcp/tools/macos/__tests__/build_run_macos.test.ts +++ b/src/mcp/tools/macos/__tests__/build_run_macos.test.ts @@ -82,10 +82,10 @@ describe('build_run_macos', () => { command: string[], description: string, logOutput: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { callCount++; - executorCalls.push({ command, description, logOutput, timeout }); + executorCalls.push({ command, description, logOutput, opts }); if (callCount === 1) { // First call for build @@ -131,7 +131,7 @@ describe('build_run_macos', () => { ], description: 'macOS Build', logOutput: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); // Verify build settings command was called @@ -178,10 +178,10 @@ describe('build_run_macos', () => { command: string[], description: string, logOutput: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { callCount++; - executorCalls.push({ command, description, logOutput, timeout }); + executorCalls.push({ command, description, logOutput, opts }); if (callCount === 1) { // First call for build @@ -227,7 +227,7 @@ describe('build_run_macos', () => { ], description: 'macOS Build', logOutput: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); // Verify build settings command was called @@ -445,10 +445,10 @@ describe('build_run_macos', () => { command: string[], description: string, logOutput: boolean, - timeout?: number, + opts?: { cwd?: string }, ) => { callCount++; - executorCalls.push({ command, description, logOutput, timeout }); + executorCalls.push({ command, description, logOutput, opts }); if (callCount === 1) { // First call for build @@ -493,7 +493,7 @@ describe('build_run_macos', () => { ], description: 'macOS Build', logOutput: true, - timeout: undefined, + opts: { cwd: '/path/to' }, }); }); }); diff --git a/src/test-utils/mock-executors.ts b/src/test-utils/mock-executors.ts index bbe20112..eb3ad02e 100644 --- a/src/test-utils/mock-executors.ts +++ b/src/test-utils/mock-executors.ts @@ -33,6 +33,13 @@ export function createMockExecutor( process?: unknown; exitCode?: number; shouldThrow?: Error; + onExecute?: ( + command: string[], + logPrefix?: string, + useShell?: boolean, + opts?: { env?: Record; cwd?: string }, + detached?: boolean, + ) => void; } | Error | string, @@ -65,13 +72,20 @@ export function createMockExecutor( spawnfile: 'sh', } as unknown as ChildProcess; - return async () => ({ - success: result.success ?? true, - output: result.output ?? '', - error: result.error, - process: (result.process ?? mockProcess) as ChildProcess, - exitCode: result.exitCode ?? (result.success === false ? 1 : 0), - }); + return async (command, logPrefix, useShell, opts, detached) => { + // Call onExecute callback if provided + if (result.onExecute) { + result.onExecute(command, logPrefix, useShell, opts, detached); + } + + return { + success: result.success ?? true, + output: result.output ?? '', + error: result.error, + process: (result.process ?? mockProcess) as ChildProcess, + exitCode: result.exitCode ?? (result.success === false ? 1 : 0), + }; + }; } /** diff --git a/src/utils/__tests__/build-utils.test.ts b/src/utils/__tests__/build-utils.test.ts index 9364e76a..838576e8 100644 --- a/src/utils/__tests__/build-utils.test.ts +++ b/src/utils/__tests__/build-utils.test.ts @@ -260,4 +260,89 @@ describe('build-utils Sentry Classification', () => { expect(result.content[0].text).toContain('❌ [stderr] Some error without exit code'); }); }); + + describe('Working Directory (cwd) Handling', () => { + it('should pass project directory as cwd for workspace builds', async () => { + let capturedOptions: any; + const mockExecutor = createMockExecutor({ + success: true, + output: 'BUILD SUCCEEDED', + exitCode: 0, + onExecute: (_command, _logPrefix, _useShell, opts) => { + capturedOptions = opts; + }, + }); + + await executeXcodeBuildCommand( + { + scheme: 'TestScheme', + configuration: 'Debug', + workspacePath: '/path/to/project/MyProject.xcworkspace', + }, + mockPlatformOptions, + false, + 'build', + mockExecutor, + ); + + expect(capturedOptions).toBeDefined(); + expect(capturedOptions.cwd).toBe('/path/to/project'); + }); + + it('should pass project directory as cwd for project builds', async () => { + let capturedOptions: any; + const mockExecutor = createMockExecutor({ + success: true, + output: 'BUILD SUCCEEDED', + exitCode: 0, + onExecute: (_command, _logPrefix, _useShell, opts) => { + capturedOptions = opts; + }, + }); + + await executeXcodeBuildCommand( + { + scheme: 'TestScheme', + configuration: 'Debug', + projectPath: '/path/to/project/MyProject.xcodeproj', + }, + mockPlatformOptions, + false, + 'build', + mockExecutor, + ); + + expect(capturedOptions).toBeDefined(); + expect(capturedOptions.cwd).toBe('/path/to/project'); + }); + + it('should merge cwd with existing execOpts', async () => { + let capturedOptions: any; + const mockExecutor = createMockExecutor({ + success: true, + output: 'BUILD SUCCEEDED', + exitCode: 0, + onExecute: (_command, _logPrefix, _useShell, opts) => { + capturedOptions = opts; + }, + }); + + await executeXcodeBuildCommand( + { + scheme: 'TestScheme', + configuration: 'Debug', + workspacePath: '/path/to/project/MyProject.xcworkspace', + }, + mockPlatformOptions, + false, + 'build', + mockExecutor, + { env: { CUSTOM_VAR: 'value' } }, + ); + + expect(capturedOptions).toBeDefined(); + expect(capturedOptions.cwd).toBe('/path/to/project'); + expect(capturedOptions.env).toEqual({ CUSTOM_VAR: 'value' }); + }); + }); }); diff --git a/src/utils/build-utils.ts b/src/utils/build-utils.ts index 565c4127..4701a100 100644 --- a/src/utils/build-utils.ts +++ b/src/utils/build-utils.ts @@ -227,7 +227,11 @@ export async function executeXcodeBuildCommand( } } else { // Use standard xcodebuild - result = await executor(command, platformOptions.logPrefix, true, execOpts); + // Pass projectDir as cwd to ensure CocoaPods relative paths resolve correctly + result = await executor(command, platformOptions.logPrefix, true, { + ...execOpts, + cwd: projectDir, + }); } // Grep warnings and errors from stdout (build output)