Skip to content

Commit 8333c1a

Browse files
authored
Refactor: Pass project directory as cwd to executor (#167)
1 parent 56376aa commit 8333c1a

5 files changed

Lines changed: 132 additions & 29 deletions

File tree

src/mcp/tools/device/__tests__/build_device.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,16 @@ describe('build_device plugin', () => {
132132
args: string[];
133133
logPrefix: string;
134134
silent: boolean;
135-
timeout: number | undefined;
135+
opts: { cwd?: string } | undefined;
136136
}> = [];
137137

138138
const stubExecutor = async (
139139
args: string[],
140140
logPrefix: string,
141141
silent: boolean,
142-
timeout?: number,
142+
opts?: { cwd?: string },
143143
) => {
144-
commandCalls.push({ args, logPrefix, silent, timeout });
144+
commandCalls.push({ args, logPrefix, silent, opts });
145145
return {
146146
success: true,
147147
output: 'Build succeeded',
@@ -175,7 +175,7 @@ describe('build_device plugin', () => {
175175
],
176176
logPrefix: 'iOS Device Build',
177177
silent: true,
178-
timeout: undefined,
178+
opts: { cwd: '/path/to' },
179179
});
180180
});
181181

@@ -184,16 +184,16 @@ describe('build_device plugin', () => {
184184
args: string[];
185185
logPrefix: string;
186186
silent: boolean;
187-
timeout: number | undefined;
187+
opts: { cwd?: string } | undefined;
188188
}> = [];
189189

190190
const stubExecutor = async (
191191
args: string[],
192192
logPrefix: string,
193193
silent: boolean,
194-
timeout?: number,
194+
opts?: { cwd?: string },
195195
) => {
196-
commandCalls.push({ args, logPrefix, silent, timeout });
196+
commandCalls.push({ args, logPrefix, silent, opts });
197197
return {
198198
success: true,
199199
output: 'Build succeeded',
@@ -227,7 +227,7 @@ describe('build_device plugin', () => {
227227
],
228228
logPrefix: 'iOS Device Build',
229229
silent: true,
230-
timeout: undefined,
230+
opts: { cwd: '/path/to' },
231231
});
232232
});
233233

@@ -293,16 +293,16 @@ describe('build_device plugin', () => {
293293
args: string[];
294294
logPrefix: string;
295295
silent: boolean;
296-
timeout: number | undefined;
296+
opts: { cwd?: string } | undefined;
297297
}> = [];
298298

299299
const stubExecutor = async (
300300
args: string[],
301301
logPrefix: string,
302302
silent: boolean,
303-
timeout?: number,
303+
opts?: { cwd?: string },
304304
) => {
305-
commandCalls.push({ args, logPrefix, silent, timeout });
305+
commandCalls.push({ args, logPrefix, silent, opts });
306306
return {
307307
success: true,
308308
output: 'Build succeeded',
@@ -342,7 +342,7 @@ describe('build_device plugin', () => {
342342
],
343343
logPrefix: 'iOS Device Build',
344344
silent: true,
345-
timeout: undefined,
345+
opts: { cwd: '/path/to' },
346346
});
347347
});
348348
});

src/mcp/tools/macos/__tests__/build_run_macos.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ describe('build_run_macos', () => {
8282
command: string[],
8383
description: string,
8484
logOutput: boolean,
85-
timeout?: number,
85+
opts?: { cwd?: string },
8686
) => {
8787
callCount++;
88-
executorCalls.push({ command, description, logOutput, timeout });
88+
executorCalls.push({ command, description, logOutput, opts });
8989

9090
if (callCount === 1) {
9191
// First call for build
@@ -131,7 +131,7 @@ describe('build_run_macos', () => {
131131
],
132132
description: 'macOS Build',
133133
logOutput: true,
134-
timeout: undefined,
134+
opts: { cwd: '/path/to' },
135135
});
136136

137137
// Verify build settings command was called
@@ -178,10 +178,10 @@ describe('build_run_macos', () => {
178178
command: string[],
179179
description: string,
180180
logOutput: boolean,
181-
timeout?: number,
181+
opts?: { cwd?: string },
182182
) => {
183183
callCount++;
184-
executorCalls.push({ command, description, logOutput, timeout });
184+
executorCalls.push({ command, description, logOutput, opts });
185185

186186
if (callCount === 1) {
187187
// First call for build
@@ -227,7 +227,7 @@ describe('build_run_macos', () => {
227227
],
228228
description: 'macOS Build',
229229
logOutput: true,
230-
timeout: undefined,
230+
opts: { cwd: '/path/to' },
231231
});
232232

233233
// Verify build settings command was called
@@ -445,10 +445,10 @@ describe('build_run_macos', () => {
445445
command: string[],
446446
description: string,
447447
logOutput: boolean,
448-
timeout?: number,
448+
opts?: { cwd?: string },
449449
) => {
450450
callCount++;
451-
executorCalls.push({ command, description, logOutput, timeout });
451+
executorCalls.push({ command, description, logOutput, opts });
452452

453453
if (callCount === 1) {
454454
// First call for build
@@ -493,7 +493,7 @@ describe('build_run_macos', () => {
493493
],
494494
description: 'macOS Build',
495495
logOutput: true,
496-
timeout: undefined,
496+
opts: { cwd: '/path/to' },
497497
});
498498
});
499499
});

src/test-utils/mock-executors.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ export function createMockExecutor(
3333
process?: unknown;
3434
exitCode?: number;
3535
shouldThrow?: Error;
36+
onExecute?: (
37+
command: string[],
38+
logPrefix?: string,
39+
useShell?: boolean,
40+
opts?: { env?: Record<string, string>; cwd?: string },
41+
detached?: boolean,
42+
) => void;
3643
}
3744
| Error
3845
| string,
@@ -65,13 +72,20 @@ export function createMockExecutor(
6572
spawnfile: 'sh',
6673
} as unknown as ChildProcess;
6774

68-
return async () => ({
69-
success: result.success ?? true,
70-
output: result.output ?? '',
71-
error: result.error,
72-
process: (result.process ?? mockProcess) as ChildProcess,
73-
exitCode: result.exitCode ?? (result.success === false ? 1 : 0),
74-
});
75+
return async (command, logPrefix, useShell, opts, detached) => {
76+
// Call onExecute callback if provided
77+
if (result.onExecute) {
78+
result.onExecute(command, logPrefix, useShell, opts, detached);
79+
}
80+
81+
return {
82+
success: result.success ?? true,
83+
output: result.output ?? '',
84+
error: result.error,
85+
process: (result.process ?? mockProcess) as ChildProcess,
86+
exitCode: result.exitCode ?? (result.success === false ? 1 : 0),
87+
};
88+
};
7589
}
7690

7791
/**

src/utils/__tests__/build-utils.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,89 @@ describe('build-utils Sentry Classification', () => {
260260
expect(result.content[0].text).toContain('❌ [stderr] Some error without exit code');
261261
});
262262
});
263+
264+
describe('Working Directory (cwd) Handling', () => {
265+
it('should pass project directory as cwd for workspace builds', async () => {
266+
let capturedOptions: any;
267+
const mockExecutor = createMockExecutor({
268+
success: true,
269+
output: 'BUILD SUCCEEDED',
270+
exitCode: 0,
271+
onExecute: (_command, _logPrefix, _useShell, opts) => {
272+
capturedOptions = opts;
273+
},
274+
});
275+
276+
await executeXcodeBuildCommand(
277+
{
278+
scheme: 'TestScheme',
279+
configuration: 'Debug',
280+
workspacePath: '/path/to/project/MyProject.xcworkspace',
281+
},
282+
mockPlatformOptions,
283+
false,
284+
'build',
285+
mockExecutor,
286+
);
287+
288+
expect(capturedOptions).toBeDefined();
289+
expect(capturedOptions.cwd).toBe('/path/to/project');
290+
});
291+
292+
it('should pass project directory as cwd for project builds', async () => {
293+
let capturedOptions: any;
294+
const mockExecutor = createMockExecutor({
295+
success: true,
296+
output: 'BUILD SUCCEEDED',
297+
exitCode: 0,
298+
onExecute: (_command, _logPrefix, _useShell, opts) => {
299+
capturedOptions = opts;
300+
},
301+
});
302+
303+
await executeXcodeBuildCommand(
304+
{
305+
scheme: 'TestScheme',
306+
configuration: 'Debug',
307+
projectPath: '/path/to/project/MyProject.xcodeproj',
308+
},
309+
mockPlatformOptions,
310+
false,
311+
'build',
312+
mockExecutor,
313+
);
314+
315+
expect(capturedOptions).toBeDefined();
316+
expect(capturedOptions.cwd).toBe('/path/to/project');
317+
});
318+
319+
it('should merge cwd with existing execOpts', async () => {
320+
let capturedOptions: any;
321+
const mockExecutor = createMockExecutor({
322+
success: true,
323+
output: 'BUILD SUCCEEDED',
324+
exitCode: 0,
325+
onExecute: (_command, _logPrefix, _useShell, opts) => {
326+
capturedOptions = opts;
327+
},
328+
});
329+
330+
await executeXcodeBuildCommand(
331+
{
332+
scheme: 'TestScheme',
333+
configuration: 'Debug',
334+
workspacePath: '/path/to/project/MyProject.xcworkspace',
335+
},
336+
mockPlatformOptions,
337+
false,
338+
'build',
339+
mockExecutor,
340+
{ env: { CUSTOM_VAR: 'value' } },
341+
);
342+
343+
expect(capturedOptions).toBeDefined();
344+
expect(capturedOptions.cwd).toBe('/path/to/project');
345+
expect(capturedOptions.env).toEqual({ CUSTOM_VAR: 'value' });
346+
});
347+
});
263348
});

src/utils/build-utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ export async function executeXcodeBuildCommand(
227227
}
228228
} else {
229229
// Use standard xcodebuild
230-
result = await executor(command, platformOptions.logPrefix, true, execOpts);
230+
// Pass projectDir as cwd to ensure CocoaPods relative paths resolve correctly
231+
result = await executor(command, platformOptions.logPrefix, true, {
232+
...execOpts,
233+
cwd: projectDir,
234+
});
231235
}
232236

233237
// Grep warnings and errors from stdout (build output)

0 commit comments

Comments
 (0)