From e1346e20b9e9c695d33d2393c687dc7bf37f046b Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:15:49 -0400 Subject: [PATCH 1/8] refactor(expo-native): move native SDK code from @clerk/expo into packages/expo-native Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/{expo => expo-native}/.gitignore | 0 packages/{expo => expo-native}/android/build.gradle | 0 .../{expo => expo-native}/android/src/main/AndroidManifest.xml | 0 .../src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt | 0 .../main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt | 0 .../android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt | 0 .../src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt | 0 .../java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt | 0 .../main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt | 0 .../test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt | 0 .../expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt | 0 packages/{expo => expo-native}/app.plugin.js | 0 packages/{expo => expo-native}/expo-module.config.json | 0 .../{expo => expo-native}/ios/ClerkAppDelegateSubscriber.swift | 0 packages/{expo => expo-native}/ios/ClerkAuthNativeView.swift | 0 packages/{expo => expo-native}/ios/ClerkExpo.podspec | 0 packages/{expo => expo-native}/ios/ClerkExpoModule.swift | 0 packages/{expo => expo-native}/ios/ClerkNativeBridge.swift | 0 packages/{expo => expo-native}/ios/ClerkNativeViewHost.swift | 0 .../{expo => expo-native}/ios/ClerkUserButtonNativeView.swift | 0 .../{expo => expo-native}/ios/ClerkUserProfileNativeView.swift | 0 .../ios/Tests/ClerkAuthNativeViewPaperTests.swift | 0 .../{expo => expo-native}/ios/Tests/ClerkNativeBridgeTests.swift | 0 .../ios/Tests/ClerkNativeViewHostTests.swift | 0 .../ios/Tests/ClerkUserProfileCustomPageStateTests.swift | 0 packages/{expo => expo-native}/react-native.config.js | 0 packages/{expo/src/native => expo-native/src}/AuthView.tsx | 0 packages/{expo/src/native => expo-native/src}/AuthView.types.ts | 0 .../src/native => expo-native/src}/EmbeddedNavigation.types.ts | 0 packages/{expo/src/native => expo-native/src}/UserButton.tsx | 0 .../src/native => expo-native/src}/UserProfileCustomPages.tsx | 0 packages/{expo/src/native => expo-native/src}/UserProfileView.tsx | 0 .../src/native => expo-native/src}/__tests__/AuthView.test.tsx | 0 .../src/native => expo-native/src}/__tests__/UserButton.test.tsx | 0 .../src}/__tests__/UserProfileCustomPages.test.tsx | 0 .../native => expo-native/src}/__tests__/UserProfileView.test.tsx | 0 .../{expo => expo-native}/src/__tests__/appPlugin.theme.test.js | 0 .../src}/__tests__/useAuthViewState.test.tsx | 0 packages/{expo/src/native => expo-native/src}/index.ts | 0 .../src/specs/NativeClerkAuthView.android.ts | 0 packages/{expo => expo-native}/src/specs/NativeClerkAuthView.ts | 0 .../src/specs/NativeClerkUserButtonView.android.ts | 0 .../{expo => expo-native}/src/specs/NativeClerkUserButtonView.ts | 0 .../src/specs/NativeClerkUserProfileView.android.ts | 0 .../{expo => expo-native}/src/specs/NativeClerkUserProfileView.ts | 0 .../src/specs/__tests__/native-view.web.test.ts | 0 packages/{expo/src/native => expo-native/src}/useAuthViewState.ts | 0 47 files changed, 0 insertions(+), 0 deletions(-) rename packages/{expo => expo-native}/.gitignore (100%) rename packages/{expo => expo-native}/android/build.gradle (100%) rename packages/{expo => expo-native}/android/src/main/AndroidManifest.xml (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt (100%) rename packages/{expo => expo-native}/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt (100%) rename packages/{expo => expo-native}/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt (100%) rename packages/{expo => expo-native}/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt (100%) rename packages/{expo => expo-native}/app.plugin.js (100%) rename packages/{expo => expo-native}/expo-module.config.json (100%) rename packages/{expo => expo-native}/ios/ClerkAppDelegateSubscriber.swift (100%) rename packages/{expo => expo-native}/ios/ClerkAuthNativeView.swift (100%) rename packages/{expo => expo-native}/ios/ClerkExpo.podspec (100%) rename packages/{expo => expo-native}/ios/ClerkExpoModule.swift (100%) rename packages/{expo => expo-native}/ios/ClerkNativeBridge.swift (100%) rename packages/{expo => expo-native}/ios/ClerkNativeViewHost.swift (100%) rename packages/{expo => expo-native}/ios/ClerkUserButtonNativeView.swift (100%) rename packages/{expo => expo-native}/ios/ClerkUserProfileNativeView.swift (100%) rename packages/{expo => expo-native}/ios/Tests/ClerkAuthNativeViewPaperTests.swift (100%) rename packages/{expo => expo-native}/ios/Tests/ClerkNativeBridgeTests.swift (100%) rename packages/{expo => expo-native}/ios/Tests/ClerkNativeViewHostTests.swift (100%) rename packages/{expo => expo-native}/ios/Tests/ClerkUserProfileCustomPageStateTests.swift (100%) rename packages/{expo => expo-native}/react-native.config.js (100%) rename packages/{expo/src/native => expo-native/src}/AuthView.tsx (100%) rename packages/{expo/src/native => expo-native/src}/AuthView.types.ts (100%) rename packages/{expo/src/native => expo-native/src}/EmbeddedNavigation.types.ts (100%) rename packages/{expo/src/native => expo-native/src}/UserButton.tsx (100%) rename packages/{expo/src/native => expo-native/src}/UserProfileCustomPages.tsx (100%) rename packages/{expo/src/native => expo-native/src}/UserProfileView.tsx (100%) rename packages/{expo/src/native => expo-native/src}/__tests__/AuthView.test.tsx (100%) rename packages/{expo/src/native => expo-native/src}/__tests__/UserButton.test.tsx (100%) rename packages/{expo/src/native => expo-native/src}/__tests__/UserProfileCustomPages.test.tsx (100%) rename packages/{expo/src/native => expo-native/src}/__tests__/UserProfileView.test.tsx (100%) rename packages/{expo => expo-native}/src/__tests__/appPlugin.theme.test.js (100%) rename packages/{expo/src/native => expo-native/src}/__tests__/useAuthViewState.test.tsx (100%) rename packages/{expo/src/native => expo-native/src}/index.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkAuthView.android.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkAuthView.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkUserButtonView.android.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkUserButtonView.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkUserProfileView.android.ts (100%) rename packages/{expo => expo-native}/src/specs/NativeClerkUserProfileView.ts (100%) rename packages/{expo => expo-native}/src/specs/__tests__/native-view.web.test.ts (100%) rename packages/{expo/src/native => expo-native/src}/useAuthViewState.ts (100%) diff --git a/packages/expo/.gitignore b/packages/expo-native/.gitignore similarity index 100% rename from packages/expo/.gitignore rename to packages/expo-native/.gitignore diff --git a/packages/expo/android/build.gradle b/packages/expo-native/android/build.gradle similarity index 100% rename from packages/expo/android/build.gradle rename to packages/expo-native/android/build.gradle diff --git a/packages/expo/android/src/main/AndroidManifest.xml b/packages/expo-native/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/expo/android/src/main/AndroidManifest.xml rename to packages/expo-native/android/src/main/AndroidManifest.xml diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt diff --git a/packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt similarity index 100% rename from packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt rename to packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt diff --git a/packages/expo/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt b/packages/expo-native/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt similarity index 100% rename from packages/expo/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt rename to packages/expo-native/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt diff --git a/packages/expo/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt b/packages/expo-native/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt similarity index 100% rename from packages/expo/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt rename to packages/expo-native/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt diff --git a/packages/expo/app.plugin.js b/packages/expo-native/app.plugin.js similarity index 100% rename from packages/expo/app.plugin.js rename to packages/expo-native/app.plugin.js diff --git a/packages/expo/expo-module.config.json b/packages/expo-native/expo-module.config.json similarity index 100% rename from packages/expo/expo-module.config.json rename to packages/expo-native/expo-module.config.json diff --git a/packages/expo/ios/ClerkAppDelegateSubscriber.swift b/packages/expo-native/ios/ClerkAppDelegateSubscriber.swift similarity index 100% rename from packages/expo/ios/ClerkAppDelegateSubscriber.swift rename to packages/expo-native/ios/ClerkAppDelegateSubscriber.swift diff --git a/packages/expo/ios/ClerkAuthNativeView.swift b/packages/expo-native/ios/ClerkAuthNativeView.swift similarity index 100% rename from packages/expo/ios/ClerkAuthNativeView.swift rename to packages/expo-native/ios/ClerkAuthNativeView.swift diff --git a/packages/expo/ios/ClerkExpo.podspec b/packages/expo-native/ios/ClerkExpo.podspec similarity index 100% rename from packages/expo/ios/ClerkExpo.podspec rename to packages/expo-native/ios/ClerkExpo.podspec diff --git a/packages/expo/ios/ClerkExpoModule.swift b/packages/expo-native/ios/ClerkExpoModule.swift similarity index 100% rename from packages/expo/ios/ClerkExpoModule.swift rename to packages/expo-native/ios/ClerkExpoModule.swift diff --git a/packages/expo/ios/ClerkNativeBridge.swift b/packages/expo-native/ios/ClerkNativeBridge.swift similarity index 100% rename from packages/expo/ios/ClerkNativeBridge.swift rename to packages/expo-native/ios/ClerkNativeBridge.swift diff --git a/packages/expo/ios/ClerkNativeViewHost.swift b/packages/expo-native/ios/ClerkNativeViewHost.swift similarity index 100% rename from packages/expo/ios/ClerkNativeViewHost.swift rename to packages/expo-native/ios/ClerkNativeViewHost.swift diff --git a/packages/expo/ios/ClerkUserButtonNativeView.swift b/packages/expo-native/ios/ClerkUserButtonNativeView.swift similarity index 100% rename from packages/expo/ios/ClerkUserButtonNativeView.swift rename to packages/expo-native/ios/ClerkUserButtonNativeView.swift diff --git a/packages/expo/ios/ClerkUserProfileNativeView.swift b/packages/expo-native/ios/ClerkUserProfileNativeView.swift similarity index 100% rename from packages/expo/ios/ClerkUserProfileNativeView.swift rename to packages/expo-native/ios/ClerkUserProfileNativeView.swift diff --git a/packages/expo/ios/Tests/ClerkAuthNativeViewPaperTests.swift b/packages/expo-native/ios/Tests/ClerkAuthNativeViewPaperTests.swift similarity index 100% rename from packages/expo/ios/Tests/ClerkAuthNativeViewPaperTests.swift rename to packages/expo-native/ios/Tests/ClerkAuthNativeViewPaperTests.swift diff --git a/packages/expo/ios/Tests/ClerkNativeBridgeTests.swift b/packages/expo-native/ios/Tests/ClerkNativeBridgeTests.swift similarity index 100% rename from packages/expo/ios/Tests/ClerkNativeBridgeTests.swift rename to packages/expo-native/ios/Tests/ClerkNativeBridgeTests.swift diff --git a/packages/expo/ios/Tests/ClerkNativeViewHostTests.swift b/packages/expo-native/ios/Tests/ClerkNativeViewHostTests.swift similarity index 100% rename from packages/expo/ios/Tests/ClerkNativeViewHostTests.swift rename to packages/expo-native/ios/Tests/ClerkNativeViewHostTests.swift diff --git a/packages/expo/ios/Tests/ClerkUserProfileCustomPageStateTests.swift b/packages/expo-native/ios/Tests/ClerkUserProfileCustomPageStateTests.swift similarity index 100% rename from packages/expo/ios/Tests/ClerkUserProfileCustomPageStateTests.swift rename to packages/expo-native/ios/Tests/ClerkUserProfileCustomPageStateTests.swift diff --git a/packages/expo/react-native.config.js b/packages/expo-native/react-native.config.js similarity index 100% rename from packages/expo/react-native.config.js rename to packages/expo-native/react-native.config.js diff --git a/packages/expo/src/native/AuthView.tsx b/packages/expo-native/src/AuthView.tsx similarity index 100% rename from packages/expo/src/native/AuthView.tsx rename to packages/expo-native/src/AuthView.tsx diff --git a/packages/expo/src/native/AuthView.types.ts b/packages/expo-native/src/AuthView.types.ts similarity index 100% rename from packages/expo/src/native/AuthView.types.ts rename to packages/expo-native/src/AuthView.types.ts diff --git a/packages/expo/src/native/EmbeddedNavigation.types.ts b/packages/expo-native/src/EmbeddedNavigation.types.ts similarity index 100% rename from packages/expo/src/native/EmbeddedNavigation.types.ts rename to packages/expo-native/src/EmbeddedNavigation.types.ts diff --git a/packages/expo/src/native/UserButton.tsx b/packages/expo-native/src/UserButton.tsx similarity index 100% rename from packages/expo/src/native/UserButton.tsx rename to packages/expo-native/src/UserButton.tsx diff --git a/packages/expo/src/native/UserProfileCustomPages.tsx b/packages/expo-native/src/UserProfileCustomPages.tsx similarity index 100% rename from packages/expo/src/native/UserProfileCustomPages.tsx rename to packages/expo-native/src/UserProfileCustomPages.tsx diff --git a/packages/expo/src/native/UserProfileView.tsx b/packages/expo-native/src/UserProfileView.tsx similarity index 100% rename from packages/expo/src/native/UserProfileView.tsx rename to packages/expo-native/src/UserProfileView.tsx diff --git a/packages/expo/src/native/__tests__/AuthView.test.tsx b/packages/expo-native/src/__tests__/AuthView.test.tsx similarity index 100% rename from packages/expo/src/native/__tests__/AuthView.test.tsx rename to packages/expo-native/src/__tests__/AuthView.test.tsx diff --git a/packages/expo/src/native/__tests__/UserButton.test.tsx b/packages/expo-native/src/__tests__/UserButton.test.tsx similarity index 100% rename from packages/expo/src/native/__tests__/UserButton.test.tsx rename to packages/expo-native/src/__tests__/UserButton.test.tsx diff --git a/packages/expo/src/native/__tests__/UserProfileCustomPages.test.tsx b/packages/expo-native/src/__tests__/UserProfileCustomPages.test.tsx similarity index 100% rename from packages/expo/src/native/__tests__/UserProfileCustomPages.test.tsx rename to packages/expo-native/src/__tests__/UserProfileCustomPages.test.tsx diff --git a/packages/expo/src/native/__tests__/UserProfileView.test.tsx b/packages/expo-native/src/__tests__/UserProfileView.test.tsx similarity index 100% rename from packages/expo/src/native/__tests__/UserProfileView.test.tsx rename to packages/expo-native/src/__tests__/UserProfileView.test.tsx diff --git a/packages/expo/src/__tests__/appPlugin.theme.test.js b/packages/expo-native/src/__tests__/appPlugin.theme.test.js similarity index 100% rename from packages/expo/src/__tests__/appPlugin.theme.test.js rename to packages/expo-native/src/__tests__/appPlugin.theme.test.js diff --git a/packages/expo/src/native/__tests__/useAuthViewState.test.tsx b/packages/expo-native/src/__tests__/useAuthViewState.test.tsx similarity index 100% rename from packages/expo/src/native/__tests__/useAuthViewState.test.tsx rename to packages/expo-native/src/__tests__/useAuthViewState.test.tsx diff --git a/packages/expo/src/native/index.ts b/packages/expo-native/src/index.ts similarity index 100% rename from packages/expo/src/native/index.ts rename to packages/expo-native/src/index.ts diff --git a/packages/expo/src/specs/NativeClerkAuthView.android.ts b/packages/expo-native/src/specs/NativeClerkAuthView.android.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkAuthView.android.ts rename to packages/expo-native/src/specs/NativeClerkAuthView.android.ts diff --git a/packages/expo/src/specs/NativeClerkAuthView.ts b/packages/expo-native/src/specs/NativeClerkAuthView.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkAuthView.ts rename to packages/expo-native/src/specs/NativeClerkAuthView.ts diff --git a/packages/expo/src/specs/NativeClerkUserButtonView.android.ts b/packages/expo-native/src/specs/NativeClerkUserButtonView.android.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkUserButtonView.android.ts rename to packages/expo-native/src/specs/NativeClerkUserButtonView.android.ts diff --git a/packages/expo/src/specs/NativeClerkUserButtonView.ts b/packages/expo-native/src/specs/NativeClerkUserButtonView.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkUserButtonView.ts rename to packages/expo-native/src/specs/NativeClerkUserButtonView.ts diff --git a/packages/expo/src/specs/NativeClerkUserProfileView.android.ts b/packages/expo-native/src/specs/NativeClerkUserProfileView.android.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkUserProfileView.android.ts rename to packages/expo-native/src/specs/NativeClerkUserProfileView.android.ts diff --git a/packages/expo/src/specs/NativeClerkUserProfileView.ts b/packages/expo-native/src/specs/NativeClerkUserProfileView.ts similarity index 100% rename from packages/expo/src/specs/NativeClerkUserProfileView.ts rename to packages/expo-native/src/specs/NativeClerkUserProfileView.ts diff --git a/packages/expo/src/specs/__tests__/native-view.web.test.ts b/packages/expo-native/src/specs/__tests__/native-view.web.test.ts similarity index 100% rename from packages/expo/src/specs/__tests__/native-view.web.test.ts rename to packages/expo-native/src/specs/__tests__/native-view.web.test.ts diff --git a/packages/expo/src/native/useAuthViewState.ts b/packages/expo-native/src/useAuthViewState.ts similarity index 100% rename from packages/expo/src/native/useAuthViewState.ts rename to packages/expo-native/src/useAuthViewState.ts From 5520da881558d3f2f5bd9049ff656c37204bd949 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:28:11 -0400 Subject: [PATCH 2/8] feat(expo-native): scaffold @clerk/expo-native package Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/expo-native/LICENSE | 21 ++++ packages/expo-native/README.md | 107 ++++++++++++++++++ packages/expo-native/android/build.gradle | 20 +++- packages/expo-native/app.plugin.d.ts | 5 + packages/expo-native/app.plugin.js | 104 +++-------------- packages/expo-native/package.json | 83 ++++++++++++++ packages/expo-native/src/AuthView.tsx | 8 +- packages/expo-native/src/UserButton.tsx | 6 +- packages/expo-native/src/UserProfileView.tsx | 8 +- .../src/__tests__/AuthView.test.tsx | 4 +- .../src/__tests__/UserButton.test.tsx | 4 +- .../src/__tests__/UserProfileView.test.tsx | 4 +- .../src/__tests__/appPlugin.theme.test.js | 49 +------- .../src/__tests__/useAuthViewState.test.tsx | 6 +- packages/expo-native/src/index.ts | 6 +- .../src/specs/NativeClerkModule.ts | 6 + .../src/specs/NativeClerkModule.types.ts | 9 ++ .../src/specs/NativeClerkModule.web.ts | 3 + packages/expo-native/src/useAuthViewState.ts | 6 +- .../expo-native/src/utils/native-module.ts | 7 ++ .../expo-native/tsconfig.declarations.json | 16 +++ packages/expo-native/tsconfig.json | 26 +++++ packages/expo-native/tsconfig.test.json | 6 + packages/expo-native/tsdown.config.mts | 33 ++++++ packages/expo-native/vitest.config.mts | 10 ++ packages/expo-native/vitest.setup.mts | 22 ++++ pnpm-lock.yaml | 25 ++++ 27 files changed, 437 insertions(+), 167 deletions(-) create mode 100644 packages/expo-native/LICENSE create mode 100644 packages/expo-native/README.md create mode 100644 packages/expo-native/app.plugin.d.ts create mode 100644 packages/expo-native/package.json create mode 100644 packages/expo-native/src/specs/NativeClerkModule.ts create mode 100644 packages/expo-native/src/specs/NativeClerkModule.types.ts create mode 100644 packages/expo-native/src/specs/NativeClerkModule.web.ts create mode 100644 packages/expo-native/src/utils/native-module.ts create mode 100644 packages/expo-native/tsconfig.declarations.json create mode 100644 packages/expo-native/tsconfig.json create mode 100644 packages/expo-native/tsconfig.test.json create mode 100644 packages/expo-native/tsdown.config.mts create mode 100644 packages/expo-native/vitest.config.mts create mode 100644 packages/expo-native/vitest.setup.mts diff --git a/packages/expo-native/LICENSE b/packages/expo-native/LICENSE new file mode 100644 index 00000000000..66914b6af7c --- /dev/null +++ b/packages/expo-native/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Clerk, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/expo-native/README.md b/packages/expo-native/README.md new file mode 100644 index 00000000000..35fe6587c9d --- /dev/null +++ b/packages/expo-native/README.md @@ -0,0 +1,107 @@ +

+ + + + + + +
+

@clerk/expo-native

+

+ +
+ +[![Chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) +[![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_expo_native) +[![Follow on X](https://img.shields.io/twitter/follow/clerk?style=social)](https://x.com/intent/follow?screen_name=clerk) + +[Changelog](https://github.com/clerk/javascript/blob/main/packages/expo-native/CHANGELOG.md) +· +[Report a Bug](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml) +· +[Request a Feature](https://feedback.clerk.com/roadmap) +· +[Get help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native) + +
+ +## Getting Started + +`@clerk/expo-native` adds Clerk's prebuilt native components to an Expo app that uses [`@clerk/expo`](https://github.com/clerk/javascript/tree/main/packages/expo). It contains the native modules built on the Clerk iOS and Android SDKs: + +- **iOS**: [clerk-ios](https://github.com/clerk/clerk-ios) (SwiftUI) +- **Android**: [clerk-android](https://github.com/clerk/clerk-android) (Jetpack Compose) + +Install it only if you use `AuthView`, `UserProfileView`, `UserButton`, or biometric credentials. Apps that only use `@clerk/expo` do not include the Clerk native SDKs. + +### Prerequisites + +- `@clerk/expo` +- Expo 54 or later, with a [development build](https://docs.expo.dev/develop/development-builds/introduction/) (Expo Go is not supported) +- iOS 17 or later + +### Installation + +```sh +npx expo install @clerk/expo @clerk/expo-native +``` + +Add the config plugin alongside `@clerk/expo` in your app config, then rebuild your native app: + +```json +{ + "expo": { + "plugins": ["@clerk/expo", "@clerk/expo-native"] + } +} +``` + +The plugin accepts the following options: + +- `keychainService`: keychain service identifier to share the session with app extensions. +- `theme`: path to a JSON file with `colors`, `darkColors`, and `design` keys that styles the native components. + +## Usage + +```tsx +import { useAuth } from '@clerk/expo'; +import { AuthView } from '@clerk/expo-native'; + +export default function SignInScreen() { + const { isSignedIn } = useAuth(); + + if (isSignedIn) { + return null; + } + + return ; +} +``` + +For further information, guides, and examples visit the [Expo reference documentation](https://clerk.com/docs/references/expo/overview?utm_source=github&utm_medium=clerk_expo_native). + +## Support + +For help, visit our [support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native). + +## Community + +Join our [Discord community](https://clerk.com/discord) to connect with other developers. + +## Contributing + +We're open to all community contributions! If you'd like to contribute in any way, please read [our contribution guidelines](https://github.com/clerk/javascript/blob/main/docs/CONTRIBUTING.md) and [code of conduct](https://github.com/clerk/javascript/blob/main/docs/CODE_OF_CONDUCT.md). + +## Security + +`@clerk/expo-native` follows good practices of security, but 100% security cannot be assured. + +`@clerk/expo-native` is provided **"as is"** without any **warranty**. Use at your own risk. + +_For more information and to report security issues, please refer to our [security documentation](https://github.com/clerk/javascript/blob/main/docs/SECURITY.md)._ + +## License + +This project is licensed under the **MIT license**. + +See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/expo-native/LICENSE) for more information. diff --git a/packages/expo-native/android/build.gradle b/packages/expo-native/android/build.gradle index 8975e848f7d..5cb80b92dd8 100644 --- a/packages/expo-native/android/build.gradle +++ b/packages/expo-native/android/build.gradle @@ -13,8 +13,22 @@ applyKotlinExpoModulesCorePlugin() group = 'com.clerk.expo' version = '1.0.0' -def clerkExpoPackageJson = new JsonSlurper().parse(new File(projectDir, "../package.json")) -def clerkExpoVersion = clerkExpoPackageJson.version.toString() +// The native SDKs report the @clerk/expo version in the x-clerk-host-sdk-version header. +def resolveClerkExpoVersion() { + try { + def process = new ProcessBuilder("node", "--print", "require.resolve('@clerk/expo/package.json')") + .directory(projectDir) + .start() + def clerkExpoPackageJsonPath = process.inputStream.text.trim() + if (process.waitFor() == 0 && clerkExpoPackageJsonPath) { + return new JsonSlurper().parse(new File(clerkExpoPackageJsonPath)).version.toString() + } + } catch (Exception ignored) { + } + return new JsonSlurper().parse(new File(projectDir, "../package.json")).version.toString() +} + +def clerkExpoVersion = resolveClerkExpoVersion() // Dependency versions - centralized for easier updates // See: https://docs.gradle.org/current/userguide/version_catalogs.html for app-level version catalogs @@ -97,7 +111,7 @@ try { } } catch (Exception ignored) { // Future Gradle versions with Isolated Projects may block cross-project configuration. - // In that case, users should add '@clerk/expo' to their app.json plugins array instead, + // In that case, users should add '@clerk/expo-native' to their app.json plugins array instead, // which applies the flag via the config plugin's withClerkAndroid. } diff --git a/packages/expo-native/app.plugin.d.ts b/packages/expo-native/app.plugin.d.ts new file mode 100644 index 00000000000..76cfe1c1acd --- /dev/null +++ b/packages/expo-native/app.plugin.d.ts @@ -0,0 +1,5 @@ +import type { ConfigPlugin } from '@expo/config-plugins'; + +declare const withClerkExpoNative: ConfigPlugin<{ keychainService?: string; theme?: string } | void>; + +export = withClerkExpoNative; diff --git a/packages/expo-native/app.plugin.js b/packages/expo-native/app.plugin.js index f8483877ff3..f8faa236dea 100644 --- a/packages/expo-native/app.plugin.js +++ b/packages/expo-native/app.plugin.js @@ -1,6 +1,6 @@ /** - * Expo config plugin for @clerk/clerk-expo - * Automatically configures iOS and Android to work with Clerk native components + * Expo config plugin for @clerk/expo-native + * Configures iOS and Android for the Clerk native SDKs (clerk-ios / clerk-android) * * When this plugin is used: * 1. iOS is configured with the required deployment target and metadata @@ -9,13 +9,11 @@ * Native modules and views are registered via Expo Modules autolinking. */ const { - AndroidConfig, + createRunOncePlugin, withXcodeProject, withDangerousMod, withInfoPlist, withAppBuildGradle, - withAndroidManifest, - withEntitlementsPlist, } = require('@expo/config-plugins'); const path = require('path'); const fs = require('fs'); @@ -23,33 +21,14 @@ const packageJson = require('./package.json'); const CLERK_MIN_IOS_VERSION = '17.0'; -const addHostedAuthIntentFilter = (mainActivity, packageName) => { - const callbackHost = `${packageName}.hosted-callback`; - const intentFilters = mainActivity['intent-filter'] || []; - const hasAndroidName = (entries, name) => entries?.some(entry => entry.$?.['android:name'] === name); - const callbackIsRegistered = intentFilters.some( - intentFilter => - hasAndroidName(intentFilter.action, 'android.intent.action.VIEW') && - hasAndroidName(intentFilter.category, 'android.intent.category.DEFAULT') && - hasAndroidName(intentFilter.category, 'android.intent.category.BROWSABLE') && - intentFilter.data?.some( - data => data.$?.['android:scheme'] === 'clerk' && data.$?.['android:host'] === callbackHost, - ), - ); - - if (callbackIsRegistered) { - return; +// The native SDKs report the @clerk/expo version in the x-clerk-host-sdk-version header. +const resolveHostSdkVersion = projectRoot => { + try { + const paths = [projectRoot, process.cwd(), __dirname].filter(Boolean); + return require(require.resolve('@clerk/expo/package.json', { paths })).version; + } catch { + return packageJson.version; } - - intentFilters.push({ - action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }], - category: [ - { $: { 'android:name': 'android.intent.category.DEFAULT' } }, - { $: { 'android:name': 'android.intent.category.BROWSABLE' } }, - ], - data: [{ $: { 'android:scheme': 'clerk', 'android:host': callbackHost } }], - }); - mainActivity['intent-filter'] = intentFilters; }; const withClerkIOS = config => { @@ -110,8 +89,9 @@ const withClerkIOS = config => { return config; }); + const hostSdkVersion = resolveHostSdkVersion(config._internal?.projectRoot); config = withInfoPlist(config, modConfig => { - modConfig.modResults.ClerkExpoVersion = packageJson.version; + modConfig.modResults.ClerkExpoVersion = hostSdkVersion; return modConfig; }); @@ -125,15 +105,6 @@ const withClerkIOS = config => { const withClerkAndroid = config => { console.log('✅ Clerk Android plugin loaded'); - config = withAndroidManifest(config, modConfig => { - const packageName = config.android?.package; - if (packageName) { - const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(modConfig.modResults); - addHostedAuthIntentFilter(mainActivity, packageName); - } - return modConfig; - }); - return withAppBuildGradle(config, modConfig => { let buildGradle = modConfig.modResults.contents; @@ -186,15 +157,6 @@ const withClerkAndroid = config => { }); }; -/** - * Combined Clerk Expo plugin - * - * When this plugin is configured in app.json/app.config.js: - * 1. iOS gets the deployment target and metadata required by Clerk native views - * 2. Android gets packaging exclusions for dependency conflicts - * - * Native modules and views are registered via Expo Modules autolinking. - */ /** * Write ClerkKeychainService to Info.plist when keychainService is provided. * This allows extension apps (watch, widget, app clip) to share the same @@ -212,37 +174,6 @@ const withClerkKeychainService = (config, { keychainService } = {}) => { }); }; -const withClerkFaceIDPermission = (config, { faceIDPermission } = {}) => { - if (faceIDPermission === undefined) { - return config; - } - - if (typeof faceIDPermission !== 'string' || faceIDPermission.trim().length === 0) { - throw new Error('Clerk: faceIDPermission must be a non-empty string'); - } - - return withInfoPlist(config, modConfig => { - if (!Object.hasOwn(modConfig.modResults, 'NSFaceIDUsageDescription')) { - modConfig.modResults.NSFaceIDUsageDescription = faceIDPermission; - } - return modConfig; - }); -}; - -/** - * Add Sign in with Apple entitlement to the iOS app. - * Required for the native Apple Sign In flow via ASAuthorizationController. - */ -const withClerkAppleSignIn = config => { - return withEntitlementsPlist(config, modConfig => { - if (!modConfig.modResults['com.apple.developer.applesignin']) { - modConfig.modResults['com.apple.developer.applesignin'] = ['Default']; - console.log('✅ Added Sign in with Apple entitlement'); - } - return modConfig; - }); -}; - /** * Apply a custom theme to Clerk native components (iOS + Android). * @@ -363,23 +294,16 @@ const withClerkTheme = (config, props = {}) => { return config; }; -const withClerkExpo = (config, props = {}) => { - const { appleSignIn = true } = props; +const withClerkExpoNative = (config, props = {}) => { config = withClerkIOS(config); - if (appleSignIn !== false) { - config = withClerkAppleSignIn(config); - } config = withClerkAndroid(config); config = withClerkKeychainService(config, props); - config = withClerkFaceIDPermission(config, props); config = withClerkTheme(config, props); return config; }; -module.exports = withClerkExpo; +module.exports = createRunOncePlugin(withClerkExpoNative, packageJson.name, packageJson.version); module.exports._testing = { - addHostedAuthIntentFilter, - withClerkFaceIDPermission, validateThemeJson, isPlainObject, VALID_COLOR_KEYS, diff --git a/packages/expo-native/package.json b/packages/expo-native/package.json new file mode 100644 index 00000000000..25e1addf9d4 --- /dev/null +++ b/packages/expo-native/package.json @@ -0,0 +1,83 @@ +{ + "name": "@clerk/expo-native", + "version": "0.0.1", + "description": "Native Clerk components and native SDK integration for Expo, powered by clerk-ios and clerk-android", + "keywords": [ + "react", + "react-native", + "expo", + "clerk", + "auth", + "authentication", + "native" + ], + "homepage": "https://clerk.com/", + "bugs": { + "url": "https://github.com/clerk/javascript/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/clerk/javascript.git", + "directory": "packages/expo-native" + }, + "license": "MIT", + "author": "Clerk", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "source": "./src/index.ts", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "android", + "ios", + "src/specs", + "expo-module.config.json", + "react-native.config.js", + "app.plugin.js", + "app.plugin.d.ts" + ], + "scripts": { + "build": "tsdown", + "build:declarations": "tsc -p tsconfig.declarations.json", + "clean": "rimraf ./dist", + "dev": "tsdown --watch", + "dev:pub": "pnpm dev -- --env.publish", + "format": "node ../../scripts/format-package.mjs", + "format:check": "node ../../scripts/format-package.mjs --check", + "lint": "eslint src", + "test": "vitest run", + "test:watch": "vitest watch" + }, + "dependencies": { + "tslib": "catalog:repo" + }, + "devDependencies": { + "@clerk/expo": "workspace:*", + "@expo/config-plugins": "^54.0.4", + "react-native": "^0.86.0" + }, + "peerDependencies": { + "@clerk/expo": "workspace:^", + "expo": "catalog:peer-expo", + "react": "^18.0.0 || ^19.0.0", + "react-native": ">=0.75" + }, + "engines": { + "node": ">=20.9.0" + }, + "publishConfig": { + "access": "public" + }, + "codegenConfig": { + "name": "ClerkExpoSpec", + "type": "all", + "jsSrcsDir": "src/specs" + } +} diff --git a/packages/expo-native/src/AuthView.tsx b/packages/expo-native/src/AuthView.tsx index 3f4b51ee346..7734de4dd86 100644 --- a/packages/expo-native/src/AuthView.tsx +++ b/packages/expo-native/src/AuthView.tsx @@ -2,9 +2,9 @@ import { useCallback } from 'react'; import type { NativeSyntheticEvent } from 'react-native'; import { Text, View } from 'react-native'; -import NativeClerkAuthView from '../specs/NativeClerkAuthView'; -import { isNativeSupported } from '../utils/native-module'; import type { AuthViewProps } from './AuthView.types'; +import NativeClerkAuthView from './specs/NativeClerkAuthView'; +import { isNativeSupported } from './utils/native-module'; type AuthNativeEvent = NativeSyntheticEvent>; @@ -24,7 +24,7 @@ type AuthNativeEvent = NativeSyntheticEvent>; * * @example * ```tsx - * import { AuthView } from '@clerk/expo/native'; + * import { AuthView } from '@clerk/expo-native'; * import { useAuth } from '@clerk/expo'; * * export default function SignInScreen() { @@ -63,7 +63,7 @@ export function AuthView({ {!isNativeSupported ? 'Native AuthView is only available on iOS and Android' - : 'Native AuthView requires the @clerk/expo plugin. Add "@clerk/expo" to your app.json plugins array.'} + : 'Native AuthView requires a development build with the @clerk/expo-native config plugin. Add "@clerk/expo-native" to your app.json plugins array and rebuild your native app.'} ); diff --git a/packages/expo-native/src/UserButton.tsx b/packages/expo-native/src/UserButton.tsx index a1406f0652e..f3038fd4efc 100644 --- a/packages/expo-native/src/UserButton.tsx +++ b/packages/expo-native/src/UserButton.tsx @@ -3,8 +3,7 @@ import { useMemo, useRef } from 'react'; import type { NativeSyntheticEvent } from 'react-native'; import { StyleSheet, useWindowDimensions } from 'react-native'; -import NativeClerkUserButtonView from '../specs/NativeClerkUserButtonView'; -import { isNativeSupported } from '../utils/native-module'; +import NativeClerkUserButtonView from './specs/NativeClerkUserButtonView'; import type { NativeUserProfileNavigationHandle, UserProfileCustomDestination, @@ -16,6 +15,7 @@ import { UserProfileCustomPageHosts, useUserProfileCustomPages, } from './UserProfileCustomPages'; +import { isNativeSupported } from './utils/native-module'; type CustomizableNativeUserButtonProps = ComponentProps> & { customPages?: string; @@ -47,7 +47,7 @@ export interface UserButtonProps { * * @example * ```tsx - * import { UserButton } from '@clerk/expo/native'; + * import { UserButton } from '@clerk/expo-native'; * * export default function Home() { * return ( diff --git a/packages/expo-native/src/UserProfileView.tsx b/packages/expo-native/src/UserProfileView.tsx index 3c39d955375..32aa4b5a6a9 100644 --- a/packages/expo-native/src/UserProfileView.tsx +++ b/packages/expo-native/src/UserProfileView.tsx @@ -3,9 +3,8 @@ import { useCallback, useMemo, useRef } from 'react'; import type { NativeSyntheticEvent, StyleProp, ViewStyle } from 'react-native'; import { StyleSheet, Text, View } from 'react-native'; -import NativeClerkUserProfileView from '../specs/NativeClerkUserProfileView'; -import { isNativeSupported } from '../utils/native-module'; import type { EmbeddedNavigationProps } from './EmbeddedNavigation.types'; +import NativeClerkUserProfileView from './specs/NativeClerkUserProfileView'; import type { NativeUserProfileNavigationHandle, UserProfileCustomDestination, @@ -17,6 +16,7 @@ import { UserProfileCustomPageHosts, useUserProfileCustomPages, } from './UserProfileCustomPages'; +import { isNativeSupported } from './utils/native-module'; type CustomizableNativeUserProfileProps = ComponentProps> & { customPages?: string; @@ -75,7 +75,7 @@ export interface UserProfileViewProps extends EmbeddedNavigationProps { * * @example * ```tsx - * import { UserProfileView } from '@clerk/expo/native'; + * import { UserProfileView } from '@clerk/expo-native'; * import { useAuth } from '@clerk/expo'; * * export default function ProfileScreen() { @@ -128,7 +128,7 @@ export function UserProfileView({ {!isNativeSupported ? 'Native UserProfileView is only available on iOS and Android' - : 'Native UserProfileView requires the @clerk/expo plugin. Add "@clerk/expo" to your app.json plugins array.'} + : 'Native UserProfileView requires a development build with the @clerk/expo-native config plugin. Add "@clerk/expo-native" to your app.json plugins array and rebuild your native app.'} ); diff --git a/packages/expo-native/src/__tests__/AuthView.test.tsx b/packages/expo-native/src/__tests__/AuthView.test.tsx index 6b3029ecdda..528e251fb2e 100644 --- a/packages/expo-native/src/__tests__/AuthView.test.tsx +++ b/packages/expo-native/src/__tests__/AuthView.test.tsx @@ -10,13 +10,13 @@ const mocks = vi.hoisted(() => { }; }); -vi.mock('../../specs/NativeClerkAuthView', () => { +vi.mock('../specs/NativeClerkAuthView', () => { return { default: mocks.NativeClerkAuthView, }; }); -vi.mock('../../utils/native-module', () => { +vi.mock('../utils/native-module', () => { return { isNativeSupported: true, }; diff --git a/packages/expo-native/src/__tests__/UserButton.test.tsx b/packages/expo-native/src/__tests__/UserButton.test.tsx index d4eaef80382..d414aa0f89f 100644 --- a/packages/expo-native/src/__tests__/UserButton.test.tsx +++ b/packages/expo-native/src/__tests__/UserButton.test.tsx @@ -8,14 +8,14 @@ const mocks = vi.hoisted(() => ({ nativeProps: vi.fn(), })); -vi.mock('../../specs/NativeClerkUserButtonView', () => ({ +vi.mock('../specs/NativeClerkUserButtonView', () => ({ default: React.forwardRef((props: Record, _ref) => { mocks.nativeProps(props); return null; }), })); -vi.mock('../../utils/native-module', () => ({ +vi.mock('../utils/native-module', () => ({ isNativeSupported: true, })); diff --git a/packages/expo-native/src/__tests__/UserProfileView.test.tsx b/packages/expo-native/src/__tests__/UserProfileView.test.tsx index 34af7275e6d..619c417305e 100644 --- a/packages/expo-native/src/__tests__/UserProfileView.test.tsx +++ b/packages/expo-native/src/__tests__/UserProfileView.test.tsx @@ -14,7 +14,7 @@ const mocks = vi.hoisted(() => { }; }); -vi.mock('../../specs/NativeClerkUserProfileView', () => { +vi.mock('../specs/NativeClerkUserProfileView', () => { return { default: React.forwardRef((props: { children?: React.ReactNode }, ref) => { React.useImperativeHandle(ref, () => ({ navigateCustomPage: mocks.navigateCustomPage })); @@ -24,7 +24,7 @@ vi.mock('../../specs/NativeClerkUserProfileView', () => { }; }); -vi.mock('../../utils/native-module', () => { +vi.mock('../utils/native-module', () => { return { isNativeSupported: true, }; diff --git a/packages/expo-native/src/__tests__/appPlugin.theme.test.js b/packages/expo-native/src/__tests__/appPlugin.theme.test.js index fcff7577b0e..c0f89d02ab8 100644 --- a/packages/expo-native/src/__tests__/appPlugin.theme.test.js +++ b/packages/expo-native/src/__tests__/appPlugin.theme.test.js @@ -2,54 +2,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export const clerkPlugin = require('../../app.plugin.js'); -const { withClerkFaceIDPermission, validateThemeJson } = clerkPlugin._testing; - -function applyInfoPlistMod(config, modResults) { - return config.mods.ios.infoPlist({ - ...config, - modRequest: {}, - modResults, - }); -} - -describe('withClerkFaceIDPermission', () => { - test('adds the configured Face ID usage description', async () => { - const config = withClerkFaceIDPermission( - { name: 'test', slug: 'test' }, - { faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID for secure sign-in.' }, - ); - - const result = await applyInfoPlistMod(config, {}); - - expect(result.modResults.NSFaceIDUsageDescription).toBe('Allow $(PRODUCT_NAME) to use Face ID for secure sign-in.'); - }); - - test('preserves an app-provided Face ID usage description', async () => { - const config = withClerkFaceIDPermission( - { name: 'test', slug: 'test' }, - { faceIDPermission: 'Clerk-provided description' }, - ); - - const result = await applyInfoPlistMod(config, { - NSFaceIDUsageDescription: 'App-provided description', - }); - - expect(result.modResults.NSFaceIDUsageDescription).toBe('App-provided description'); - }); - - test('does not configure the Info.plist without an explicit permission description', () => { - const config = { name: 'test', slug: 'test' }; - - expect(withClerkFaceIDPermission(config)).toBe(config); - expect(config).not.toHaveProperty('mods'); - }); - - test.each([null, '', ' ', true])('rejects an invalid permission description: %j', faceIDPermission => { - expect(() => withClerkFaceIDPermission({ name: 'test', slug: 'test' }, { faceIDPermission })).toThrow( - 'faceIDPermission must be a non-empty string', - ); - }); -}); +const { validateThemeJson } = clerkPlugin._testing; describe('validateThemeJson', () => { beforeEach(() => { diff --git a/packages/expo-native/src/__tests__/useAuthViewState.test.tsx b/packages/expo-native/src/__tests__/useAuthViewState.test.tsx index 988f2a57729..aab5496325c 100644 --- a/packages/expo-native/src/__tests__/useAuthViewState.test.tsx +++ b/packages/expo-native/src/__tests__/useAuthViewState.test.tsx @@ -1,7 +1,7 @@ import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import type { NativeAuthFlowState } from '../../specs/NativeClerkModule.types'; +import type { NativeAuthFlowState } from '../specs/NativeClerkModule.types'; import { useAuthViewState } from '../useAuthViewState'; const mocks = vi.hoisted(() => ({ @@ -13,11 +13,11 @@ const mocks = vi.hoisted(() => ({ remove: vi.fn(), })); -vi.mock('../../hooks/useAuth', () => ({ +vi.mock('@clerk/expo', () => ({ useAuth: () => mocks.auth, })); -vi.mock('../../utils/native-module', () => ({ +vi.mock('../utils/native-module', () => ({ get ClerkExpoModule() { return mocks.module; }, diff --git a/packages/expo-native/src/index.ts b/packages/expo-native/src/index.ts index 2a62047dd85..a20538f6186 100644 --- a/packages/expo-native/src/index.ts +++ b/packages/expo-native/src/index.ts @@ -7,12 +7,12 @@ * * ## Installation * - * Native components require the `@clerk/expo` plugin to be configured in your `app.json`: + * Native components require the `@clerk/expo-native` config plugin in your `app.json`, alongside `@clerk/expo`: * * ```json * { * "expo": { - * "plugins": ["@clerk/expo"] + * "plugins": ["@clerk/expo", "@clerk/expo-native"] * } * } * ``` @@ -25,7 +25,7 @@ * - {@link UserProfileView} - User profile and account management, renders inline * - {@link UserButton} - Avatar button that opens the native user profile * - * @module @clerk/expo/native + * @module @clerk/expo-native */ export { AuthView } from './AuthView'; diff --git a/packages/expo-native/src/specs/NativeClerkModule.ts b/packages/expo-native/src/specs/NativeClerkModule.ts new file mode 100644 index 00000000000..072d011f539 --- /dev/null +++ b/packages/expo-native/src/specs/NativeClerkModule.ts @@ -0,0 +1,6 @@ +import { requireOptionalNativeModule } from 'expo'; + +import type { Spec } from './NativeClerkModule.types'; + +// Optional so it resolves to null in Expo Go instead of throwing at import time. +export default requireOptionalNativeModule('ClerkExpo'); diff --git a/packages/expo-native/src/specs/NativeClerkModule.types.ts b/packages/expo-native/src/specs/NativeClerkModule.types.ts new file mode 100644 index 00000000000..9df94448a91 --- /dev/null +++ b/packages/expo-native/src/specs/NativeClerkModule.types.ts @@ -0,0 +1,9 @@ +export type NativeAuthFlowState = { + isLoaded: boolean; + isAuthFlowComplete: boolean; +}; + +export interface Spec { + addListener?(eventName: string, listener?: (...args: unknown[]) => void): { remove: () => void }; + getAuthFlowState?(): Promise; +} diff --git a/packages/expo-native/src/specs/NativeClerkModule.web.ts b/packages/expo-native/src/specs/NativeClerkModule.web.ts new file mode 100644 index 00000000000..625a2ed22ae --- /dev/null +++ b/packages/expo-native/src/specs/NativeClerkModule.web.ts @@ -0,0 +1,3 @@ +import type { Spec } from './NativeClerkModule.types'; + +export default null as Spec | null; diff --git a/packages/expo-native/src/useAuthViewState.ts b/packages/expo-native/src/useAuthViewState.ts index ecb793f84f1..a4c4a5f3b61 100644 --- a/packages/expo-native/src/useAuthViewState.ts +++ b/packages/expo-native/src/useAuthViewState.ts @@ -1,8 +1,8 @@ +import { useAuth } from '@clerk/expo'; import { useEffect, useState } from 'react'; -import { useAuth } from '../hooks/useAuth'; -import type { NativeAuthFlowState } from '../specs/NativeClerkModule.types'; -import { ClerkExpoModule as ClerkExpo } from '../utils/native-module'; +import type { NativeAuthFlowState } from './specs/NativeClerkModule.types'; +import { ClerkExpoModule as ClerkExpo } from './utils/native-module'; const nativeAuthFlowChangedEvent = 'clerkNativeAuthFlowChanged'; diff --git a/packages/expo-native/src/utils/native-module.ts b/packages/expo-native/src/utils/native-module.ts new file mode 100644 index 00000000000..a416ccd8917 --- /dev/null +++ b/packages/expo-native/src/utils/native-module.ts @@ -0,0 +1,7 @@ +import { Platform } from 'react-native'; + +import NativeClerkModule from '../specs/NativeClerkModule'; + +export const isNativeSupported = Platform.OS === 'ios' || Platform.OS === 'android'; + +export const ClerkExpoModule = isNativeSupported ? NativeClerkModule : null; diff --git a/packages/expo-native/tsconfig.declarations.json b/packages/expo-native/tsconfig.declarations.json new file mode 100644 index 00000000000..ac04a85ce27 --- /dev/null +++ b/packages/expo-native/tsconfig.declarations.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "incremental": false, + "skipLibCheck": true, + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "declarationMap": true, + "sourceMap": false, + "declarationDir": "./dist" + }, + "include": ["src"], + "exclude": ["**/__tests__/**/*", "app.plugin.js"] +} diff --git a/packages/expo-native/tsconfig.json b/packages/expo-native/tsconfig.json new file mode 100644 index 00000000000..5510c6beb95 --- /dev/null +++ b/packages/expo-native/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "outDir": "dist", + "lib": ["es2019", "dom"], + "jsx": "react-jsx", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "importHelpers": true, + "declaration": true, + "declarationMap": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "sourceMap": false, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "allowJs": true, + "target": "ES2019", + "noEmitOnError": false, + "incremental": true, + "moduleSuffixes": [".web", ".ios", ".android", ".native", ""] + }, + "include": ["src", "app.plugin.js"] +} diff --git a/packages/expo-native/tsconfig.test.json b/packages/expo-native/tsconfig.test.json new file mode 100644 index 00000000000..5635d6cd1b7 --- /dev/null +++ b/packages/expo-native/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/expo-native/tsdown.config.mts b/packages/expo-native/tsdown.config.mts new file mode 100644 index 00000000000..e84f7249174 --- /dev/null +++ b/packages/expo-native/tsdown.config.mts @@ -0,0 +1,33 @@ +import type { Options } from 'tsdown'; +import { defineConfig } from 'tsdown'; + +import { runAfterLast } from '../../scripts/utils.ts'; + +function preserveRelativeImports(id: string) { + return id.startsWith('.'); +} + +export default defineConfig(overrideOptions => { + const isWatch = !!overrideOptions.watch; + const shouldPublish = !!overrideOptions.env?.publish; + + const options: Options = { + format: 'cjs', + fixedExtension: false, + outDir: './dist', + entry: ['./src/**/*.{ts,tsx,js,jsx}', '!./src/**/*.test.{ts,tsx,js}', '!./src/**/__tests__/**'], + unbundle: true, + clean: true, + minify: false, + sourcemap: true, + deps: { + // Keep relative import specifiers unchanged so Metro can apply platform-specific resolution. + neverBundle: preserveRelativeImports, + }, + define: { + __DEV__: `${isWatch}`, + }, + }; + + return runAfterLast(['pnpm build:declarations', shouldPublish && 'pkglab pub --ping'])(options); +}); diff --git a/packages/expo-native/vitest.config.mts b/packages/expo-native/vitest.config.mts new file mode 100644 index 00000000000..20bc22a33db --- /dev/null +++ b/packages/expo-native/vitest.config.mts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [], + test: { + environment: 'jsdom', + includeSource: ['**/*.{js,ts,jsx,tsx}'], + setupFiles: './vitest.setup.mts', + }, +}); diff --git a/packages/expo-native/vitest.setup.mts b/packages/expo-native/vitest.setup.mts new file mode 100644 index 00000000000..283c324f550 --- /dev/null +++ b/packages/expo-native/vitest.setup.mts @@ -0,0 +1,22 @@ +import { cleanup } from '@testing-library/react'; +import { afterEach, beforeAll, vi } from 'vitest'; + +// Mock globalThis.expo for expo-modules-core +if (!globalThis.expo) { + // @ts-expect-error - Mocking expo for tests + globalThis.expo = { + EventEmitter: vi.fn(), + }; +} + +// Define __DEV__ for expo-modules-core +if (typeof globalThis.__DEV__ === 'undefined') { + // @ts-expect-error - Mocking __DEV__ for tests + globalThis.__DEV__ = false; +} + +beforeAll(() => {}); + +afterEach(() => { + cleanup(); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be2b32a7f6a..39ddc5fae02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -634,6 +634,9 @@ importers: '@clerk/clerk-js': specifier: workspace:^ version: link:../clerk-js + '@clerk/expo-native': + specifier: workspace:>=0.0.1 + version: link:../expo-native '@clerk/react': specifier: workspace:^ version: link:../react @@ -708,6 +711,28 @@ importers: specifier: ~54.0.36 version: 54.0.36(@babel/core@7.29.7)(bufferutil@4.1.0)(graphql@16.14.1)(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + packages/expo-native: + dependencies: + expo: + specifier: catalog:peer-expo + version: 54.0.36(@babel/core@7.29.7)(bufferutil@4.1.0)(graphql@16.14.1)(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) + react: + specifier: 18.3.1 + version: 18.3.1 + tslib: + specifier: catalog:repo + version: 2.8.1 + devDependencies: + '@clerk/expo': + specifier: workspace:* + version: link:../expo + '@expo/config-plugins': + specifier: ^54.0.4 + version: 54.0.5 + react-native: + specifier: ^0.86.0 + version: 0.86.0(@babel/core@7.29.7)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) + packages/expo-passkeys: dependencies: '@clerk/shared': From 170964735a8569f1898b4d5dca061761160324f4 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:28:13 -0400 Subject: [PATCH 3/8] feat(expo): re-export native components from @clerk/expo-native and delegate its config plugin Co-Authored-By: Claude Opus 5.5 (1M context) --- package.json | 2 +- packages/expo/app.plugin.js | 156 ++++++++++++++++++ packages/expo/native/index.d.ts | 1 + packages/expo/native/package.json | 2 +- packages/expo/package.json | 16 +- packages/expo/src/__tests__/appPlugin.test.js | 120 ++++++++++++++ .../__tests__/useBiometricCredentials.test.ts | 19 ++- .../useBiometricCredentials.shared.ts | 15 +- .../expo/src/native/__tests__/index.test.ts | 57 +++++++ packages/expo/src/native/index.ts | 27 +++ .../expo/src/native/loadClerkExpoNative.ts | 18 ++ .../expo/src/provider/nativeClientSync.tsx | 2 +- 12 files changed, 416 insertions(+), 19 deletions(-) create mode 100644 packages/expo/app.plugin.js create mode 100644 packages/expo/native/index.d.ts create mode 100644 packages/expo/src/__tests__/appPlugin.test.js create mode 100644 packages/expo/src/native/__tests__/index.test.ts create mode 100644 packages/expo/src/native/index.ts create mode 100644 packages/expo/src/native/loadClerkExpoNative.ts diff --git a/package.json b/package.json index 5884e61374c..7620564b4fd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "changeset": "changeset", "changeset:empty": "pnpm changeset --empty", "clean": "turbo run clean", - "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/expo --filter=!@clerk/tanstack-react-start --filter=!@clerk/chrome-extension", + "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/expo --filter=!@clerk/expo-native --filter=!@clerk/tanstack-react-start --filter=!@clerk/chrome-extension", "dev:fe-libs": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/clerk-js --filter=@clerk/ui --filter=@clerk/shared", "dev:js": "TURBO_UI=0 FORCE_COLOR=1 turbo dev:current --filter=@clerk/clerk-js", "dev:sandbox": "TURBO_UI=0 FORCE_COLOR=1 turbo dev:sandbox:serve", diff --git a/packages/expo/app.plugin.js b/packages/expo/app.plugin.js new file mode 100644 index 00000000000..fbfa61128bd --- /dev/null +++ b/packages/expo/app.plugin.js @@ -0,0 +1,156 @@ +/** + * Expo config plugin for @clerk/expo + * + * When this plugin is used: + * 1. Android registers the hosted auth callback intent filter + * 2. iOS gets the Sign in with Apple entitlement and, when configured, the Face ID usage description + * 3. If @clerk/expo-native is installed, its config plugin is applied for the Clerk native SDKs + */ +const { AndroidConfig, withAndroidManifest, withEntitlementsPlist, withInfoPlist } = require('@expo/config-plugins'); + +const CLERK_EXPO_NATIVE = '@clerk/expo-native'; +const CLERK_EXPO_NATIVE_OPTIONS = ['keychainService', 'theme']; + +const addHostedAuthIntentFilter = (mainActivity, packageName) => { + const callbackHost = `${packageName}.hosted-callback`; + const intentFilters = mainActivity['intent-filter'] || []; + const hasAndroidName = (entries, name) => entries?.some(entry => entry.$?.['android:name'] === name); + const callbackIsRegistered = intentFilters.some( + intentFilter => + hasAndroidName(intentFilter.action, 'android.intent.action.VIEW') && + hasAndroidName(intentFilter.category, 'android.intent.category.DEFAULT') && + hasAndroidName(intentFilter.category, 'android.intent.category.BROWSABLE') && + intentFilter.data?.some( + data => data.$?.['android:scheme'] === 'clerk' && data.$?.['android:host'] === callbackHost, + ), + ); + + if (callbackIsRegistered) { + return; + } + + intentFilters.push({ + action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }], + category: [ + { $: { 'android:name': 'android.intent.category.DEFAULT' } }, + { $: { 'android:name': 'android.intent.category.BROWSABLE' } }, + ], + data: [{ $: { 'android:scheme': 'clerk', 'android:host': callbackHost } }], + }); + mainActivity['intent-filter'] = intentFilters; +}; + +const withClerkHostedAuthCallback = config => { + return withAndroidManifest(config, modConfig => { + const packageName = config.android?.package; + if (packageName) { + const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(modConfig.modResults); + addHostedAuthIntentFilter(mainActivity, packageName); + } + return modConfig; + }); +}; + +const withClerkFaceIDPermission = (config, { faceIDPermission } = {}) => { + if (faceIDPermission === undefined) { + return config; + } + + if (typeof faceIDPermission !== 'string' || faceIDPermission.trim().length === 0) { + throw new Error('Clerk: faceIDPermission must be a non-empty string'); + } + + return withInfoPlist(config, modConfig => { + if (!Object.hasOwn(modConfig.modResults, 'NSFaceIDUsageDescription')) { + modConfig.modResults.NSFaceIDUsageDescription = faceIDPermission; + } + return modConfig; + }); +}; + +/** + * Add Sign in with Apple entitlement to the iOS app. + * Required for the native Apple Sign In flow via ASAuthorizationController. + */ +const withClerkAppleSignIn = config => { + return withEntitlementsPlist(config, modConfig => { + if (!modConfig.modResults['com.apple.developer.applesignin']) { + modConfig.modResults['com.apple.developer.applesignin'] = ['Default']; + console.log('✅ Added Sign in with Apple entitlement'); + } + return modConfig; + }); +}; + +const resolveClerkExpoNativePlugin = config => { + try { + const paths = [config._internal?.projectRoot, process.cwd()].filter(Boolean); + return require(require.resolve(`${CLERK_EXPO_NATIVE}/app.plugin.js`, { paths })); + } catch { + return null; + } +}; + +const getListedPluginProps = (config, name) => { + for (const entry of config.plugins || []) { + if (entry === name) { + return {}; + } + if (Array.isArray(entry) && entry[0] === name) { + return entry[1] || {}; + } + } + return {}; +}; + +/** + * Apply the @clerk/expo-native config plugin when it is installed, so apps that only list + * "@clerk/expo" keep the iOS deployment target and native SDK configuration they need. + */ +const withClerkExpoNative = (config, props = {}, resolvePlugin = resolveClerkExpoNativePlugin) => { + const nativeProps = Object.fromEntries( + CLERK_EXPO_NATIVE_OPTIONS.filter(option => props[option] !== undefined).map(option => [option, props[option]]), + ); + const nativeOptionNames = Object.keys(nativeProps) + .map(option => `"${option}"`) + .join(', '); + + if (config._internal?.pluginHistory?.[CLERK_EXPO_NATIVE]) { + if (nativeOptionNames) { + console.warn( + `⚠️ Clerk: ${nativeOptionNames} passed to the "@clerk/expo" plugin will be ignored because the "${CLERK_EXPO_NATIVE}" plugin already ran. Pass them to the "${CLERK_EXPO_NATIVE}" plugin instead.`, + ); + } + return config; + } + + const clerkExpoNativePlugin = resolvePlugin(config); + if (!clerkExpoNativePlugin) { + if (nativeOptionNames) { + console.warn( + `⚠️ Clerk: ${nativeOptionNames} configure Clerk native components, which have moved to ${CLERK_EXPO_NATIVE}. Install it with \`npx expo install ${CLERK_EXPO_NATIVE}\` and add "${CLERK_EXPO_NATIVE}" to the plugins array in your app config.`, + ); + } + return config; + } + + return clerkExpoNativePlugin(config, { ...nativeProps, ...getListedPluginProps(config, CLERK_EXPO_NATIVE) }); +}; + +const withClerkExpo = (config, props = {}) => { + const { appleSignIn = true } = props; + if (appleSignIn !== false) { + config = withClerkAppleSignIn(config); + } + config = withClerkHostedAuthCallback(config); + config = withClerkFaceIDPermission(config, props); + config = withClerkExpoNative(config, props); + return config; +}; + +module.exports = withClerkExpo; +module.exports._testing = { + addHostedAuthIntentFilter, + withClerkExpoNative, + withClerkFaceIDPermission, +}; diff --git a/packages/expo/native/index.d.ts b/packages/expo/native/index.d.ts new file mode 100644 index 00000000000..0c04017d415 --- /dev/null +++ b/packages/expo/native/index.d.ts @@ -0,0 +1 @@ +export * from '@clerk/expo-native'; diff --git a/packages/expo/native/package.json b/packages/expo/native/package.json index 6ae24b71af4..928da2cc7c8 100644 --- a/packages/expo/native/package.json +++ b/packages/expo/native/package.json @@ -1,4 +1,4 @@ { "main": "../dist/native/index.js", - "types": "../dist/native/index.d.ts" + "types": "./index.d.ts" } diff --git a/packages/expo/package.json b/packages/expo/package.json index 95aa54c6c84..dc84ba82b93 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -30,7 +30,7 @@ }, "./app.plugin.js": "./app.plugin.js", "./native": { - "types": "./dist/native/index.d.ts", + "types": "./native/index.d.ts", "default": "./dist/native/index.js" }, "./web": { @@ -85,8 +85,6 @@ "types": "./dist/index.d.ts", "files": [ "dist", - "android", - "ios", "native", "web", "local-credentials", @@ -99,9 +97,6 @@ "hosted-auth", "experimental", "legacy", - "src/specs", - "expo-module.config.json", - "react-native.config.js", "app.plugin.js", "app.plugin.d.ts" ], @@ -142,6 +137,7 @@ }, "peerDependencies": { "@clerk/expo-google-signin": ">=0.1.0", + "@clerk/expo-native": "workspace:>=0.0.1", "@clerk/expo-passkeys": ">=0.0.6", "expo": "catalog:peer-expo", "expo-apple-authentication": ">=7.0.0", @@ -159,6 +155,9 @@ "@clerk/expo-google-signin": { "optional": true }, + "@clerk/expo-native": { + "optional": true + }, "@clerk/expo-passkeys": { "optional": true }, @@ -192,10 +191,5 @@ }, "publishConfig": { "access": "public" - }, - "codegenConfig": { - "name": "ClerkExpoSpec", - "type": "all", - "jsSrcsDir": "src/specs" } } diff --git a/packages/expo/src/__tests__/appPlugin.test.js b/packages/expo/src/__tests__/appPlugin.test.js new file mode 100644 index 00000000000..78e46629533 --- /dev/null +++ b/packages/expo/src/__tests__/appPlugin.test.js @@ -0,0 +1,120 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export +const { withClerkExpoNative, withClerkFaceIDPermission } = require('../../app.plugin.js')._testing; + +function applyInfoPlistMod(config, modResults) { + return config.mods.ios.infoPlist({ + ...config, + modRequest: {}, + modResults, + }); +} + +describe('withClerkFaceIDPermission', () => { + test('adds the configured Face ID usage description', async () => { + const config = withClerkFaceIDPermission( + { name: 'test', slug: 'test' }, + { faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID for secure sign-in.' }, + ); + + const result = await applyInfoPlistMod(config, {}); + + expect(result.modResults.NSFaceIDUsageDescription).toBe('Allow $(PRODUCT_NAME) to use Face ID for secure sign-in.'); + }); + + test('preserves an app-provided Face ID usage description', async () => { + const config = withClerkFaceIDPermission( + { name: 'test', slug: 'test' }, + { faceIDPermission: 'Clerk-provided description' }, + ); + + const result = await applyInfoPlistMod(config, { + NSFaceIDUsageDescription: 'App-provided description', + }); + + expect(result.modResults.NSFaceIDUsageDescription).toBe('App-provided description'); + }); + + test('does not configure the Info.plist without an explicit permission description', () => { + const config = { name: 'test', slug: 'test' }; + + expect(withClerkFaceIDPermission(config)).toBe(config); + expect(config).not.toHaveProperty('mods'); + }); + + test.each([null, '', ' ', true])('rejects an invalid permission description: %j', faceIDPermission => { + expect(() => withClerkFaceIDPermission({ name: 'test', slug: 'test' }, { faceIDPermission })).toThrow( + 'faceIDPermission must be a non-empty string', + ); + }); +}); + +describe('withClerkExpoNative', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + test('applies the @clerk/expo-native plugin with its options when it is installed', () => { + const nativePlugin = vi.fn(config => ({ ...config, applied: true })); + const config = { name: 'test', slug: 'test' }; + + const result = withClerkExpoNative( + config, + { keychainService: 'com.example.shared', theme: './theme.json', appleSignIn: false }, + () => nativePlugin, + ); + + expect(result.applied).toBe(true); + expect(nativePlugin).toHaveBeenCalledWith(config, { keychainService: 'com.example.shared', theme: './theme.json' }); + }); + + test('prefers options passed to an explicitly listed @clerk/expo-native plugin', () => { + const nativePlugin = vi.fn(config => config); + const config = { + name: 'test', + slug: 'test', + plugins: ['@clerk/expo', ['@clerk/expo-native', { theme: './native-theme.json' }]], + }; + + withClerkExpoNative(config, { keychainService: 'com.example.shared', theme: './theme.json' }, () => nativePlugin); + + expect(nativePlugin).toHaveBeenCalledWith(config, { + keychainService: 'com.example.shared', + theme: './native-theme.json', + }); + }); + + test('does not apply the @clerk/expo-native plugin twice', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const nativePlugin = vi.fn(config => config); + const config = { + name: 'test', + slug: 'test', + _internal: { pluginHistory: { '@clerk/expo-native': { name: '@clerk/expo-native', version: '0.0.1' } } }, + }; + + expect(withClerkExpoNative(config, {}, () => nativePlugin)).toBe(config); + expect(nativePlugin).not.toHaveBeenCalled(); + expect(warn).not.toHaveBeenCalled(); + + withClerkExpoNative(config, { theme: './theme.json' }, () => nativePlugin); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('Pass them to the "@clerk/expo-native" plugin instead')); + }); + + test('leaves the config untouched when @clerk/expo-native is not installed', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const config = { name: 'test', slug: 'test' }; + + expect(withClerkExpoNative(config, {}, () => null)).toBe(config); + expect(warn).not.toHaveBeenCalled(); + }); + + test('warns with install instructions when native-only options are passed without @clerk/expo-native', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const config = { name: 'test', slug: 'test' }; + + expect(withClerkExpoNative(config, { keychainService: 'com.example.shared' }, () => null)).toBe(config); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('npx expo install @clerk/expo-native')); + }); +}); diff --git a/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts b/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts index dee295ff1a5..1b7a7c46c5d 100644 --- a/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts +++ b/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts @@ -24,6 +24,7 @@ const mocks = vi.hoisted(() => ({ useClerk: vi.fn(), setActive: vi.fn(), synchronizeNativeClientToJs: vi.fn(), + isNativeModuleInstalled: true, nativeModule: { getTrustedDeviceAvailability: vi.fn(), listTrustedDevices: vi.fn(), @@ -38,7 +39,9 @@ vi.mock('@clerk/react', () => ({ })); vi.mock('../../utils/native-module', () => ({ - ClerkExpoModule: mocks.nativeModule, + get ClerkExpoModule() { + return mocks.isNativeModuleInstalled ? mocks.nativeModule : null; + }, })); vi.mock('react-native', () => ({ @@ -539,13 +542,25 @@ describe('useBiometricCredentials on iOS', () => { }); }); + test('explains how to install @clerk/expo-native when the native module is missing', async () => { + mocks.isNativeModuleInstalled = false; + + try { + await expect(renderBiometricCredentials().signIn()).rejects.toThrow( + 'Biometric credentials require the @clerk/expo-native package in a development build. Install it with `npx expo install @clerk/expo-native`', + ); + } finally { + mocks.isNativeModuleInstalled = true; + } + }); + test('explains that the development client must contain the native methods', async () => { const signInWithTrustedDevice = mocks.nativeModule.signInWithTrustedDevice; Object.assign(mocks.nativeModule, { signInWithTrustedDevice: undefined }); try { await expect(renderBiometricCredentials().signIn()).rejects.toThrow( - 'Biometric credentials require a development build containing a compatible version of @clerk/expo.', + 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native.', ); } finally { Object.assign(mocks.nativeModule, { signInWithTrustedDevice }); diff --git a/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts b/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts index 3e92e5287be..49f611363f1 100644 --- a/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts +++ b/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts @@ -14,6 +14,9 @@ import type { const DEFAULT_POLICY = 'biometry_current_set'; +const CLERK_EXPO_NATIVE_INSTALL_INSTRUCTIONS = + 'Install it with `npx expo install @clerk/expo-native`, add "@clerk/expo-native" to the plugins array in your app config, then rebuild your development build.'; + function toBiometricCredentialPlatform(platform: string): BiometricCredentialPlatform { return platform === 'ios' || platform === 'android' ? platform : 'unknown'; } @@ -25,15 +28,21 @@ function toBiometricCredentialStatus(status: string): BiometricCredentialStatus function getNativeModule(): NativeBiometricCredentialModule { const nativeModule = ClerkExpoModule; + if (!nativeModule) { + return errorThrower.throw( + `Biometric credentials require the @clerk/expo-native package in a development build. ${CLERK_EXPO_NATIVE_INSTALL_INSTRUCTIONS}`, + ); + } + if ( - !nativeModule?.getTrustedDeviceAvailability || + !nativeModule.getTrustedDeviceAvailability || !nativeModule.listTrustedDevices || !nativeModule.enrollTrustedDevice || !nativeModule.revokeTrustedDevice || !nativeModule.signInWithTrustedDevice ) { return errorThrower.throw( - 'Biometric credentials require a development build containing a compatible version of @clerk/expo.', + 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native.', ); } @@ -86,7 +95,7 @@ function createBiometricCredentials(clerk: ReturnType): UseBiom const nativeModule = getNativeModule(); if (typeof nativeModule.reverifyWithBiometrics !== 'function') { return errorThrower.throw( - 'Biometric reverification requires a development build containing a compatible version of @clerk/expo.', + 'Biometric reverification requires a development build containing a compatible version of @clerk/expo-native.', ); } const level = params?.level ?? 'first_factor'; diff --git a/packages/expo/src/native/__tests__/index.test.ts b/packages/expo/src/native/__tests__/index.test.ts new file mode 100644 index 00000000000..6d936310043 --- /dev/null +++ b/packages/expo/src/native/__tests__/index.test.ts @@ -0,0 +1,57 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +const importNativeEntry = () => import('../index'); + +describe('@clerk/expo/native', () => { + afterEach(() => { + vi.doUnmock('../loadClerkExpoNative'); + vi.resetModules(); + vi.restoreAllMocks(); + }); + + describe('when @clerk/expo-native is not installed', () => { + beforeEach(() => { + vi.doMock('../loadClerkExpoNative', () => ({ loadClerkExpoNative: () => null })); + }); + + test.each(['AuthView', 'UserButton', 'UserProfileView'] as const)( + 'rendering %s throws an error with install instructions', + async name => { + const Component = (await importNativeEntry())[name] as (props: object) => unknown; + + expect(() => Component({})).toThrow( + /`.+` is unavailable\. Native components have moved to the @clerk\/expo-native package\. Install it with `npx expo install @clerk\/expo-native`/, + ); + }, + ); + + test.each(['useAuthViewState', 'useUserProfileCustomPageNavigation'] as const)( + 'calling %s throws an error with install instructions', + async name => { + const hook = (await importNativeEntry())[name] as () => unknown; + + expect(() => hook()).toThrow(`\`${name}\` is unavailable.`); + expect(() => hook()).toThrow('add "@clerk/expo-native" to the plugins array in your app config'); + }, + ); + }); + + test('re-exports @clerk/expo-native when it is installed', async () => { + const clerkExpoNative = { + AuthView: vi.fn(() => null), + UserButton: vi.fn(() => null), + UserProfileView: vi.fn(() => null), + useAuthViewState: vi.fn(), + useUserProfileCustomPageNavigation: vi.fn(), + }; + vi.doMock('../loadClerkExpoNative', () => ({ loadClerkExpoNative: () => clerkExpoNative })); + + const nativeEntry = await importNativeEntry(); + + expect(nativeEntry.AuthView).toBe(clerkExpoNative.AuthView); + expect(nativeEntry.UserButton).toBe(clerkExpoNative.UserButton); + expect(nativeEntry.UserProfileView).toBe(clerkExpoNative.UserProfileView); + expect(nativeEntry.useAuthViewState).toBe(clerkExpoNative.useAuthViewState); + expect(nativeEntry.useUserProfileCustomPageNavigation).toBe(clerkExpoNative.useUserProfileCustomPageNavigation); + }); +}); diff --git a/packages/expo/src/native/index.ts b/packages/expo/src/native/index.ts new file mode 100644 index 00000000000..34cc5902993 --- /dev/null +++ b/packages/expo/src/native/index.ts @@ -0,0 +1,27 @@ +import { errorThrower } from '../errorThrower'; +import type { ClerkExpoNativeModule } from './loadClerkExpoNative'; +import { loadClerkExpoNative } from './loadClerkExpoNative'; + +// Public types are declared in native/index.d.ts, which re-exports them from @clerk/expo-native. + +const CLERK_EXPO_NATIVE_MISSING_MESSAGE = + 'Native components have moved to the @clerk/expo-native package. ' + + 'Install it with `npx expo install @clerk/expo-native`, add "@clerk/expo-native" to the plugins array in your app config, ' + + 'then rebuild your native app. You can then import them from "@clerk/expo-native".'; + +const clerkExpoNative = loadClerkExpoNative(); + +function resolveExport(name: K) { + return ( + clerkExpoNative?.[name] ?? + function ClerkExpoNativeMissing(): never { + return errorThrower.throw(`\`${name}\` is unavailable. ${CLERK_EXPO_NATIVE_MISSING_MESSAGE}`); + } + ); +} + +export const AuthView = resolveExport('AuthView'); +export const UserButton = resolveExport('UserButton'); +export const UserProfileView = resolveExport('UserProfileView'); +export const useAuthViewState = resolveExport('useAuthViewState'); +export const useUserProfileCustomPageNavigation = resolveExport('useUserProfileCustomPageNavigation'); diff --git a/packages/expo/src/native/loadClerkExpoNative.ts b/packages/expo/src/native/loadClerkExpoNative.ts new file mode 100644 index 00000000000..06f8e66b9dc --- /dev/null +++ b/packages/expo/src/native/loadClerkExpoNative.ts @@ -0,0 +1,18 @@ +type ClerkExpoNativeExport = (...args: never[]) => unknown; + +export type ClerkExpoNativeModule = Partial< + Record< + 'AuthView' | 'UserButton' | 'UserProfileView' | 'useAuthViewState' | 'useUserProfileCustomPageNavigation', + ClerkExpoNativeExport + > +>; + +export function loadClerkExpoNative(): ClerkExpoNativeModule | null { + try { + // Synchronous require() in try/catch so Metro treats @clerk/expo-native as an optional dependency. + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require('@clerk/expo-native') as ClerkExpoNativeModule; + } catch { + return null; + } +} diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 5f40ed5287c..99adbcd6e38 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -1163,7 +1163,7 @@ export function useNativeClientBootstrap({ if (__DEV__) { console.debug( `[ClerkProvider] Native Clerk module not available. ` + - `To enable native features, add "@clerk/expo" to your app.json plugins array.`, + `To enable native features, install @clerk/expo-native and add "@clerk/expo-native" to your app.json plugins array.`, ); } } else if (__DEV__) { From a01e01c7a9027439657529ec495f89df5c332938 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:28:14 -0400 Subject: [PATCH 4/8] chore(expo-native): wire the package into CI, the native fixture, and add changesets Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/expo-native-components-moved.md | 25 +++++++++++++++++++ .changeset/introduce-expo-native.md | 5 ++++ .../clerk-monorepo/references/package-map.md | 1 + .github/labeler.yml | 4 +++ .github/workflows/api-changes.yml | 2 ++ .github/workflows/expo-native-build.yml | 10 +++++--- integration/templates/expo-native/app.json | 2 +- 7 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 .changeset/expo-native-components-moved.md create mode 100644 .changeset/introduce-expo-native.md diff --git a/.changeset/expo-native-components-moved.md b/.changeset/expo-native-components-moved.md new file mode 100644 index 00000000000..6cf1fedf844 --- /dev/null +++ b/.changeset/expo-native-components-moved.md @@ -0,0 +1,25 @@ +--- +'@clerk/expo': minor +--- + +The native components (`AuthView`, `UserButton`, `UserProfileView`), the native module behind biometric credentials, and native client sync have moved to the new `@clerk/expo-native` package. Apps that don't install it no longer include the Clerk iOS and Android SDKs and no longer require iOS 17. + +If you use the native components or `useBiometricCredentials()`, install the new package: + +```sh +npx expo install @clerk/expo-native +``` + +add its config plugin alongside `@clerk/expo` in your app config: + +```json +{ + "expo": { + "plugins": ["@clerk/expo", "@clerk/expo-native"] + } +} +``` + +then rebuild your native app. `@clerk/expo/native` keeps working and re-exports the components from `@clerk/expo-native`, which you can also import from directly. Without `@clerk/expo-native` installed, rendering a component from `@clerk/expo/native` throws an error explaining how to install it. + +The `keychainService` and `theme` config plugin options now belong to the `@clerk/expo-native` plugin. The `@clerk/expo` plugin forwards them when `@clerk/expo-native` is installed, and warns otherwise. diff --git a/.changeset/introduce-expo-native.md b/.changeset/introduce-expo-native.md new file mode 100644 index 00000000000..637d416fff9 --- /dev/null +++ b/.changeset/introduce-expo-native.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo-native': minor +--- + +Add `@clerk/expo-native`, the optional companion package to `@clerk/expo` that contains Clerk's prebuilt native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module built on the Clerk iOS and Android SDKs. It includes a config plugin that accepts the `keychainService` and `theme` options. diff --git a/.claude/skills/clerk-monorepo/references/package-map.md b/.claude/skills/clerk-monorepo/references/package-map.md index c5cabf3765b..b86fb4e5a44 100644 --- a/.claude/skills/clerk-monorepo/references/package-map.md +++ b/.claude/skills/clerk-monorepo/references/package-map.md @@ -34,6 +34,7 @@ backwards-compatibility contract (see `breaking-changes.md`). | `@clerk/react-router` | adapter | | React Router v7 SDK. | | `@clerk/tanstack-react-start` | adapter | | TanStack React Start SDK. | | `@clerk/expo` | adapter | | React Native / Expo SDK. | +| `@clerk/expo-native` | adapter | | Native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module for Expo, built on clerk-ios / clerk-android. Optional companion to `@clerk/expo`. | | `@clerk/expo-passkeys` | adapter | | Passkeys companion library for Expo. | | `@clerk/chrome-extension` | browser-runtime | | SDK for Chrome extension contexts. | | `@clerk/localizations` | ui/i18n | | Translation strings for the UI components. Consumed by `ui`. | diff --git a/.github/labeler.yml b/.github/labeler.yml index b6f18eeb545..675d48e803f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -28,6 +28,10 @@ expo: - changed-files: - any-glob-to-any-file: packages/expo/** +expo-native: + - changed-files: + - any-glob-to-any-file: packages/expo-native/** + express: - changed-files: - any-glob-to-any-file: packages/express/** diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml index 25971a1ad1b..4d7ca8eda83 100644 --- a/.github/workflows/api-changes.yml +++ b/.github/workflows/api-changes.yml @@ -19,6 +19,7 @@ on: - 'packages/clerk-js/**' - 'packages/expo/**' - 'packages/expo-google-signin/**' + - 'packages/expo-native/**' - 'packages/expo-passkeys/**' - 'packages/express/**' - 'packages/fastify/**' @@ -56,6 +57,7 @@ env: --filter=@clerk/clerk-js --filter=@clerk/expo --filter=@clerk/expo-google-signin + --filter=@clerk/expo-native --filter=@clerk/expo-passkeys --filter=@clerk/express --filter=@clerk/fastify diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index bf96461fe88..609392e24a5 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -11,6 +11,7 @@ on: - 'integration/tests/expo-native/**' - 'packages/expo/**' - 'packages/expo-google-signin/**' + - 'packages/expo-native/**' workflow_dispatch: permissions: @@ -85,6 +86,7 @@ jobs: packages/clerk-js \ packages/expo \ packages/expo-google-signin \ + packages/expo-native \ packages/react \ packages/shared \ "$FIXTURE_DIR" | @@ -121,10 +123,11 @@ jobs: - name: Build and pack Clerk packages if: steps.native-build-cache.outputs.cache-hit != 'true' run: | - pnpm --filter @clerk/expo... build + pnpm turbo build --filter=@clerk/expo-native... mkdir -p "$SDK_PACK_DIR" pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR" pnpm --filter @clerk/expo-google-signin pack --pack-destination "$SDK_PACK_DIR" + pnpm --filter @clerk/expo-native pack --pack-destination "$SDK_PACK_DIR" - name: Install fixture dependencies if: steps.native-build-cache.outputs.cache-hit != 'true' @@ -135,10 +138,11 @@ jobs: run: | cp "package.sdk-$EXPO_SDK.json" package.json pnpm install --no-frozen-lockfile - # [0-9] keeps this glob off the clerk-expo-google-signin tarball. + # [0-9] keeps this glob off the clerk-expo-google-signin and clerk-expo-native tarballs. SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-[0-9]*.tgz)" GOOGLE_SIGNIN_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-google-signin-*.tgz)" - pnpm add "$SDK_TARBALL" "$GOOGLE_SIGNIN_TARBALL" -w + NATIVE_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-native-*.tgz)" + pnpm add "$SDK_TARBALL" "$GOOGLE_SIGNIN_TARBALL" "$NATIVE_TARBALL" -w # expo-dev-client makes even release builds boot into the dev # launcher (unreachable Metro in CI), which stalls every Maestro # flow on a blank screen. Skip it on e2e jobs only. diff --git a/integration/templates/expo-native/app.json b/integration/templates/expo-native/app.json index 58e258ea523..4ea4b82bb8f 100644 --- a/integration/templates/expo-native/app.json +++ b/integration/templates/expo-native/app.json @@ -12,6 +12,6 @@ "android": { "package": "com.clerk.exponativebuildfixture" }, - "plugins": ["expo-secure-store", "@clerk/expo", "expo-web-browser"] + "plugins": ["expo-secure-store", "@clerk/expo", "@clerk/expo-native", "expo-web-browser"] } } From d3be19af0b3214b63dc70ef2a2562bb1b288fc06 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:31:42 -0400 Subject: [PATCH 5/8] fix(expo): clarify config plugin warnings for @clerk/expo-native options Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/expo/app.plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/expo/app.plugin.js b/packages/expo/app.plugin.js index fbfa61128bd..29c276c2067 100644 --- a/packages/expo/app.plugin.js +++ b/packages/expo/app.plugin.js @@ -118,7 +118,7 @@ const withClerkExpoNative = (config, props = {}, resolvePlugin = resolveClerkExp if (config._internal?.pluginHistory?.[CLERK_EXPO_NATIVE]) { if (nativeOptionNames) { console.warn( - `⚠️ Clerk: ${nativeOptionNames} passed to the "@clerk/expo" plugin will be ignored because the "${CLERK_EXPO_NATIVE}" plugin already ran. Pass them to the "${CLERK_EXPO_NATIVE}" plugin instead.`, + `⚠️ Clerk: The following "@clerk/expo" plugin options are ignored because the "${CLERK_EXPO_NATIVE}" plugin already ran: ${nativeOptionNames}. Pass them to the "${CLERK_EXPO_NATIVE}" plugin instead.`, ); } return config; @@ -128,7 +128,7 @@ const withClerkExpoNative = (config, props = {}, resolvePlugin = resolveClerkExp if (!clerkExpoNativePlugin) { if (nativeOptionNames) { console.warn( - `⚠️ Clerk: ${nativeOptionNames} configure Clerk native components, which have moved to ${CLERK_EXPO_NATIVE}. Install it with \`npx expo install ${CLERK_EXPO_NATIVE}\` and add "${CLERK_EXPO_NATIVE}" to the plugins array in your app config.`, + `⚠️ Clerk: The following "@clerk/expo" plugin options require ${CLERK_EXPO_NATIVE} and are ignored: ${nativeOptionNames}. Install it with \`npx expo install ${CLERK_EXPO_NATIVE}\` and add "${CLERK_EXPO_NATIVE}" to the plugins array in your app config.`, ); } return config; From dd05e528852594db6241bbfc0ae715b510a9ec10 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:38:50 -0400 Subject: [PATCH 6/8] refactor(expo-native): drop the dependency on @clerk/expo @clerk/expo-native now takes useAuth from @clerk/react, so the two workspace packages no longer form a cycle. The config plugin records the installed @clerk/expo version for the native host SDK header on both platforms via Info.plist and gradle.properties, and fails early if the installed @clerk/expo still bundles the native module. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/expo-native-build.yml | 2 +- packages/expo-native/android/build.gradle | 19 ++----- packages/expo-native/app.plugin.js | 44 ++++++++++++---- packages/expo-native/package.json | 3 +- .../src/__tests__/appPlugin.test.js | 50 +++++++++++++++++++ .../src/__tests__/useAuthViewState.test.tsx | 2 +- packages/expo-native/src/useAuthViewState.ts | 2 +- packages/expo/package.json | 3 +- pnpm-lock.yaml | 12 ++--- 9 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 packages/expo-native/src/__tests__/appPlugin.test.js diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 609392e24a5..375d3cc47d1 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -123,7 +123,7 @@ jobs: - name: Build and pack Clerk packages if: steps.native-build-cache.outputs.cache-hit != 'true' run: | - pnpm turbo build --filter=@clerk/expo-native... + pnpm --filter @clerk/expo... build mkdir -p "$SDK_PACK_DIR" pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR" pnpm --filter @clerk/expo-google-signin pack --pack-destination "$SDK_PACK_DIR" diff --git a/packages/expo-native/android/build.gradle b/packages/expo-native/android/build.gradle index 5cb80b92dd8..18c81a17cbe 100644 --- a/packages/expo-native/android/build.gradle +++ b/packages/expo-native/android/build.gradle @@ -13,22 +13,9 @@ applyKotlinExpoModulesCorePlugin() group = 'com.clerk.expo' version = '1.0.0' -// The native SDKs report the @clerk/expo version in the x-clerk-host-sdk-version header. -def resolveClerkExpoVersion() { - try { - def process = new ProcessBuilder("node", "--print", "require.resolve('@clerk/expo/package.json')") - .directory(projectDir) - .start() - def clerkExpoPackageJsonPath = process.inputStream.text.trim() - if (process.waitFor() == 0 && clerkExpoPackageJsonPath) { - return new JsonSlurper().parse(new File(clerkExpoPackageJsonPath)).version.toString() - } - } catch (Exception ignored) { - } - return new JsonSlurper().parse(new File(projectDir, "../package.json")).version.toString() -} - -def clerkExpoVersion = resolveClerkExpoVersion() +// Set by the @clerk/expo-native config plugin to the installed @clerk/expo version. +def clerkExpoVersion = rootProject.findProperty("clerkExpo.hostSdkVersion")?.toString() ?: + new JsonSlurper().parse(new File(projectDir, "../package.json")).version.toString() // Dependency versions - centralized for easier updates // See: https://docs.gradle.org/current/userguide/version_catalogs.html for app-level version catalogs diff --git a/packages/expo-native/app.plugin.js b/packages/expo-native/app.plugin.js index f8faa236dea..98a22f4b09d 100644 --- a/packages/expo-native/app.plugin.js +++ b/packages/expo-native/app.plugin.js @@ -14,24 +14,29 @@ const { withDangerousMod, withInfoPlist, withAppBuildGradle, + withGradleProperties, } = require('@expo/config-plugins'); const path = require('path'); const fs = require('fs'); const packageJson = require('./package.json'); const CLERK_MIN_IOS_VERSION = '17.0'; +const HOST_SDK_VERSION_GRADLE_PROPERTY = 'clerkExpo.hostSdkVersion'; -// The native SDKs report the @clerk/expo version in the x-clerk-host-sdk-version header. -const resolveHostSdkVersion = projectRoot => { +const resolveClerkExpo = projectRoot => { try { - const paths = [projectRoot, process.cwd(), __dirname].filter(Boolean); - return require(require.resolve('@clerk/expo/package.json', { paths })).version; + const paths = [projectRoot, process.cwd()].filter(Boolean); + const clerkExpoPackageJsonPath = require.resolve('@clerk/expo/package.json', { paths }); + return { + dir: path.dirname(clerkExpoPackageJsonPath), + version: require(clerkExpoPackageJsonPath).version, + }; } catch { - return packageJson.version; + return null; } }; -const withClerkIOS = config => { +const withClerkIOS = (config, hostSdkVersion) => { console.log('✅ Clerk iOS plugin loaded'); // IMPORTANT: Set iOS deployment target in Podfile.properties.json BEFORE pod install @@ -89,7 +94,6 @@ const withClerkIOS = config => { return config; }); - const hostSdkVersion = resolveHostSdkVersion(config._internal?.projectRoot); config = withInfoPlist(config, modConfig => { modConfig.modResults.ClerkExpoVersion = hostSdkVersion; return modConfig; @@ -102,9 +106,17 @@ const withClerkIOS = config => { * Add packaging exclusions to Android app build.gradle to resolve * duplicate META-INF file conflicts from clerk-android dependencies. */ -const withClerkAndroid = config => { +const withClerkAndroid = (config, hostSdkVersion) => { console.log('✅ Clerk Android plugin loaded'); + config = withGradleProperties(config, modConfig => { + modConfig.modResults = modConfig.modResults.filter( + item => !(item.type === 'property' && item.key === HOST_SDK_VERSION_GRADLE_PROPERTY), + ); + modConfig.modResults.push({ type: 'property', key: HOST_SDK_VERSION_GRADLE_PROPERTY, value: hostSdkVersion }); + return modConfig; + }); + return withAppBuildGradle(config, modConfig => { let buildGradle = modConfig.modResults.contents; @@ -294,9 +306,18 @@ const withClerkTheme = (config, props = {}) => { return config; }; -const withClerkExpoNative = (config, props = {}) => { - config = withClerkIOS(config); - config = withClerkAndroid(config); +const withClerkExpoNative = (config, props = {}, resolve = resolveClerkExpo) => { + const clerkExpo = resolve(config._internal?.projectRoot); + if (clerkExpo && fs.existsSync(path.join(clerkExpo.dir, 'expo-module.config.json'))) { + throw new Error( + `Clerk: @clerk/expo@${clerkExpo.version} still bundles the Clerk native module, which conflicts with @clerk/expo-native. Upgrade @clerk/expo to a version that supports @clerk/expo-native.`, + ); + } + // Native requests report the @clerk/expo version in the x-clerk-host-sdk-version header. + const hostSdkVersion = clerkExpo?.version ?? packageJson.version; + + config = withClerkIOS(config, hostSdkVersion); + config = withClerkAndroid(config, hostSdkVersion); config = withClerkKeychainService(config, props); config = withClerkTheme(config, props); return config; @@ -304,6 +325,7 @@ const withClerkExpoNative = (config, props = {}) => { module.exports = createRunOncePlugin(withClerkExpoNative, packageJson.name, packageJson.version); module.exports._testing = { + withClerkExpoNative, validateThemeJson, isPlainObject, VALID_COLOR_KEYS, diff --git a/packages/expo-native/package.json b/packages/expo-native/package.json index 25e1addf9d4..f732b7297da 100644 --- a/packages/expo-native/package.json +++ b/packages/expo-native/package.json @@ -56,15 +56,14 @@ "test:watch": "vitest watch" }, "dependencies": { + "@clerk/react": "workspace:^", "tslib": "catalog:repo" }, "devDependencies": { - "@clerk/expo": "workspace:*", "@expo/config-plugins": "^54.0.4", "react-native": "^0.86.0" }, "peerDependencies": { - "@clerk/expo": "workspace:^", "expo": "catalog:peer-expo", "react": "^18.0.0 || ^19.0.0", "react-native": ">=0.75" diff --git a/packages/expo-native/src/__tests__/appPlugin.test.js b/packages/expo-native/src/__tests__/appPlugin.test.js new file mode 100644 index 00000000000..bb611cb434b --- /dev/null +++ b/packages/expo-native/src/__tests__/appPlugin.test.js @@ -0,0 +1,50 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export +const { withClerkExpoNative } = require('../../app.plugin.js')._testing; + +const applyMod = (config, platform, mod, modResults) => + config.mods[platform][mod]({ ...config, modRequest: {}, modResults }); + +describe('withClerkExpoNative', () => { + let clerkExpoDir; + + beforeEach(() => { + vi.spyOn(console, 'log').mockImplementation(() => {}); + clerkExpoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'clerk-expo-')); + }); + + afterEach(() => { + vi.restoreAllMocks(); + fs.rmSync(clerkExpoDir, { recursive: true, force: true }); + }); + + test('reports the installed @clerk/expo version on iOS and Android', async () => { + const config = withClerkExpoNative({ name: 'test', slug: 'test' }, {}, () => ({ + dir: clerkExpoDir, + version: '4.8.0', + })); + + const infoPlist = await applyMod(config, 'ios', 'infoPlist', {}); + const gradleProperties = await applyMod(config, 'android', 'gradleProperties', [ + { type: 'property', key: 'clerkExpo.hostSdkVersion', value: '4.7.0' }, + ]); + + expect(infoPlist.modResults.ClerkExpoVersion).toBe('4.8.0'); + expect(gradleProperties.modResults).toEqual([ + { type: 'property', key: 'clerkExpo.hostSdkVersion', value: '4.8.0' }, + ]); + }); + + test('throws when the installed @clerk/expo still bundles the native module', () => { + fs.writeFileSync(path.join(clerkExpoDir, 'expo-module.config.json'), '{}'); + + expect(() => + withClerkExpoNative({ name: 'test', slug: 'test' }, {}, () => ({ dir: clerkExpoDir, version: '4.7.1' })), + ).toThrow('@clerk/expo@4.7.1 still bundles the Clerk native module'); + }); +}); diff --git a/packages/expo-native/src/__tests__/useAuthViewState.test.tsx b/packages/expo-native/src/__tests__/useAuthViewState.test.tsx index aab5496325c..7b16d1816ef 100644 --- a/packages/expo-native/src/__tests__/useAuthViewState.test.tsx +++ b/packages/expo-native/src/__tests__/useAuthViewState.test.tsx @@ -13,7 +13,7 @@ const mocks = vi.hoisted(() => ({ remove: vi.fn(), })); -vi.mock('@clerk/expo', () => ({ +vi.mock('@clerk/react', () => ({ useAuth: () => mocks.auth, })); diff --git a/packages/expo-native/src/useAuthViewState.ts b/packages/expo-native/src/useAuthViewState.ts index a4c4a5f3b61..c32f8e3e637 100644 --- a/packages/expo-native/src/useAuthViewState.ts +++ b/packages/expo-native/src/useAuthViewState.ts @@ -1,4 +1,4 @@ -import { useAuth } from '@clerk/expo'; +import { useAuth } from '@clerk/react'; import { useEffect, useState } from 'react'; import type { NativeAuthFlowState } from './specs/NativeClerkModule.types'; diff --git a/packages/expo/package.json b/packages/expo/package.json index dc84ba82b93..46deb108a47 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -122,6 +122,7 @@ }, "devDependencies": { "@clerk/expo-google-signin": "workspace:*", + "@clerk/expo-native": "workspace:*", "@clerk/expo-passkeys": "workspace:*", "@expo/config-plugins": "^54.0.4", "@types/base-64": "^1.0.2", @@ -137,7 +138,7 @@ }, "peerDependencies": { "@clerk/expo-google-signin": ">=0.1.0", - "@clerk/expo-native": "workspace:>=0.0.1", + "@clerk/expo-native": ">=0.1.0", "@clerk/expo-passkeys": ">=0.0.6", "expo": "catalog:peer-expo", "expo-apple-authentication": ">=7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39ddc5fae02..0b78f04fdd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -634,9 +634,6 @@ importers: '@clerk/clerk-js': specifier: workspace:^ version: link:../clerk-js - '@clerk/expo-native': - specifier: workspace:>=0.0.1 - version: link:../expo-native '@clerk/react': specifier: workspace:^ version: link:../react @@ -665,6 +662,9 @@ importers: '@clerk/expo-google-signin': specifier: workspace:* version: link:../expo-google-signin + '@clerk/expo-native': + specifier: workspace:* + version: link:../expo-native '@clerk/expo-passkeys': specifier: workspace:* version: link:../expo-passkeys @@ -713,6 +713,9 @@ importers: packages/expo-native: dependencies: + '@clerk/react': + specifier: workspace:^ + version: link:../react expo: specifier: catalog:peer-expo version: 54.0.36(@babel/core@7.29.7)(bufferutil@4.1.0)(graphql@16.14.1)(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -723,9 +726,6 @@ importers: specifier: catalog:repo version: 2.8.1 devDependencies: - '@clerk/expo': - specifier: workspace:* - version: link:../expo '@expo/config-plugins': specifier: ^54.0.4 version: 54.0.5 From 5ef3c7d6bf66ed751b163fd8eb0b5f1de25fd738 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:34:21 -0400 Subject: [PATCH 7/8] chore(expo): leave biometric credentials out of the native split changeset Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/expo-native-components-moved.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/expo-native-components-moved.md b/.changeset/expo-native-components-moved.md index 6cf1fedf844..5f4a79a356e 100644 --- a/.changeset/expo-native-components-moved.md +++ b/.changeset/expo-native-components-moved.md @@ -2,9 +2,9 @@ '@clerk/expo': minor --- -The native components (`AuthView`, `UserButton`, `UserProfileView`), the native module behind biometric credentials, and native client sync have moved to the new `@clerk/expo-native` package. Apps that don't install it no longer include the Clerk iOS and Android SDKs and no longer require iOS 17. +The native components (`AuthView`, `UserButton`, `UserProfileView`) and native client sync have moved to the new `@clerk/expo-native` package. Apps that don't install it no longer include the Clerk iOS and Android SDKs and no longer require iOS 17. -If you use the native components or `useBiometricCredentials()`, install the new package: +If you use the native components, install the new package: ```sh npx expo install @clerk/expo-native From 170d56f62d2b36b47b84110c1ee8cff67f47d026 Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:08:47 -0400 Subject: [PATCH 8/8] refactor(expo-native-components): rename @clerk/expo-native to @clerk/expo-native-components Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/expo-native-components-moved.md | 10 ++-- .changeset/introduce-expo-native.md | 4 +- .../clerk-monorepo/references/package-map.md | 54 +++++++++---------- .github/labeler.yml | 4 +- .github/workflows/api-changes.yml | 4 +- .github/workflows/expo-native-build.yml | 8 +-- package.json | 2 +- .../.gitignore | 0 .../LICENSE | 0 .../README.md | 28 +++++----- .../android/build.gradle | 4 +- .../android/src/main/AndroidManifest.xml | 0 .../expo/modules/clerk/ClerkAuthViewModule.kt | 0 .../clerk/ClerkComposeNativeViewHost.kt | 0 .../expo/modules/clerk/ClerkExpoModule.kt | 0 .../clerk/ClerkUserButtonViewModule.kt | 0 .../clerk/ClerkUserProfileCustomPageState.kt | 0 .../clerk/ClerkUserProfileViewModule.kt | 0 .../clerk/BiometricCredentialBridgeTest.kt | 0 .../ClerkUserProfileCustomPageStateTest.kt | 0 .../expo-native-components/app.plugin.d.ts | 5 ++ .../app.plugin.js | 10 ++-- .../expo-module.config.json | 0 .../ios/ClerkAppDelegateSubscriber.swift | 0 .../ios/ClerkAuthNativeView.swift | 0 .../ios/ClerkExpo.podspec | 0 .../ios/ClerkExpoModule.swift | 0 .../ios/ClerkNativeBridge.swift | 0 .../ios/ClerkNativeViewHost.swift | 0 .../ios/ClerkUserButtonNativeView.swift | 0 .../ios/ClerkUserProfileNativeView.swift | 0 .../Tests/ClerkAuthNativeViewPaperTests.swift | 0 .../ios/Tests/ClerkNativeBridgeTests.swift | 0 .../ios/Tests/ClerkNativeViewHostTests.swift | 0 ...ClerkUserProfileCustomPageStateTests.swift | 0 .../package.json | 4 +- .../react-native.config.js | 0 .../src/AuthView.tsx | 4 +- .../src/AuthView.types.ts | 0 .../src/EmbeddedNavigation.types.ts | 0 .../src/UserButton.tsx | 2 +- .../src/UserProfileCustomPages.tsx | 0 .../src/UserProfileView.tsx | 4 +- .../src/__tests__/AuthView.test.tsx | 0 .../src/__tests__/UserButton.test.tsx | 0 .../__tests__/UserProfileCustomPages.test.tsx | 0 .../src/__tests__/UserProfileView.test.tsx | 0 .../src/__tests__/appPlugin.test.js | 11 ++-- .../src/__tests__/appPlugin.theme.test.js | 0 .../src/__tests__/useAuthViewState.test.tsx | 0 .../src/index.ts | 6 +-- .../src/specs/NativeClerkAuthView.android.ts | 0 .../src/specs/NativeClerkAuthView.ts | 0 .../src/specs/NativeClerkModule.ts | 0 .../src/specs/NativeClerkModule.types.ts | 0 .../src/specs/NativeClerkModule.web.ts | 0 .../NativeClerkUserButtonView.android.ts | 0 .../src/specs/NativeClerkUserButtonView.ts | 0 .../NativeClerkUserProfileView.android.ts | 0 .../src/specs/NativeClerkUserProfileView.ts | 0 .../specs/__tests__/native-view.web.test.ts | 0 .../src/useAuthViewState.ts | 0 .../src/utils/native-module.ts | 0 .../tsconfig.declarations.json | 0 .../tsconfig.json | 0 .../tsconfig.test.json | 0 .../tsdown.config.mts | 0 .../vitest.config.mts | 0 .../vitest.setup.mts | 0 packages/expo-native/app.plugin.d.ts | 5 -- packages/expo/app.plugin.js | 12 ++--- packages/expo/native/index.d.ts | 2 +- packages/expo/package.json | 6 +-- packages/expo/src/__tests__/appPlugin.test.js | 42 +++++++++------ .../__tests__/useBiometricCredentials.test.ts | 6 +-- .../useBiometricCredentials.shared.ts | 8 +-- .../expo/src/native/__tests__/index.test.ts | 8 +-- packages/expo/src/native/index.ts | 8 +-- .../expo/src/native/loadClerkExpoNative.ts | 4 +- .../expo/src/provider/nativeClientSync.tsx | 2 +- pnpm-lock.yaml | 6 +-- 81 files changed, 142 insertions(+), 131 deletions(-) rename packages/{expo-native => expo-native-components}/.gitignore (100%) rename packages/{expo-native => expo-native-components}/LICENSE (100%) rename packages/{expo-native => expo-native-components}/README.md (74%) rename packages/{expo-native => expo-native-components}/android/build.gradle (96%) rename packages/{expo-native => expo-native-components}/android/src/main/AndroidManifest.xml (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt (100%) rename packages/{expo-native => expo-native-components}/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt (100%) create mode 100644 packages/expo-native-components/app.plugin.d.ts rename packages/{expo-native => expo-native-components}/app.plugin.js (96%) rename packages/{expo-native => expo-native-components}/expo-module.config.json (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkAppDelegateSubscriber.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkAuthNativeView.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkExpo.podspec (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkExpoModule.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkNativeBridge.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkNativeViewHost.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkUserButtonNativeView.swift (100%) rename packages/{expo-native => expo-native-components}/ios/ClerkUserProfileNativeView.swift (100%) rename packages/{expo-native => expo-native-components}/ios/Tests/ClerkAuthNativeViewPaperTests.swift (100%) rename packages/{expo-native => expo-native-components}/ios/Tests/ClerkNativeBridgeTests.swift (100%) rename packages/{expo-native => expo-native-components}/ios/Tests/ClerkNativeViewHostTests.swift (100%) rename packages/{expo-native => expo-native-components}/ios/Tests/ClerkUserProfileCustomPageStateTests.swift (100%) rename packages/{expo-native => expo-native-components}/package.json (95%) rename packages/{expo-native => expo-native-components}/react-native.config.js (100%) rename packages/{expo-native => expo-native-components}/src/AuthView.tsx (92%) rename packages/{expo-native => expo-native-components}/src/AuthView.types.ts (100%) rename packages/{expo-native => expo-native-components}/src/EmbeddedNavigation.types.ts (100%) rename packages/{expo-native => expo-native-components}/src/UserButton.tsx (98%) rename packages/{expo-native => expo-native-components}/src/UserProfileCustomPages.tsx (100%) rename packages/{expo-native => expo-native-components}/src/UserProfileView.tsx (96%) rename packages/{expo-native => expo-native-components}/src/__tests__/AuthView.test.tsx (100%) rename packages/{expo-native => expo-native-components}/src/__tests__/UserButton.test.tsx (100%) rename packages/{expo-native => expo-native-components}/src/__tests__/UserProfileCustomPages.test.tsx (100%) rename packages/{expo-native => expo-native-components}/src/__tests__/UserProfileView.test.tsx (100%) rename packages/{expo-native => expo-native-components}/src/__tests__/appPlugin.test.js (80%) rename packages/{expo-native => expo-native-components}/src/__tests__/appPlugin.theme.test.js (100%) rename packages/{expo-native => expo-native-components}/src/__tests__/useAuthViewState.test.tsx (100%) rename packages/{expo-native => expo-native-components}/src/index.ts (87%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkAuthView.android.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkAuthView.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkModule.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkModule.types.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkModule.web.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkUserButtonView.android.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkUserButtonView.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkUserProfileView.android.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/NativeClerkUserProfileView.ts (100%) rename packages/{expo-native => expo-native-components}/src/specs/__tests__/native-view.web.test.ts (100%) rename packages/{expo-native => expo-native-components}/src/useAuthViewState.ts (100%) rename packages/{expo-native => expo-native-components}/src/utils/native-module.ts (100%) rename packages/{expo-native => expo-native-components}/tsconfig.declarations.json (100%) rename packages/{expo-native => expo-native-components}/tsconfig.json (100%) rename packages/{expo-native => expo-native-components}/tsconfig.test.json (100%) rename packages/{expo-native => expo-native-components}/tsdown.config.mts (100%) rename packages/{expo-native => expo-native-components}/vitest.config.mts (100%) rename packages/{expo-native => expo-native-components}/vitest.setup.mts (100%) delete mode 100644 packages/expo-native/app.plugin.d.ts diff --git a/.changeset/expo-native-components-moved.md b/.changeset/expo-native-components-moved.md index 5f4a79a356e..6c3fd2fc364 100644 --- a/.changeset/expo-native-components-moved.md +++ b/.changeset/expo-native-components-moved.md @@ -2,12 +2,12 @@ '@clerk/expo': minor --- -The native components (`AuthView`, `UserButton`, `UserProfileView`) and native client sync have moved to the new `@clerk/expo-native` package. Apps that don't install it no longer include the Clerk iOS and Android SDKs and no longer require iOS 17. +The native components (`AuthView`, `UserButton`, `UserProfileView`) and native client sync have moved to the new `@clerk/expo-native-components` package. Apps that don't install it no longer include the Clerk iOS and Android SDKs and no longer require iOS 17. If you use the native components, install the new package: ```sh -npx expo install @clerk/expo-native +npx expo install @clerk/expo-native-components ``` add its config plugin alongside `@clerk/expo` in your app config: @@ -15,11 +15,11 @@ add its config plugin alongside `@clerk/expo` in your app config: ```json { "expo": { - "plugins": ["@clerk/expo", "@clerk/expo-native"] + "plugins": ["@clerk/expo", "@clerk/expo-native-components"] } } ``` -then rebuild your native app. `@clerk/expo/native` keeps working and re-exports the components from `@clerk/expo-native`, which you can also import from directly. Without `@clerk/expo-native` installed, rendering a component from `@clerk/expo/native` throws an error explaining how to install it. +then rebuild your native app. `@clerk/expo/native` keeps working and re-exports the components from `@clerk/expo-native-components`, which you can also import from directly. Without `@clerk/expo-native-components` installed, rendering a component from `@clerk/expo/native` throws an error explaining how to install it. -The `keychainService` and `theme` config plugin options now belong to the `@clerk/expo-native` plugin. The `@clerk/expo` plugin forwards them when `@clerk/expo-native` is installed, and warns otherwise. +The `keychainService` and `theme` config plugin options now belong to the `@clerk/expo-native-components` plugin. The `@clerk/expo` plugin forwards them when `@clerk/expo-native-components` is installed, and warns otherwise. diff --git a/.changeset/introduce-expo-native.md b/.changeset/introduce-expo-native.md index 637d416fff9..a7a0839fb61 100644 --- a/.changeset/introduce-expo-native.md +++ b/.changeset/introduce-expo-native.md @@ -1,5 +1,5 @@ --- -'@clerk/expo-native': minor +'@clerk/expo-native-components': minor --- -Add `@clerk/expo-native`, the optional companion package to `@clerk/expo` that contains Clerk's prebuilt native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module built on the Clerk iOS and Android SDKs. It includes a config plugin that accepts the `keychainService` and `theme` options. +Add `@clerk/expo-native-components`, the optional companion package to `@clerk/expo` that contains Clerk's prebuilt native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module built on the Clerk iOS and Android SDKs. It includes a config plugin that accepts the `keychainService` and `theme` options. diff --git a/.claude/skills/clerk-monorepo/references/package-map.md b/.claude/skills/clerk-monorepo/references/package-map.md index b86fb4e5a44..47abcecfa41 100644 --- a/.claude/skills/clerk-monorepo/references/package-map.md +++ b/.claude/skills/clerk-monorepo/references/package-map.md @@ -16,33 +16,33 @@ browser directly), **adapter** (framework SDK), **ui/i18n**, **tooling**. The "B two packages whose runtime is pushed into apps pinned to older SDKs, so they carry the strict backwards-compatibility contract (see `breaking-changes.md`). -| Package | Category | BC | Purpose | -| ----------------------------- | --------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `@clerk/shared` | foundational | | Internal utilities used by all SDKs (storage, events, React helpers). Hosts the shared types as `@clerk/shared/types`. Most-depended-on package. | -| `@clerk/backend` | foundational | | Backend API REST client, JWT verification, webhook helpers. Used by every server adapter. | -| `@clerk/clerk-js` | browser-runtime | ⚠️ | The browser runtime (script tag). Backwards-compat sensitive. | -| `@clerk/ui` | ui | ⚠️ | React components for the hosted sign-in / sign-up flows (`packages/ui/src/components`). Consumed by the react/astro/vue/chrome-extension adapters. Backwards-compat sensitive. | -| `@clerk/mosaic` | ui | | Experimental next-generation React components. Public ESM package (`UserButton` + `styles.css`). Ships unstyled, accessible primitives (dialog, menu, popover, ...) internally under `src/primitives/`. Reads Clerk context from the host SDK via `@clerk/shared`. | -| `@clerk/react` | adapter (core) | | React hooks and context (`useAuth`, `useUser`, `useOrganization`, ...). Shared by the React-based adapters. | -| `@clerk/nextjs` | adapter | | Next.js SDK: middleware, route handlers, server components. | -| `@clerk/express` | adapter | | Express middleware and server helpers. | -| `@clerk/fastify` | adapter | | Fastify plugin. | -| `@clerk/hono` | adapter | | Hono SDK (edge / serverless). | -| `@clerk/astro` | adapter | | Astro integration (components + server utilities). | -| `@clerk/nuxt` | adapter | | Nuxt module (Vue). | -| `@clerk/vue` | adapter | | Vue 3 composables and components. | -| `@clerk/react-router` | adapter | | React Router v7 SDK. | -| `@clerk/tanstack-react-start` | adapter | | TanStack React Start SDK. | -| `@clerk/expo` | adapter | | React Native / Expo SDK. | -| `@clerk/expo-native` | adapter | | Native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module for Expo, built on clerk-ios / clerk-android. Optional companion to `@clerk/expo`. | -| `@clerk/expo-passkeys` | adapter | | Passkeys companion library for Expo. | -| `@clerk/chrome-extension` | browser-runtime | | SDK for Chrome extension contexts. | -| `@clerk/localizations` | ui/i18n | | Translation strings for the UI components. Consumed by `ui`. | -| `@clerk/testing` | tooling | | E2E test helpers for consumers (Playwright + Cypress). | -| `@clerk/msw` | tooling | | MSW request handlers for mocking the Clerk API in tests. Private (not published). | -| `@clerk/swingset` | tooling | | Component explorer for `@clerk/mosaic`. Private (not published). | -| `@clerk/upgrade` | tooling | | CLI codemod tool for upgrading consumers between SDK versions. | -| `@clerk/eslint-plugin` | tooling | | ESLint plugin enforcing Clerk patterns across JavaScript frameworks (lint rules shipped to apps). Published. | +| Package | Category | BC | Purpose | +| ------------------------------- | --------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `@clerk/shared` | foundational | | Internal utilities used by all SDKs (storage, events, React helpers). Hosts the shared types as `@clerk/shared/types`. Most-depended-on package. | +| `@clerk/backend` | foundational | | Backend API REST client, JWT verification, webhook helpers. Used by every server adapter. | +| `@clerk/clerk-js` | browser-runtime | ⚠️ | The browser runtime (script tag). Backwards-compat sensitive. | +| `@clerk/ui` | ui | ⚠️ | React components for the hosted sign-in / sign-up flows (`packages/ui/src/components`). Consumed by the react/astro/vue/chrome-extension adapters. Backwards-compat sensitive. | +| `@clerk/mosaic` | ui | | Experimental next-generation React components. Public ESM package (`UserButton` + `styles.css`). Ships unstyled, accessible primitives (dialog, menu, popover, ...) internally under `src/primitives/`. Reads Clerk context from the host SDK via `@clerk/shared`. | +| `@clerk/react` | adapter (core) | | React hooks and context (`useAuth`, `useUser`, `useOrganization`, ...). Shared by the React-based adapters. | +| `@clerk/nextjs` | adapter | | Next.js SDK: middleware, route handlers, server components. | +| `@clerk/express` | adapter | | Express middleware and server helpers. | +| `@clerk/fastify` | adapter | | Fastify plugin. | +| `@clerk/hono` | adapter | | Hono SDK (edge / serverless). | +| `@clerk/astro` | adapter | | Astro integration (components + server utilities). | +| `@clerk/nuxt` | adapter | | Nuxt module (Vue). | +| `@clerk/vue` | adapter | | Vue 3 composables and components. | +| `@clerk/react-router` | adapter | | React Router v7 SDK. | +| `@clerk/tanstack-react-start` | adapter | | TanStack React Start SDK. | +| `@clerk/expo` | adapter | | React Native / Expo SDK. | +| `@clerk/expo-native-components` | adapter | | Native components (`AuthView`, `UserButton`, `UserProfileView`) and the native module for Expo, built on clerk-ios / clerk-android. Optional companion to `@clerk/expo`. | +| `@clerk/expo-passkeys` | adapter | | Passkeys companion library for Expo. | +| `@clerk/chrome-extension` | browser-runtime | | SDK for Chrome extension contexts. | +| `@clerk/localizations` | ui/i18n | | Translation strings for the UI components. Consumed by `ui`. | +| `@clerk/testing` | tooling | | E2E test helpers for consumers (Playwright + Cypress). | +| `@clerk/msw` | tooling | | MSW request handlers for mocking the Clerk API in tests. Private (not published). | +| `@clerk/swingset` | tooling | | Component explorer for `@clerk/mosaic`. Private (not published). | +| `@clerk/upgrade` | tooling | | CLI codemod tool for upgrading consumers between SDK versions. | +| `@clerk/eslint-plugin` | tooling | | ESLint plugin enforcing Clerk patterns across JavaScript frameworks (lint rules shipped to apps). Published. | Tests: `pnpm turbo test --filter=@clerk/` (or `pnpm --filter @clerk/ test` after a build). Most packages use vitest; `@clerk/backend` runs a multi-runtime suite (node + edge + diff --git a/.github/labeler.yml b/.github/labeler.yml index 675d48e803f..d07fbb87c3f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -28,9 +28,9 @@ expo: - changed-files: - any-glob-to-any-file: packages/expo/** -expo-native: +expo-native-components: - changed-files: - - any-glob-to-any-file: packages/expo-native/** + - any-glob-to-any-file: packages/expo-native-components/** express: - changed-files: diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml index 4d7ca8eda83..9feadb28f55 100644 --- a/.github/workflows/api-changes.yml +++ b/.github/workflows/api-changes.yml @@ -19,7 +19,7 @@ on: - 'packages/clerk-js/**' - 'packages/expo/**' - 'packages/expo-google-signin/**' - - 'packages/expo-native/**' + - 'packages/expo-native-components/**' - 'packages/expo-passkeys/**' - 'packages/express/**' - 'packages/fastify/**' @@ -57,7 +57,7 @@ env: --filter=@clerk/clerk-js --filter=@clerk/expo --filter=@clerk/expo-google-signin - --filter=@clerk/expo-native + --filter=@clerk/expo-native-components --filter=@clerk/expo-passkeys --filter=@clerk/express --filter=@clerk/fastify diff --git a/.github/workflows/expo-native-build.yml b/.github/workflows/expo-native-build.yml index 375d3cc47d1..cd69db5c340 100644 --- a/.github/workflows/expo-native-build.yml +++ b/.github/workflows/expo-native-build.yml @@ -11,7 +11,7 @@ on: - 'integration/tests/expo-native/**' - 'packages/expo/**' - 'packages/expo-google-signin/**' - - 'packages/expo-native/**' + - 'packages/expo-native-components/**' workflow_dispatch: permissions: @@ -86,7 +86,7 @@ jobs: packages/clerk-js \ packages/expo \ packages/expo-google-signin \ - packages/expo-native \ + packages/expo-native-components \ packages/react \ packages/shared \ "$FIXTURE_DIR" | @@ -127,7 +127,7 @@ jobs: mkdir -p "$SDK_PACK_DIR" pnpm --filter @clerk/expo pack --pack-destination "$SDK_PACK_DIR" pnpm --filter @clerk/expo-google-signin pack --pack-destination "$SDK_PACK_DIR" - pnpm --filter @clerk/expo-native pack --pack-destination "$SDK_PACK_DIR" + pnpm --filter @clerk/expo-native-components pack --pack-destination "$SDK_PACK_DIR" - name: Install fixture dependencies if: steps.native-build-cache.outputs.cache-hit != 'true' @@ -141,7 +141,7 @@ jobs: # [0-9] keeps this glob off the clerk-expo-google-signin and clerk-expo-native tarballs. SDK_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-[0-9]*.tgz)" GOOGLE_SIGNIN_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-google-signin-*.tgz)" - NATIVE_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-native-*.tgz)" + NATIVE_TARBALL="$(ls "$SDK_PACK_DIR"/clerk-expo-native-components-*.tgz)" pnpm add "$SDK_TARBALL" "$GOOGLE_SIGNIN_TARBALL" "$NATIVE_TARBALL" -w # expo-dev-client makes even release builds boot into the dev # launcher (unreachable Metro in CI), which stalls every Maestro diff --git a/package.json b/package.json index 7620564b4fd..3f4c74549d6 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "changeset": "changeset", "changeset:empty": "pnpm changeset --empty", "clean": "turbo run clean", - "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/expo --filter=!@clerk/expo-native --filter=!@clerk/tanstack-react-start --filter=!@clerk/chrome-extension", + "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/expo --filter=!@clerk/expo-native-components --filter=!@clerk/tanstack-react-start --filter=!@clerk/chrome-extension", "dev:fe-libs": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/clerk-js --filter=@clerk/ui --filter=@clerk/shared", "dev:js": "TURBO_UI=0 FORCE_COLOR=1 turbo dev:current --filter=@clerk/clerk-js", "dev:sandbox": "TURBO_UI=0 FORCE_COLOR=1 turbo dev:sandbox:serve", diff --git a/packages/expo-native/.gitignore b/packages/expo-native-components/.gitignore similarity index 100% rename from packages/expo-native/.gitignore rename to packages/expo-native-components/.gitignore diff --git a/packages/expo-native/LICENSE b/packages/expo-native-components/LICENSE similarity index 100% rename from packages/expo-native/LICENSE rename to packages/expo-native-components/LICENSE diff --git a/packages/expo-native/README.md b/packages/expo-native-components/README.md similarity index 74% rename from packages/expo-native/README.md rename to packages/expo-native-components/README.md index 35fe6587c9d..dceac483ebb 100644 --- a/packages/expo-native/README.md +++ b/packages/expo-native-components/README.md @@ -1,33 +1,33 @@

- +
-

@clerk/expo-native

+

@clerk/expo-native-components

[![Chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) -[![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_expo_native) +[![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_expo_native_components) [![Follow on X](https://img.shields.io/twitter/follow/clerk?style=social)](https://x.com/intent/follow?screen_name=clerk) -[Changelog](https://github.com/clerk/javascript/blob/main/packages/expo-native/CHANGELOG.md) +[Changelog](https://github.com/clerk/javascript/blob/main/packages/expo-native-components/CHANGELOG.md) · [Report a Bug](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml) · [Request a Feature](https://feedback.clerk.com/roadmap) · -[Get help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native) +[Get help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native_components)
## Getting Started -`@clerk/expo-native` adds Clerk's prebuilt native components to an Expo app that uses [`@clerk/expo`](https://github.com/clerk/javascript/tree/main/packages/expo). It contains the native modules built on the Clerk iOS and Android SDKs: +`@clerk/expo-native-components` adds Clerk's prebuilt native components to an Expo app that uses [`@clerk/expo`](https://github.com/clerk/javascript/tree/main/packages/expo). It contains the native modules built on the Clerk iOS and Android SDKs: - **iOS**: [clerk-ios](https://github.com/clerk/clerk-ios) (SwiftUI) - **Android**: [clerk-android](https://github.com/clerk/clerk-android) (Jetpack Compose) @@ -43,7 +43,7 @@ Install it only if you use `AuthView`, `UserProfileView`, `UserButton`, or biome ### Installation ```sh -npx expo install @clerk/expo @clerk/expo-native +npx expo install @clerk/expo @clerk/expo-native-components ``` Add the config plugin alongside `@clerk/expo` in your app config, then rebuild your native app: @@ -51,7 +51,7 @@ Add the config plugin alongside `@clerk/expo` in your app config, then rebuild y ```json { "expo": { - "plugins": ["@clerk/expo", "@clerk/expo-native"] + "plugins": ["@clerk/expo", "@clerk/expo-native-components"] } } ``` @@ -65,7 +65,7 @@ The plugin accepts the following options: ```tsx import { useAuth } from '@clerk/expo'; -import { AuthView } from '@clerk/expo-native'; +import { AuthView } from '@clerk/expo-native-components'; export default function SignInScreen() { const { isSignedIn } = useAuth(); @@ -78,11 +78,11 @@ export default function SignInScreen() { } ``` -For further information, guides, and examples visit the [Expo reference documentation](https://clerk.com/docs/references/expo/overview?utm_source=github&utm_medium=clerk_expo_native). +For further information, guides, and examples visit the [Expo reference documentation](https://clerk.com/docs/references/expo/overview?utm_source=github&utm_medium=clerk_expo_native_components). ## Support -For help, visit our [support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native). +For help, visit our [support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_expo_native_components). ## Community @@ -94,9 +94,9 @@ We're open to all community contributions! If you'd like to contribute in any wa ## Security -`@clerk/expo-native` follows good practices of security, but 100% security cannot be assured. +`@clerk/expo-native-components` follows good practices of security, but 100% security cannot be assured. -`@clerk/expo-native` is provided **"as is"** without any **warranty**. Use at your own risk. +`@clerk/expo-native-components` is provided **"as is"** without any **warranty**. Use at your own risk. _For more information and to report security issues, please refer to our [security documentation](https://github.com/clerk/javascript/blob/main/docs/SECURITY.md)._ @@ -104,4 +104,4 @@ _For more information and to report security issues, please refer to our [securi This project is licensed under the **MIT license**. -See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/expo-native/LICENSE) for more information. +See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/expo-native-components/LICENSE) for more information. diff --git a/packages/expo-native/android/build.gradle b/packages/expo-native-components/android/build.gradle similarity index 96% rename from packages/expo-native/android/build.gradle rename to packages/expo-native-components/android/build.gradle index 18c81a17cbe..facf8a66952 100644 --- a/packages/expo-native/android/build.gradle +++ b/packages/expo-native-components/android/build.gradle @@ -13,7 +13,7 @@ applyKotlinExpoModulesCorePlugin() group = 'com.clerk.expo' version = '1.0.0' -// Set by the @clerk/expo-native config plugin to the installed @clerk/expo version. +// Set by the @clerk/expo-native-components config plugin to the installed @clerk/expo version. def clerkExpoVersion = rootProject.findProperty("clerkExpo.hostSdkVersion")?.toString() ?: new JsonSlurper().parse(new File(projectDir, "../package.json")).version.toString() @@ -98,7 +98,7 @@ try { } } catch (Exception ignored) { // Future Gradle versions with Isolated Projects may block cross-project configuration. - // In that case, users should add '@clerk/expo-native' to their app.json plugins array instead, + // In that case, users should add '@clerk/expo-native-components' to their app.json plugins array instead, // which applies the flag via the config plugin's withClerkAndroid. } diff --git a/packages/expo-native/android/src/main/AndroidManifest.xml b/packages/expo-native-components/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/expo-native/android/src/main/AndroidManifest.xml rename to packages/expo-native-components/android/src/main/AndroidManifest.xml diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt diff --git a/packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt similarity index 100% rename from packages/expo-native/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt rename to packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt diff --git a/packages/expo-native/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt b/packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt similarity index 100% rename from packages/expo-native/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt rename to packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt diff --git a/packages/expo-native/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt b/packages/expo-native-components/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt similarity index 100% rename from packages/expo-native/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt rename to packages/expo-native-components/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt diff --git a/packages/expo-native-components/app.plugin.d.ts b/packages/expo-native-components/app.plugin.d.ts new file mode 100644 index 00000000000..8808449dd73 --- /dev/null +++ b/packages/expo-native-components/app.plugin.d.ts @@ -0,0 +1,5 @@ +import type { ConfigPlugin } from '@expo/config-plugins'; + +declare const withClerkExpoNativeComponents: ConfigPlugin<{ keychainService?: string; theme?: string } | void>; + +export = withClerkExpoNativeComponents; diff --git a/packages/expo-native/app.plugin.js b/packages/expo-native-components/app.plugin.js similarity index 96% rename from packages/expo-native/app.plugin.js rename to packages/expo-native-components/app.plugin.js index 98a22f4b09d..36e10092b7a 100644 --- a/packages/expo-native/app.plugin.js +++ b/packages/expo-native-components/app.plugin.js @@ -1,5 +1,5 @@ /** - * Expo config plugin for @clerk/expo-native + * Expo config plugin for @clerk/expo-native-components * Configures iOS and Android for the Clerk native SDKs (clerk-ios / clerk-android) * * When this plugin is used: @@ -306,11 +306,11 @@ const withClerkTheme = (config, props = {}) => { return config; }; -const withClerkExpoNative = (config, props = {}, resolve = resolveClerkExpo) => { +const withClerkExpoNativeComponents = (config, props = {}, resolve = resolveClerkExpo) => { const clerkExpo = resolve(config._internal?.projectRoot); if (clerkExpo && fs.existsSync(path.join(clerkExpo.dir, 'expo-module.config.json'))) { throw new Error( - `Clerk: @clerk/expo@${clerkExpo.version} still bundles the Clerk native module, which conflicts with @clerk/expo-native. Upgrade @clerk/expo to a version that supports @clerk/expo-native.`, + `Clerk: @clerk/expo@${clerkExpo.version} still bundles the Clerk native module, which conflicts with @clerk/expo-native-components. Upgrade @clerk/expo to a version that supports @clerk/expo-native-components.`, ); } // Native requests report the @clerk/expo version in the x-clerk-host-sdk-version header. @@ -323,9 +323,9 @@ const withClerkExpoNative = (config, props = {}, resolve = resolveClerkExpo) => return config; }; -module.exports = createRunOncePlugin(withClerkExpoNative, packageJson.name, packageJson.version); +module.exports = createRunOncePlugin(withClerkExpoNativeComponents, packageJson.name, packageJson.version); module.exports._testing = { - withClerkExpoNative, + withClerkExpoNativeComponents, validateThemeJson, isPlainObject, VALID_COLOR_KEYS, diff --git a/packages/expo-native/expo-module.config.json b/packages/expo-native-components/expo-module.config.json similarity index 100% rename from packages/expo-native/expo-module.config.json rename to packages/expo-native-components/expo-module.config.json diff --git a/packages/expo-native/ios/ClerkAppDelegateSubscriber.swift b/packages/expo-native-components/ios/ClerkAppDelegateSubscriber.swift similarity index 100% rename from packages/expo-native/ios/ClerkAppDelegateSubscriber.swift rename to packages/expo-native-components/ios/ClerkAppDelegateSubscriber.swift diff --git a/packages/expo-native/ios/ClerkAuthNativeView.swift b/packages/expo-native-components/ios/ClerkAuthNativeView.swift similarity index 100% rename from packages/expo-native/ios/ClerkAuthNativeView.swift rename to packages/expo-native-components/ios/ClerkAuthNativeView.swift diff --git a/packages/expo-native/ios/ClerkExpo.podspec b/packages/expo-native-components/ios/ClerkExpo.podspec similarity index 100% rename from packages/expo-native/ios/ClerkExpo.podspec rename to packages/expo-native-components/ios/ClerkExpo.podspec diff --git a/packages/expo-native/ios/ClerkExpoModule.swift b/packages/expo-native-components/ios/ClerkExpoModule.swift similarity index 100% rename from packages/expo-native/ios/ClerkExpoModule.swift rename to packages/expo-native-components/ios/ClerkExpoModule.swift diff --git a/packages/expo-native/ios/ClerkNativeBridge.swift b/packages/expo-native-components/ios/ClerkNativeBridge.swift similarity index 100% rename from packages/expo-native/ios/ClerkNativeBridge.swift rename to packages/expo-native-components/ios/ClerkNativeBridge.swift diff --git a/packages/expo-native/ios/ClerkNativeViewHost.swift b/packages/expo-native-components/ios/ClerkNativeViewHost.swift similarity index 100% rename from packages/expo-native/ios/ClerkNativeViewHost.swift rename to packages/expo-native-components/ios/ClerkNativeViewHost.swift diff --git a/packages/expo-native/ios/ClerkUserButtonNativeView.swift b/packages/expo-native-components/ios/ClerkUserButtonNativeView.swift similarity index 100% rename from packages/expo-native/ios/ClerkUserButtonNativeView.swift rename to packages/expo-native-components/ios/ClerkUserButtonNativeView.swift diff --git a/packages/expo-native/ios/ClerkUserProfileNativeView.swift b/packages/expo-native-components/ios/ClerkUserProfileNativeView.swift similarity index 100% rename from packages/expo-native/ios/ClerkUserProfileNativeView.swift rename to packages/expo-native-components/ios/ClerkUserProfileNativeView.swift diff --git a/packages/expo-native/ios/Tests/ClerkAuthNativeViewPaperTests.swift b/packages/expo-native-components/ios/Tests/ClerkAuthNativeViewPaperTests.swift similarity index 100% rename from packages/expo-native/ios/Tests/ClerkAuthNativeViewPaperTests.swift rename to packages/expo-native-components/ios/Tests/ClerkAuthNativeViewPaperTests.swift diff --git a/packages/expo-native/ios/Tests/ClerkNativeBridgeTests.swift b/packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift similarity index 100% rename from packages/expo-native/ios/Tests/ClerkNativeBridgeTests.swift rename to packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift diff --git a/packages/expo-native/ios/Tests/ClerkNativeViewHostTests.swift b/packages/expo-native-components/ios/Tests/ClerkNativeViewHostTests.swift similarity index 100% rename from packages/expo-native/ios/Tests/ClerkNativeViewHostTests.swift rename to packages/expo-native-components/ios/Tests/ClerkNativeViewHostTests.swift diff --git a/packages/expo-native/ios/Tests/ClerkUserProfileCustomPageStateTests.swift b/packages/expo-native-components/ios/Tests/ClerkUserProfileCustomPageStateTests.swift similarity index 100% rename from packages/expo-native/ios/Tests/ClerkUserProfileCustomPageStateTests.swift rename to packages/expo-native-components/ios/Tests/ClerkUserProfileCustomPageStateTests.swift diff --git a/packages/expo-native/package.json b/packages/expo-native-components/package.json similarity index 95% rename from packages/expo-native/package.json rename to packages/expo-native-components/package.json index f732b7297da..0d6f430f012 100644 --- a/packages/expo-native/package.json +++ b/packages/expo-native-components/package.json @@ -1,5 +1,5 @@ { - "name": "@clerk/expo-native", + "name": "@clerk/expo-native-components", "version": "0.0.1", "description": "Native Clerk components and native SDK integration for Expo, powered by clerk-ios and clerk-android", "keywords": [ @@ -18,7 +18,7 @@ "repository": { "type": "git", "url": "git+https://github.com/clerk/javascript.git", - "directory": "packages/expo-native" + "directory": "packages/expo-native-components" }, "license": "MIT", "author": "Clerk", diff --git a/packages/expo-native/react-native.config.js b/packages/expo-native-components/react-native.config.js similarity index 100% rename from packages/expo-native/react-native.config.js rename to packages/expo-native-components/react-native.config.js diff --git a/packages/expo-native/src/AuthView.tsx b/packages/expo-native-components/src/AuthView.tsx similarity index 92% rename from packages/expo-native/src/AuthView.tsx rename to packages/expo-native-components/src/AuthView.tsx index 7734de4dd86..99e1d395348 100644 --- a/packages/expo-native/src/AuthView.tsx +++ b/packages/expo-native-components/src/AuthView.tsx @@ -24,7 +24,7 @@ type AuthNativeEvent = NativeSyntheticEvent>; * * @example * ```tsx - * import { AuthView } from '@clerk/expo-native'; + * import { AuthView } from '@clerk/expo-native-components'; * import { useAuth } from '@clerk/expo'; * * export default function SignInScreen() { @@ -63,7 +63,7 @@ export function AuthView({ {!isNativeSupported ? 'Native AuthView is only available on iOS and Android' - : 'Native AuthView requires a development build with the @clerk/expo-native config plugin. Add "@clerk/expo-native" to your app.json plugins array and rebuild your native app.'} + : 'Native AuthView requires a development build with the @clerk/expo-native-components config plugin. Add "@clerk/expo-native-components" to your app.json plugins array and rebuild your native app.'} ); diff --git a/packages/expo-native/src/AuthView.types.ts b/packages/expo-native-components/src/AuthView.types.ts similarity index 100% rename from packages/expo-native/src/AuthView.types.ts rename to packages/expo-native-components/src/AuthView.types.ts diff --git a/packages/expo-native/src/EmbeddedNavigation.types.ts b/packages/expo-native-components/src/EmbeddedNavigation.types.ts similarity index 100% rename from packages/expo-native/src/EmbeddedNavigation.types.ts rename to packages/expo-native-components/src/EmbeddedNavigation.types.ts diff --git a/packages/expo-native/src/UserButton.tsx b/packages/expo-native-components/src/UserButton.tsx similarity index 98% rename from packages/expo-native/src/UserButton.tsx rename to packages/expo-native-components/src/UserButton.tsx index f3038fd4efc..fc2504781b2 100644 --- a/packages/expo-native/src/UserButton.tsx +++ b/packages/expo-native-components/src/UserButton.tsx @@ -47,7 +47,7 @@ export interface UserButtonProps { * * @example * ```tsx - * import { UserButton } from '@clerk/expo-native'; + * import { UserButton } from '@clerk/expo-native-components'; * * export default function Home() { * return ( diff --git a/packages/expo-native/src/UserProfileCustomPages.tsx b/packages/expo-native-components/src/UserProfileCustomPages.tsx similarity index 100% rename from packages/expo-native/src/UserProfileCustomPages.tsx rename to packages/expo-native-components/src/UserProfileCustomPages.tsx diff --git a/packages/expo-native/src/UserProfileView.tsx b/packages/expo-native-components/src/UserProfileView.tsx similarity index 96% rename from packages/expo-native/src/UserProfileView.tsx rename to packages/expo-native-components/src/UserProfileView.tsx index 32aa4b5a6a9..eb3e59bee96 100644 --- a/packages/expo-native/src/UserProfileView.tsx +++ b/packages/expo-native-components/src/UserProfileView.tsx @@ -75,7 +75,7 @@ export interface UserProfileViewProps extends EmbeddedNavigationProps { * * @example * ```tsx - * import { UserProfileView } from '@clerk/expo-native'; + * import { UserProfileView } from '@clerk/expo-native-components'; * import { useAuth } from '@clerk/expo'; * * export default function ProfileScreen() { @@ -128,7 +128,7 @@ export function UserProfileView({ {!isNativeSupported ? 'Native UserProfileView is only available on iOS and Android' - : 'Native UserProfileView requires a development build with the @clerk/expo-native config plugin. Add "@clerk/expo-native" to your app.json plugins array and rebuild your native app.'} + : 'Native UserProfileView requires a development build with the @clerk/expo-native-components config plugin. Add "@clerk/expo-native-components" to your app.json plugins array and rebuild your native app.'} ); diff --git a/packages/expo-native/src/__tests__/AuthView.test.tsx b/packages/expo-native-components/src/__tests__/AuthView.test.tsx similarity index 100% rename from packages/expo-native/src/__tests__/AuthView.test.tsx rename to packages/expo-native-components/src/__tests__/AuthView.test.tsx diff --git a/packages/expo-native/src/__tests__/UserButton.test.tsx b/packages/expo-native-components/src/__tests__/UserButton.test.tsx similarity index 100% rename from packages/expo-native/src/__tests__/UserButton.test.tsx rename to packages/expo-native-components/src/__tests__/UserButton.test.tsx diff --git a/packages/expo-native/src/__tests__/UserProfileCustomPages.test.tsx b/packages/expo-native-components/src/__tests__/UserProfileCustomPages.test.tsx similarity index 100% rename from packages/expo-native/src/__tests__/UserProfileCustomPages.test.tsx rename to packages/expo-native-components/src/__tests__/UserProfileCustomPages.test.tsx diff --git a/packages/expo-native/src/__tests__/UserProfileView.test.tsx b/packages/expo-native-components/src/__tests__/UserProfileView.test.tsx similarity index 100% rename from packages/expo-native/src/__tests__/UserProfileView.test.tsx rename to packages/expo-native-components/src/__tests__/UserProfileView.test.tsx diff --git a/packages/expo-native/src/__tests__/appPlugin.test.js b/packages/expo-native-components/src/__tests__/appPlugin.test.js similarity index 80% rename from packages/expo-native/src/__tests__/appPlugin.test.js rename to packages/expo-native-components/src/__tests__/appPlugin.test.js index bb611cb434b..bbb2d247250 100644 --- a/packages/expo-native/src/__tests__/appPlugin.test.js +++ b/packages/expo-native-components/src/__tests__/appPlugin.test.js @@ -5,12 +5,12 @@ import path from 'node:path'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export -const { withClerkExpoNative } = require('../../app.plugin.js')._testing; +const { withClerkExpoNativeComponents } = require('../../app.plugin.js')._testing; const applyMod = (config, platform, mod, modResults) => config.mods[platform][mod]({ ...config, modRequest: {}, modResults }); -describe('withClerkExpoNative', () => { +describe('withClerkExpoNativeComponents', () => { let clerkExpoDir; beforeEach(() => { @@ -24,7 +24,7 @@ describe('withClerkExpoNative', () => { }); test('reports the installed @clerk/expo version on iOS and Android', async () => { - const config = withClerkExpoNative({ name: 'test', slug: 'test' }, {}, () => ({ + const config = withClerkExpoNativeComponents({ name: 'test', slug: 'test' }, {}, () => ({ dir: clerkExpoDir, version: '4.8.0', })); @@ -44,7 +44,10 @@ describe('withClerkExpoNative', () => { fs.writeFileSync(path.join(clerkExpoDir, 'expo-module.config.json'), '{}'); expect(() => - withClerkExpoNative({ name: 'test', slug: 'test' }, {}, () => ({ dir: clerkExpoDir, version: '4.7.1' })), + withClerkExpoNativeComponents({ name: 'test', slug: 'test' }, {}, () => ({ + dir: clerkExpoDir, + version: '4.7.1', + })), ).toThrow('@clerk/expo@4.7.1 still bundles the Clerk native module'); }); }); diff --git a/packages/expo-native/src/__tests__/appPlugin.theme.test.js b/packages/expo-native-components/src/__tests__/appPlugin.theme.test.js similarity index 100% rename from packages/expo-native/src/__tests__/appPlugin.theme.test.js rename to packages/expo-native-components/src/__tests__/appPlugin.theme.test.js diff --git a/packages/expo-native/src/__tests__/useAuthViewState.test.tsx b/packages/expo-native-components/src/__tests__/useAuthViewState.test.tsx similarity index 100% rename from packages/expo-native/src/__tests__/useAuthViewState.test.tsx rename to packages/expo-native-components/src/__tests__/useAuthViewState.test.tsx diff --git a/packages/expo-native/src/index.ts b/packages/expo-native-components/src/index.ts similarity index 87% rename from packages/expo-native/src/index.ts rename to packages/expo-native-components/src/index.ts index a20538f6186..c102a43da33 100644 --- a/packages/expo-native/src/index.ts +++ b/packages/expo-native-components/src/index.ts @@ -7,12 +7,12 @@ * * ## Installation * - * Native components require the `@clerk/expo-native` config plugin in your `app.json`, alongside `@clerk/expo`: + * Native components require the `@clerk/expo-native-components` config plugin in your `app.json`, alongside `@clerk/expo`: * * ```json * { * "expo": { - * "plugins": ["@clerk/expo", "@clerk/expo-native"] + * "plugins": ["@clerk/expo", "@clerk/expo-native-components"] * } * } * ``` @@ -25,7 +25,7 @@ * - {@link UserProfileView} - User profile and account management, renders inline * - {@link UserButton} - Avatar button that opens the native user profile * - * @module @clerk/expo-native + * @module @clerk/expo-native-components */ export { AuthView } from './AuthView'; diff --git a/packages/expo-native/src/specs/NativeClerkAuthView.android.ts b/packages/expo-native-components/src/specs/NativeClerkAuthView.android.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkAuthView.android.ts rename to packages/expo-native-components/src/specs/NativeClerkAuthView.android.ts diff --git a/packages/expo-native/src/specs/NativeClerkAuthView.ts b/packages/expo-native-components/src/specs/NativeClerkAuthView.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkAuthView.ts rename to packages/expo-native-components/src/specs/NativeClerkAuthView.ts diff --git a/packages/expo-native/src/specs/NativeClerkModule.ts b/packages/expo-native-components/src/specs/NativeClerkModule.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkModule.ts rename to packages/expo-native-components/src/specs/NativeClerkModule.ts diff --git a/packages/expo-native/src/specs/NativeClerkModule.types.ts b/packages/expo-native-components/src/specs/NativeClerkModule.types.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkModule.types.ts rename to packages/expo-native-components/src/specs/NativeClerkModule.types.ts diff --git a/packages/expo-native/src/specs/NativeClerkModule.web.ts b/packages/expo-native-components/src/specs/NativeClerkModule.web.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkModule.web.ts rename to packages/expo-native-components/src/specs/NativeClerkModule.web.ts diff --git a/packages/expo-native/src/specs/NativeClerkUserButtonView.android.ts b/packages/expo-native-components/src/specs/NativeClerkUserButtonView.android.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkUserButtonView.android.ts rename to packages/expo-native-components/src/specs/NativeClerkUserButtonView.android.ts diff --git a/packages/expo-native/src/specs/NativeClerkUserButtonView.ts b/packages/expo-native-components/src/specs/NativeClerkUserButtonView.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkUserButtonView.ts rename to packages/expo-native-components/src/specs/NativeClerkUserButtonView.ts diff --git a/packages/expo-native/src/specs/NativeClerkUserProfileView.android.ts b/packages/expo-native-components/src/specs/NativeClerkUserProfileView.android.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkUserProfileView.android.ts rename to packages/expo-native-components/src/specs/NativeClerkUserProfileView.android.ts diff --git a/packages/expo-native/src/specs/NativeClerkUserProfileView.ts b/packages/expo-native-components/src/specs/NativeClerkUserProfileView.ts similarity index 100% rename from packages/expo-native/src/specs/NativeClerkUserProfileView.ts rename to packages/expo-native-components/src/specs/NativeClerkUserProfileView.ts diff --git a/packages/expo-native/src/specs/__tests__/native-view.web.test.ts b/packages/expo-native-components/src/specs/__tests__/native-view.web.test.ts similarity index 100% rename from packages/expo-native/src/specs/__tests__/native-view.web.test.ts rename to packages/expo-native-components/src/specs/__tests__/native-view.web.test.ts diff --git a/packages/expo-native/src/useAuthViewState.ts b/packages/expo-native-components/src/useAuthViewState.ts similarity index 100% rename from packages/expo-native/src/useAuthViewState.ts rename to packages/expo-native-components/src/useAuthViewState.ts diff --git a/packages/expo-native/src/utils/native-module.ts b/packages/expo-native-components/src/utils/native-module.ts similarity index 100% rename from packages/expo-native/src/utils/native-module.ts rename to packages/expo-native-components/src/utils/native-module.ts diff --git a/packages/expo-native/tsconfig.declarations.json b/packages/expo-native-components/tsconfig.declarations.json similarity index 100% rename from packages/expo-native/tsconfig.declarations.json rename to packages/expo-native-components/tsconfig.declarations.json diff --git a/packages/expo-native/tsconfig.json b/packages/expo-native-components/tsconfig.json similarity index 100% rename from packages/expo-native/tsconfig.json rename to packages/expo-native-components/tsconfig.json diff --git a/packages/expo-native/tsconfig.test.json b/packages/expo-native-components/tsconfig.test.json similarity index 100% rename from packages/expo-native/tsconfig.test.json rename to packages/expo-native-components/tsconfig.test.json diff --git a/packages/expo-native/tsdown.config.mts b/packages/expo-native-components/tsdown.config.mts similarity index 100% rename from packages/expo-native/tsdown.config.mts rename to packages/expo-native-components/tsdown.config.mts diff --git a/packages/expo-native/vitest.config.mts b/packages/expo-native-components/vitest.config.mts similarity index 100% rename from packages/expo-native/vitest.config.mts rename to packages/expo-native-components/vitest.config.mts diff --git a/packages/expo-native/vitest.setup.mts b/packages/expo-native-components/vitest.setup.mts similarity index 100% rename from packages/expo-native/vitest.setup.mts rename to packages/expo-native-components/vitest.setup.mts diff --git a/packages/expo-native/app.plugin.d.ts b/packages/expo-native/app.plugin.d.ts deleted file mode 100644 index 76cfe1c1acd..00000000000 --- a/packages/expo-native/app.plugin.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ConfigPlugin } from '@expo/config-plugins'; - -declare const withClerkExpoNative: ConfigPlugin<{ keychainService?: string; theme?: string } | void>; - -export = withClerkExpoNative; diff --git a/packages/expo/app.plugin.js b/packages/expo/app.plugin.js index 29c276c2067..ecba866b513 100644 --- a/packages/expo/app.plugin.js +++ b/packages/expo/app.plugin.js @@ -4,11 +4,11 @@ * When this plugin is used: * 1. Android registers the hosted auth callback intent filter * 2. iOS gets the Sign in with Apple entitlement and, when configured, the Face ID usage description - * 3. If @clerk/expo-native is installed, its config plugin is applied for the Clerk native SDKs + * 3. If @clerk/expo-native-components is installed, its config plugin is applied for the Clerk native SDKs */ const { AndroidConfig, withAndroidManifest, withEntitlementsPlist, withInfoPlist } = require('@expo/config-plugins'); -const CLERK_EXPO_NATIVE = '@clerk/expo-native'; +const CLERK_EXPO_NATIVE = '@clerk/expo-native-components'; const CLERK_EXPO_NATIVE_OPTIONS = ['keychainService', 'theme']; const addHostedAuthIntentFilter = (mainActivity, packageName) => { @@ -104,10 +104,10 @@ const getListedPluginProps = (config, name) => { }; /** - * Apply the @clerk/expo-native config plugin when it is installed, so apps that only list + * Apply the @clerk/expo-native-components config plugin when it is installed, so apps that only list * "@clerk/expo" keep the iOS deployment target and native SDK configuration they need. */ -const withClerkExpoNative = (config, props = {}, resolvePlugin = resolveClerkExpoNativePlugin) => { +const withClerkExpoNativeComponents = (config, props = {}, resolvePlugin = resolveClerkExpoNativePlugin) => { const nativeProps = Object.fromEntries( CLERK_EXPO_NATIVE_OPTIONS.filter(option => props[option] !== undefined).map(option => [option, props[option]]), ); @@ -144,13 +144,13 @@ const withClerkExpo = (config, props = {}) => { } config = withClerkHostedAuthCallback(config); config = withClerkFaceIDPermission(config, props); - config = withClerkExpoNative(config, props); + config = withClerkExpoNativeComponents(config, props); return config; }; module.exports = withClerkExpo; module.exports._testing = { addHostedAuthIntentFilter, - withClerkExpoNative, + withClerkExpoNativeComponents, withClerkFaceIDPermission, }; diff --git a/packages/expo/native/index.d.ts b/packages/expo/native/index.d.ts index 0c04017d415..9d8fb781ea9 100644 --- a/packages/expo/native/index.d.ts +++ b/packages/expo/native/index.d.ts @@ -1 +1 @@ -export * from '@clerk/expo-native'; +export * from '@clerk/expo-native-components'; diff --git a/packages/expo/package.json b/packages/expo/package.json index 46deb108a47..095cc4ddba5 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -122,7 +122,7 @@ }, "devDependencies": { "@clerk/expo-google-signin": "workspace:*", - "@clerk/expo-native": "workspace:*", + "@clerk/expo-native-components": "workspace:*", "@clerk/expo-passkeys": "workspace:*", "@expo/config-plugins": "^54.0.4", "@types/base-64": "^1.0.2", @@ -138,7 +138,7 @@ }, "peerDependencies": { "@clerk/expo-google-signin": ">=0.1.0", - "@clerk/expo-native": ">=0.1.0", + "@clerk/expo-native-components": ">=0.1.0", "@clerk/expo-passkeys": ">=0.0.6", "expo": "catalog:peer-expo", "expo-apple-authentication": ">=7.0.0", @@ -156,7 +156,7 @@ "@clerk/expo-google-signin": { "optional": true }, - "@clerk/expo-native": { + "@clerk/expo-native-components": { "optional": true }, "@clerk/expo-passkeys": { diff --git a/packages/expo/src/__tests__/appPlugin.test.js b/packages/expo/src/__tests__/appPlugin.test.js index 78e46629533..55f9e5abdc3 100644 --- a/packages/expo/src/__tests__/appPlugin.test.js +++ b/packages/expo/src/__tests__/appPlugin.test.js @@ -1,7 +1,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export -const { withClerkExpoNative, withClerkFaceIDPermission } = require('../../app.plugin.js')._testing; +const { withClerkExpoNativeComponents, withClerkFaceIDPermission } = require('../../app.plugin.js')._testing; function applyInfoPlistMod(config, modResults) { return config.mods.ios.infoPlist({ @@ -50,16 +50,16 @@ describe('withClerkFaceIDPermission', () => { }); }); -describe('withClerkExpoNative', () => { +describe('withClerkExpoNativeComponents', () => { afterEach(() => { vi.restoreAllMocks(); }); - test('applies the @clerk/expo-native plugin with its options when it is installed', () => { + test('applies the @clerk/expo-native-components plugin with its options when it is installed', () => { const nativePlugin = vi.fn(config => ({ ...config, applied: true })); const config = { name: 'test', slug: 'test' }; - const result = withClerkExpoNative( + const result = withClerkExpoNativeComponents( config, { keychainService: 'com.example.shared', theme: './theme.json', appleSignIn: false }, () => nativePlugin, @@ -69,15 +69,19 @@ describe('withClerkExpoNative', () => { expect(nativePlugin).toHaveBeenCalledWith(config, { keychainService: 'com.example.shared', theme: './theme.json' }); }); - test('prefers options passed to an explicitly listed @clerk/expo-native plugin', () => { + test('prefers options passed to an explicitly listed @clerk/expo-native-components plugin', () => { const nativePlugin = vi.fn(config => config); const config = { name: 'test', slug: 'test', - plugins: ['@clerk/expo', ['@clerk/expo-native', { theme: './native-theme.json' }]], + plugins: ['@clerk/expo', ['@clerk/expo-native-components', { theme: './native-theme.json' }]], }; - withClerkExpoNative(config, { keychainService: 'com.example.shared', theme: './theme.json' }, () => nativePlugin); + withClerkExpoNativeComponents( + config, + { keychainService: 'com.example.shared', theme: './theme.json' }, + () => nativePlugin, + ); expect(nativePlugin).toHaveBeenCalledWith(config, { keychainService: 'com.example.shared', @@ -85,36 +89,40 @@ describe('withClerkExpoNative', () => { }); }); - test('does not apply the @clerk/expo-native plugin twice', () => { + test('does not apply the @clerk/expo-native-components plugin twice', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const nativePlugin = vi.fn(config => config); const config = { name: 'test', slug: 'test', - _internal: { pluginHistory: { '@clerk/expo-native': { name: '@clerk/expo-native', version: '0.0.1' } } }, + _internal: { + pluginHistory: { '@clerk/expo-native-components': { name: '@clerk/expo-native-components', version: '0.0.1' } }, + }, }; - expect(withClerkExpoNative(config, {}, () => nativePlugin)).toBe(config); + expect(withClerkExpoNativeComponents(config, {}, () => nativePlugin)).toBe(config); expect(nativePlugin).not.toHaveBeenCalled(); expect(warn).not.toHaveBeenCalled(); - withClerkExpoNative(config, { theme: './theme.json' }, () => nativePlugin); - expect(warn).toHaveBeenCalledWith(expect.stringContaining('Pass them to the "@clerk/expo-native" plugin instead')); + withClerkExpoNativeComponents(config, { theme: './theme.json' }, () => nativePlugin); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('Pass them to the "@clerk/expo-native-components" plugin instead'), + ); }); - test('leaves the config untouched when @clerk/expo-native is not installed', () => { + test('leaves the config untouched when @clerk/expo-native-components is not installed', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const config = { name: 'test', slug: 'test' }; - expect(withClerkExpoNative(config, {}, () => null)).toBe(config); + expect(withClerkExpoNativeComponents(config, {}, () => null)).toBe(config); expect(warn).not.toHaveBeenCalled(); }); - test('warns with install instructions when native-only options are passed without @clerk/expo-native', () => { + test('warns with install instructions when native-only options are passed without @clerk/expo-native-components', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); const config = { name: 'test', slug: 'test' }; - expect(withClerkExpoNative(config, { keychainService: 'com.example.shared' }, () => null)).toBe(config); - expect(warn).toHaveBeenCalledWith(expect.stringContaining('npx expo install @clerk/expo-native')); + expect(withClerkExpoNativeComponents(config, { keychainService: 'com.example.shared' }, () => null)).toBe(config); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('npx expo install @clerk/expo-native-components')); }); }); diff --git a/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts b/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts index 1b7a7c46c5d..0c15d195c86 100644 --- a/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts +++ b/packages/expo/src/biometric-credentials/__tests__/useBiometricCredentials.test.ts @@ -542,12 +542,12 @@ describe('useBiometricCredentials on iOS', () => { }); }); - test('explains how to install @clerk/expo-native when the native module is missing', async () => { + test('explains how to install @clerk/expo-native-components when the native module is missing', async () => { mocks.isNativeModuleInstalled = false; try { await expect(renderBiometricCredentials().signIn()).rejects.toThrow( - 'Biometric credentials require the @clerk/expo-native package in a development build. Install it with `npx expo install @clerk/expo-native`', + 'Biometric credentials require the @clerk/expo-native-components package in a development build. Install it with `npx expo install @clerk/expo-native-components`', ); } finally { mocks.isNativeModuleInstalled = true; @@ -560,7 +560,7 @@ describe('useBiometricCredentials on iOS', () => { try { await expect(renderBiometricCredentials().signIn()).rejects.toThrow( - 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native.', + 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native-components.', ); } finally { Object.assign(mocks.nativeModule, { signInWithTrustedDevice }); diff --git a/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts b/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts index 49f611363f1..65af8878503 100644 --- a/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts +++ b/packages/expo/src/biometric-credentials/useBiometricCredentials.shared.ts @@ -15,7 +15,7 @@ import type { const DEFAULT_POLICY = 'biometry_current_set'; const CLERK_EXPO_NATIVE_INSTALL_INSTRUCTIONS = - 'Install it with `npx expo install @clerk/expo-native`, add "@clerk/expo-native" to the plugins array in your app config, then rebuild your development build.'; + 'Install it with `npx expo install @clerk/expo-native-components`, add "@clerk/expo-native-components" to the plugins array in your app config, then rebuild your development build.'; function toBiometricCredentialPlatform(platform: string): BiometricCredentialPlatform { return platform === 'ios' || platform === 'android' ? platform : 'unknown'; @@ -30,7 +30,7 @@ function getNativeModule(): NativeBiometricCredentialModule { if (!nativeModule) { return errorThrower.throw( - `Biometric credentials require the @clerk/expo-native package in a development build. ${CLERK_EXPO_NATIVE_INSTALL_INSTRUCTIONS}`, + `Biometric credentials require the @clerk/expo-native-components package in a development build. ${CLERK_EXPO_NATIVE_INSTALL_INSTRUCTIONS}`, ); } @@ -42,7 +42,7 @@ function getNativeModule(): NativeBiometricCredentialModule { !nativeModule.signInWithTrustedDevice ) { return errorThrower.throw( - 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native.', + 'Biometric credentials require a development build containing a compatible version of @clerk/expo-native-components.', ); } @@ -95,7 +95,7 @@ function createBiometricCredentials(clerk: ReturnType): UseBiom const nativeModule = getNativeModule(); if (typeof nativeModule.reverifyWithBiometrics !== 'function') { return errorThrower.throw( - 'Biometric reverification requires a development build containing a compatible version of @clerk/expo-native.', + 'Biometric reverification requires a development build containing a compatible version of @clerk/expo-native-components.', ); } const level = params?.level ?? 'first_factor'; diff --git a/packages/expo/src/native/__tests__/index.test.ts b/packages/expo/src/native/__tests__/index.test.ts index 6d936310043..f7904bb2a8b 100644 --- a/packages/expo/src/native/__tests__/index.test.ts +++ b/packages/expo/src/native/__tests__/index.test.ts @@ -9,7 +9,7 @@ describe('@clerk/expo/native', () => { vi.restoreAllMocks(); }); - describe('when @clerk/expo-native is not installed', () => { + describe('when @clerk/expo-native-components is not installed', () => { beforeEach(() => { vi.doMock('../loadClerkExpoNative', () => ({ loadClerkExpoNative: () => null })); }); @@ -20,7 +20,7 @@ describe('@clerk/expo/native', () => { const Component = (await importNativeEntry())[name] as (props: object) => unknown; expect(() => Component({})).toThrow( - /`.+` is unavailable\. Native components have moved to the @clerk\/expo-native package\. Install it with `npx expo install @clerk\/expo-native`/, + /`.+` is unavailable\. Native components have moved to the @clerk\/expo-native-components package\. Install it with `npx expo install @clerk\/expo-native-components`/, ); }, ); @@ -31,12 +31,12 @@ describe('@clerk/expo/native', () => { const hook = (await importNativeEntry())[name] as () => unknown; expect(() => hook()).toThrow(`\`${name}\` is unavailable.`); - expect(() => hook()).toThrow('add "@clerk/expo-native" to the plugins array in your app config'); + expect(() => hook()).toThrow('add "@clerk/expo-native-components" to the plugins array in your app config'); }, ); }); - test('re-exports @clerk/expo-native when it is installed', async () => { + test('re-exports @clerk/expo-native-components when it is installed', async () => { const clerkExpoNative = { AuthView: vi.fn(() => null), UserButton: vi.fn(() => null), diff --git a/packages/expo/src/native/index.ts b/packages/expo/src/native/index.ts index 34cc5902993..67acaa9ab68 100644 --- a/packages/expo/src/native/index.ts +++ b/packages/expo/src/native/index.ts @@ -2,12 +2,12 @@ import { errorThrower } from '../errorThrower'; import type { ClerkExpoNativeModule } from './loadClerkExpoNative'; import { loadClerkExpoNative } from './loadClerkExpoNative'; -// Public types are declared in native/index.d.ts, which re-exports them from @clerk/expo-native. +// Public types are declared in native/index.d.ts, which re-exports them from @clerk/expo-native-components. const CLERK_EXPO_NATIVE_MISSING_MESSAGE = - 'Native components have moved to the @clerk/expo-native package. ' + - 'Install it with `npx expo install @clerk/expo-native`, add "@clerk/expo-native" to the plugins array in your app config, ' + - 'then rebuild your native app. You can then import them from "@clerk/expo-native".'; + 'Native components have moved to the @clerk/expo-native-components package. ' + + 'Install it with `npx expo install @clerk/expo-native-components`, add "@clerk/expo-native-components" to the plugins array in your app config, ' + + 'then rebuild your native app. You can then import them from "@clerk/expo-native-components".'; const clerkExpoNative = loadClerkExpoNative(); diff --git a/packages/expo/src/native/loadClerkExpoNative.ts b/packages/expo/src/native/loadClerkExpoNative.ts index 06f8e66b9dc..194b0bed1d9 100644 --- a/packages/expo/src/native/loadClerkExpoNative.ts +++ b/packages/expo/src/native/loadClerkExpoNative.ts @@ -9,9 +9,9 @@ export type ClerkExpoNativeModule = Partial< export function loadClerkExpoNative(): ClerkExpoNativeModule | null { try { - // Synchronous require() in try/catch so Metro treats @clerk/expo-native as an optional dependency. + // Synchronous require() in try/catch so Metro treats @clerk/expo-native-components as an optional dependency. // eslint-disable-next-line @typescript-eslint/no-require-imports - return require('@clerk/expo-native') as ClerkExpoNativeModule; + return require('@clerk/expo-native-components') as ClerkExpoNativeModule; } catch { return null; } diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 99adbcd6e38..5cdb87dc6ae 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -1163,7 +1163,7 @@ export function useNativeClientBootstrap({ if (__DEV__) { console.debug( `[ClerkProvider] Native Clerk module not available. ` + - `To enable native features, install @clerk/expo-native and add "@clerk/expo-native" to your app.json plugins array.`, + `To enable native features, install @clerk/expo-native-components and add "@clerk/expo-native-components" to your app.json plugins array.`, ); } } else if (__DEV__) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b78f04fdd7..d37b3a454fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -662,9 +662,9 @@ importers: '@clerk/expo-google-signin': specifier: workspace:* version: link:../expo-google-signin - '@clerk/expo-native': + '@clerk/expo-native-components': specifier: workspace:* - version: link:../expo-native + version: link:../expo-native-components '@clerk/expo-passkeys': specifier: workspace:* version: link:../expo-passkeys @@ -711,7 +711,7 @@ importers: specifier: ~54.0.36 version: 54.0.36(@babel/core@7.29.7)(bufferutil@4.1.0)(graphql@16.14.1)(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10) - packages/expo-native: + packages/expo-native-components: dependencies: '@clerk/react': specifier: workspace:^