Skip to content

chore: widen expo peer range to >=51 - #3

Open
EliottGand wants to merge 3 commits into
mainfrom
chore/widen-expo-peer-range
Open

EliottGand wants to merge 3 commits into
mainfrom
chore/widen-expo-peer-range

Conversation

@EliottGand

@EliottGand EliottGand commented Sep 21, 2026 •

Copy link
Copy Markdown

What changed

package.json — peerDependencies.expo: "^51.0.9" → ">=51". Plus a new test:plugin script.

README.md — a "Supported Expo SDK versions" section stating what actually works per platform per SDK, now including what has been verified at runtime on a device, and a short Development/Tests section.

test/ (new) — the repo's first tests. They run the plugin's iOS and Android mods against the real AppDelegate / MainActivity sources shipped by expo-template-bare-minimum for SDK 51, 52, 53, 54 and 57 (checked in under test/fixtures/).

.github/workflows/ci.yml (new) — the repo's first CI: runs those tests, and an expo prebuild --platform android smoke check against a freshly created app on the current SDK.

No source change to the plugin. The package version is not bumped: the release is left to the maintainer.

Why

The pin is several SDKs behind, and it is a hard install failure, not a warning. Reproduced against the published package in a fresh Expo SDK 57 app:

$ npm install @bam.tech/react-native-keyevent-expo-config-plugin@1.0.52
npm error Found: expo@57.0.24
npm error Could not resolve dependency:
npm error peer expo@"^51.0.9" from @bam.tech/react-native-keyevent-expo-config-plugin@1.0.52

PROVEN ON DEVICE: Android key events actually reach JS on SDK 57

This is the part that a passing expo prebuild does not establish, so it was done for real.

A fresh Expo SDK 57 app (expo@57.0.24, react-native@0.86.3) with react-native-keyevent@0.3.2 and this plugin installed from a local path, prebuilt for Android, then fully built and installed with npx expo run:android (BUILD SUCCESSFUL in 1m 25s) on the Android emulator Medium_Phone_API_36.1 (API 36, arm64). The app screen renders the last key code, a running event count, and a scrolling event log, wired to KeyEvent.onKeyDownListener / onKeyUpListener with the documented cleanup.

Hardware keys were then injected into the running app and the on-screen state was read back from the accessibility tree after each press:

Key pressed keyCode in JS pressedKey Events added On-screen state read back
Volume up 24 – DOWN + UP lastKeyCode 24, eventCount 2
Volume down 25 – DOWN + UP lastKeyCode 25, eventCount 4, log of 4
a 29 a DOWN + UP lastKeyCode 29, eventCount 6, log of 6
Back 4 – DOWN + UP lastKeyCode 4, eventCount 2 (fresh launch)
Enter 66 – DOWN + UP in log
z 54 z DOWN + UP lastKeyCode 54, eventCount 6, log of 6

So on Expo SDK 57 / RN 0.86.3 with the new architecture enabled, the MainActivity.kt overrides the plugin generates are wired correctly and volume keys, character keys, Enter and Back all reach JS as both onKeyDown and onKeyUp.

One behavioural note worth knowing: the generated onKeyDown / onKeyUp call super before returning true, so a Back press still finishes the activity. Observing Back on screen required the test app to handle hardwareBackPress itself; without that the event does reach JS but the activity is gone before you can see it.

Automated regression coverage (new)

yarn test:plugin — 17 tests, all passing locally, on Node's built-in test runner:

  • Android: for SDK 51, 52, 53, 54 and 57, the Kotlin MainActivity gets both generated blocks, the imports and all three overrides, placed inside the class body; the merge is idempotent.
  • iOS Objective-C++ (SDK 51, 52): both generated blocks are produced.
  • iOS Swift (SDK 53, 54, 57): assert.rejects with code === 'ERR_NO_MATCH'. The test asserts the breakage on purpose — it exists so the gap cannot be forgotten, and it should be rewritten if and when the iOS mod is ported to Swift.

CI (run 35621289060, conclusion success): both jobs green on this branch.

Job Result
Plugin mod tests (yarn build + yarn test:plugin) success
expo prebuild --platform android (current SDK) success, against expo@~57.0.24 from a fresh create-expo-app, asserting both generated blocks land in MainActivity.kt

On the repo's existing scripts: yarn lint and yarn test are red on main for unrelated toolchain reasons (eslint 10 refuses .eslintrc.js; jest-expo cannot resolve jest/package.json). I did not fix them. The new tests are deliberately independent of both, run under their own test:plugin script, and CI runs only yarn build + yarn test:plugin. Fixing the eslint/jest toolchain is a separate change.

The iOS problem (pre-existing, not introduced here, not fixed here)

Error: [ios.appDelegate]: withIosAppDelegateBaseMod: Failed to match "@implementation AppDelegate" in contents:
    at addLines (.../@expo/config-plugins/build/utils/generateCode.js:85:19)

expo-template-bare-minimum ships AppDelegate.mm + AppDelegate.h on SDK 51/52 and AppDelegate.swift from SDK 53 onwards. The plugin's two iOS mods anchor on #import "AppDelegate.h" and @implementation AppDelegate; mergeContents throws rather than no-opping, so it is a hard prebuild failure. Already true on main; the pin just made SDK 53+ users hit an install error first.

I have not attempted the Swift port: that is a behavioural change and belongs in its own PR with the maintainer's call on how to express keyCommands / keyInput: in AppDelegate.swift.

What is still NOT proven

  • iOS at runtime: nothing. Not on any SDK. SDK 51/52 iOS is "the text merge succeeds", never "key events reach JS on a simulator or device".
  • Android runtime on SDKs other than 57. SDK 51–56 rows come from running the mods against template sources, not from a build or a device.
  • Java MainActivity. The plugin emits Kotlin only; a hand-written MainActivity.java would get Kotlin spliced into it. Untested, now stated in the README.
  • Physical devices and real external keyboards / remotes. Everything above is an emulator with injected key events.
  • onKeyMultiple. The override is generated and compiles, but no multiple-key event was produced on device.

What a reviewer should still decide

  • Whether >=51 is the range you want. It is fully open at the top, so it will never warn on a future SDK either, including ones that break the plugin. >=51 <58 is the alternative.
  • The iOS story. Widening the range makes a broken-on-iOS install possible where it previously errored out early. If you would rather hold the widen until the Swift mod exists, that is a reasonable call.
  • Whether @expo/config-plugins: ~8.0.0 should be relaxed or moved to a peer dependency. Left alone deliberately; bundling a v8 copy next to an app's v57 is a latent hazard worth its own look.

🤖 Generated with Claude Code

Eliott Gandiolle and others added 3 commits September 21, 2026 17:24
The peerDependency was pinned to `expo: "^51.0.9"`, which is now several SDKs
behind. Installing `@bam.tech/react-native-keyevent-expo-config-plugin@1.0.52`
into an Expo SDK 57 app fails outright with npm:

    npm error Could not resolve dependency:
    npm error peer expo@"^51.0.9" from @bam.tech/react-native-keyevent-expo-config-plugin@1.0.52

Widening to `>=51` unblocks that install. No source change: the plugin's own
`@expo/config-plugins` dependency and the APIs it uses are untouched.

Verified:

- `withAppDelegate`, `withMainActivity` and `mergeContents`
  (`@expo/config-plugins/build/utils/generateCode`) still exist with identical
  signatures in @expo/config-plugins 9.1.7, 11.0.7 and 57.0.9.
- `yarn build` (expo-module build) passes and produces no change to `build/`.
- Fresh SDK 57 `create-expo-app` project with the plugin installed from this
  working tree: `npx expo prebuild --clean --platform android` succeeds and both
  generated blocks land in `MainActivity.kt`.
- `npx expo prebuild --clean --platform ios` on the same project FAILS. From SDK
  53 the Expo template ships `AppDelegate.swift`, so the plugin's ObjC anchors
  (`#import "AppDelegate.h"`, `@implementation AppDelegate`) no longer match and
  `mergeContents` throws ERR_NO_MATCH. That is pre-existing and not introduced
  here; the README now documents it instead of leaving it implied by the peer
  range.

`yarn lint` and `yarn test` are already broken on main (eslint 10 vs the
`.eslintrc.js` flat-config migration, and a missing `jest` for jest-expo); both
fail identically before and after this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runs the plugin's iOS and Android mods against the AppDelegate / MainActivity
sources shipped by expo-template-bare-minimum for SDK 51, 52, 53, 54 and 57.

- Android Kotlin succeeds on every SDK tested and contains both generated blocks
- iOS Objective-C++ (SDK 51/52) succeeds
- iOS Swift (SDK 53+) fails with ERR_NO_MATCH, asserted on purpose so the known
  breakage cannot regress silently in either direction

The tests run on Node's built-in test runner via `yarn test:plugin`, independent
of `yarn lint` / `yarn test`, which are red on main for unrelated toolchain
reasons.

Adds a CI workflow running those tests plus an `expo prebuild --platform android`
smoke check against a freshly created app on the current SDK.

Also records in the README what is now verified on a real Android emulator:
volume, character, enter and back presses all reach JS via react-native-keyevent
on Expo SDK 57 / RN 0.86.3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`node --test "test/*.test.js"` only expands the glob on Node 21+, so CI on
Node 20 failed with "Could not find .../test/*.test.js".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant