Skip to content

Conversation

@cameroncooke
Copy link
Collaborator

@cameroncooke cameroncooke commented Aug 22, 2025

Summary

Implements support for TEST_RUNNER_ environment variable passthrough to xcodebuild test commands, addressing issue #101. This enables test frameworks to receive environment variables from MCP clients through xcodebuild's built-in TEST_RUNNER_ prefix mechanism.

Background/Details

The issue requested the ability to pass environment variables to test runners when using xcodebuild test commands. This is particularly useful for:

  • Enabling/disabling test features (e.g., TEST_RUNNER_USE_DEV_MODE=YES)
  • Configuring test behavior dynamically
  • Passing runtime configuration to UI tests
  • Supporting test frameworks that rely on environment variables

xcodebuild has built-in support for TEST_RUNNER_ prefixed environment variables - it automatically strips the prefix and passes the remaining variables to the test runner process.

Solution

Implemented an explicit parameter approach that:

  1. Infrastructure Updates:

    • Enhanced CommandExecutor with CommandExecOptions interface for environment and working directory support
    • Updated executeXcodeBuildCommand to accept execution options for environment variable passing
    • Modified handleTestLogic to support test runner environment variables
  2. New Utility Function:

    • Added normalizeTestRunnerEnv() in src/utils/environment.ts
    • Automatically prefixes variables with TEST_RUNNER_ if not already prefixed
    • Handles null/undefined values gracefully
    • Preserves existing TEST_RUNNER_ prefixed variables
  3. Tool Integration:

    • Added testRunnerEnv parameter to all three test tools: test_device, test_macos, test_sim
    • Parameter accepts a record of key-value pairs for environment variables
    • Variables are automatically normalized and passed to xcodebuild via environment
  4. Security Design:

    • Uses explicit parameter approach instead of automatic environment detection
    • Prevents injection attacks by validating input through Zod schemas
    • Follows principle of least privilege - only explicitly provided variables are passed

Testing

  • Reproduction Test: Created UI test that validates TEST_RUNNER_ mechanism works
  • Unit Tests: 18 comprehensive tests for normalizeTestRunnerEnv function covering edge cases
  • Integration Tests: 8 behavioral tests validating end-to-end parameter flow
  • Manual Validation:
    • ✅ Test with testRunnerEnv: { USE_DEV_MODE: "YES" } - passes
    • ✅ Test without testRunnerEnv parameter - fails (as expected)
    • Validated both success and failure cases using Reloaderoo

Architecture Benefits

This explicit approach provides several advantages over automatic environment detection:

  • Security: Prevents unintended environment variable leakage
  • Clarity: Makes variable passing explicit and intentional
  • Separation of Concerns: MCP server environment vs tool execution environment remain distinct
  • Compatibility: Works with MCP architecture where server env vars are for the server itself

Notes

  • Maintains backward compatibility - existing tools work unchanged
  • No breaking changes to existing APIs
  • Follows project's dependency injection testing patterns
  • All quality checks pass (build, tests, lint, typecheck)
  • Implementation covers all three test tool variants (device, simulator, macOS)

Summary by CodeRabbit

  • New Features

    • Pass environment variables to test runs on device, simulator, and macOS via an optional testRunnerEnv input (auto-prefixed as TEST_RUNNER_). Execution now supports custom env and working directory options.
  • Documentation

    • Added a comprehensive guide for using Reloaderoo CLI with XcodeBuildMCP, including categorized examples.
    • Added a plan outlining TEST_RUNNER_ environment variable support and usage patterns.
  • Tests

    • Introduced an iOS UI test target and scheme for MCPTest.
    • Added unit and integration tests validating environment normalization and passthrough.

Implements explicit testRunnerEnv parameter for all test tools (test_device,
test_macos, test_sim) to support passing environment variables to xcodebuild
test commands with automatic TEST_RUNNER_ prefix handling.

## Key Features
- Added testRunnerEnv parameter to all three test tools
- Automatic TEST_RUNNER_ prefix normalization for environment variables
- Updated CommandExecutor infrastructure to support execution options
- Enhanced executeXcodeBuildCommand to accept environment variables
- Updated handleTestLogic for simulator test integration

## Security & Architecture Benefits
- Explicit parameter approach prevents environment variable injection attacks
- Follows principle of least privilege - only specified variables are passed
- Maintains clear separation between MCP server config and tool execution
- Provides audit trail of exactly which variables are used per test

## Implementation Details
- normalizeTestRunnerEnv() utility automatically adds TEST_RUNNER_ prefix
- CommandExecOptions interface supports env and cwd execution options
- All tools validate parameters with Zod schemas
- Comprehensive test coverage with 26 new tests (18 unit + 8 integration)

## Usage Example
```typescript
test_device({
  projectPath: "/path/to/project.xcodeproj",
  scheme: "MyScheme",
  deviceId: "device-uuid",
  testRunnerEnv: { "USE_DEV_MODE": "YES" }  // Becomes TEST_RUNNER_USE_DEV_MODE
})
```

Addresses GitHub issue #101 with a secure, explicit approach that prevents
injection vulnerabilities while providing the requested functionality.

Tested via Reloaderoo with real xcodebuild execution validation.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Walkthrough

Adds extensive docs, introduces TEST_RUNNER_ environment passthrough via a new testRunnerEnv parameter, updates execution APIs (CommandExecutor with CommandExecOptions), threads execOpts through executeXcodeBuildCommand, and adds normalization utilities and tests. Updates iOS example project by adding a UI test target, scheme test entry, and a Swift UITest verifying env passthrough.

Changes

Cohort / File(s) Summary
Documentation: Reloaderoo CLI usage
docs/RELOADEROO_FOR_XCODEBUILDMCP.md
New guide showing npx-based CLI invocations for XcodeBuildMCP tools, read-resource URIs, and categorized examples.
Documentation: TEST_RUNNER_ env plan
docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md
Design doc proposing explicit testRunnerEnv, CommandExecOptions, executor and build-utils updates, normalization utility, tool schema changes, and testing strategy.
Execution pipeline API
src/utils/CommandExecutor.ts, src/utils/command.ts, src/utils/execution/index.ts
Adds CommandExecOptions (env, cwd). Updates CommandExecutor and defaultExecutor signatures to accept opts, merges env/cwd, re-exports new type.
Build utilities
src/utils/build-utils.ts
executeXcodeBuildCommand gains optional execOpts?: CommandExecOptions and passes it to the executor for xcodebuild path.
Env normalization utility + tests
src/utils/environment.ts, src/utils/__tests__/environment.test.ts, src/utils/__tests__/test-runner-env-integration.test.ts
Adds normalizeTestRunnerEnv ensuring TEST_RUNNER_ prefix and filtering null/undefined; comprehensive unit and integration tests for normalization behavior.
Test execution flow
src/utils/test-common.ts
handleTestLogic accepts optional testRunnerEnv, normalizes via normalizeTestRunnerEnv, constructs execOpts, and forwards to executeXcodeBuildCommand.
Public tools: add testRunnerEnv
src/mcp/tools/device/test_device.ts, src/mcp/tools/macos/test_macos.ts, src/mcp/tools/simulator/test_sim.ts
Extend schemas/types to include optional testRunnerEnv; compute execOpts using normalization and pass to build/test execution.
Example iOS project: new UI test target
example_projects/iOS/MCPTest.xcodeproj/project.pbxproj, example_projects/iOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme, example_projects/iOS/MCPTestUITests/MCPTestUITests.swift
Adds MCPTestUITests target/configs, includes it in scheme Testables, and adds UITests asserting env passthrough (USE_DEV_MODE).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Tool as MCP Tool (device/macos/sim)
  participant TestLogic as handleTestLogic
  participant Env as normalizeTestRunnerEnv
  participant Build as executeXcodeBuildCommand
  participant Exec as CommandExecutor
  participant XCB as xcodebuild

  User->>Tool: call test_* with testRunnerEnv?
  Tool->>TestLogic: forward params (incl. testRunnerEnv)
  alt testRunnerEnv provided
    TestLogic->>Env: normalize(userVars)
    Env-->>TestLogic: TEST_RUNNER_* map
    TestLogic->>Build: execOpts = { env: TEST_RUNNER_* }
  else no env
    TestLogic->>Build: execOpts = undefined
  end
  Build->>Exec: run(args, opts=execOpts)
  Exec->>XCB: spawn with env merged (process.env + opts.env)
  XCB-->>Exec: test results / xcresult
  Exec-->>Build: CommandResponse
  Build-->>TestLogic: path to xcresult
  TestLogic-->>Tool: parsed test outcome
  Tool-->>User: result
  note over Env,Exec: New/changed: env normalization + opts passthrough
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit taps keys in a midnight glow,
Whispering vars with a TEST_RUNNER bow.
Hops through schemes, to xcodebuild’s gate,
Env in its paws—no time to wait!
UITests bloom, carrots in tow,
Reloaderoo hums—onward we go. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/test-runner-env-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@claude
Copy link
Contributor

claude bot commented Aug 22, 2025

Claude finished @cameroncooke's task —— View job


PR Review: TEST_RUNNER_ Environment Variable Support

Review Tasks:

  • Analyze implementation architecture and design decisions
  • Review security approach and validate design
  • Evaluate test coverage and quality
  • Check for code quality issues and consistency
  • Assess performance considerations
  • Provide comprehensive feedback

Overall Assessment: Excellent Implementation ✅

This is a high-quality PR that demonstrates strong software engineering practices and thoughtful design decisions. The implementation successfully addresses issue #101 with a secure, well-tested approach.

🎯 Strengths

1. Security-First Design

  • Explicit parameter approach prevents unintended environment variable leakage
  • Input validation through Zod schemas prevents injection attacks
  • Separation of concerns between MCP server environment and tool execution environment
  • No automatic environment detection that could expose sensitive variables

2. Robust Architecture

  • Dependency injection pattern maintained throughout (CommandExecutor, FileSystemExecutor)
  • Type safety with comprehensive Zod schemas and TypeScript interfaces
  • Consistent implementation across all three test tools (device, simulator, macOS)
  • Clean separation between business logic and MCP handlers

3. Comprehensive Testing

  • 18 unit tests for normalizeTestRunnerEnv() covering edge cases
  • 8 integration tests validating end-to-end parameter flow
  • UI test validation with real xctest proving the mechanism works
  • Edge case coverage: null values, special characters, prefix variations

4. Code Quality

  • Clear naming conventions (testRunnerEnv parameter)
  • Helpful parameter descriptions for AI agents
  • Proper error handling and logging
  • Immutable operations (original input objects unchanged)

🔍 Detailed Review

Implementation Quality - src/utils/environment.ts:78-86

The normalizeTestRunnerEnv() function is well-implemented:

export function normalizeTestRunnerEnv(vars: Record<string, string>): Record<string, string> {
  const normalized: Record<string, string> = {};
  for (const [key, value] of Object.entries(vars ?? {})) {
    if (value == null) continue;
    const prefixedKey = key.startsWith('TEST_RUNNER_') ? key : `TEST_RUNNER_${key}`;
    normalized[prefixedKey] = value;
  }
  return normalized;
}
  • ✅ Handles null/undefined gracefully
  • ✅ Avoids double-prefixing
  • ✅ Simple, readable logic
  • ✅ Immutable operation

Tool Integration - src/mcp/tools/simulator/test_sim.ts:57-62

Parameter integration is clean and consistent:

testRunnerEnv: z
  .record(z.string(), z.string())
  .optional()
  .describe('Environment variables to pass to the test runner (TEST_RUNNER_ prefix added automatically)'),
  • ✅ Clear parameter name and description
  • ✅ Optional parameter (backward compatible)
  • ✅ Proper Zod validation

Execution Flow - src/utils/test-common.ts:179-181

Environment variable passing is properly implemented:

const execOpts: CommandExecOptions | undefined = params.testRunnerEnv
  ? { env: normalizeTestRunnerEnv(params.testRunnerEnv) }
  : undefined;
  • ✅ Only passes environment when specified
  • ✅ Uses the normalization function correctly

🧪 Testing Excellence

Unit Test Coverage - src/utils/__tests__/environment.test.ts

The test suite is exceptionally thorough:

  • Basic functionality: Prefix addition and preservation
  • Edge cases: Empty objects, null values, special characters
  • Real-world scenarios: GitHub issue examples, mixed environments
  • Prefix handling: Case sensitivity, partial matches
  • Input validation: Undefined/null inputs, immutability

Integration Validation - example_projects/iOS/MCPTestUITests/MCPTestUITests.swift

The UI test provides real-world validation:

func testEnvironmentVariablePassthrough() throws {
    let env = ProcessInfo.processInfo.environment
    let value = env["USE_DEV_MODE"] ?? "<nil>"
    XCTAssertEqual(value, "YES", "Expected USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE")
}
  • ✅ Tests actual xcodebuild TEST_RUNNER_ mechanism
  • ✅ Provides clear failure message for debugging

📈 Performance Considerations

  • Minimal overhead: Environment normalization is O(n) with input size
  • Lazy evaluation: Environment only processed when testRunnerEnv provided
  • No memory leaks: Temporary directories properly cleaned up
  • Efficient string operations: Simple prefix checks and concatenation

🔒 Security Analysis

  • No injection vulnerabilities: Input validated through Zod schemas
  • Explicit control: Users must intentionally specify variables to pass
  • Environment isolation: Server environment remains separate from execution
  • Validated prefixing: Prevents arbitrary environment manipulation

🔄 Backward Compatibility

  • Non-breaking changes: testRunnerEnv parameter is optional
  • Existing functionality preserved: All current test workflows work unchanged
  • Graceful defaults: Tools behave exactly as before when parameter omitted

📝 Minor Suggestions (Non-blocking)

  1. Documentation Enhancement: Consider adding a usage example to the main docs/TOOLS.md showing the testRunnerEnv parameter in action.

  2. Error Messaging: The current Zod validation provides good error messages, but consider if more specific guidance about the TEST_RUNNER_ mechanism would help users.

🏆 Recommendation: APPROVE

This PR demonstrates exceptional software engineering practices:

  • Addresses the requirement completely with a secure, explicit approach
  • Maintains high code quality with comprehensive testing and documentation
  • Follows project patterns consistently across all affected tools
  • Provides excellent testing coverage including real-world validation

The implementation is production-ready and ready for merge.


@cameroncooke
Copy link
Collaborator Author

Fixes #101

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 22, 2025

Open in StackBlitz

npm i https://pkg.pr.new/cameroncooke/XcodeBuildMCP/xcodebuildmcp@102

commit: cf7c1c1

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/command.ts (1)

47-50: Shell injection risk: unescaped $ and backticks allow command substitution inside double quotes

When useShell is true, arguments containing $(...) or backticks will still be expanded by the shell even if wrapped in double quotes. Current escaping only handles " and , leaving $ and ` unescaped. A malicious arg like 'foo$(uname -a)' would execute uname.

Apply this minimal hardening to also escape $ and ` inside quoted args:

-          return `"${arg.replace(/(["\\])/g, '\\$1')}"`;
+          // Also escape $ and backticks to prevent variable/command substitution within double quotes
+          return `"${arg.replace(/(["\\$`])/g, '\\$1')}"`;

Optionally, prefer avoiding a shell entirely (more secure and simpler) when not needed:

-  useShell: boolean = true,
+  useShell: boolean = false,

If changing the default is too risky now, consider toggling useShell=false at call sites that don’t require shell features (pipes, redirects).

I can add unit tests demonstrating safe handling of args like 'a$(echo hacked)' and 'uname' under both modes.

🧹 Nitpick comments (28)
src/utils/CommandExecutor.ts (2)

3-6: Clarify semantics for env merging and cwd via TSDoc

Document whether env replaces or merges with process.env, and whether cwd must be absolute. This helps implementers keep behavior consistent.

Apply this diff to add inline docs:

 export interface CommandExecOptions {
-  env?: Record<string, string>;
-  cwd?: string;
+  /**
+   * Environment variables for the spawned process.
+   * Note: Values must be strings. Callers should pre-normalize (e.g., drop null/undefined).
+   * Recommendation: decide at call site whether to merge with process.env or pass a minimal env.
+   */
+  env?: Record<string, string>;
+  /**
+   * Working directory for the spawned process.
+   * Prefer an absolute path to avoid ambiguity.
+   */
+  cwd?: string;
 }

11-17: Signature change may break external implementers; consider transitional overloads

Switching from the 4th param env?: Record<string,string> to opts?: CommandExecOptions is a breaking type change for any out-of-repo implementers. Provide an overload that accepts the legacy env position to ease migration, while steering new code to opts.

Apply this diff to introduce backward-compatible overloads:

-export type CommandExecutor = (
-  command: string[],
-  logPrefix?: string,
-  useShell?: boolean,
-  opts?: CommandExecOptions,
-  detached?: boolean,
-) => Promise<CommandResponse>;
+export interface CommandExecutor {
+  /**
+   * Preferred signature using CommandExecOptions.
+   */
+  (
+    command: string[],
+    logPrefix?: string,
+    useShell?: boolean,
+    opts?: CommandExecOptions,
+    detached?: boolean,
+  ): Promise<CommandResponse>;
+  /**
+   * Deprecated legacy signature for migration: env as the 4th param.
+   * Implementations should map `env` to `{ env }`.
+   */
+  (
+    command: string[],
+    logPrefix?: string,
+    useShell?: boolean,
+    env?: Record<string, string>,
+    detached?: boolean,
+  ): Promise<CommandResponse>;
+}

Optionally, annotate the legacy signature with a @deprecated tag in a TSDoc block if you prefer stricter guidance.

example_projects/iOS/MCPTestUITests/MCPTestUITests.swift (2)

11-14: Doc comment is now outdated relative to this PR

The comment says “before implementation in Node layer,” but this PR implements the passthrough. Update to reflect the new, expected behavior.

Apply this diff:

-/// Current behavior (before implementation in Node layer):
-/// - Running via XcodeBuildMCP test tools does not yet pass TEST_RUNNER_
-///   variables through, so this test will fail and serve as a repro.
+/// Note: This repository now supports passing TEST_RUNNER_ variables.
+/// This test will only pass when invoked with TEST_RUNNER_USE_DEV_MODE=YES
+/// (xcodebuild strips the prefix and the runner sees USE_DEV_MODE=YES).

20-30: Make the repro test self-skipping when not configured

As written, the test will fail when run without TEST_RUNNER_USE_DEV_MODE=YES, which is brittle for casual runs. Skip unless the runner actually receives USE_DEV_MODE.

Apply this diff:

     func testEnvironmentVariablePassthrough() throws {
-        let env = ProcessInfo.processInfo.environment
-        let value = env["USE_DEV_MODE"] ?? "<nil>"
+        let env = ProcessInfo.processInfo.environment
+        guard let value = env["USE_DEV_MODE"] else {
+            throw XCTSkip("USE_DEV_MODE not set. Provide TEST_RUNNER_USE_DEV_MODE=YES to enable this repro.")
+        }
         XCTAssertEqual(
-            value,
+            value,
             "YES",
-            "Expected USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE. Actual: \(value)"
+            "Expected USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE. Actual: \(value)"
         )
     }
docs/RELOADEROO_FOR_XCODEBUILDMCP.md (5)

5-5: Tighten wording and fix “MCP server” phrasing

Minor clarity improvement.

Apply this diff:

-You can use this guide to prompt your agent, but providing the entire document will give you no actual benefits. You will end up using more context than just using MCP server directly. So it's recommended that you curate this document by removing the example commands that you don't need and just keeping the ones that are right for your project. You'll then want to keep this file within your project workspace and then include it in the context window when you need to interact your agent to use XcodeBuildMCP tools.
+You can use this guide to prompt your agent, but providing the entire document will give you no benefits. You will end up using more context than using the MCP server directly. It's recommended that you curate this document by removing unneeded examples and keeping only those relevant to your project. Keep the curated file in your workspace and include it in the context window when you need to use XcodeBuildMCP tools.

7-8: Fix typo and punctuation in the IMPORTANT note

“derrived” → “derived”, and smoother phrasing.

Apply this diff:

-> [!IMPORTANT]
-> Please remove this introduction before you prompt your agent with this file or any derrived version of it.
+> [!IMPORTANT]
+> Please remove this introduction before you prompt your agent with this file or any derived version of it.

55-57: Showcase new testRunnerEnv support in test_device example

Add a concrete example of passing a TEST_RUNNER_ variable via the new parameter.

Apply this diff:

-  npx reloaderoo@latest inspect call-tool test_device --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme", "deviceId": "DEVICE_UDID"}' -- node build/index.js
+  npx reloaderoo@latest inspect call-tool test_device --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme", "deviceId": "DEVICE_UDID", "testRunnerEnv": {"USE_DEV_MODE": "YES"}}' -- node build/index.js

101-104: Document testRunnerEnv for simulator testing

Reflect the new feature in the simulator example as well.

Apply this diff:

-  npx reloaderoo@latest inspect call-tool test_sim --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme", "simulatorName": "iPhone 16"}' -- node build/index.js
+  npx reloaderoo@latest inspect call-tool test_sim --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme", "simulatorName": "iPhone 16", "testRunnerEnv": {"USE_DEV_MODE": "YES"}}' -- node build/index.js

149-150: Include testRunnerEnv in macOS test example

Helps users understand the macOS flow as well.

Apply this diff:

-  npx reloaderoo@latest inspect call-tool test_macos --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme"}' -- node build/index.js
+  npx reloaderoo@latest inspect call-tool test_macos --params '{"projectPath": "/path/to/MyProject.xcodeproj", "scheme": "MyScheme", "testRunnerEnv": {"USE_DEV_MODE": "YES"}}' -- node build/index.js
example_projects/iOS/MCPTest.xcodeproj/project.pbxproj (1)

128-129: Suspicious Xcode metadata: LastSwiftUpdateCheck/CreatedOnToolsVersion set to 2600/26.0

These values suggest Xcode “26.0”, which is inconsistent with LastUpgradeCheck = 1620 and typical Xcode versions. Might be a tooling glitch.

Consider setting:

  • LastSwiftUpdateCheck to 1620 (or your actual Xcode version),
  • CreatedOnToolsVersion for UITests to 16.x,
    to avoid future migration prompts.

Also applies to: 135-137

src/utils/environment.ts (2)

70-86: Broaden types to match documented behavior; keep null/undefined-safe iteration

Docstring and PR notes say null/undefined values are skipped, but the signature only allows string. Align types to avoid unnecessary casting at call sites and to make intent explicit.

Apply:

