Skip to content

feat(apple): Add non-interactive Apple Snapshots mode#1297

Merged
cameroncooke merged 5 commits into
masterfrom
cam/apple-snapshots-non-interactive
Jul 1, 2026
Merged

feat(apple): Add non-interactive Apple Snapshots mode#1297
cameroncooke merged 5 commits into
masterfrom
cam/apple-snapshots-non-interactive

Conversation

@cameroncooke

@cameroncooke cameroncooke commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Stack

This is 3/3 in the Apple Snapshots stack replacing the original unified PR #1293.

This is the top of the stack. The final tree of this stack was verified to exactly match original PR #1293.

Summary

Adds unattended/agent-friendly execution for the Apple Snapshots wizard introduced in #1296.

The main goal is that non-interactive mode never waits on stdin. It either proceeds from explicit arguments and safe single-target assumptions, or it fails with actionable manual setup instructions and the CLI flag needed to continue.

What changed

  • Adds Apple Snapshots CLI options:
    • --non-interactive
    • --app-target
    • --hosted-test-target
  • Threads non-interactive behavior through:
    • CLI/run routing
    • project lookup
    • git safety checks
    • preview/missing-preview confirmation
    • sentry-cli preflight
    • hosted XCTest target selection
  • Adds app-target and hosted-test-target resolution behavior for unattended runs.
  • Adds prompt suppression paths so agentic setup cannot hang on stdin.
  • Updates help output for the new flags.
  • Adds tests for non-interactive success/failure paths, explicit target handling, and prompt-free behavior.

Behavior contract in this layer

  • App target selection uses --app-target when provided.
  • Without --app-target, the wizard auto-selects only when there is a single app target.
  • If the app target cannot be resolved safely in non-interactive mode, the wizard fails with guidance to pass --app-target.
  • Hosted XCTest target selection uses --hosted-test-target when provided, as long as the target exists and has a non-empty TEST_HOST.
  • Without --hosted-test-target, the wizard first narrows hosted XCTest targets to those whose TEST_HOST matches the selected app target's bundle/executable names.
  • If app-specific hosted-test matching finds one target, the wizard uses it.
  • If app-specific hosted-test matching finds multiple targets, non-interactive mode fails with guidance to pass --hosted-test-target.
  • If app-specific hosted-test matching is inconclusive but the project has exactly one hosted XCTest target, the wizard assumes the common case that this target belongs to the selected app.
  • If there are multiple hosted XCTest targets and no safe match, non-interactive mode fails with guidance.
  • Missing Swift previews are not fatal. Non-interactive mode logs the limitation and continues because agents may still want the project wiring and manual next steps.
  • Non-interactive mode must never wait on stdin.

What this PR intentionally does not do

  • Does not configure Sentry auth, DSNs, runtime SDK initialization, dSYM upload scripts, Fastlane dSYM setup, MCP config, or CI workflow files.
  • Does not change the existing ios wizard into a SnapshotPreviews setup path.
  • Does not make unsafe guesses when target resolution is ambiguous.

Suggested review focus

  • The target-selection contract for explicit CLI choices, single-target assumptions, and non-interactive failures.
  • Hosted XCTest detection via TEST_HOST and app bundle/executable name matching.
  • Confirming all non-interactive paths avoid stdin prompts.
  • Error messages/manual setup guidance for ambiguous target/project structures.
  • Help output and CLI option behavior.

Risk

Medium. This layer controls unattended behavior and is the most important one for agentic setup safety. The main risk is choosing the wrong app/test target in unusual projects or leaving a prompt path reachable in non-interactive mode.

Validation

Before submitting the stack:

Co-Authored-By: GPT-5 Codex noreply@openai.com

cameroncooke commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from c5849cc to 4309c60 Compare June 25, 2026 20:57
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from 1ecaccd to 0e81310 Compare June 25, 2026 20:57
@cameroncooke
cameroncooke marked this pull request as ready for review June 25, 2026 21:07

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Setup failures exit zero
    • Added abort() calls at lines 79 and 88 to ensure non-zero exit code when target resolution fails in non-interactive mode.

Create PR

Or push these changes by commenting:

@cursor push b0e0508f41
Preview (b0e0508f41)
diff --git a/src/apple/snapshots/apple-snapshots-wizard.ts b/src/apple/snapshots/apple-snapshots-wizard.ts
--- a/src/apple/snapshots/apple-snapshots-wizard.ts
+++ b/src/apple/snapshots/apple-snapshots-wizard.ts
@@ -6,6 +6,7 @@
 
 import { withTelemetry } from '../../telemetry';
 import {
+  abort,
   abortIfCancelled,
   askForItemSelection,
   confirmContinueIfNoOrDirtyGitRepo,
@@ -75,7 +76,7 @@
 
   const appTargetName = await resolveAppTargetName(xcProject, options);
   if (!appTargetName) {
-    return;
+    await abort();
   }
 
   const hostedTestTargetName = await resolveHostedTestTargetName(
@@ -84,7 +85,7 @@
     options,
   );
   if (!hostedTestTargetName) {
-    return;
+    await abort();
   }
 
   const selectedTargetHasSwiftPreviews = targetHasSwiftPreviews(

You can send follow-ups to the cloud agent here.

Comment thread src/apple/snapshots/apple-snapshots-wizard.ts
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from 4309c60 to d5375ef Compare June 25, 2026 21:34
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from 0e81310 to 7d5069f Compare June 25, 2026 21:34
Comment thread src/apple/snapshots/apple-snapshots-wizard.ts
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from d5375ef to 18e0e3e Compare June 25, 2026 21:58
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from 7d5069f to 1156eb3 Compare June 25, 2026 21:58
Comment thread src/apple/snapshots/apple-snapshots-wizard.ts
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from 18e0e3e to df4041e Compare June 25, 2026 22:29
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch 2 times, most recently from fe68f49 to a291a73 Compare June 25, 2026 22:42
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch 4 times, most recently from cb5aea2 to e05dc8b Compare June 26, 2026 08:59
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from a291a73 to a12c102 Compare June 29, 2026 09:29
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from e05dc8b to 2d53259 Compare June 29, 2026 09:29
@philprime
philprime self-requested a review June 29, 2026 09:38
Comment thread e2e-tests/tests/pnpm-workspace.test.ts
Comment thread src/apple/snapshots/snapshots-cli-preflight.ts Outdated
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from 2d53259 to 5d69d75 Compare June 29, 2026 13:27
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from a12c102 to 842c852 Compare June 29, 2026 13:27
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from 01ff854 to f1f61e5 Compare June 29, 2026 14:44

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Wrong non-interactive no-app-target error
    • Separated the zero-targets case from multiple-targets case in non-interactive mode to show 'No application target found.' instead of misleadingly suggesting --app-target when no targets exist.

Create PR

Or push these changes by commenting:

@cursor push 29a69bdb06
Preview (29a69bdb06)
diff --git a/src/apple/snapshots/apple-snapshots-wizard.ts b/src/apple/snapshots/apple-snapshots-wizard.ts
--- a/src/apple/snapshots/apple-snapshots-wizard.ts
+++ b/src/apple/snapshots/apple-snapshots-wizard.ts
@@ -191,15 +191,22 @@
     return undefined;
   }
 
-  if (options.nonInteractive && appTargetNames.length !== 1) {
-    clack.log.error(
-      [
-        'Could not automatically select an Xcode app target in non-interactive mode.',
-        `Available app targets: ${formatList(appTargetNames)}.`,
-        'Pass --app-target <target-name> to select the app target that SnapshotPreviews should use.',
-      ].join(' '),
-    );
-    return undefined;
+  if (options.nonInteractive) {
+    if (appTargetNames.length === 0) {
+      clack.log.error('No application target found.');
+      return undefined;
+    }
+
+    if (appTargetNames.length !== 1) {
+      clack.log.error(
+        [
+          'Could not automatically select an Xcode app target in non-interactive mode.',
+          `Available app targets: ${formatList(appTargetNames)}.`,
+          'Pass --app-target <target-name> to select the app target that SnapshotPreviews should use.',
+        ].join(' '),
+      );
+      return undefined;
+    }
   }
 
   return await selectXcodeTarget(xcodeProject, {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit f1f61e5. Configure here.

Comment thread src/apple/snapshots/apple-snapshots-wizard.ts
Comment thread src/apple/snapshots/apple-snapshots-wizard.ts

@itaybre itaybre left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some comments here and there.
I am mainly concern about using undefined in the tests and not testing for false too

Comment thread test/apple/snapshots/snapshots-cli-preflight.test.ts Outdated
Comment thread src/run.ts Outdated
skipConnect: boolean;
debug: boolean;
quiet: boolean;
nonInteractive: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

l: If nonInteractive is either true / false here, why it is optional in other places?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've simplified this now, this is no longer optional where it's used exclusively by snapshot wizard codepaths, where it was threaded into existing shared functions like for example confirmContinueIfNoOrDirtyGitRepo we keep as an optional as apple-wizard doesn't need to pass argument as it's always interactive this avoid a ton of churn adding in explicit false at all the callsites.

options,
);
if (!hostedTestTargetName) {
return await abort('Apple Snapshots setup did not complete.');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

m: Should we tell the user why it didn't complete

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In all abort paths we have clack.log.error(...) which prints the underlying error message.

@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from e2831b8 to abe66f5 Compare June 30, 2026 15:05
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-wizard branch from 3fc21e5 to 2e5797b Compare June 30, 2026 15:05
@cameroncooke
cameroncooke changed the base branch from cam/apple-snapshots-wizard to graphite-base/1297 June 30, 2026 15:15
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from abe66f5 to e4e647a Compare June 30, 2026 15:33
@cameroncooke
cameroncooke changed the base branch from graphite-base/1297 to cam/apple-snapshots-wizard June 30, 2026 15:33

@itaybre itaybre left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

cameroncooke commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Jul 1, 4:39 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 1, 4:48 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jul 1, 4:53 PM UTC: Graphite couldn't merge this PR because it was not satisfying all requirements (Failed CI: 'All required jobs passed or were skipped', 'cloudflare-worker E2E Tests (macos-15)').

cameroncooke added a commit that referenced this pull request Jul 1, 2026
## Stack

This is **1/3** in the Apple Snapshots stack replacing the original unified PR #1293.

- 1/3: #1295 — Xcode project primitives (this PR)
- 2/3: #1296 — Interactive Apple Snapshots wizard
- 3/3: #1297 — Non-interactive Apple Snapshots mode

The top of the stack is intended to be an exact, lossless re-expression of #1293.

## Summary

This PR refactors the existing Apple/Xcode project infrastructure needed by the Apple Snapshots wizard, without exposing the new `appleSnapshots` wizard yet.

The goal is to make the shared Xcode helpers reviewable independently before adding the user-facing SnapshotPreviews flow. Existing Sentry Apple setup remains routed through the current `ios` wizard; this layer should be shared infrastructure, not a new runtime setup behavior.

## What changed

- Split Xcode project lookup/selection helpers so target discovery can be reused by the snapshots flow.
- Move Sentry Swift package metadata into a reusable package spec.
- Extend `XcodeProject` with reusable helpers for:
  - hosted XCTest target detection via `TEST_HOST`
  - app bundle ID / executable lookup
  - Swift package product linking
  - source file resolution
  - Swift source insertion
  - synchronized-folder source membership support
- Preserve existing `configureXcodeProject` / `updateXcodeProject` behavior for the normal Apple SDK setup path.
- Add focused tests for the refactored project lookup, Xcode mutation, and git helper behavior.

## What this PR intentionally does not do

- Does not add the `appleSnapshots` integration option.
- Does not add the Apple Snapshots wizard entrypoint.
- Does not configure SnapshotPreviews yet.
- Does not configure Sentry auth, DSNs, runtime SDK initialization, dSYM upload scripts, Fastlane setup, MCP config, or CI workflow files.

Those pieces land in the upstack PRs.

## Suggested review focus

- `.pbxproj` mutation correctness and idempotency.
- Shared Swift package product linking behavior.
- Hosted XCTest target detection via `TEST_HOST`.
- Ensuring existing `ios` wizard behavior did not accidentally change.
- Whether the helper boundaries are general enough for both normal Apple SDK setup and SnapshotPreviews setup.

## Risk

Medium. This refactors shared Apple/Xcode project mutation code used by existing setup paths. The risk is primarily around unusual Xcode project graphs, package product linking, target discovery, and source membership handling.

## Validation

Before submitting the stack:

- `yarn test` passed: 947 passed, 4 skipped
- `yarn build` passed
- Final stack tree matched original PR #1293 exactly
- `gt submit --stack --dry-run` passed

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
@cameroncooke
cameroncooke changed the base branch from cam/apple-snapshots-wizard to graphite-base/1297 July 1, 2026 16:40
cameroncooke added a commit that referenced this pull request Jul 1, 2026
## Stack

This is **2/3** in the Apple Snapshots stack replacing the original unified PR #1293.

- 1/3: #1295 — Xcode project primitives
- 2/3: #1296 — Interactive Apple Snapshots wizard (this PR)
- 3/3: #1297 — Non-interactive Apple Snapshots mode

This PR builds on #1295. The top of the stack is intended to be an exact, lossless re-expression of #1293.

## Summary

Adds a standalone **Apple Snapshots** wizard for configuring Sentry SnapshotPreviews in Apple/Xcode projects without running the normal iOS runtime SDK setup.

The new `appleSnapshots` integration is available from the wizard picker and via `--integration appleSnapshots`. It reuses the Xcode project discovery path from #1295, asks for the app target that hosts Swift previews, then selects a hosted XCTest target for running SnapshotPreviews.

This is intentionally separate from the existing `ios` wizard. Snapshot setup is related to Apple projects, but it should not silently configure normal runtime SDK behavior. Keeping this as its own wizard makes the user-visible behavior narrower: project wiring plus explicit next-step guidance for snapshot export/upload.

## What changed

- Adds the `appleSnapshots` integration and CLI routing.
- Adds SnapshotPreviews package configuration.
- Links:
  - `SnapshottingTests` to the hosted test target
  - `SnapshotPreferences` to the selected app target only when Swift previews are detected
- Creates or reuses a generated `SnapshotTest` subclass.
- Ensures the generated test file belongs to the selected XCTest target.
- Adds scheme inference for generated `xcodebuild test` guidance only.
- Prints local export/upload guidance for:
  - `xcodebuild`
  - `sentry-cli snapshots upload`
  - existing Fastlane snapshot upload lanes
- Adds tests for SnapshotPreviews package setup, generated test files, source insertion, smoke coverage, scheme inference, preflight behavior, CLI constants, and run routing.
- Updates the help snapshot and changelog for the interactive wizard surface.

## Behavior contract in this layer

- App target selection follows the existing interactive wizard pattern.
- Hosted XCTest target selection uses hosted test target detection from #1295.
- Missing Swift previews are not fatal. Interactive mode asks whether to continue.
- Snapshot scheme inference is used only to generate `xcodebuild test` guidance.
- If the correct scheme cannot be inferred safely, the wizard still completes and prints guidance without a scheme.
- The wizard does not configure Sentry auth, DSNs, runtime SDK initialization, dSYM upload scripts, Fastlane dSYM setup, MCP config, or CI workflow files.

## What this PR intentionally does not do

- Does not add unattended/non-interactive behavior.
- Does not add `--non-interactive`, `--app-target`, or `--hosted-test-target` flags for Apple Snapshots.
- Does not guarantee prompt-free execution for agentic runs.

Those pieces land in #1297.

## Suggested review focus

- SnapshotPreviews package URL, minimum version, and product names.
- `.pbxproj` mutation correctness and idempotency for package/product linking.
- Generated snapshot test file placement/reuse for both classic source phases and synchronized folders.
- Hosted XCTest target selection behavior in interactive mode.
- Scheme inference being limited to generated `xcodebuild test` guidance.
- Ensuring the existing `ios` wizard did not pick up accidental runtime behavior changes.

## Risk

Medium. The new wizard mutates Xcode project graphs, links package products, and creates/reuses source files. The new `appleSnapshots` flow is isolated from auth/runtime/dSYM/CI configuration, but unusual Xcode project structures, ambiguous schemes, or non-standard hosted test target settings are the areas most likely to need reviewer scrutiny.

## Validation

Before submitting the stack:

- `yarn test` passed: 947 passed, 4 skipped
- `yarn build` passed
- Final stack tree matched original PR #1293 exactly
- `gt submit --stack --dry-run` passed

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
@cameroncooke
cameroncooke changed the base branch from graphite-base/1297 to master July 1, 2026 16:46
cameroncooke and others added 5 commits July 1, 2026 16:47
Co-Authored-By: GPT-5 Codex <noreply@openai.com>

# Conflicts:
#	src/apple/snapshots/apple-snapshots-wizard.ts
#	test/apple/snapshots/apple-snapshots-wizard.test.ts
Require non-interactive Apple Snapshots setup to opt into snapshots preflight behavior explicitly.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Report a missing application target directly before handling non-interactive ambiguity. This avoids suggesting --app-target when the project has no selectable app targets.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Previously `nonInteractive` was an optional flag threaded through the Apple
snapshots wizard and git-check helpers as `boolean | undefined`, forcing every
call site to reason about the undefined case. Default it to `false` at each
boundary and treat it as a required boolean so the flag carries a single,
explicit meaning.

Add unit tests covering the non-interactive abort paths of
confirmContinueIfNoOrDirtyGitRepo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When linking the SnapshotPreviews products or adding the test file fails,
the wizard used clack.outro and returned, which exited cleanly and recorded
no failure telemetry. Use abort instead so the run is marked as failed and
exits with a non-zero status, matching the other failure paths.

Also extract the repeated "Apple Snapshots setup did not complete." message
into a shared constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cameroncooke
cameroncooke force-pushed the cam/apple-snapshots-non-interactive branch from e4e647a to 8fb7b90 Compare July 1, 2026 16:47
@cameroncooke
cameroncooke merged commit 3fd04f8 into master Jul 1, 2026
108 of 110 checks passed
@cameroncooke
cameroncooke deleted the cam/apple-snapshots-non-interactive branch July 1, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants