Skip to content

Expo Prebuild — Phase 1, PR 1: scaffold app.config.ts + config plugins#7544

Draft
hawkrives wants to merge 8 commits into
masterfrom
claude/expo-prebuild-pr1-scaffolding
Draft

Expo Prebuild — Phase 1, PR 1: scaffold app.config.ts + config plugins#7544
hawkrives wants to merge 8 commits into
masterfrom
claude/expo-prebuild-pr1-scaffolding

Conversation

@hawkrives

Copy link
Copy Markdown
Member

Summary

First of three PRs for Phase 1 of the Expo Prebuild migration (design spec in #7542). Scaffolding only — no behavior change. The existing ios/ directory remains authoritative; nothing in this PR runs expo prebuild or alters the build. The cutover happens in PR 2.

This PR introduces the declarative config and the local config plugins that Phase 1 cutover will rely on, all under unit-test coverage.

What's added

  • app.config.ts — 12 declarative iOS fields ported from Info.plist / project settings (bundle ID, build number, ATS exceptions, status bar style, audio background mode, supported orientations, etc.).
  • 2 library plugins registered: expo-build-properties (deployment target 14.0, newArchEnabled: false) and the three @react-native-vector-icons/* font packages.
  • 3 local config plugins under plugins/:
    • with-app-delegate-customizations — patches AppDelegate.swift with the --reset-state launch arg handler, URLCache setup, AVAudioSession playback category, and import AVFoundation. Uses begin/end markers for idempotency.
    • with-alternate-icons — adds the icon_type_windmill alternate icon entry to CFBundleIcons + CFBundleIcons~ipad, and registers the four windmill@{2,3}x{,~iPad}.png resources on the main app target.
    • with-xcuitest-target — patches the Podfile to disable Expo autolinking for the UITests target (nested inherit! :none block + Pod::Podfile::TargetDefinition.prepend shim) and ensures the AllAboutOlafUITests PBXNativeTarget exists in the Xcode project.
  • Devdeps: @expo/config-plugins@~10.1.2, expo-build-properties@~0.14.6, @types/xcode@3.0.0 (pinned to the Expo SDK 53–compatible series).

Test plan

  • mise run agent:pre-commit — pretty, lint, tsc, jest all green (235 passed across 40 suites locally).
  • Plugin string-transform helpers (patchAppDelegate, patchPodfileForUITests, addAlternateIcons) tested for happy path, idempotency, and missing-anchor errors.
  • Xcode project mutators (addWindmillResources, ensureUITestTarget) tested against the real ios/AllAboutOlaf.xcodeproj/project.pbxproj fixture, including post-strip recreation and double-application idempotency.
  • CI green on this branch.
  • Reviewer sanity-check on the plugin shape — these are not yet executed end-to-end (that's PR 2's job), so this PR validates the unit contracts only.

Notes for review

  • The xcode npm package's addTarget writes name/productName wrapped in quotes but findTargetKey does an unquoted exact match — ensureUITestTarget strips the quotes post-creation so subsequent lookups work. Worth a glance at plugins/with-xcuitest-target.ts:120-124.
  • expo-build-properties@latest resolves to v55, which conflicts with Expo SDK 53's peer range. Same story for @expo/config-plugins. Both are pinned to the ~ range that Expo 53 actually accepts.
  • The vector-icons plugin shape is per-font (each package ships its own app.plugin.js), not the centralized @react-native-vector-icons/common/plugin that the spec originally suggested.

https://claude.ai/code/session_01TFkK4X2U6kGX137MerKD8k

claude added 8 commits April 16, 2026 23:47
…dencies

Preparing for Expo Prebuild migration Phase 1. These packages are
consumed by app.config.ts and the local config plugins authored in
subsequent commits. No runtime behavior change.

Versions pinned to match what Expo SDK 53 bundles internally:
- @expo/config-plugins@~10.1.2 (Expo 53 declares ~10.1.2 as a dep)
- expo-build-properties@~0.14.6 (SDK 53-compatible series)

The default `npm install --save-dev <pkg>` resolved to 55.x for both,
which is the SDK 55 line and would conflict with expo prebuild's nested
copy of config-plugins@10.1.x. Pinning ensures our plugin imports
resolve to the same version expo prebuild loads.

Pre-flight findings (Task 0 of the PR 1 scaffolding plan):
- react-native-change-icon plugin status: Finding C — installed
  v5.0.0 ships no plugin; npm latest is also 5.0.0. Task 5 proceeds as
  written (author with-alternate-icons.ts locally).
- use_native_modules! without community CLI: Deferred — verification
  requires `pod install` (macOS-only). Will let CI on the
  implementation branch exercise it; revisit before PR 2 finalizes
  removal of @react-native-community/cli devDeps.
- @react-native-vector-icons plugin shape: each font package
  (@react-native-vector-icons/{entypo,ionicons,material-design-icons})
  ships its own app.plugin.js that registers its single font via
  withInfoPlist. Plan A's assumption of a centralized
  '@react-native-vector-icons/common/plugin' was incorrect. Task 3 will
  register each font package individually as a string-form plugin.

See docs/superpowers/plans/2026-04-16-expo-prebuild-pr1-scaffolding.md
Declares the iOS fields from the design spec (Section A of the Config
Plugin Inventory) that previously lived in Info.plist, pbxproj, or as
hand-managed constants. Plugins array is empty and wired up in
subsequent commits.

A Jest test at __tests__/app-config.test.ts asserts on the exported
shape so this config can't silently drift.

No behavior change: expo prebuild is not yet invoked, so app.config.ts
has no effect on the build yet.

See docs/superpowers/specs/2026-04-16-expo-prebuild-migration-design.md
Adds library-provided config plugins to app.config.ts:

- expo-build-properties: sets iOS deployment target to 14.0 and pins
  newArchEnabled to false. Replaces the hand-edited Podfile that
  currently sets `ENV['RCT_NEW_ARCH_ENABLED'] = '0'` at the top-level
  (removed when ios/ is regenerated in PR 2).

- @react-native-vector-icons/{entypo,ionicons,material-design-icons}:
  each font package ships its own app.plugin.js that registers its
  single .ttf via withInfoPlist on UIAppFonts. Registering all three
  individually replaces the hand-maintained Copy Files build phase
  references to ${PODS_ROOT}/../../node_modules/...

  (The plan originally assumed a centralized
  '@react-native-vector-icons/common/plugin' with iconFontNames
  options; Task 0.3 found the real shape is one plugin per font
  package, hardcoding its font name.)

Still no behavior change: expo prebuild is not yet invoked.

See docs/superpowers/specs/2026-04-16-expo-prebuild-migration-design.md
Preserves three AppDelegate.swift customizations for the forthcoming
Expo prebuild cutover:

1. --reset-state launch argument handler: scrubs Library/Application
   Support/{bundleId}, RCTAsyncLocalStorage_V1, and UserDefaults for
   the bundle ID. Used by XCUITests to start from a clean slate.

2. URLCache sizing: 4 MiB memory, 20 MiB disk. Tunes the shared URL
   cache for the app's network traffic profile.

3. AVAudioSession.playback category: ensures streaming audio plays
   while the device's mute switch is engaged.

Insertion is idempotent (bracketed by marker comments) and anchors on
`self.moduleName = "..."` which Expo's template writes from app.config
`expo.name`. Fails loudly if the anchor is missing rather than producing
a silently-wrong AppDelegate.

The transformation is expressed as pure helpers (`patchAppDelegate`,
`addImportIfMissing`) wrapped by a thin withAppDelegate adapter; 8 unit
tests cover baseline insertion, idempotency, `import AVFoundation`
handling, and the missing-anchor error case.

Plugin is registered with app.config.ts in Task 7. No behavior change
yet.

See docs/superpowers/specs/2026-04-16-expo-prebuild-migration-design.md
Reproduces the existing `icon_type_windmill` alternate-icon wiring as a
config plugin with two pure helpers:

1. addAlternateIcons(plist): ensures CFBundleAlternateIcons on both
   CFBundleIcons and CFBundleIcons~ipad contains the windmill entry
   (CFBundleIconFiles: ["windmill"], UIPrerenderedIcon: true).

2. addWindmillResources(project): registers the four windmill*.png
   files (@2x, @3x, @2x~iPad, @3x~iPad) under ios/../images/icons/ as
   bundle resources on the AllAboutOlaf target.

Both transformations are idempotent. 7 unit tests cover baseline
insertion, duplicate-avoidance, preservation of unrelated entries, and
the missing-target error case. The pbxproj tests load the real
ios/AllAboutOlaf.xcodeproj/project.pbxproj as an in-memory fixture
(never modified on disk).

Also adds @types/xcode@3.0.0 as a devDependency so the xcode npm package
(transitive dep of @expo/config-plugins) is properly typed.

Plugin is registered with app.config.ts in Task 7. No behavior change
yet.

See docs/superpowers/specs/2026-04-16-expo-prebuild-migration-design.md
Patches the Podfile to disable Expo autolinking inside the
AllAboutOlafUITests target and ensures the UITests PBXNativeTarget
exists in the Xcode project, recreating it idempotently when missing.
Wires with-app-delegate-customizations, with-alternate-icons, and
with-xcuitest-target into the Expo plugins array so prebuild applies
the AAO-specific iOS modifications.
Removes __tests__/app-config.test.ts — its assertions just mirrored the
literal values in app.config.ts and caught no real failure mode.

Replaces the per-fragment toContain assertions in the three plugin tests
with snapshot tests for the happy-path output. Behavioral assertions
(idempotency, missing-anchor errors, addImportIfMissing edge cases) stay
explicit since snapshots can't express them meaningfully.

Also drops plugins/types.ts — a 3-line ConfigPlugin re-export shim — and
imports the type directly from @expo/config-plugins in each plugin.
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.

2 participants