-export function normalizeTestRunnerEnv(vars: Record<string, string>): Record<string, string> {
+export function normalizeTestRunnerEnv(
+  vars: Record<string, string | null | undefined> | null | undefined,
+): Record<string, string> {
   const normalized: Record<string, string> = {};
-  for (const [key, value] of Object.entries(vars ?? {})) {
+  for (const [key, value] of Object.entries(vars ?? {})) {
     if (value == null) continue;
     const prefixedKey = key.startsWith('TEST_RUNNER_') ? key : `TEST_RUNNER_${key}`;
     normalized[prefixedKey] = value;
   }
   return normalized;
 }

70-86: Optional hardening: validate/sanitize keys to typical env var format

While env injection risk is low (passed via spawn env), odd keys (spaces, “=”) can cause downstream issues. Consider normalizing or rejecting invalid names.

For example, warn and skip if the unprefixed name doesn’t match /^[A-Za-z_][A-Za-z0-9_]*$/.

src/utils/build-utils.ts (3)

44-51: Update JSDoc to include execOpts parameter

The function now accepts execOpts; document it for consumers (env, cwd) to encourage consistent usage.


98-110: Default cwd to project directory when not provided

Many workflows benefit from running xcodebuild in the project dir (especially with relative paths). If execOpts.cwd is not set, default it to projectDir.

Apply:

@@
-    let result;
+    let result;
+    const finalExecOpts = execOpts?.cwd ? execOpts : { ...execOpts, cwd: projectDir || execOpts?.cwd };
@@
-      result = await executor(command, platformOptions.logPrefix, true, execOpts);
+      result = await executor(command, platformOptions.logPrefix, true, finalExecOpts);

Also applies to: 229-230


221-226: Consider threading execOpts into xcodemake path as well (future-proofing)

If callers later use execOpts for things like DEVELOPER_DIR or custom PATH, builds via xcodemake would miss them.

Propagate execOpts (or at least its env/cwd) into executeXcodemakeCommand/executeMakeCommand if their APIs allow it.

src/mcp/tools/device/test_device.ts (2)

41-47: Validate testRunnerEnv keys to a safe pattern

To reduce surprises (e.g., spaces or “=”), constrain keys to typical env var names, while still allowing optional TEST_RUNNER_ prefix pre-normalization.

-  testRunnerEnv: z
-    .record(z.string(), z.string())
+  testRunnerEnv: z
+    .record(z.string().regex(/^(TEST_RUNNER_)?[A-Za-z_][A-Za-z0-9_]*$/), z.string())
     .optional()
     .describe(
       'Environment variables to pass to the test runner (TEST_RUNNER_ prefix added automatically)',
     ),

279-295: Expose full validation schema for better UX/Docs

You pass baseSchemaObject.shape to the tool schema, but the actual validation (mutual exclusivity) is in testDeviceSchema. Exposing testDeviceSchema would surface those constraints in generated UIs.

Consider:

  • schema: testDeviceSchema (instead of baseSchemaObject.shape)
  • Keep handler validator as-is.
src/utils/__tests__/test-runner-env-integration.test.ts (1)

90-106: Add collision and dollar-sign value scenarios

Two gaps worth testing:

  • Collision precedence (explicitly prefixed should override normalized).
  • Preservation of values containing $ (important to assert normalization is value-transparent).

Apply this diff within the “Core normalization functionality” describe block:

+    it('should prefer explicitly prefixed keys on collision', () => {
+      const vars = { FOO: 'one', TEST_RUNNER_FOO: 'two' };
+      const normalized = normalizeTestRunnerEnv(vars as any);
+      expect(normalized).toEqual({ TEST_RUNNER_FOO: 'two' });
+    });
+
+    it('should preserve values with dollar signs verbatim', () => {
+      const vars = { PATH_HINT: '$HOME/bin:$PATH' };
+      const normalized = normalizeTestRunnerEnv(vars);
+      expect(normalized).toEqual({ TEST_RUNNER_PATH_HINT: '$HOME/bin:$PATH' });
+    });
src/mcp/tools/macos/test_macos.ts (1)

321-324: Consider mentioning testRunnerEnv in the tool description

A short note helps discoverability for users scanning the MCP tool help.

Apply this diff to append a sentence:

-  description:
-    'Runs tests for a macOS project or workspace using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme. Example: test_macos({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme" })',
+  description:
+    'Runs tests for a macOS project or workspace using xcodebuild test and parses xcresult output. Provide exactly one of projectPath or workspacePath. IMPORTANT: Requires scheme. Accepts optional testRunnerEnv (Record<string,string>) to pass TEST_RUNNER_* variables to the test runner. Example: test_macos({ projectPath: "/path/to/MyProject.xcodeproj", scheme: "MyScheme", testRunnerEnv: { USE_DEV_MODE: "YES" } })',
src/utils/command.ts (1)

86-118: “detached” behavior is misnamed or incomplete

detached changes waiting semantics but does not set spawn’s detached option. Either:

  • Pass the flag through to spawnOpts.detached, or
  • Rename the parameter to nonBlocking/asyncStart to reflect behavior.

Minimal change to pass the flag through:

-    const spawnOpts: Parameters<typeof spawn>[2] = {
+    const spawnOpts: Parameters<typeof spawn>[2] = {
       stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin, pipe stdout/stderr
       env: { ...process.env, ...(opts?.env ?? {}) },
       cwd: opts?.cwd,
+      detached,
     };

If this semantic change is undesirable now, consider a follow-up rename instead.

docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md (8)

257-261: Harden Zod schema: accept nulls, constrain keys, and document precedence

The current schema allows any string key (including empty/invalid env names) and only string | undefined values. If the intent is to “handle null/undefined,” accept null and filter it. Also constrain keys to env‑safe names.

-testRunnerEnv: z
-  .record(z.string(), z.string().optional())
-  .optional()
-  .describe('Test runner environment variables (TEST_RUNNER_ prefix added automatically)')
+const envKey = z.string()
+  .min(1)
+  .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'ENV keys must match ^[A-Za-z_][A-Za-z0-9_]*$');
+const envVal = z.string().or(z.literal('')).nullable().optional();
+
+testRunnerEnv: z
+  .record(envKey, envVal)
+  .optional()
+  .describe('Test runner environment variables (keys auto-prefixed with TEST_RUNNER_; null/undefined dropped)')

Optional: Add docs/tests clarifying that explicit TEST_RUNNER_FOO takes precedence over FOO.


120-125: Shell example: quote values, mention multi-var export alternative

Minor UX nit: quote values to survive spaces/specials, and consider env or export patterns for readability.

-TEST_RUNNER_USE_DEV_MODE=YES \
-TEST_RUNNER_runsForEachTargetApplicationUIConfiguration=NO \
+TEST_RUNNER_USE_DEV_MODE="YES" \
+TEST_RUNNER_runsForEachTargetApplicationUIConfiguration="NO" \
 xcodebuild test ...

Alternative:

  • env TEST_RUNNER_USE_DEV_MODE="YES" TEST_RUNNER_runsForEachTargetApplicationUIConfiguration="NO" xcodebuild test …
  • Or set via export in the shell session.

101-107: Clarify messaging: explicit unprefixed input vs “only TEST_RUNNER_ can be set”

Two statements read inconsistently:

  • Lines 101–107: “User Experience: users provide unprefixed keys; prefix added automatically.”
  • Lines 350–357: “Prefix Enforcement: Only TEST_RUNNER_ prefixed variables can be set.”

Suggest rewording the latter to: “Only variables sent to xcodebuild are TEST_RUNNER_-prefixed; users may provide unprefixed or already-prefixed keys; the system normalizes and enforces the prefix.” This avoids confusion and matches the proposed utility behavior.

Also applies to: 350-357


139-148: Swift repro: prefer XCTSkip over failing when precondition isn’t met

If the test’s purpose is to validate passthrough only when configured, skipping is less noisy than failing.

-  guard useDevMode else {
-    XCTFail("Test requires USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE")
-    return
-  }
+  guard useDevMode else {
+    throw XCTSkip("Requires USE_DEV_MODE=YES via TEST_RUNNER_USE_DEV_MODE")
+  }

265-274: Log only keys (not values) when debugging exec env

Reinforce “never log values” by demonstrating a safe debug pattern in examples.

-const execEnv = normalizeTestRunnerEnv(params.testRunnerEnv);
+const execEnv = normalizeTestRunnerEnv(params.testRunnerEnv);
+if (params.verbose) {
+  const keys = Object.keys(execEnv).sort();
+  logger.debug(`Passing TEST_RUNNER_ keys: ${keys.join(', ')}`);
+}

Also applies to: 295-299


373-376: Add timeout to CommandExecOptions for resilience

Since you’re generalizing exec options, adding an optional timeoutMs?: number now would help avoid hung xcodebuild processes in CI.

Example:

export type CommandExecOptions = {
  cwd?: string;
  env?: Record<string, string | undefined>;
  timeoutMs?: number;
};

Ensure the default executor wires this into spawn/manual timers.


133-137: Fix markdownlint issues: remove trailing colons from headings

MD026 flags trailing punctuation in headings. Remove the trailing “:” from “Tasks:” headings.

-#### Tasks:
+#### Tasks

Also applies to: 337-341, 344-349


314-323: Integration tests: add collision test for prefixed + unprefixed keys

To lock in behavior, add a test where both TEST_RUNNER_FOO and FOO are passed. Assert that only one final key exists and document precedence.

I can contribute the test case if you’d like.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2db0694 and cf7c1c1.

📒 Files selected for processing (16)
  • docs/RELOADEROO_FOR_XCODEBUILDMCP.md (1 hunks)
  • docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md (1 hunks)
  • example_projects/iOS/MCPTest.xcodeproj/project.pbxproj (10 hunks)
  • example_projects/iOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme (1 hunks)
  • example_projects/iOS/MCPTestUITests/MCPTestUITests.swift (1 hunks)
  • src/mcp/tools/device/test_device.ts (4 hunks)
  • src/mcp/tools/macos/test_macos.ts (4 hunks)
  • src/mcp/tools/simulator/test_sim.ts (2 hunks)
  • src/utils/CommandExecutor.ts (1 hunks)
  • src/utils/__tests__/environment.test.ts (1 hunks)
  • src/utils/__tests__/test-runner-env-integration.test.ts (1 hunks)
  • src/utils/build-utils.ts (3 hunks)
  • src/utils/command.ts (3 hunks)
  • src/utils/environment.ts (1 hunks)
  • src/utils/execution/index.ts (1 hunks)
  • src/utils/test-common.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/utils/command.ts (2)
src/utils/CommandExecutor.ts (1)
  • CommandExecOptions (3-6)
src/utils/execution/index.ts (1)
  • CommandExecOptions (8-8)
src/utils/__tests__/environment.test.ts (1)
src/utils/environment.ts (1)
  • normalizeTestRunnerEnv (78-86)
src/utils/test-common.ts (2)
src/utils/CommandExecutor.ts (1)
  • CommandExecOptions (3-6)
src/utils/environment.ts (1)
  • normalizeTestRunnerEnv (78-86)
src/utils/build-utils.ts (1)
src/utils/CommandExecutor.ts (1)
  • CommandExecOptions (3-6)
src/utils/__tests__/test-runner-env-integration.test.ts (1)
src/utils/environment.ts (1)
  • normalizeTestRunnerEnv (78-86)
🪛 LanguageTool
docs/RELOADEROO_FOR_XCODEBUILDMCP.md

[style] ~5-~5: Consider making your message more concise.
Context: ...t will give you no actual benefits. You will end up using more context than just using MCP server...

(DO_END_UP_VBG)


[grammar] ~5-~5: There might be a mistake here.
Context: ...ill end up using more context than just using MCP server directly. So it's recommende...

(QB_NEW_EN)


[grammar] ~7-~7: There might be a mistake here.
Context: ...use XcodeBuildMCP tools. > [!IMPORTANT] > Please remove this introduction before...

(QB_NEW_EN)


[grammar] ~8-~8: Ensure spelling is correct
Context: ...prompt your agent with this file or any derrived version of it. ## Installation Reload...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md

[grammar] ~58-~58: There might be a mistake here.
Context: ...alysis ### XcodeBuildMCP Execution Flow 1. All Xcode commands flow through `execute...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ... 1. Automatic Detection (❌ Rejected) - Scan process.env for TEST_RUNNER_ vari...

(QB_NEW_EN)


[grammar] ~81-~81: There might be a mistake here.
Context: ...nt 2. Explicit Parameter (✅ Chosen) - Add testRunnerEnv parameter to test to...

(QB_NEW_EN)


[grammar] ~87-~87: There might be a mistake here.
Context: ...Approach** (🤔 Future Enhancement) - Both automatic + explicit with explicit o...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...xplicit with explicit overriding - Issue: Adds complexity, deferred for fu...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...exity, deferred for future consideration ### Expert Analysis Summary **RepoPrompt Ana...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...licit approach as optimal, highlighting: - Security benefits of explicit allow-list...

(QB_NEW_EN)


[grammar] ~95-~95: There might be a mistake here.
Context: ...benefits of explicit allow-list approach - Architectural soundness of extending Com...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...l soundness of extending CommandExecutor - Recommendation for automatic prefix hand...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...e and later prove fix works #### Tasks: - [ ] Create test in `example_projects/iOS...

(QB_NEW_EN)


[grammar] ~158-~158: There might be a mistake here.
Context: .../utils/CommandExecutor.ts **Changes**: - AddCommandExecOptions` type for execut...

(QB_NEW_EN)


[grammar] ~180-~180: There might be a mistake here.
Context: .../utils/execution/index.ts **Changes**: - Re-exportCommandExecOptions` type ```...

(QB_NEW_EN)


[grammar] ~191-~191: There might be a mistake here.
Context: ...**: src/utils/command.ts Changes: - Modify getDefaultCommandExecutor to me...

(QB_NEW_EN)


[grammar] ~204-~204: There might be a mistake here.
Context: ...src/utils/environment.ts Changes: - Add normalizeTestRunnerEnv function `...

(QB_NEW_EN)


[grammar] ~227-~227: There might be a mistake here.
Context: ...src/utils/build-utils.ts Changes: - Add optional `execOpts?: CommandExecOpti...

(QB_NEW_EN)


[grammar] ~250-~250: There might be a mistake here.
Context: ...ols/device/test_device.ts **Changes**: - AddtestRunnerEnv` to Zod schema with v...

(QB_NEW_EN)


[grammar] ~281-~281: There might be a mistake here.
Context: ... device test tool - Schema addition for testRunnerEnv - Import normalizeTestRunnerEnv - Pass ...

(QB_NEW_EN)


[grammar] ~282-~282: There might be a mistake here.
Context: ...a addition for testRunnerEnv - Import normalizeTestRunnerEnv - Pass execution options to `executeXcodeB...

(QB_NEW_EN)


[grammar] ~289-~289: There might be a mistake here.
Context: ...ols/simulator/test_sim.ts **Changes**: - AddtestRunnerEnv` to schema - Pass thr...

(QB_NEW_EN)


[grammar] ~295-~295: There might be a mistake here.
Context: ... src/utils/test/index.ts Changes: - Update handleTestLogic signature to ac...

(QB_NEW_EN)


[grammar] ~296-~296: There might be a mistake here.
Context: ...e handleTestLogic signature to accept testRunnerEnv?: Record<string, string | undefined> - Import and use normalizeTestRunnerEnv ...

(QB_NEW_EN)


[grammar] ~297-~297: There might be a mistake here.
Context: ..., string | undefined>- Import and usenormalizeTestRunnerEnv- Pass execution options toexecuteXcodeB...

(QB_NEW_EN)


[grammar] ~308-~308: There might be a mistake here.
Context: ...tests__/environment.test.ts **Tests**: - TestnormalizeTestRunnerEnv` with vario...

(QB_NEW_EN)


[grammar] ~327-~327: There might be a mistake here.
Context: ...files in each tool directory Tests: - Verify schema exports include new `testR...

(QB_NEW_EN)


[grammar] ~337-~337: There might be a mistake here.
Context: ...Reproduction Test Validation Tasks: - Run reproduction test from Phase 0 with ...

(QB_NEW_EN)


[grammar] ~344-~344: There might be a mistake here.
Context: ... Real-World Scenario Testing Tasks: - Test with actual iOS project using `runs...

(QB_NEW_EN)


[grammar] ~345-~345: There might be a mistake here.
Context: ...rld Scenario Testing Tasks: - Test with actual iOS project using `runsForEachTa...

(QB_NEW_EN)


[grammar] ~345-~345: There might be a mistake here.
Context: ...*: - Test with actual iOS project using runsForEachTargetApplicationUIConfiguration - Verify performance difference when varia...

(QB_NEW_EN)


[grammar] ~352-~352: There might be a mistake here.
Context: ...ty Considerations ### Security Benefits - No Environment Leakage: Only explicit ...

(QB_NEW_EN)


[grammar] ~358-~358: There might be a mistake here.
Context: ... can be set ### Security Best Practices - Never log environment variable values (k...

(QB_NEW_EN)


[grammar] ~366-~366: There might be a mistake here.
Context: ...tectural Benefits ### Clean Integration - Extends existing CommandExecutor patte...

(QB_NEW_EN)


[grammar] ~396-~396: There might be a mistake here.
Context: ...to test tools and have variables appear in test runner environment 2. Security...

(QB_NEW_EN)


[grammar] ~397-~397: There might be a mistake here.
Context: ...unintended environment variable leakage from server process 3. Usability: Users ...

(QB_NEW_EN)


[grammar] ~411-~411: There might be a mistake here.
Context: ...0**: 1-2 hours (reproduction test setup) - Phase 1: 4-6 hours (infrastructure cha...

(QB_NEW_EN)


[grammar] ~412-~412: There might be a mistake here.
Context: ... 1**: 4-6 hours (infrastructure changes) - Phase 2: 3-4 hours (tool integration) ...

(QB_NEW_EN)


[grammar] ~413-~413: There might be a mistake here.
Context: ...Phase 2*: 3-4 hours (tool integration) - Phase 3: 4-5 hours (testing) - **Pha...

(QB_NEW_EN)


[grammar] ~414-~414: There might be a mistake here.
Context: ...tion) - Phase 3: 4-5 hours (testing) - Phase 4: 2-3 hours (validation) **Tot...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md

14-14: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


133-133: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: claude-review
🔇 Additional comments (20)
src/utils/execution/index.ts (1)

8-8: Re-exporting CommandExecOptions from the execution facade — LGTM

Centralizing the types here keeps imports consistent for consumers. No concerns.

example_projects/iOS/MCPTest.xcodeproj/xcshareddata/xcschemes/MCPTest.xcscheme (1)

32-44: UI test target wiring into the scheme — LGTM

The Testables entry correctly references MCPTestUITests.xctest and enables parallelization. No blocking issues.

example_projects/iOS/MCPTest.xcodeproj/project.pbxproj (1)

10-17: Target dependency wiring looks correct

The PBXContainerItemProxy and PBXTargetDependency correctly point MCPTestUITests at MCPTest. This is necessary for UI tests to run.

Also applies to: 194-200

src/utils/environment.ts (1)

78-86: LGTM: clear, side-effect-free normalization

Creates a new map, preserves TEST_RUNNER_ keys, and skips nullish values. Implementation is straightforward.

src/utils/test-common.ts (2)

183-201: Good propagation of exec options through the build command path

execOpts threading into executeXcodeBuildCommand for the ‘test’ action cleanly enables TEST_RUNNER_ env passthrough.


178-182: No env clobbering: merge semantics already handled in CommandExecutor

I checked src/utils/command.ts and confirmed that the default spawn options merge the provided opts.env with process.env:

// src/utils/command.ts:67–71
const spawnOpts: Parameters<typeof spawn>[2] = {
  stdio: ['ignore', 'pipe', 'pipe'],
  env: { ...process.env, ...(opts?.env ?? {}) },
  cwd: opts?.cwd,
};

Since opts.env is overlaid onto process.env, passing through execOpts as { env: normalizeTestRunnerEnv(...) } will not clobber PATH or other variables. No changes are needed here.

src/utils/build-utils.ts (1)

229-230: LGTM: exec options are forwarded to the executor for xcodebuild path

This enables environment passthrough for test runs, while leaving xcodemake unaffected.

src/mcp/tools/device/test_device.ts (1)

199-203: Correctly builds exec options with normalized TEST_RUNNER_ env

The normalization and conditional execOpts creation look solid and match the intended security posture.

src/utils/__tests__/environment.test.ts (1)

9-44: Solid coverage of core normalization paths

Basic functionality tests precisely capture the contract (prefixing, preservation, mixed keys). Nice job keeping assertions explicit.

src/mcp/tools/simulator/test_sim.ts (2)

57-62: Schema addition looks right and constrains values to strings

Using z.record(z.string(), z.string()) is appropriate; optional is correct. The description communicates behavior clearly.


105-106: Normalization of testRunnerEnv confirmed

handleTestLogic in src/utils/test-common.ts (lines 179–180) wraps params.testRunnerEnv with normalizeTestRunnerEnv before assigning to execOpts, preventing any raw pass-through. No further changes needed.

src/utils/__tests__/test-runner-env-integration.test.ts (1)

12-166: Good end-to-end normalization scenarios

Realistic permutations, null filtering, special chars, and immutability are well covered. This complements the unit suite nicely.

src/mcp/tools/macos/test_macos.ts (2)

42-47: Schema: clear, minimally permissive, and aligned with intended usage

Optional testRunnerEnv with string-only values is a sensible boundary. Description is explicit about automatic prefixing.


243-247: Correct construction of execOpts with normalized env

Creating execOpts only when provided and normalizing via normalizeTestRunnerEnv is exactly what we want to avoid leaking arbitrary env names.

src/utils/command.ts (4)

17-21: Type re-exports are correct and help keep import sites stable

Re-exporting CommandExecOptions alongside existing types is clean and backward compatible.


29-31: Docstring updates correctly reflect the new opts parameter

Clear guidance on opts.env and opts.cwd. No issues.


37-38: Signature change to accept opts is appropriate

Consolidating env and cwd under CommandExecOptions is a good API surface improvement.


69-71: env and cwd merge behavior is correct

Merging process.env with opts?.env and threading cwd is the right approach. No leakage of testRunnerEnv into logs since it’s not interpolated in the command string.

docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md (2)

1-7: Strong, security‑aware plan; aligns with PR objectives

This implementation plan is thoughtful and consistent with the PR goals: explicit env allow‑listing, Zod validation, threading execution options, and end‑to‑end tests. Nice job keeping backward compatibility and calling out non‑logging of values.


232-240: All executeXcodeBuildCommand invocations support the new optional execOpts parameter

I’ve verified that every direct call to executeXcodeBuildCommand either passes the sixth execOpts argument when needed (e.g. in test runners) or omits it—relying on its optional signature—so there are no broken call sites. Additionally, the CommandExecOptions type is correctly re-exported:

• src/utils/test-common.ts and both macOS/device test modules pass execOpts as the sixth argument.
• All other usages (clean, simulator, macOS/device build flows) supply only five arguments, which remains compatible.
• The CommandExecOptions type is re-exported in src/utils/command.ts and src/utils/execution/index.ts.

No further changes required.

Comment on lines +14 to +22
```
TEST_RUNNER_<VAR> Set an environment variable whose name is prefixed
with TEST_RUNNER_ to have that variable passed, with
its prefix stripped, to all test runner processes
launched during a test action. For example,
TEST_RUNNER_Foo=Bar xcodebuild test ... sets the
environment variable Foo=Bar in the test runner's
environment.
```
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add fenced language and include authoritative citation for the man page snippet

For markdownlint and clarity, specify a fenced language (use text) for the man-page excerpt. Also, please add a citation to the current xcodebuild(1) manual to future‑proof the claim.


🌐 Web query:

Point me to the official Apple developer documentation or man page reference that describes TEST_RUNNER_ environment variable behavior for xcodebuild test.

💡 Result:

The TEST_RUNNER_ environment variable behavior for xcodebuild test is officially documented in the xcodebuild(1) man page[3][4]. When you set an environment variable prefixed with TEST_RUNNER_ (e.g., TEST_RUNNER_Foo=Bar), this variable is passed to all test runner processes during a test action, with the prefix stripped (so Foo=Bar is visible within the test runner's environment)[3][4].

The exact reference states:

Set an environment variable whose name is prefixed with TEST_RUNNER_ to have that variable passed, with its prefix stripped, to all test runner processes launched during a test action. For example, TEST_RUNNER_Foo=Bar xcodebuild test ... sets the environment variable Foo=Bar in the test runner's environment[3][4].

Key points:

  • The prefix TEST_RUNNER_ is stripped, so code in your test runner accesses ProcessInfo.processInfo.environment["Foo"], not TEST_RUNNER_Foo[3][4][1].
  • This mechanism allows you to reliably inject environment variables into your tests via the CLI, CI/CD systems, or custom scripts[1][3][4].
  • Existing variables can be modified using the token __CURRENT_VALUE__ to represent their current value[3].

For further practical details and Apple's guidance on this topic, review the official Apple developer man page[3], the GitHub pages mirror[4], and relevant WWDC documentation[1]. The full reference for Xcode build environment settings is available at the Apple Developer Documentation[4].

Citations:


docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md – add fenced code language and official citation

Please update the snippet at lines 14–22 to use a fenced code block with text as the language and include an authoritative citation to the xcodebuild(1) man page. For example:

Set an environment variable whose name is prefixed with TEST_RUNNER_ to have that variable passed, with its prefix stripped, to all test runner processes launched during a test action. For example, TEST_RUNNER_Foo=Bar xcodebuild test … sets the environment variable Foo=Bar in the test runner’s environment.
# Reference: xcodebuild(1) man page (https://developer.apple.com/man-pages/xcodebuild.1.html)
  • ensures markdownlint passes the snippet as plain text
  • future-proofs the documentation with an official Apple citation
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

14-14: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md around lines 14 to 22, the fenced
code block should be marked as plain text and include an authoritative citation
to the xcodebuild(1) man page; replace the existing block fence language with
```text and append a reference line such as "Reference: xcodebuild(1) man page
(https://developer.apple.com/man-pages/xcodebuild.1.html)" at the end of the
block so the snippet is treated as plain text by markdownlint and includes the
official Apple citation.

Comment on lines +195 to +198
// In the returned function:
const env = { ...process.env, ...(opts?.env ?? {}) };
// Pass env and opts?.cwd to spawn/exec call
```
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Filter undefined before merging env to avoid child_process spawn errors

Merging { ...process.env, ...(opts?.env ?? {}) } will include keys with undefined values if any slip through, which can crash spawn/exec. Filter them proactively.

-const env = { ...process.env, ...(opts?.env ?? {}) };
+const merged = { ...process.env, ...(opts?.env ?? {}) } as Record<string, unknown>;
+const env = Object.fromEntries(
+  Object.entries(merged).filter(([, v]) => v !== undefined && v !== null).map(([k, v]) => [k, String(v)])
+);
📝 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
// In the returned function:
const env = { ...process.env, ...(opts?.env ?? {}) };
// Pass env and opts?.cwd to spawn/exec call
```
// In the returned function:
const merged = { ...process.env, ...(opts?.env ?? {}) } as Record<string, unknown>;
const env = Object.fromEntries(
Object.entries(merged)
.filter(([, v]) => v !== undefined && v !== null)
.map(([k, v]) => [k, String(v)])
);
// Pass env and opts?.cwd to spawn/exec call
🤖 Prompt for AI Agents
In docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md around lines 195 to 198, filter
out undefined values from opts?.env before merging with process.env to prevent
child_process spawn/exec errors; create a new object from opts?.env that removes
entries with undefined values (e.g., via Object.entries(...).filter(...) then
Object.fromEntries(...)) and merge that with process.env when building env, and
ensure you pass that filtered env and opts?.cwd into the spawn/exec call.

Comment on lines +208 to +221
export function normalizeTestRunnerEnv(
userVars?: Record<string, string | undefined>
): Record<string, string> {
const result: Record<string, string> = {};
if (userVars) {
for (const [key, value] of Object.entries(userVars)) {
if (value !== undefined) {
result[`TEST_RUNNER_${key}`] = value;
}
}
}
return result;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

normalizeTestRunnerEnv will double‑prefix keys and doesn’t handle nulls; tighten sanitization

The sample implementation unconditionally prefixes keys, causing TEST_RUNNER_TEST_RUNNER_FOO if callers already supply a prefixed key. It also doesn’t accept or filter null values (contradicting the plan) and risks passing non‑strings to the process env if callers later broaden types. Let’s preserve existing prefixes, drop null/undefined, trim keys, and enforce an env‑safe key pattern.

Apply this diff to the snippet:

-export function normalizeTestRunnerEnv(
-  userVars?: Record<string, string | undefined>
-): Record<string, string> {
-  const result: Record<string, string> = {};
-  if (userVars) {
-    for (const [key, value] of Object.entries(userVars)) {
-      if (value !== undefined) {
-        result[`TEST_RUNNER_${key}`] = value;
-      }
-    }
-  }
-  return result;
-}
+export function normalizeTestRunnerEnv(
+  userVars?: Record<string, string | null | undefined>
+): Record<string, string> {
+  const result: Record<string, string> = {};
+  if (!userVars) return result;
+  for (const [rawKey, rawVal] of Object.entries(userVars)) {
+    if (rawVal == null) continue; // drop null/undefined
+    const key = String(rawKey).trim();
+    // Accept only ENV-safe keys: letters, digits, underscore, starting with letter/underscore
+    if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
+    const finalKey = key.startsWith('TEST_RUNNER_') ? key : `TEST_RUNNER_${key}`;
+    result[finalKey] = String(rawVal);
+  }
+  return result;
+}

Follow‑ups:

  • Define precedence if both FOO and TEST_RUNNER_FOO appear; commonly, let explicit TEST_RUNNER_FOO win. I can add a test for this if helpful.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/TEST_RUNNER_ENV_IMPLEMENTATION_PLAN.md around lines 208 to 221, the
normalizeTestRunnerEnv implementation unconditionally prefixes keys (causing
TEST_RUNNER_TEST_RUNNER_FOO), does not treat null values as absent, doesn’t trim
keys, and may allow non-string values; update the function to: skip null or
undefined values, trim key names, detect and preserve an existing TEST_RUNNER_
prefix instead of re‑prefixing, enforce that keys match a safe env var pattern
(e.g., /^[A-Z0-9_]+$/) after trimming and uppercasing, and resolve precedence so
an explicit TEST_RUNNER_FOO provided wins over FOO; ensure returned values are
strings only.

Comment on lines +185 to +191
8BC6F14D2E58FBAD008DD7EC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

UITest Sources build phase is empty — tests may not compile or run

PBXSourcesBuildPhase.files is empty for MCPTestUITests. Unless you're relying on new Xcode auto-synchronization that also wires build files (unlikely), no test sources will be compiled.

Run this to confirm the UITest file is referenced as a PBXBuildFile and included in the Sources phase:

If empty, add MCPTestUITests.swift to the target’s Sources.

I can generate a pbxproj patch to add the build file entry if you share the exact relative path of MCPTestUITests.swift.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Expect at least one build file for MCPTestUITests.swift and its inclusion in the Sources phase
rg -n "MCPTestUITests\\.swift" -C3 example_projects/iOS | cat
rg -nP '(?s)8BC6F14D2E58FBAD008DD7EC \/\* Sources \*\/.*?files = \((.*?)\);' example_projects/iOS/MCPTest.xcodeproj/project.pbxproj -C0 | cat

Length of output: 82


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Locate any UITest Swift files under the iOS project to determine their relative paths
find example_projects/iOS -type f -name "*UITests.swift"

Length of output: 135


Add MCPTestUITests.swift to the MCPTestUITests target’s Sources build phase

The PBXSourcesBuildPhase for the MCPTestUITests target (ID 8BC6F14D2E58FBAD008DD7EC) is empty, and project.pbxproj contains no PBXFileReference or PBXBuildFile entries for the UI test file at
example_projects/iOS/MCPTestUITests/MCPTestUITests.swift. Without these entries, Xcode won’t compile or run your UI tests.

Please update example_projects/iOS/MCPTest.xcodeproj/project.pbxproj as follows:

  • In the PBXFileReference section, add an entry for MCPTestUITests/MCPTestUITests.swift, e.g.
      /* Begin PBXFileReference section */
    +   <FILE_REF_GUID> /* MCPTestUITests.swift */ = {
    +     isa = PBXFileReference;
    +     lastKnownFileType = sourcecode.swift;
    +     name = MCPTestUITests.swift;
    +     path = MCPTestUITests/MCPTestUITests.swift;
    +     sourceTree = "<group>";
    +   };
      /* End PBXFileReference section */
  • In the PBXBuildFile section, add a build-file entry pointing to that fileRef, e.g.
      /* Begin PBXBuildFile section */
    +   <BUILD_FILE_GUID> /* MCPTestUITests.swift in Sources */ = {
    +     isa = PBXBuildFile;
    +     fileRef = <FILE_REF_GUID> /* MCPTestUITests.swift */;
    +   };
      /* End PBXBuildFile section */
  • In the PBXSourcesBuildPhase (ID 8BC6F14D2E58FBAD008DD7EC), include the new build-file GUID in the files array, e.g.
      8BC6F14D2E58FBAD008DD7EC /* Sources */ = {
        isa = PBXSourcesBuildPhase;
        buildActionMask = 2147483647;
    -   files = (
    -   );
    +   files = (
    +     <BUILD_FILE_GUID> /* MCPTestUITests.swift in Sources */,
    +   );
        runOnlyForDeploymentPostprocessing = 0;
      };

Let me know if you’d like a fully populated patch with the actual GUIDs.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In example_projects/iOS/MCPTest.xcodeproj/project.pbxproj around lines 185 to
191, the PBXSourcesBuildPhase for the MCPTestUITests target (ID
8BC6F14D2E58FBAD008DD7EC) is empty and the MCPTestUITests.swift file is not
referenced; add a PBXFileReference entry for
example_projects/iOS/MCPTestUITests/MCPTestUITests.swift in the PBXFileReference
section, add a corresponding PBXBuildFile entry in the PBXBuildFile section that
points to that fileRef, and then insert the new PBXBuildFile GUID into the files
array of the PBXSourcesBuildPhase with ID 8BC6F14D2E58FBAD008DD7EC so Xcode
includes and compiles the UI test file.

Comment on lines +381 to +399
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = BR6WD3M6ZD;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cameroncooke.test.MCPTestUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MCPTest;
};
name = Debug;
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

Fix invalid iOS deployment target (26.0) on UITest target

26.0 is not a valid iOS deployment version and will break builds. Align the UITest target with the project-level IPHONEOS_DEPLOYMENT_TARGET (18.2) or inherit it.

Apply this diff in both Debug and Release configurations:

-				IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 18.2; // align with project-level setting

Also applies to: 402-421

🤖 Prompt for AI Agents
In example_projects/iOS/MCPTest.xcodeproj/project.pbxproj around lines 381-399
(and similarly 402-421 for Release), the UITest build configurations set
IPHONEOS_DEPLOYMENT_TARGET = 26.0 which is invalid; change
IPHONEOS_DEPLOYMENT_TARGET to 18.2 to match the project-level deployment target
or remove the IPHONEOS_DEPLOYMENT_TARGET entry so the target inherits the
project value, and apply the same fix in both Debug and Release configuration
blocks.

Comment on lines +36 to +38
if isDevMode {
XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
}
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

Fix: XCTSkip must be thrown (or tried) to compile

XCTSkip is throwing; calling it without try/throw will fail to compile.

Apply this diff:

-        if isDevMode {
-            XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
-        }
+        if isDevMode {
+            throw XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
+        }
📝 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
if isDevMode {
XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
}
if isDevMode {
throw XCTSkip("Dev mode: skipping heavy or duplicated UI configuration runs")
}
🤖 Prompt for AI Agents
In example_projects/iOS/MCPTestUITests/MCPTestUITests.swift around lines 36–38,
the call to XCTSkip is a throwing function and must be invoked with try (or
handled); change the line to call try XCTSkip("Dev mode: skipping heavy or
duplicated UI configuration runs") and ensure the enclosing test method
signature includes throws (e.g., func testSomething() throws) or otherwise
handle the thrown error with do/try/catch so the file compiles.

Comment on lines +166 to +207
describe('Prefix Handling Edge Cases', () => {
it('should not double-prefix already prefixed keys', () => {
const input = { TEST_RUNNER_FOO: 'value1' };
const result = normalizeTestRunnerEnv(input);

expect(result).toEqual({
TEST_RUNNER_FOO: 'value1',
});

// Ensure no double prefixing occurred
expect(result).not.toHaveProperty('TEST_RUNNER_TEST_RUNNER_FOO');
});

it('should handle partial prefix matches correctly', () => {
const input = {
TEST_RUN: 'value1', // Should get prefixed (not TEST_RUNNER_)
TEST_RUNNER: 'value2', // Should get prefixed (no underscore)
TEST_RUNNER_FOO: 'value3', // Should not get prefixed
};
const result = normalizeTestRunnerEnv(input);

expect(result).toEqual({
TEST_RUNNER_TEST_RUN: 'value1',
TEST_RUNNER_TEST_RUNNER: 'value2',
TEST_RUNNER_FOO: 'value3',
});
});

it('should handle case-sensitive prefix detection', () => {
const input = {
test_runner_foo: 'value1', // lowercase - should get prefixed
Test_Runner_Bar: 'value2', // mixed case - should get prefixed
TEST_RUNNER_BAZ: 'value3', // correct case - should not get prefixed
};
const result = normalizeTestRunnerEnv(input);

expect(result).toEqual({
TEST_RUNNER_test_runner_foo: 'value1',
TEST_RUNNER_Test_Runner_Bar: 'value2',
TEST_RUNNER_BAZ: 'value3',
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add collision-precedence test (explicitly prefixed key should win)

Edge-case coverage is strong, but we’re missing the “dual-provided key” case. Today, when both FOO and TEST_RUNNER_FOO are present, the implementation will iterate in insertion order and let the explicitly prefixed key overwrite the normalized one. Let’s lock that behavior in tests to avoid regressions.

Apply this diff inside the “Prefix Handling Edge Cases” describe block:

+    it('should prefer explicitly prefixed keys when collisions occur', () => {
+      const input = {
+        FOO: 'normalized-value',
+        TEST_RUNNER_FOO: 'explicit-value',
+      };
+      const result = normalizeTestRunnerEnv(input as any);
+      expect(result).toEqual({
+        TEST_RUNNER_FOO: 'explicit-value',
+      });
+    });
📝 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
describe('Prefix Handling Edge Cases', () => {
it('should not double-prefix already prefixed keys', () => {
const input = { TEST_RUNNER_FOO: 'value1' };
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_FOO: 'value1',
});
// Ensure no double prefixing occurred
expect(result).not.toHaveProperty('TEST_RUNNER_TEST_RUNNER_FOO');
});
it('should handle partial prefix matches correctly', () => {
const input = {
TEST_RUN: 'value1', // Should get prefixed (not TEST_RUNNER_)
TEST_RUNNER: 'value2', // Should get prefixed (no underscore)
TEST_RUNNER_FOO: 'value3', // Should not get prefixed
};
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_TEST_RUN: 'value1',
TEST_RUNNER_TEST_RUNNER: 'value2',
TEST_RUNNER_FOO: 'value3',
});
});
it('should handle case-sensitive prefix detection', () => {
const input = {
test_runner_foo: 'value1', // lowercase - should get prefixed
Test_Runner_Bar: 'value2', // mixed case - should get prefixed
TEST_RUNNER_BAZ: 'value3', // correct case - should not get prefixed
};
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_test_runner_foo: 'value1',
TEST_RUNNER_Test_Runner_Bar: 'value2',
TEST_RUNNER_BAZ: 'value3',
});
});
describe('Prefix Handling Edge Cases', () => {
it('should not double-prefix already prefixed keys', () => {
const input = { TEST_RUNNER_FOO: 'value1' };
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_FOO: 'value1',
});
// Ensure no double prefixing occurred
expect(result).not.toHaveProperty('TEST_RUNNER_TEST_RUNNER_FOO');
});
it('should handle partial prefix matches correctly', () => {
const input = {
TEST_RUN: 'value1', // Should get prefixed (not TEST_RUNNER_)
TEST_RUNNER: 'value2', // Should get prefixed (no underscore)
TEST_RUNNER_FOO: 'value3', // Should not get prefixed
};
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_TEST_RUN: 'value1',
TEST_RUNNER_TEST_RUNNER: 'value2',
TEST_RUNNER_FOO: 'value3',
});
});
it('should handle case-sensitive prefix detection', () => {
const input = {
test_runner_foo: 'value1', // lowercase - should get prefixed
Test_Runner_Bar: 'value2', // mixed case - should get prefixed
TEST_RUNNER_BAZ: 'value3', // correct case - should not get prefixed
};
const result = normalizeTestRunnerEnv(input);
expect(result).toEqual({
TEST_RUNNER_test_runner_foo: 'value1',
TEST_RUNNER_Test_Runner_Bar: 'value2',
TEST_RUNNER_BAZ: 'value3',
});
});
it('should prefer explicitly prefixed keys when collisions occur', () => {
const input = {
FOO: 'normalized-value',
TEST_RUNNER_FOO: 'explicit-value',
};
const result = normalizeTestRunnerEnv(input as any);
expect(result).toEqual({
TEST_RUNNER_FOO: 'explicit-value',
});
});
});
🤖 Prompt for AI Agents
In src/utils/__tests__/environment.test.ts around lines 166 to 207, add a new
test inside the "Prefix Handling Edge Cases" describe block that verifies
collision precedence: create an input object containing both a plain key (e.g.
FOO) and its explicitly prefixed counterpart (TEST_RUNNER_FOO) with different
values, call normalizeTestRunnerEnv, and assert that the resulting map contains
only TEST_RUNNER_FOO with the explicitly prefixed value (i.e., the explicit
prefixed key wins), and that no normalized duplicate (e.g. TEST_RUNNER_FOO from
FOO) overwrites or coexists; keep the test name clear like "should prefer
explicitly prefixed key when both plain and prefixed are provided".


// Types
export type { CommandExecutor, CommandResponse } from '../CommandExecutor.ts';
export type { CommandExecutor, CommandResponse, CommandExecOptions } from '../CommandExecutor.ts';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify downstream imports now consume CommandExecOptions from this facade

To avoid mixed import paths, ensure all modules import CommandExecOptions from utils/execution/index.ts rather than the underlying file.

Run this script to find any direct imports from CommandExecutor.ts that should be switched to the execution facade:


🏁 Script executed:

#!/bin/bash
# Find direct imports of CommandExecutor.ts to standardize on utils/execution
rg -nP -C1 --type=ts "from ['\"][^'\"]*CommandExecutor\.ts['\"]"

Length of output: 1134


Standardize imports to use the utils/execution facade

We still have direct imports of CommandExecutor, CommandResponse, and CommandExecOptions from the underlying CommandExecutor.ts file. To avoid mixed import paths, all downstream modules should import these types from the new facade at src/utils/execution/index.ts (i.e. utils/execution).

Files requiring updates:

  • src/utils/command.ts
    • Line 17:

    - import { CommandExecutor, CommandResponse, CommandExecOptions } from './CommandExecutor.ts';
    + import type { CommandExecutor, CommandResponse, CommandExecOptions } from './execution';

    • Line 20:

    - export { CommandExecutor, CommandResponse, CommandExecOptions } from './CommandExecutor.ts';
    + export type { CommandExecutor, CommandResponse, CommandExecOptions } from './execution';
  • src/test-utils/mock-executors.ts
    • Line 19:

    - import { CommandExecutor } from '../utils/CommandExecutor.ts';
    + import type { CommandExecutor } from '../utils/execution';

Please apply these changes so that every module consistently consumes types via the utils/execution facade.

🤖 Prompt for AI Agents
In src/utils/execution/index.ts around line 8, the facade should re-export
CommandExecutor, CommandResponse, and CommandExecOptions so downstream modules
import from "utils/execution"; update this file to export those types (already
showing export line) and then update imports in the callers: change
src/utils/command.ts at lines 17 and 20 to import the types from
"utils/execution" instead of the underlying CommandExecutor.ts, and change
src/test-utils/mock-executors.ts at line 19 to import from "utils/execution" as
well; ensure all import paths use the facade path (utils/execution) and run
type-check to confirm no remaining direct imports of CommandExecutor.ts remain.

@eastlondoner
Copy link

the PR build is working for me!

@cameroncooke cameroncooke merged commit e4bb19c into main Aug 22, 2025
17 checks passed
@cameroncooke cameroncooke deleted the feature/test-runner-env-support branch August 22, 2025 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants