Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,29 @@ jobs:

steps:
- uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

Comment on lines 18 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Upgrade actions to v4 to avoid Node 16 deprecation breakages (actionlint flagged setup-node@v3)

actions/checkout@v3 and actions/setup-node@v3 are on the deprecated Node 16 runtime and will intermittently fail. Move to v4.

-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
@@
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v3
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'npm'
+        cache-dependency-path: package-lock.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: package-lock.json
🧰 Tools
🪛 actionlint (1.7.7)

18-18: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


21-21: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
.github/workflows/ci.yml lines 18-25: update the GitHub Actions usages from
actions/checkout@v3 and actions/setup-node@v3 to their v4 releases to avoid Node
16 deprecation failures; change uses: actions/checkout@v3 -> uses:
actions/checkout@v4 and uses: actions/setup-node@v3 -> uses:
actions/setup-node@v4, preserving the existing inputs (node-version and cache)
and verify workflow syntax with a quick linter/run.

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Lint
run: npm run lint

- name: Check formatting
run: npm run format:check

- name: Type check
run: npm run typecheck

- name: Run tests
run: npm test

- run: npx pkg-pr-new publish
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unconditional Publishing Causes CI Failures

The npx pkg-pr-new publish command, now in the main CI workflow, runs unconditionally on all builds, including main branch pushes. Its original dedicated workflow ran only on PRs and had pull-requests: write permission. This change will cause unintended publishing on main and likely permission errors.

Fix in Cursor Fix in Web

56 changes: 0 additions & 56 deletions .github/workflows/pkg-pr-new.yml

This file was deleted.

76 changes: 76 additions & 0 deletions src/mcp/tools/utilities/__tests__/clean.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,80 @@ describe('clean (unified) tool', () => {
const text = String(result.content?.[1]?.text ?? result.content?.[0]?.text ?? '');
expect(text).toContain('Invalid parameters');
});

it('uses iOS platform by default', async () => {
let capturedCommand: string[] = [];
const mockExecutor = async (command: string[]) => {
capturedCommand = command;
return { success: true, output: 'clean success' };
};

const result = await cleanLogic(
{ projectPath: '/p.xcodeproj', scheme: 'App' } as any,
mockExecutor,
);
expect(result.isError).not.toBe(true);

// Check that the command contains iOS platform destination
const commandStr = capturedCommand.join(' ');
expect(commandStr).toContain('-destination');
expect(commandStr).toContain('platform=iOS');
});

it('accepts custom platform parameter', async () => {
let capturedCommand: string[] = [];
const mockExecutor = async (command: string[]) => {
capturedCommand = command;
return { success: true, output: 'clean success' };
};

const result = await cleanLogic(
{
projectPath: '/p.xcodeproj',
scheme: 'App',
platform: 'macOS',
} as any,
mockExecutor,
);
expect(result.isError).not.toBe(true);

// Check that the command contains macOS platform destination
const commandStr = capturedCommand.join(' ');
expect(commandStr).toContain('-destination');
expect(commandStr).toContain('platform=macOS');
});

it('accepts iOS Simulator platform parameter (maps to iOS for clean)', async () => {
let capturedCommand: string[] = [];
const mockExecutor = async (command: string[]) => {
capturedCommand = command;
return { success: true, output: 'clean success' };
};

const result = await cleanLogic(
{
projectPath: '/p.xcodeproj',
scheme: 'App',
platform: 'iOS Simulator',
} as any,
mockExecutor,
);
expect(result.isError).not.toBe(true);

// For clean operations, iOS Simulator should be mapped to iOS platform
const commandStr = capturedCommand.join(' ');
expect(commandStr).toContain('-destination');
expect(commandStr).toContain('platform=iOS');
});

it('handler validation: rejects invalid platform values', async () => {
const result = await (tool as any).handler({
projectPath: '/p.xcodeproj',
scheme: 'App',
platform: 'InvalidPlatform',
});
expect(result.isError).toBe(true);
const text = String(result.content?.[1]?.text ?? result.content?.[0]?.text ?? '');
expect(text).toContain('Invalid parameters');
});
});
58 changes: 56 additions & 2 deletions src/mcp/tools/utilities/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ const baseOptions = {
.describe(
'If true, prefers xcodebuild over the experimental incremental build system, useful for when incremental build system fails.',
),
platform: z
.enum([
'macOS',
'iOS',
'iOS Simulator',
'watchOS',
'watchOS Simulator',
'tvOS',
'tvOS Simulator',
'visionOS',
'visionOS Simulator',
])
.optional()
.describe(
'Optional: Platform to clean for (defaults to iOS). Choose from macOS, iOS, iOS Simulator, watchOS, watchOS Simulator, tvOS, tvOS Simulator, visionOS, visionOS Simulator',
),
};

const baseSchemaObject = z.object({
Expand Down Expand Up @@ -67,6 +83,32 @@ export async function cleanLogic(
'Invalid parameters:\nscheme: scheme is required when workspacePath is provided.',
);
}

// Use provided platform or default to iOS
const targetPlatform = params.platform ?? 'iOS';

// Map human-friendly platform names to XcodePlatform enum values
// This is safer than direct key lookup and handles the space-containing simulator names
const platformMap = {
macOS: XcodePlatform.macOS,
iOS: XcodePlatform.iOS,
'iOS Simulator': XcodePlatform.iOSSimulator,
watchOS: XcodePlatform.watchOS,
'watchOS Simulator': XcodePlatform.watchOSSimulator,
tvOS: XcodePlatform.tvOS,
'tvOS Simulator': XcodePlatform.tvOSSimulator,
visionOS: XcodePlatform.visionOS,
'visionOS Simulator': XcodePlatform.visionOSSimulator,
};

const platformEnum = platformMap[targetPlatform];
if (!platformEnum) {
return createErrorResponse(
'Parameter validation failed',
`Invalid parameters:\nplatform: unsupported value "${targetPlatform}".`,
);
}

const hasProjectPath = typeof params.projectPath === 'string';
const typedParams: SharedBuildParams = {
...(hasProjectPath
Expand All @@ -80,10 +122,22 @@ export async function cleanLogic(
extraArgs: params.extraArgs,
};

// For clean operations, simulator platforms should be mapped to their device equivalents
// since clean works at the build product level, not runtime level, and build products
// are shared between device and simulator platforms
const cleanPlatformMap: Partial<Record<XcodePlatform, XcodePlatform>> = {
[XcodePlatform.iOSSimulator]: XcodePlatform.iOS,
[XcodePlatform.watchOSSimulator]: XcodePlatform.watchOS,
[XcodePlatform.tvOSSimulator]: XcodePlatform.tvOS,
[XcodePlatform.visionOSSimulator]: XcodePlatform.visionOS,
};

const cleanPlatform = cleanPlatformMap[platformEnum] ?? platformEnum;

return executeXcodeBuildCommand(
typedParams,
{
platform: XcodePlatform.macOS,
platform: cleanPlatform,
logPrefix: 'Clean',
},
false,
Expand All @@ -95,7 +149,7 @@ export async function cleanLogic(
export default {
name: 'clean',
description:
"Cleans build products for either a project or a workspace using xcodebuild. Provide exactly one of projectPath or workspacePath. Example: clean({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme' })",
"Cleans build products for either a project or a workspace using xcodebuild. Provide exactly one of projectPath or workspacePath. Platform defaults to iOS if not specified. Example: clean({ projectPath: '/path/to/MyProject.xcodeproj', scheme: 'MyScheme', platform: 'iOS' })",
schema: baseSchemaObject.shape,
handler: createTypedTool<CleanParams>(
cleanSchema as z.ZodType<CleanParams>,
Expand Down