From 9d4396e245627f510ca52282f02038b1f89cabc5 Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Thu, 5 Mar 2026 21:21:40 +0530 Subject: [PATCH 01/13] [docs] Add cross-links between llms-*.txt files (#43599) Co-authored-by: Aman Mittal <10234615+amandeepmittal@users.noreply.github.com> --- docs/scripts/generate-llms/llms-eas-txt.js | 7 ++++- docs/scripts/generate-llms/llms-full-txt.js | 7 ++++- docs/scripts/generate-llms/llms-sdk.js | 7 ++++- docs/scripts/generate-llms/llms-txt.js | 6 +++- docs/scripts/generate-llms/shared.js | 35 ++++++++++++++++++++- 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/docs/scripts/generate-llms/llms-eas-txt.js b/docs/scripts/generate-llms/llms-eas-txt.js index a7cb8566f4411d..547ed40a0590c0 100644 --- a/docs/scripts/generate-llms/llms-eas-txt.js +++ b/docs/scripts/generate-llms/llms-eas-txt.js @@ -23,7 +23,12 @@ function generateFullMarkdown({ title, description }) { const markdownPaths = allHrefs.map(href => getMarkdownPathFromHref(buildDir, href)); const contentChunks = readUniqueMarkdownContent(markdownPaths, { warnOnMissing: true }); - return composeMarkdownDocument({ title, description, contentChunks }); + return composeMarkdownDocument({ + title, + description, + contentChunks, + currentFilename: OUTPUT_FILENAME_EAS_DOCS, + }); } export async function generateLlmsEasTxt() { diff --git a/docs/scripts/generate-llms/llms-full-txt.js b/docs/scripts/generate-llms/llms-full-txt.js index 5a5148ee4750fd..f6324f327d38b0 100644 --- a/docs/scripts/generate-llms/llms-full-txt.js +++ b/docs/scripts/generate-llms/llms-full-txt.js @@ -23,7 +23,12 @@ function generateFullMarkdown({ title, description }) { const markdownPaths = allHrefs.map(href => getMarkdownPathFromHref(buildDir, href)); const contentChunks = readUniqueMarkdownContent(markdownPaths, { warnOnMissing: true }); - return composeMarkdownDocument({ title, description, contentChunks }); + return composeMarkdownDocument({ + title, + description, + contentChunks, + currentFilename: OUTPUT_FILENAME_EXPO_DOCS, + }); } export async function generateLlmsFullTxt() { diff --git a/docs/scripts/generate-llms/llms-sdk.js b/docs/scripts/generate-llms/llms-sdk.js index 9da93e019afd81..2a7db23e927abc 100644 --- a/docs/scripts/generate-llms/llms-sdk.js +++ b/docs/scripts/generate-llms/llms-sdk.js @@ -31,7 +31,12 @@ function generateFullMarkdown() { }); const contentChunks = readUniqueMarkdownContent(matchingFiles); - return composeMarkdownDocument({ title: TITLE, description: DESCRIPTION, contentChunks }); + return composeMarkdownDocument({ + title: TITLE, + description: DESCRIPTION, + contentChunks, + currentFilename: OUTPUT_FILENAME, + }); } export async function generateLlmsSdkTxt() { diff --git a/docs/scripts/generate-llms/llms-txt.js b/docs/scripts/generate-llms/llms-txt.js index 0a2ae7d6af06dc..5ec123efe9798e 100644 --- a/docs/scripts/generate-llms/llms-txt.js +++ b/docs/scripts/generate-llms/llms-txt.js @@ -3,6 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import ts from 'typescript'; +import { generateCrossLinksSection } from './shared.js'; import { home, learn, general, eas, reference } from '../../constants/navigation.js'; const OUTPUT_DIRECTORY_NAME = 'public'; @@ -53,7 +54,10 @@ function generateFullMarkdown({ title, description, sections }) { }); return ( - `# ${title}\n\n${description}\n\n` + filteredSections.map(generateSectionMarkdown).join('') + `# ${title}\n\n${description}\n\n` + + filteredSections.map(generateSectionMarkdown).join('') + + '\n' + + generateCrossLinksSection(OUTPUT_FILENAME_LLMS_TXT) ); } diff --git a/docs/scripts/generate-llms/shared.js b/docs/scripts/generate-llms/shared.js index 60b8d994812ab2..cc499dd7525173 100644 --- a/docs/scripts/generate-llms/shared.js +++ b/docs/scripts/generate-llms/shared.js @@ -3,6 +3,24 @@ import path from 'node:path'; export const OUTPUT_DIRECTORY_NAME = 'public'; export const BUILD_OUTPUT_DIR = 'out'; +export const DOCS_BASE_URL = 'https://docs.expo.dev'; + +const LLMS_FILES = [ + { filename: 'llms.txt', description: 'A list of all available documentation files' }, + { + filename: 'llms-full.txt', + description: + 'Complete documentation for Expo, including Expo Router, Expo Modules API, development process, and more', + }, + { + filename: 'llms-eas.txt', + description: 'Complete documentation for Expo Application Services (EAS)', + }, + { + filename: 'llms-sdk.txt', + description: 'Complete documentation for the latest Expo SDK', + }, +]; const CONTENT_SEPARATOR = '\n\n---\n\n'; @@ -100,12 +118,27 @@ export function readUniqueMarkdownContent(markdownPaths, { warnOnMissing = false return contentChunks; } -export function composeMarkdownDocument({ title, description, contentChunks }) { +export function generateCrossLinksSection(currentFilename) { + const otherFiles = LLMS_FILES.filter(f => f.filename !== currentFilename); + let section = '## Other Expo documentation files\n\n'; + + for (const file of otherFiles) { + section += `- [/${file.filename}](${DOCS_BASE_URL}/${file.filename}): ${file.description}\n`; + } + + return section; +} + +export function composeMarkdownDocument({ title, description, contentChunks, currentFilename }) { let fullContent = `# ${title}\n\n${description}\n\n`; for (const content of contentChunks) { fullContent += content + CONTENT_SEPARATOR; } + if (currentFilename) { + fullContent += generateCrossLinksSection(currentFilename); + } + return fullContent; } From 8bd87c0e9efdb20407aa738c7435b518713cf819 Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Thu, 5 Mar 2026 21:22:14 +0530 Subject: [PATCH 02/13] [docs] Fix internal links in multiple pages (#43584) --- docs/pages/archive/classic-updates/offline-support.mdx | 2 +- docs/pages/archive/glossary.mdx | 4 ++-- .../sending-notifications-custom-fcm-legacy.mdx | 2 +- docs/pages/build-reference/troubleshooting.mdx | 2 +- docs/pages/build/updates.mdx | 4 ++-- .../develop/development-builds/share-with-your-team.mdx | 2 +- .../eas-update/integration-in-existing-native-apps.mdx | 2 +- docs/pages/eas/hosting/introduction.mdx | 2 +- docs/pages/faq.mdx | 6 +++--- docs/pages/guides/using-firebase.mdx | 2 +- docs/pages/guides/using-vexo.mdx | 2 +- docs/pages/guides/why-metro.mdx | 2 +- docs/pages/linking/into-other-apps.mdx | 2 +- docs/pages/router/advanced/modals.mdx | 2 +- docs/pages/router/advanced/redirects.mdx | 2 +- docs/pages/submit/android.mdx | 2 +- docs/pages/submit/ios.mdx | 2 +- docs/pages/troubleshooting/overview.mdx | 2 +- docs/pages/tutorial/eas/configure-development-build.mdx | 2 +- docs/pages/workflow/configuration.mdx | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/pages/archive/classic-updates/offline-support.mdx b/docs/pages/archive/classic-updates/offline-support.mdx index b08e37a97e0129..ec26a91958dc4e 100644 --- a/docs/pages/archive/classic-updates/offline-support.mdx +++ b/docs/pages/archive/classic-updates/offline-support.mdx @@ -14,7 +14,7 @@ To force JS updates to run in the background (rather than synchronously checking ## Cache your assets after downloading -By default, all of your assets (images, fonts, and so on) are [uploaded to Expo's CDN](/guides/assets/) when you publish updates to your app. Once they're downloaded, you can [cache them](/archive/classic-updates/preloading-and-caching-assets) so you don't need to download them a second time. If you publish changes, the cache will be invalidated and the changed version will be downloaded. +By default, all of your assets (images, fonts, and so on) are [uploaded to Expo's CDN](/develop/user-interface/assets/) when you publish updates to your app. Once they're downloaded, you can [cache them](/archive/classic-updates/preloading-and-caching-assets) so you don't need to download them a second time. If you publish changes, the cache will be invalidated and the changed version will be downloaded. ## Bundle your assets inside your standalone binary diff --git a/docs/pages/archive/glossary.mdx b/docs/pages/archive/glossary.mdx index c6a4d367ce2ace..c6a90a40191243 100644 --- a/docs/pages/archive/glossary.mdx +++ b/docs/pages/archive/glossary.mdx @@ -12,12 +12,12 @@ The term "detach" was previously used in Expo to mean [ejecting](#eject) your ap The term "eject" was popularized by [create-react-app](https://github.com/facebook/create-react-app), and it is used in Expo to describe leaving the cozy comfort of the standard Expo development environment, where you do not have to deal with build configuration or native code. When you "eject" from Expo, you have two choices: -- _Eject to bare workflow_, where you jump between [workflows](/archive/managed-vs-bare/) and move into the bare workflow, where you can continue to use Expo APIs but have access and full control over your native Android and iOS projects. +- _Eject to bare workflow_, where you jump between [workflows](/workflow/overview/) and move into the bare workflow, where you can continue to use Expo APIs but have access and full control over your native Android and iOS projects. - _Eject to ExpoKit_, where you get the native projects along with [ExpoKit](#expokit). This option is deprecated and support for ExpoKit was removed after SDK 38. ### ExpoKit -ExpoKit is an Objective-C and Java library that allows you to use the [Expo SDK](/more/glossary-of-terms/#expo-sdk) and platform and your existing Expo project as part of a larger standard native project — one that you would normally create using Xcode, Android Studio, or `react-native init`. For more information, see [Ejecting to ExpoKit](/archive/expokit/eject/). +ExpoKit is an Objective-C and Java library that allows you to use the [Expo SDK](/more/glossary-of-terms/#expo-sdk) and platform and your existing Expo project as part of a larger standard native project — one that you would normally create using Xcode, Android Studio, or `react-native init`. For more information, see [Ejecting to ExpoKit](#eject). **Support for ExpoKit ended after SDK 38. Expo modules can implement support for custom native configuration, and projects that need even more custom native code can [expose their Android Studio and Xcode projects with `npx expo prebuild`](/workflow/customizing/).** diff --git a/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx b/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx index 587521340125aa..459cf2507bda96 100644 --- a/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx +++ b/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx @@ -8,7 +8,7 @@ description: Learn how to send notifications with FCM legacy server. > **info** For documentation on communicating with the newer FCMv1 service, see [Send notifications with FCMv1 and APNs](/push-notifications/sending-notifications-custom/). This guide is based on [Google's documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref), and this section covers the basics to get you started. > -> Before sending a notification directly through FCM, you will need to [obtain a device token](/push-notifications/obtaining-a-device-token-for-fcm-or-apns). +> Before sending a notification directly through FCM, you will need to [obtain a device token](/push-notifications/sending-notifications-custom/). Communicating with FCM is done by sending a POST request. However, before sending or receiving any notifications, you'll need to follow the steps to [configure FCM](/push-notifications/push-notifications-setup/#android) to configure FCM and get your `FCM-SERVER-KEY`. diff --git a/docs/pages/build-reference/troubleshooting.mdx b/docs/pages/build-reference/troubleshooting.mdx index df03bc8e569e91..00d9e46128a167 100644 --- a/docs/pages/build-reference/troubleshooting.mdx +++ b/docs/pages/build-reference/troubleshooting.mdx @@ -46,7 +46,7 @@ For example, you might see something like this on your Android builds: ]} /> -While you may or may not be interested in following up on that warning, it is not the cause of your failed build. So how do you know which logs are truly responsible? If you are building a bare project, you will already be good at this. If you are building a [managed project](/archive/managed-vs-bare/), it may be tricky because you don't directly interact with the native code, only write JavaScript. +While you may or may not be interested in following up on that warning, it is not the cause of your failed build. So how do you know which logs are truly responsible? If you are building a bare project, you will already be good at this. If you are building a [managed project](/workflow/overview/), it may be tricky because you don't directly interact with the native code, only write JavaScript. A good path forward is to **determine if the build failed due to a native or JavaScript error**. When your build fails due to a JavaScript build error, you will usually see something like this: diff --git a/docs/pages/build/updates.mdx b/docs/pages/build/updates.mdx index 9b8ebb540bc782..55cbbf96f2a989 100644 --- a/docs/pages/build/updates.mdx +++ b/docs/pages/build/updates.mdx @@ -31,7 +31,7 @@ The following example demonstrates how you might use the `"production"` channel Your native runtime may change on each build, depending on whether you modify the code in a way that changes the API contract with JavaScript. If you publish a JavaScript bundle to a binary with an incompatible native runtime (for example, a function that the JavaScript bundle expects to exist does not exist) then your app may not work as expected, or it may crash. -We recommend using a different [runtime version](/distribution/runtime-versions/) for each binary version of your app. Any time you change the native runtime (in managed apps, this happens when you add or remove a native library, or modify **app.json**), you should increment the runtime version. +We recommend using a different [runtime version](/eas-update/runtime-versions/) for each binary version of your app. Any time you change the native runtime (in managed apps, this happens when you add or remove a native library, or modify **app.json**), you should increment the runtime version. ## Previewing updates in development builds @@ -39,4 +39,4 @@ Updates published with the `runtimeVersion` field can't be loaded in Expo Go. In ## Environment variables and `eas update` -Environment variables set on the `env` field in build profiles are not available when you run `eas update`. Learn more about using [environment variables with EAS Update](/eas-update/environment-variables). +Environment variables set on the `env` field in build profiles are not available when you run `eas update`. Learn more about using [environment variables with EAS Update](/eas/environment-variables/usage/#using-environment-variables-with-eas-update). diff --git a/docs/pages/develop/development-builds/share-with-your-team.mdx b/docs/pages/develop/development-builds/share-with-your-team.mdx index 3cbc9aa046ea15..0786decf05b97d 100644 --- a/docs/pages/develop/development-builds/share-with-your-team.mdx +++ b/docs/pages/develop/development-builds/share-with-your-team.mdx @@ -66,6 +66,6 @@ You can use `eas build:resign` to codesign an existing **.ipa** for iOS to a new diff --git a/docs/pages/eas-update/integration-in-existing-native-apps.mdx b/docs/pages/eas-update/integration-in-existing-native-apps.mdx index e90660ec809495..8b0c4fc01a6cc5 100644 --- a/docs/pages/eas-update/integration-in-existing-native-apps.mdx +++ b/docs/pages/eas-update/integration-in-existing-native-apps.mdx @@ -23,7 +23,7 @@ You should have a brownfield native project with React Native installed and conf - Your app must be using the [latest Expo SDK version and its supported React Native version](/versions/latest/#each-expo-sdk-version-depends-on-a-react-native-version). - Remove any other update library integration from your app, such as react-native-code-push, and ensure that your app compiles and runs successfully in both debug and release on your supported platforms. -- Support for Expo modules (through the `expo` package) must be installed and configured in your project. [Learn more](/brownfield/installing-expo-modules/). +- Support for Expo modules (through the `expo` package) must be installed and configured in your project. [Learn more](/brownfield/overview/). - Your **metro.config.js** [must extend `expo/metro-config`](/guides/customizing-metro/#customizing) . - Your **babel.config.js** [must extend `babel-preset-expo`](/versions/latest/config/babel/). - The command `npx expo export -p android` must run successfully in your project if it supports Android, and `npx expo export -p ios` if it supports iOS. diff --git a/docs/pages/eas/hosting/introduction.mdx b/docs/pages/eas/hosting/introduction.mdx index d01627940e007c..1f50f55e754c50 100644 --- a/docs/pages/eas/hosting/introduction.mdx +++ b/docs/pages/eas/hosting/introduction.mdx @@ -158,7 +158,7 @@ For more information, see the [Web deployments with EAS Workflows](/eas/hosting/ diff --git a/docs/pages/faq.mdx b/docs/pages/faq.mdx index c24618573bc314..14db9d749b6234 100644 --- a/docs/pages/faq.mdx +++ b/docs/pages/faq.mdx @@ -21,7 +21,7 @@ When Expo was first created, React Native had yet to be publicly released. This The Expo SDK is well-tested, written in TypeScript, documented, and built for Android, iOS, and the web. Every module in the Expo SDK works together to ensure versioning always matches. This creates a nice upgrading experience. -The Expo SDK is also written with the [Expo Modules API](/modules) to make contributing, maintaining, and understanding easier. +The Expo SDK is also written with the [Expo Modules API](/modules/overview/) to make contributing, maintaining, and understanding easier. ## What is the difference between Expo and React Native? @@ -43,7 +43,7 @@ Expo supports adding custom native code and customizing that native code (Androi ## Can I use Expo in the app that is created with React Native CLI? -Yes! All Expo tools and services work great in any React Native app. For example, you can use any part of the [Expo SDK](/versions/latest), [`expo-dev-client`](/develop/development-builds/installation/) and EAS Build, Submit, and Update — they work great! Learn more about [installing `expo` in your project](/bare/installing-expo-modules), [adopting prebuild](/guides/adopting-prebuild), and [setting up EAS Build](/build/introduction). +Yes! All Expo tools and services work great in any React Native app. For example, you can use any part of the [Expo SDK](/versions/latest), [`expo-dev-client`](/develop/development-builds/create-a-build/) and EAS Build, Submit, and Update — they work great! Learn more about [installing `expo` in your project](/bare/installing-expo-modules), [adopting prebuild](/guides/adopting-prebuild), and [setting up EAS Build](/build/introduction). ## How do I share my Expo project? Can I submit it to the app stores? @@ -69,7 +69,7 @@ If the `expo` package is included in your app, it only adds 1 MB one time to the ## Can I use Expo with my native library? -You can use native Android and iOS libraries with Expo by creating a [custom native module](/modules) with Swift and Kotlin. Many popular libraries already have custom native modules. Check out our [React Native directory](https://reactnative.directory) to find popular libraries for your use case. +You can use native Android and iOS libraries with Expo by creating a [custom native module](/modules/overview/) with Swift and Kotlin. Many popular libraries already have custom native modules. Check out our [React Native directory](https://reactnative.directory) to find popular libraries for your use case. ## Can I use Expo with this web library? diff --git a/docs/pages/guides/using-firebase.mdx b/docs/pages/guides/using-firebase.mdx index 2aafeb1cd3d95c..478937fc4b6ec3 100644 --- a/docs/pages/guides/using-firebase.mdx +++ b/docs/pages/guides/using-firebase.mdx @@ -181,7 +181,7 @@ React Native Firebase requires [custom native code and cannot be used with Expo Since React Native Firebase requires custom native code, you need to install the `expo-dev-client` library in your project. It allows configuring any native code required by React Native Firebase using [Config plugins](/config-plugins/introduction/) without writing native code yourself. -To install [`expo-dev-client`](/development/getting-started/#installing--expo-dev-client--in-your-project), run the following command in your project: +To install [`expo-dev-client`](/develop/development-builds/create-a-build/), run the following command in your project: diff --git a/docs/pages/guides/using-vexo.mdx b/docs/pages/guides/using-vexo.mdx index 2fb0c415476b4f..f9b46d5843155a 100644 --- a/docs/pages/guides/using-vexo.mdx +++ b/docs/pages/guides/using-vexo.mdx @@ -59,7 +59,7 @@ With a two-line integration, Vexo starts collecting data automatically, giving y ## Compatibility -- Expo: Vexo is compatible with [Development builds](/development/introduction/) and does not require additional configuration plugins. +- Expo: Vexo is compatible with [Development builds](/develop/development-builds/introduction/) and does not require additional configuration plugins. - Expo Go: Not supported, as Vexo requires custom native code. ## Learn more about Vexo diff --git a/docs/pages/guides/why-metro.mdx b/docs/pages/guides/why-metro.mdx index 34084b7dd6a424..3d873ea5c6eb80 100644 --- a/docs/pages/guides/why-metro.mdx +++ b/docs/pages/guides/why-metro.mdx @@ -23,7 +23,7 @@ New and upcoming features that are planned to come to Metro include: - Compiling Flow code to native machine code with Static Hermes. Learn more in the [Static Hermes](https://www.youtube.com/watch?v=GUM64b-gAGg) talk by Tzvetan Mikov. - Data fetching, streaming, React Suspense, server rendering, and build-time static rendering with universal React Server Components for all platforms. Learn more in the [Universal React Server Components](https://www.youtube.com/watch?v=djhEgxQf3Kw) talk at React Conf 2024. -The Expo team collaborates with Meta to develop Metro for Expo Router, adding features like [file-based routing](/develop/file-based-routing/), [web support](/guides/customizing-metro/#web-support), [bundle splitting](/guides/customizing-metro/#bundle-splitting), [tree shaking](/guides/tree-shaking/), [CSS](/versions/latest/config/metro/#css), [DOM components](/guides/dom-components/), server components, and [API routes](/router/web/api-routes/). +The Expo team collaborates with Meta to develop Metro for Expo Router, adding features like [file-based routing](/develop/app-navigation/), [web support](/guides/customizing-metro/#web-support), [bundle splitting](/guides/customizing-metro/#bundle-splitting), [tree shaking](/guides/tree-shaking/), [CSS](/versions/latest/config/metro/#css), [DOM components](/guides/dom-components/), server components, and [API routes](/router/web/api-routes/). ## Battle-tested at scale diff --git a/docs/pages/linking/into-other-apps.mdx b/docs/pages/linking/into-other-apps.mdx index af7af9e5a38ad7..6d46837712643f 100644 --- a/docs/pages/linking/into-other-apps.mdx +++ b/docs/pages/linking/into-other-apps.mdx @@ -10,7 +10,7 @@ import { SnackInline } from '~/ui/components/Snippet'; Handling linking into other apps from your app is achieved by using the target app's URL. There are two methods you can use to open such URLs from your app: - Using the [`expo-linking`](/versions/latest/sdk/linking) API -- Using Expo Router's [`Link` component](/develop/file-based-routing/#how-does-link-work) +- Using Expo Router's [`Link` component](/develop/app-navigation/) ## Using expo-linking API diff --git a/docs/pages/router/advanced/modals.mdx b/docs/pages/router/advanced/modals.mdx index 15e51292f0ea80..6e497cd9f25028 100644 --- a/docs/pages/router/advanced/modals.mdx +++ b/docs/pages/router/advanced/modals.mdx @@ -132,7 +132,7 @@ A modal loses its previous context when it is the current screen in the navigato - On Android, the modal slides on top of the current screen. To dismiss it, use the back button to navigate back to the previous screen. - On iOS, the modal slides from the bottom of the current screen. To dismiss it, swipe it down from the top. -- On web, the modal is presented as a separate route, and the dismiss behavior has to be provided manually using [`router.canGoBack()`](/router/navigating-pages/#imperative-navigation). Here's an example of how to dismiss the modal: +- On web, the modal is presented as a separate route, and the dismiss behavior has to be provided manually using [`router.canGoBack()`](/router/basics/navigation/). Here's an example of how to dismiss the modal: {/* prettier-ignore */} ```tsx src/app/modal.tsx diff --git a/docs/pages/router/advanced/redirects.mdx b/docs/pages/router/advanced/redirects.mdx index b129b5e8fd3cf4..3da452e9b553c8 100644 --- a/docs/pages/router/advanced/redirects.mdx +++ b/docs/pages/router/advanced/redirects.mdx @@ -82,7 +82,7 @@ Unlike routes within the **app** directory, you do not need to add the `/index` ### Dynamic routes -The `source` and `destination` routes can use the [dynamic route syntax](/develop/dynamic-routes/) to create redirects for dynamic routes. You can define them in **app.json** using the `expo-router` config plugin. +The `source` and `destination` routes can use the [dynamic route syntax](/develop/app-navigation/) to create redirects for dynamic routes. You can define them in **app.json** using the `expo-router` config plugin. ```json app.json { diff --git a/docs/pages/submit/android.mdx b/docs/pages/submit/android.mdx index 0ff79dd1dbb863..d2516a6126373a 100644 --- a/docs/pages/submit/android.mdx +++ b/docs/pages/submit/android.mdx @@ -151,7 +151,7 @@ Once you have completed all the prerequisites, you can set up a CI/CD pipeline t ### Use EAS Workflows CI/CD -You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically. +You can use [EAS Workflows](/eas/workflows/get-started/) to build and submit your app automatically. 1. Create a workflow file named **.eas/workflows/submit-android.yml** at the root of your project. 2. Inside **submit-android.yml**, you can use the following workflow to kick off a job that submits an Android app: diff --git a/docs/pages/submit/ios.mdx b/docs/pages/submit/ios.mdx index 27040e8b9a81ca..60f4d7b99590c1 100644 --- a/docs/pages/submit/ios.mdx +++ b/docs/pages/submit/ios.mdx @@ -172,7 +172,7 @@ Once you have completed all the prerequisites, you can set up a CI/CD pipeline t ### Use EAS Workflows CI/CD -You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically. +You can use [EAS Workflows](/eas/workflows/get-started/) to build and submit your app automatically. 1. Create a workflow file named **.eas/workflows/submit-ios.yml** at the root of your project. 2. Inside **submit-ios.yml**, you can use the following workflow to kick off a job that submits an iOS app: diff --git a/docs/pages/troubleshooting/overview.mdx b/docs/pages/troubleshooting/overview.mdx index 101c35d7d72166..f72ea4a54497fc 100644 --- a/docs/pages/troubleshooting/overview.mdx +++ b/docs/pages/troubleshooting/overview.mdx @@ -108,7 +108,7 @@ This page lists a collection of various troubleshooting guides for Expo and EAS. diff --git a/docs/pages/tutorial/eas/configure-development-build.mdx b/docs/pages/tutorial/eas/configure-development-build.mdx index f815772d08fb37..fe4b5c7e123835 100644 --- a/docs/pages/tutorial/eas/configure-development-build.mdx +++ b/docs/pages/tutorial/eas/configure-development-build.mdx @@ -27,7 +27,7 @@ A [development build](/develop/development-builds/introduction/) is a debug vers ### Key highlights -> **Note:** If you are familiar with [Expo Go](/get-started/expo-go/), think of a development build as a customizable version of Expo Go that is unique to the requirements of a project. +> **Note:** If you are familiar with [Expo Go](/get-started/set-up-your-environment/), think of a development build as a customizable version of Expo Go that is unique to the requirements of a project. | Feature | Development Builds | Expo Go | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | diff --git a/docs/pages/workflow/configuration.mdx b/docs/pages/workflow/configuration.mdx index be70237bba02a2..e151021180ffc9 100644 --- a/docs/pages/workflow/configuration.mdx +++ b/docs/pages/workflow/configuration.mdx @@ -12,7 +12,7 @@ import { Terminal } from '~/ui/components/Snippet'; -The app config (**app.json**, **app.config.js**, **app.config.ts**) is used for configuring [Expo Prebuild](/more/glossary-of-terms/#prebuild) generation, how a project loads in [Expo Go](/get-started/expo-go/), and the OTA update manifest. +The app config (**app.json**, **app.config.js**, **app.config.ts**) is used for configuring [Expo Prebuild](/more/glossary-of-terms/#prebuild) generation, how a project loads in [Expo Go](/get-started/set-up-your-environment/), and the OTA update manifest. It must be located at the root of your project, next to the **package.json**. Here is a minimal example: From c8c0367987a8a7c6b2002e76709e3bebbd1feaf9 Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Thu, 5 Mar 2026 21:22:27 +0530 Subject: [PATCH 03/13] [docs] Fix internal links for EAS Update docs (#43582) --- docs/pages/eas-update/debug.mdx | 6 +++--- docs/pages/eas-update/deployment.mdx | 6 +++--- docs/pages/eas-update/getting-started.mdx | 2 +- docs/pages/eas-update/introduction.mdx | 2 +- docs/pages/troubleshooting/overview.mdx | 2 +- docs/pages/versions/unversioned/sdk/updates.mdx | 2 +- docs/pages/versions/v53.0.0/sdk/updates.mdx | 2 +- docs/pages/versions/v54.0.0/sdk/updates.mdx | 2 +- docs/pages/versions/v55.0.0/sdk/updates.mdx | 2 +- docs/pages/workflow/overview.mdx | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/pages/eas-update/debug.mdx b/docs/pages/eas-update/debug.mdx index 1563b7262c17b9..f92fe222de6234 100644 --- a/docs/pages/eas-update/debug.mdx +++ b/docs/pages/eas-update/debug.mdx @@ -19,7 +19,7 @@ This guide shows how to verify our configuration so that we can find the source
-> If we are not using EAS Build, our Deployments page will be empty. Follow the guide on [debugging configuration without EAS Build](/eas-update/debug-advanced/#configuration-without-eas-build) instead. +> If we are not using EAS Build, our Deployments page will be empty. Follow the guide on [debugging configuration without EAS Build](/eas-update/debug/#configuration-without-eas-build) instead. ## Go to the Deployments page @@ -89,7 +89,7 @@ The displayed deployment has the correct channel, but it is not linked to a bran ### Missing deployment -If our deployment is not displayed, it means our build is not configured properly for EAS Update. To fix this, [configure our channel](/#configure-channel), [configure our runtime version](/#configure-runtime-version) and verify our [general configuration](/eas-update/debug-advanced/#verifying-app-configuration). We'll need to rebuild our app after making these changes. +If our deployment is not displayed, it means our build is not configured properly for EAS Update. To fix this, [configure our channel](/#configure-channel), [configure our runtime version](/#configure-runtime-version) and verify our [general configuration](/eas-update/debug/#verifying-app-configuration). We'll need to rebuild our app after making these changes. ### Automatic roll back when an update crashes @@ -247,7 +247,7 @@ If we aren't using EAS Build, this section will walk through debugging the state #### Verify build configuration -Follow the [Building Locally guide](/eas-update/standalone-service/) to configure our app's channel and runtime version. We'll also need to make sure our [general configuration](/eas-update/debug-advanced/#expo-updates-configuration) is correct. +Follow the [Building Locally guide](/eas-update/standalone-service/) to configure our app's channel and runtime version. We'll also need to make sure our [general configuration](/eas-update/debug/#expo-updates-configuration) is correct. #### Verify the channel diff --git a/docs/pages/eas-update/deployment.mdx b/docs/pages/eas-update/deployment.mdx index 8be27c8665db7a..2c99843458f242 100644 --- a/docs/pages/eas-update/deployment.mdx +++ b/docs/pages/eas-update/deployment.mdx @@ -26,7 +26,7 @@ In this guide, we'll describe a simple but powerful release process that uses ** The most simple way to use EAS Update is to ignore the concept of "branches" and focus on "channels". Branches will still exist, but you will not have to interact with them directly to manage deployments. You can keep your channels pointed at a branch with the same name as the channel and think of them as a singular concept. -EAS Update branches were meant to map to Git branches and enable teams to publish changes from a Git branch directly to an EAS Update branch of the same name. This can be helpful for [previewing updates](/eas-update/develop-faster/), but for many apps, this level of integration with Git is not required. Often, developers are only interested in being able to release hotfixes to a staging or production version of their app manually, and can run `eas update --channel staging` or `eas update --channel production`, when needed rather than managing branches to accomplish the same result. +EAS Update branches were meant to map to Git branches and enable teams to publish changes from a Git branch directly to an EAS Update branch of the same name. This can be helpful for [previewing updates](/eas-update/preview/), but for many apps, this level of integration with Git is not required. Often, developers are only interested in being able to release hotfixes to a staging or production version of their app manually, and can run `eas update --channel staging` or `eas update --channel production`, when needed rather than managing branches to accomplish the same result. @@ -99,7 +99,7 @@ As explained above, preview builds will point to the "preview" channel. If you w ### Preview in development builds -Development builds can load updates from any channel, provided the runtime version is compatible. Learn more about this in [Previewing updates](/eas-update/develop-faster/). +Development builds can load updates from any channel, provided the runtime version is compatible. Learn more about this in [Previewing updates](/eas-update/preview/). ## Deploying to staging @@ -144,4 +144,4 @@ Learn more in [Rollbacks](/eas-update/rollbacks/). ## Next steps - [Learn more about the Persistent staging release process](/eas-update/deployment-patterns/#persistent-staging-flow), which is very similar to what is described here. -- [Explore using preview updates in development builds](/eas-update/develop-faster/). +- [Explore using preview updates in development builds](/eas-update/preview/). diff --git a/docs/pages/eas-update/getting-started.mdx b/docs/pages/eas-update/getting-started.mdx index 8707b177ed6305..3272fc18434f95 100644 --- a/docs/pages/eas-update/getting-started.mdx +++ b/docs/pages/eas-update/getting-started.mdx @@ -306,7 +306,7 @@ If your app is not updating as expected, see the [debugging guide](/eas-update/d diff --git a/docs/pages/eas-update/introduction.mdx b/docs/pages/eas-update/introduction.mdx index a6b07bbf736e27..aa5f76f7679714 100644 --- a/docs/pages/eas-update/introduction.mdx +++ b/docs/pages/eas-update/introduction.mdx @@ -192,7 +192,7 @@ We recommend transitioning to EAS Update or using a [self-hosted update service] diff --git a/docs/pages/troubleshooting/overview.mdx b/docs/pages/troubleshooting/overview.mdx index f72ea4a54497fc..b6cbd125166f34 100644 --- a/docs/pages/troubleshooting/overview.mdx +++ b/docs/pages/troubleshooting/overview.mdx @@ -140,7 +140,7 @@ This page lists a collection of various troubleshooting guides for Expo and EAS. diff --git a/docs/pages/versions/unversioned/sdk/updates.mdx b/docs/pages/versions/unversioned/sdk/updates.mdx index 1bb96e83059085..b24bcad428f0e5 100644 --- a/docs/pages/versions/unversioned/sdk/updates.mdx +++ b/docs/pages/versions/unversioned/sdk/updates.mdx @@ -259,7 +259,7 @@ You can configure your app to check for updates manually by doing the following ## Testing -Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug-advanced/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. +Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. **To test the content of an update in a development build**, run [`eas update`](/eas-update/getting-started/) and then browse to the update in your development build. Note that this only simulates what an update will look like in your app, and most of the [Updates API](#api) is unavailable when running in a development build. diff --git a/docs/pages/versions/v53.0.0/sdk/updates.mdx b/docs/pages/versions/v53.0.0/sdk/updates.mdx index fe376eb480a1f3..07bfea742fa593 100644 --- a/docs/pages/versions/v53.0.0/sdk/updates.mdx +++ b/docs/pages/versions/v53.0.0/sdk/updates.mdx @@ -258,7 +258,7 @@ You can configure your app to check for updates manually by doing the following ## Testing -Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug-advanced/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. +Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. **To test the content of an update in a development build**, run [`eas update`](/eas-update/getting-started/) and then browse to the update in your development build. Note that this only simulates what an update will look like in your app, and most of the [Updates API](#api) is unavailable when running in a development build. diff --git a/docs/pages/versions/v54.0.0/sdk/updates.mdx b/docs/pages/versions/v54.0.0/sdk/updates.mdx index 27843808dfb6ae..178bd411f74a47 100644 --- a/docs/pages/versions/v54.0.0/sdk/updates.mdx +++ b/docs/pages/versions/v54.0.0/sdk/updates.mdx @@ -258,7 +258,7 @@ You can configure your app to check for updates manually by doing the following ## Testing -Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug-advanced/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. +Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. **To test the content of an update in a development build**, run [`eas update`](/eas-update/getting-started/) and then browse to the update in your development build. Note that this only simulates what an update will look like in your app, and most of the [Updates API](#api) is unavailable when running in a development build. diff --git a/docs/pages/versions/v55.0.0/sdk/updates.mdx b/docs/pages/versions/v55.0.0/sdk/updates.mdx index 1bb96e83059085..b24bcad428f0e5 100644 --- a/docs/pages/versions/v55.0.0/sdk/updates.mdx +++ b/docs/pages/versions/v55.0.0/sdk/updates.mdx @@ -259,7 +259,7 @@ You can configure your app to check for updates manually by doing the following ## Testing -Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug-advanced/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. +Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](/eas-update/debug/#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. **To test the content of an update in a development build**, run [`eas update`](/eas-update/getting-started/) and then browse to the update in your development build. Note that this only simulates what an update will look like in your app, and most of the [Updates API](#api) is unavailable when running in a development build. diff --git a/docs/pages/workflow/overview.mdx b/docs/pages/workflow/overview.mdx index b14729a1ae8c51..1a4052fc1b397a 100644 --- a/docs/pages/workflow/overview.mdx +++ b/docs/pages/workflow/overview.mdx @@ -249,4 +249,4 @@ Analytics allows you to track how users interact with your app. See [analytics o The `expo-updates` library allows you to programmatically make instant updates to your app's JavaScript available to your production app. -You can use [EAS Update](/eas-update/introduction/) which provides first-class support for instant updates in a React Native app. It serves updates from the edge of a global CDN and uses modern networking protocols such as HTTP/3 for clients that support them. It is also [tailored for developers](/eas-update/develop-faster/) who use EAS Build. You can also use it for builds you have created [locally](/eas-update/standalone-service/). +You can use [EAS Update](/eas-update/introduction/) which provides first-class support for instant updates in a React Native app. It serves updates from the edge of a global CDN and uses modern networking protocols such as HTTP/3 for clients that support them. It is also [tailored for developers](/eas-update/preview/) who use EAS Build. You can also use it for builds you have created [locally](/eas-update/standalone-service/). From 8215bfcfc96abd12cddbe08cbda6fddc13a2de3e Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Thu, 5 Mar 2026 16:58:12 +0100 Subject: [PATCH 04/13] [ui] fix Android ripple effect clipped on sides (#43656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fix Android Switch ripple effect clipped on sides ## Summary Fixes https://github.com/expo/expo/issues/43554 The Switch ripple effect on Android was clipped to a rectangle instead of rendering as a circle extending beyond the component bounds. ## Root cause `ExpoComposeView` extends `ExpoView` which extends `LinearLayout`. By default, `LinearLayout` has clips the children. Also, `ExpoView.dispatchDraw()` calls `clipToPaddingBox(canvas)` which clips the drawing canvas to the view's bounds before dispatching to children. When `Host` uses `matchContents`, the view is sized to exactly fit the Switch content. The ripple effect on the Switch thumb gets cut off at the view boundary. ## Fix Disable `clipChildren` and `clipToPadding` on `ExpoComposeView` when `withHostingView = true` - `clipToPadding = false` — skips the `clipToPaddingBox` canvas clipping in `dispatchDraw`, allowing content (ripples, shadows) to render outside view bounds - `clipChildren = false` — allows child views to draw outside their own bounds within the parent ## Test plan - Open NCL → Expo UI → Switch on Android - The ripple should render as a circle, not clipped on the sides # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-modules-core/CHANGELOG.md | 1 + .../src/compose/expo/modules/kotlin/views/ExpoComposeView.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/expo-modules-core/CHANGELOG.md b/packages/expo-modules-core/CHANGELOG.md index b9f74df583f86d..5b5489deb16e7d 100644 --- a/packages/expo-modules-core/CHANGELOG.md +++ b/packages/expo-modules-core/CHANGELOG.md @@ -12,6 +12,7 @@ ### 🐛 Bug fixes +- [Android] Fixed Compose view clipping that caused Material ripple effects to be cut off (e.g., Switch thumb ripple). ([#43656](https://github.com/expo/expo/pull/43656) by [@vonovak](https://github.com/vonovak)) - [iOS] Fix memory leak due to retain cycle in SwiftUI views. ([#43468](https://github.com/expo/expo/pull/43468) by [@nishan](https://github.com/intergalacticspacehighway)) - [iOS] Fixed compilation issues due to missing fallthrough case in `EXJavaScriptSerializable`. ([#43634](https://github.com/expo/expo/pull/43634) by [@tjzel](https://github.com/tjzel)) diff --git a/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt b/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt index 5ce32dfd680772..bf3b2aa41fbe5c 100644 --- a/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +++ b/packages/expo-modules-core/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt @@ -149,6 +149,8 @@ abstract class ExpoComposeView( init { if (withHostingView) { + clipChildren = false + clipToPadding = false addComposeView() } else { this.visibility = GONE From 1f41ebef21ef0aaac015d52223cf7e0073e39780 Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Thu, 5 Mar 2026 13:22:20 -0300 Subject: [PATCH 05/13] [install-expo-modules] Add version mapping for SDK 55 and react native 0.83 (#43682) # Why Closes https://github.com/expo/expo/issues/43645 # How Add version mapping for SDK 55 and react native 0.83 to install-expo-modules # Test Plan Tested locally # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/install-expo-modules/CHANGELOG.md | 2 ++ .../install-expo-modules/src/utils/expoVersionMappings.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/packages/install-expo-modules/CHANGELOG.md b/packages/install-expo-modules/CHANGELOG.md index 3d405c0254946b..128aed1174e98d 100644 --- a/packages/install-expo-modules/CHANGELOG.md +++ b/packages/install-expo-modules/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Add version mapping for SDK 55 and react native 0.83 ([#43682](https://github.com/expo/expo/pull/43682) by [@gabrieldonadel](https://github.com/gabrieldonadel)) + ### 💡 Others ## 0.14.9 — 2026-02-25 diff --git a/packages/install-expo-modules/src/utils/expoVersionMappings.ts b/packages/install-expo-modules/src/utils/expoVersionMappings.ts index 271df2ef6b08a3..659a6d3e114ef9 100644 --- a/packages/install-expo-modules/src/utils/expoVersionMappings.ts +++ b/packages/install-expo-modules/src/utils/expoVersionMappings.ts @@ -13,6 +13,13 @@ export interface VersionInfo { export const ExpoVersionMappings: VersionInfo[] = [ // Please keep sdk versions in sorted order (latest sdk first) + { + expoPackageVersion: '~55.0.0', + sdkVersion: '55.0.0', + iosDeploymentTarget: '15.1', + reactNativeVersionRange: '~0.83.0', + supportCliIntegration: true, + }, { expoPackageVersion: '~54.0.0', sdkVersion: '54.0.0', From ff92d74f476c6e9116d953cab33c418d5ce8d42c Mon Sep 17 00:00:00 2001 From: Alan Hughes <30924086+alanjhughes@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:38:32 +0000 Subject: [PATCH 06/13] [ios][dev-launcher] Remove port scanning (#43685) --- .../ios/SwiftUI/DevLauncherViewModel.swift | 85 +------------------ .../ios/SwiftUI/NetworkUtilities.swift | 61 ------------- 2 files changed, 3 insertions(+), 143 deletions(-) diff --git a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift index c9a87a0362b44a..aecc44391f476d 100644 --- a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift +++ b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift @@ -54,19 +54,10 @@ class DevLauncherViewModel: ObservableObject { @Published var isLoadingServer: Bool = false @Published var permissionStatus: LocalNetworkPermissionStatus = .unknown - @Published var browserDevServers: [DevServer] = [] { - didSet { updateDevServers() } - } - - @Published var localDevServers: [DevServer] = [] { - didSet { updateDevServers() } - } - @Published var devServers: [DevServer] = [] private var browser: NWBrowser? private var pingTask: Task? - private var scanTask: Task? #if !os(tvOS) private let presentationContext = DevLauncherAuthPresentationContext() @@ -94,36 +85,8 @@ class DevLauncherViewModel: ObservableObject { checkForStoredCrashes() } - private func updateDevServers() { - let allServers = browserDevServers + localDevServers - var serversByPort: [String: DevServer] = [:] - - for server in allServers { - guard let port = extractPort(from: server.url) else { - serversByPort[server.url] = server - continue - } - - if let existing = serversByPort[port] { - let existingHasName = existing.description != existing.url - let newHasName = server.description != server.url - - if newHasName && !existingHasName { - serversByPort[port] = server - } else if existingHasName == newHasName { - let existingIsLinkLocal = existing.url.contains("169.254.") - let newIsLinkLocal = server.url.contains("169.254.") - - if existingIsLinkLocal && !newIsLinkLocal { - serversByPort[port] = server - } - } - } else { - serversByPort[port] = server - } - } - - devServers = serversByPort.values.sorted(by: <) + private func updateDevServers(_ servers: [DevServer]) { + devServers = servers.sorted(by: <) } private func extractPort(from url: String) -> String? { @@ -214,7 +177,6 @@ class DevLauncherViewModel: ObservableObject { stopServerDiscovery() startDevServerBrowser() - startLocalDevServerScanner() } func markNetworkPermissionGranted() { @@ -284,27 +246,11 @@ class DevLauncherViewModel: ObservableObject { func stopServerDiscovery() { pingTask?.cancel() - scanTask?.cancel() browser?.cancel() pingTask = nil - scanTask = nil browser = nil } - private func startLocalDevServerScanner() { - scanTask?.cancel() - scanTask = Task { - await scanLocalDevServers() - while !Task.isCancelled { - try? await Task.sleep(nanoseconds: 2_000_000_000) - if Task.isCancelled { - break - } - await scanLocalDevServers() - } - } - } - private func startDevServerBrowser() { pingTask?.cancel() browser?.cancel() @@ -357,31 +303,6 @@ class DevLauncherViewModel: ObservableObject { browser?.start(queue: DispatchQueue(label: "expo.devlauncher.discovery")) } - private func scanLocalDevServers() async { - guard !Task.isCancelled else { - return - } - - var discoveredServers: [DevServer] = [] - await withTaskGroup(of: DevServer?.self) { group in - for result in NetworkUtilities.getLocalEndpointsToScan() { - group.addTask { - return await self.resolveDevServer(result) - } - } - - for await server in group { - if let server { - discoveredServers.append(server) - } - } - } - - await MainActor.run { - self.localDevServers = discoveredServers - } - } - private func pingDiscoveryResults(_ results: [DiscoveryResult]) async { guard !Task.isCancelled else { return @@ -407,7 +328,7 @@ class DevLauncherViewModel: ObservableObject { } await MainActor.run { - self.browserDevServers = discoveredServers + self.updateDevServers(discoveredServers) } } diff --git a/packages/expo-dev-launcher/ios/SwiftUI/NetworkUtilities.swift b/packages/expo-dev-launcher/ios/SwiftUI/NetworkUtilities.swift index f69b5a8d666e9b..68ac1f9e512910 100644 --- a/packages/expo-dev-launcher/ios/SwiftUI/NetworkUtilities.swift +++ b/packages/expo-dev-launcher/ios/SwiftUI/NetworkUtilities.swift @@ -129,67 +129,6 @@ func connectionReceive(_ connection: NWConnection) async throws -> String { } class NetworkUtilities { - // Same approach as expo-network just without throwing. - static func getLocalIPAddress() -> String? { - var address: String? - - var ifaddr: UnsafeMutablePointer? - guard getifaddrs(&ifaddr) == 0 else { return nil } - guard let firstAddr = ifaddr else { return nil } - - defer { freeifaddrs(ifaddr) } - - for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { - let interface = ifptr.pointee - - let addrFamily = interface.ifa_addr.pointee.sa_family - if addrFamily == UInt8(AF_INET) { - let name = String(cString: interface.ifa_name) - - if name == "en0" || name == "en1" { - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - getnameinfo( - interface.ifa_addr, - socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, - socklen_t(hostname.count), - nil, - socklen_t(0), - NI_NUMERICHOST - ) - address = String(cString: hostname) - break - } - } - } - - return address - } - - static func isSimulator() -> Bool { - #if targetEnvironment(simulator) - return true - #else - return false - #endif - } - - static func getLocalEndpointsToScan() -> [DiscoveryResult] { - let portsToScan = ["8081", "8082", "8083"] - if !isSimulator() { - return [] - } - return portsToScan.map { port in - DiscoveryResult( - name: nil, - endpoint: NWEndpoint.hostPort( - host: NWEndpoint.Host.init(getLocalIPAddress() ?? "localhost"), - port: NWEndpoint.Port.init(port)! - ) - ) - } - } - static func getNWBrowserResultName(_ result: NWBrowser.Result) -> String? { if case .bonjour(let txtRecord) = result.metadata { return txtRecord.getEntry(for: "name").flatMap { From 81b5df63c52a09015e8cd704f38025cfca69da5b Mon Sep 17 00:00:00 2001 From: Alan Hughes <30924086+alanjhughes@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:39:20 +0000 Subject: [PATCH 07/13] [ios][dev-launcher] Fix missing navigation bar padding (#43672) --- packages/expo-dev-launcher/CHANGELOG.md | 1 + .../ExpoDevLauncherReactDelegateHandler.swift | 41 +++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/expo-dev-launcher/CHANGELOG.md b/packages/expo-dev-launcher/CHANGELOG.md index af7a4aab69bc85..4d984bb6ede868 100644 --- a/packages/expo-dev-launcher/CHANGELOG.md +++ b/packages/expo-dev-launcher/CHANGELOG.md @@ -9,6 +9,7 @@ ### 🐛 Bug fixes - [iOS] Fix issue when using `fullScreenModal` with `expo-router`. ([#43520](https://github.com/expo/expo/pull/43520) by [@alanjhughes](https://github.com/alanjhughes)) +- [iOS] Fix missing navigation bar padding ([#43672](https://github.com/expo/expo/pull/43672) by [@alanjhughes](https://github.com/alanjhughes)) ### 💡 Others diff --git a/packages/expo-dev-launcher/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift b/packages/expo-dev-launcher/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift index 46192e6ca3d287..a0946f4a08ed17 100644 --- a/packages/expo-dev-launcher/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift +++ b/packages/expo-dev-launcher/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift @@ -117,26 +117,49 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe ) developmentClientController.appBridge = RCTBridge.current() - guard let rootViewController = rootViewController ?? self.reactDelegate?.createRootViewController() else { + let targetVC: UIViewController +#if !os(macOS) + let windowRootVC = rootViewController?.view?.window?.rootViewController + if let windowRootVC, windowRootVC.view is DevLauncherWrapperView { + // Greenfield: set root view on the window's root VC so react-native-screens parents its + // UINavigationController to a VC in the containment hierarchy with correct layout margins. + targetVC = windowRootVC + } else if let rootViewController { + // Brownfield: the wrapper is embedded in a custom hierarchy, fall back to + // DevLauncherViewController to avoid replacing the host app's root view. + targetVC = rootViewController + } else if let fallbackVC = self.reactDelegate?.createRootViewController() { + targetVC = fallbackVC + } else { fatalError("Invalid rootViewController returned from ExpoReactDelegate") } +#else + // macOS: NSWindow has no rootViewController, fall back to DevLauncherViewController. + if let rootViewController { + targetVC = rootViewController + } else if let fallbackVC = self.reactDelegate?.createRootViewController() { + targetVC = fallbackVC + } else { + fatalError("Invalid rootViewController returned from ExpoReactDelegate") + } +#endif #if os(macOS) let newViewController = UIViewController() newViewController.view = rootView - rootViewController.view.subviews.forEach { $0.removeFromSuperview() } - rootViewController.addChild(newViewController) - rootViewController.view.addSubview(newViewController.view) + targetVC.view.subviews.forEach { $0.removeFromSuperview() } + targetVC.addChild(newViewController) + targetVC.view.addSubview(newViewController.view) newViewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - newViewController.view.topAnchor.constraint(equalTo: rootViewController.view.topAnchor), - newViewController.view.leadingAnchor.constraint(equalTo: rootViewController.view.leadingAnchor), - newViewController.view.trailingAnchor.constraint(equalTo: rootViewController.view.trailingAnchor), - newViewController.view.bottomAnchor.constraint(equalTo: rootViewController.view.bottomAnchor) + newViewController.view.topAnchor.constraint(equalTo: targetVC.view.topAnchor), + newViewController.view.leadingAnchor.constraint(equalTo: targetVC.view.leadingAnchor), + newViewController.view.trailingAnchor.constraint(equalTo: targetVC.view.trailingAnchor), + newViewController.view.bottomAnchor.constraint(equalTo: targetVC.view.bottomAnchor) ]) #else - rootViewController.view = rootView + targetVC.view = rootView #endif // it is purposeful that we don't clean up saved properties here, because we may initialize // several React instances over a single app lifetime and we want them all to have the same From 910bc28604446dfa12f3ac8c76f0ad2d2d0f04ad Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Thu, 5 Mar 2026 16:44:32 +0000 Subject: [PATCH 08/13] fix(router,router-server): Handle empty routes manifest gracefully (#43606) # Why While it's assumed that we'd always have a routes manifest, this isn't always the case. `getRoutesCore` itself self-documents that a tutorial is displayed when no routes are present, and it returns `null` for the directory structure. This `null` value is carried through to where the routes output is used, until the manifests are created. There, we instead unfortunately throw an error. This is a pre-condition that's instead asserted as an error, which is unexpected, and leads to unintended cases where the build cryptically fails. We should aim not to fail the build even when the app is in an "intermediary" state. See: https://github.com/expo/expo/pull/43602 Specifically, I believe we should gracefully continue building the app as often as possible, even if prerequisites aren't fulfilled. In this case, when no routes are present, we should continue building. If we want to enforce the "no routes" case, we should add a clear error in a place where we handle build exceptions instead. # How - Prevent `null` route tree from throwing when manifests are generated - Prevent `null` route tree from throwing when static react-navigation configs are generated # Test Plan - Build without an `app` folder and observe that the build shouldn't fail - Example: `E2E_ROUTER_SRC=noop expo export -p web` in `router-e2e` # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/@expo/router-server/CHANGELOG.md | 2 ++ .../build/getServerManifest.d.ts | 2 +- .../build/getServerManifest.d.ts.map | 2 +- .../router-server/build/getServerManifest.js | 10 +++++---- .../build/getServerManifest.js.map | 2 +- .../build/static/getServerManifest.d.ts.map | 2 +- .../build/static/getServerManifest.js | 20 ++++++++++------- .../build/static/getServerManifest.js.map | 2 +- .../router-server/src/getServerManifest.ts | 12 +++++----- .../src/static/getServerManifest.ts | 22 ++++++++++--------- packages/expo-router/CHANGELOG.md | 1 + .../build/getReactNavigationConfig.d.ts | 2 +- .../build/getReactNavigationConfig.d.ts.map | 2 +- .../build/getReactNavigationConfig.js | 8 +++---- .../build/getReactNavigationConfig.js.map | 2 +- .../src/getReactNavigationConfig.ts | 9 ++++---- 16 files changed, 57 insertions(+), 43 deletions(-) diff --git a/packages/@expo/router-server/CHANGELOG.md b/packages/@expo/router-server/CHANGELOG.md index 9f5673810350de..76e50a44b13772 100644 --- a/packages/@expo/router-server/CHANGELOG.md +++ b/packages/@expo/router-server/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Handle empty routes manifest gracefully ([#43606](https://github.com/expo/expo/pull/43606) by [@kitten](https://github.com/kitten)) + ## 55.0.8 — 2026-02-25 _This version does not introduce any user-facing changes._ diff --git a/packages/@expo/router-server/build/getServerManifest.d.ts b/packages/@expo/router-server/build/getServerManifest.d.ts index 40b827c36bd61d..0fe2fd7168076e 100644 --- a/packages/@expo/router-server/build/getServerManifest.d.ts +++ b/packages/@expo/router-server/build/getServerManifest.d.ts @@ -21,7 +21,7 @@ export interface RouteRegex { type GetServerManifestOptions = { headers?: Record; }; -export declare function getServerManifest(route: RouteNode, options: GetServerManifestOptions | undefined): RoutesManifest; +export declare function getServerManifest(route: RouteNode | null, options: GetServerManifestOptions | undefined): RoutesManifest; export declare function parseParameter(param: string): { name: string; repeat: boolean; diff --git a/packages/@expo/router-server/build/getServerManifest.d.ts.map b/packages/@expo/router-server/build/getServerManifest.d.ts.map index 40485d7c29854c..106de3ff196247 100644 --- a/packages/@expo/router-server/build/getServerManifest.d.ts.map +++ b/packages/@expo/router-server/build/getServerManifest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ;AA2BD,KAAK,wBAAwB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC7C,CAAC;AAGF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAC5C,cAAc,CAAC,MAAM,CAAC,CAqGxB;AAuJD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM;;;;EAgB3C"} \ No newline at end of file +{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ;AA2BD,KAAK,wBAAwB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC7C,CAAC;AAGF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAC5C,cAAc,CAAC,MAAM,CAAC,CAuGxB;AAuJD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM;;;;EAgB3C"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/getServerManifest.js b/packages/@expo/router-server/build/getServerManifest.js index bc0d75fb929578..3e06f8f7b29229 100644 --- a/packages/@expo/router-server/build/getServerManifest.js +++ b/packages/@expo/router-server/build/getServerManifest.js @@ -54,9 +54,11 @@ function getServerManifest(route, options) { ]; } // Remove duplicates from the runtime manifest which expands array syntax. - const flat = getFlatNodes(route) - .sort(({ route: a }, { route: b }) => (0, routing_1.sortRoutes)(b, a)) - .reverse(); + const flat = route + ? getFlatNodes(route) + .sort(({ route: a }, { route: b }) => (0, routing_1.sortRoutes)(b, a)) + .reverse() + : []; const apiRoutes = uniqueBy(flat.filter(({ route }) => route.type === 'api'), ({ normalizedContextKey }) => normalizedContextKey); const otherRoutes = uniqueBy(flat.filter(({ route }) => route.type === 'route' || (route.type === 'rewrite' && (route.methods === undefined || route.methods.includes('GET')))), ({ normalizedContextKey }) => normalizedContextKey); @@ -92,7 +94,7 @@ function getServerManifest(route, options) { redirects: getMatchableManifestForPaths(redirects), rewrites: getMatchableManifestForPaths(rewrites), }; - if (route.middleware) { + if (route?.middleware) { manifest.middleware = { file: route.middleware.contextKey, }; diff --git a/packages/@expo/router-server/build/getServerManifest.js.map b/packages/@expo/router-server/build/getServerManifest.js.map index 742a6878469898..9e35b5e248bb5d 100644 --- a/packages/@expo/router-server/build/getServerManifest.js.map +++ b/packages/@expo/router-server/build/getServerManifest.js.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2023 650 Industries.\n * Copyright © 2023 Vercel, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts\n */\nimport {\n getContextKey,\n matchGroupName,\n sortRoutes,\n type RouteNode,\n} from 'expo-router/internal/routing';\nimport { shouldLinkExternally } from 'expo-router/internal/utils';\nimport { type RouteInfo, type RoutesManifest } from 'expo-server/private';\n\nexport interface Group {\n pos: number;\n repeat: boolean;\n optional: boolean;\n}\n\nexport interface RouteRegex {\n groups: Record;\n re: RegExp;\n}\n\nfunction isNotFoundRoute(route: RouteNode) {\n return route.dynamic && route.dynamic[route.dynamic.length - 1].notFound;\n}\n\nfunction uniqueBy(arr: T[], key: (item: T) => string): T[] {\n const seen = new Set();\n return arr.filter((item) => {\n const id = key(item);\n if (seen.has(id)) {\n return false;\n }\n seen.add(id);\n return true;\n });\n}\n\ntype FlatNode = {\n /** The context key, normalized to remove `/index` */\n normalizedContextKey: string;\n /** The complete route path, including all parent route paths */\n absoluteRoutePath: string;\n /** The route node that maps to this flattened node */\n route: RouteNode;\n};\n\ntype GetServerManifestOptions = {\n headers?: Record;\n};\n\n// Given a nested route tree, return a flattened array of all routes that can be matched.\nexport function getServerManifest(\n route: RouteNode,\n options: GetServerManifestOptions | undefined\n): RoutesManifest {\n function getFlatNodes(route: RouteNode, parentRoute: string = ''): FlatNode[] {\n // Use a recreated route instead of contextKey because we duplicate nodes to support array syntax.\n const absoluteRoute = [parentRoute, route.route].filter(Boolean).join('/');\n\n if (route.children.length) {\n return route.children.map((child) => getFlatNodes(child, absoluteRoute)).flat();\n }\n\n // API Routes are handled differently to HTML routes because they have no nested behavior.\n // An HTML route can be different based on parent segments due to layout routes, therefore multiple\n // copies should be rendered. However, an API route is always the same regardless of parent segments.\n let key: string;\n if (route.type.includes('api')) {\n key = getNormalizedContextKey(route.contextKey);\n } else {\n key = getNormalizedContextKey(absoluteRoute);\n }\n\n return [\n {\n normalizedContextKey: key,\n absoluteRoutePath: '/' + absoluteRoute,\n route,\n },\n ];\n }\n\n // Remove duplicates from the runtime manifest which expands array syntax.\n const flat = getFlatNodes(route)\n .sort(({ route: a }, { route: b }) => sortRoutes(b, a))\n .reverse();\n\n const apiRoutes = uniqueBy(\n flat.filter(({ route }) => route.type === 'api'),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const otherRoutes = uniqueBy(\n flat.filter(\n ({ route }) =>\n route.type === 'route' ||\n (route.type === 'rewrite' && (route.methods === undefined || route.methods.includes('GET')))\n ),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const redirects = uniqueBy(\n flat.filter(({ route }) => route.type === 'redirect'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((redirect) => {\n // TODO(@hassankhan): ENG-16577\n // For external redirects, use `destinationContextKey` as the destination URL\n if (shouldLinkExternally(redirect.route.destinationContextKey!)) {\n redirect.absoluteRoutePath = redirect.route.destinationContextKey!;\n } else {\n redirect.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === redirect.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n }\n\n return redirect;\n })\n .reverse();\n\n const rewrites = uniqueBy(\n flat.filter(({ route }) => route.type === 'rewrite'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((rewrite) => {\n rewrite.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === rewrite.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n\n return rewrite;\n })\n .reverse();\n\n const standardRoutes = otherRoutes.filter(({ route }) => !isNotFoundRoute(route));\n const notFoundRoutes = otherRoutes.filter(({ route }) => isNotFoundRoute(route));\n\n const manifest: RoutesManifest = {\n apiRoutes: getMatchableManifestForPaths(apiRoutes),\n htmlRoutes: getMatchableManifestForPaths(standardRoutes),\n notFoundRoutes: getMatchableManifestForPaths(notFoundRoutes),\n redirects: getMatchableManifestForPaths(redirects),\n rewrites: getMatchableManifestForPaths(rewrites),\n };\n\n if (route.middleware) {\n manifest.middleware = {\n file: route.middleware.contextKey,\n };\n }\n\n if (options?.headers) {\n manifest.headers = options.headers;\n }\n\n return manifest;\n}\n\nfunction getMatchableManifestForPaths(paths: FlatNode[]): RouteInfo[] {\n return paths.map(({ normalizedContextKey, absoluteRoutePath, route }) => {\n const matcher = getNamedRouteRegex(normalizedContextKey, absoluteRoutePath, route.contextKey);\n\n if (route.generated) {\n matcher.generated = true;\n }\n\n if (route.permanent) {\n matcher.permanent = true;\n }\n\n if (route.methods) {\n matcher.methods = route.methods;\n }\n\n return matcher;\n });\n}\n\nfunction getNamedRouteRegex(\n normalizedRoute: string,\n page: string,\n file: string\n): RouteInfo {\n const result = getNamedParametrizedRoute(normalizedRoute);\n return {\n file,\n page,\n namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,\n routeKeys: result.routeKeys,\n };\n}\n\n/**\n * Builds a function to generate a minimal routeKey using only a-z and minimal\n * number of characters.\n */\nfunction buildGetSafeRouteKey() {\n let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler\n let currentLength = 1;\n\n return () => {\n let result = '';\n let incrementNext = true;\n\n // Iterate from right to left to build the key\n for (let i = 0; i < currentLength; i++) {\n if (incrementNext) {\n currentCharCode++;\n if (currentCharCode > 122) {\n currentCharCode = 97; // Reset to 'a'\n incrementNext = true; // Continue to increment the next character\n } else {\n incrementNext = false;\n }\n }\n result = String.fromCharCode(currentCharCode) + result;\n }\n\n // If all characters are 'z', increase the length of the key\n if (incrementNext) {\n currentLength++;\n currentCharCode = 96; // This will make the next key start with 'a'\n }\n\n return result;\n };\n}\n\nfunction removeTrailingSlash(route: string): string {\n return route.replace(/\\/$/, '') || '/';\n}\n\nfunction getNamedParametrizedRoute(route: string) {\n const segments = removeTrailingSlash(route).slice(1).split('/');\n const getSafeRouteKey = buildGetSafeRouteKey();\n const routeKeys: Record = {};\n return {\n namedParameterizedRoute: segments\n .map((segment, index) => {\n if (segment === '+not-found' && index === segments.length - 1) {\n segment = '[...not-found]';\n }\n if (/^\\[.*\\]$/.test(segment)) {\n const { name, optional, repeat } = parseParameter(segment);\n // replace any non-word characters since they can break\n // the named regex\n let cleanedKey = name.replace(/\\W/g, '');\n let invalidKey = false;\n\n // check if the key is still invalid and fallback to using a known\n // safe key\n if (cleanedKey.length === 0 || cleanedKey.length > 30) {\n invalidKey = true;\n }\n if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) {\n invalidKey = true;\n }\n\n // Prevent duplicates after sanitizing the key\n if (cleanedKey in routeKeys) {\n invalidKey = true;\n }\n\n if (invalidKey) {\n cleanedKey = getSafeRouteKey();\n }\n\n routeKeys[cleanedKey] = name;\n return repeat\n ? optional\n ? `(?:/(?<${cleanedKey}>.+?))?`\n : `/(?<${cleanedKey}>.+?)`\n : `/(?<${cleanedKey}>[^/]+?)`;\n } else if (/^\\(.*\\)$/.test(segment)) {\n const groupName = matchGroupName(segment)!\n .split(',')\n .map((group) => group.trim())\n .filter(Boolean);\n if (groupName.length > 1) {\n const optionalSegment = `\\\\((?:${groupName.map(escapeStringRegexp).join('|')})\\\\)`;\n // Make section optional\n return `(?:/${optionalSegment})?`;\n } else {\n // Use simpler regex for single groups\n return `(?:/${escapeStringRegexp(segment)})?`;\n }\n } else {\n return `/${escapeStringRegexp(segment)}`;\n }\n })\n .join(''),\n routeKeys,\n };\n}\n\n// regexp is based on https://github.com/sindresorhus/escape-string-regexp\nconst reHasRegExp = /[|\\\\{}()[\\]^$+*?.-]/;\nconst reReplaceRegExp = /[|\\\\{}()[\\]^$+*?.-]/g;\n\nfunction escapeStringRegexp(str: string) {\n // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23\n if (reHasRegExp.test(str)) {\n return str.replace(reReplaceRegExp, '\\\\$&');\n }\n return str;\n}\n\nexport function parseParameter(param: string) {\n let repeat = false;\n let optional = false;\n let name = param;\n\n if (/^\\[.*\\]$/.test(name)) {\n optional = true;\n name = name.slice(1, -1);\n }\n\n if (/^\\.\\.\\./.test(name)) {\n repeat = true;\n name = name.slice(3);\n }\n\n return { name, repeat, optional };\n}\n\nfunction getNormalizedContextKey(contextKey: string): string {\n return getContextKey(contextKey).replace(/\\/index$/, '') ?? '/';\n}\n"]} \ No newline at end of file +{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2023 650 Industries.\n * Copyright © 2023 Vercel, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts\n */\nimport {\n getContextKey,\n matchGroupName,\n sortRoutes,\n type RouteNode,\n} from 'expo-router/internal/routing';\nimport { shouldLinkExternally } from 'expo-router/internal/utils';\nimport { type RouteInfo, type RoutesManifest } from 'expo-server/private';\n\nexport interface Group {\n pos: number;\n repeat: boolean;\n optional: boolean;\n}\n\nexport interface RouteRegex {\n groups: Record;\n re: RegExp;\n}\n\nfunction isNotFoundRoute(route: RouteNode) {\n return route.dynamic && route.dynamic[route.dynamic.length - 1].notFound;\n}\n\nfunction uniqueBy(arr: T[], key: (item: T) => string): T[] {\n const seen = new Set();\n return arr.filter((item) => {\n const id = key(item);\n if (seen.has(id)) {\n return false;\n }\n seen.add(id);\n return true;\n });\n}\n\ntype FlatNode = {\n /** The context key, normalized to remove `/index` */\n normalizedContextKey: string;\n /** The complete route path, including all parent route paths */\n absoluteRoutePath: string;\n /** The route node that maps to this flattened node */\n route: RouteNode;\n};\n\ntype GetServerManifestOptions = {\n headers?: Record;\n};\n\n// Given a nested route tree, return a flattened array of all routes that can be matched.\nexport function getServerManifest(\n route: RouteNode | null,\n options: GetServerManifestOptions | undefined\n): RoutesManifest {\n function getFlatNodes(route: RouteNode, parentRoute: string = ''): FlatNode[] {\n // Use a recreated route instead of contextKey because we duplicate nodes to support array syntax.\n const absoluteRoute = [parentRoute, route.route].filter(Boolean).join('/');\n\n if (route.children.length) {\n return route.children.map((child) => getFlatNodes(child, absoluteRoute)).flat();\n }\n\n // API Routes are handled differently to HTML routes because they have no nested behavior.\n // An HTML route can be different based on parent segments due to layout routes, therefore multiple\n // copies should be rendered. However, an API route is always the same regardless of parent segments.\n let key: string;\n if (route.type.includes('api')) {\n key = getNormalizedContextKey(route.contextKey);\n } else {\n key = getNormalizedContextKey(absoluteRoute);\n }\n\n return [\n {\n normalizedContextKey: key,\n absoluteRoutePath: '/' + absoluteRoute,\n route,\n },\n ];\n }\n\n // Remove duplicates from the runtime manifest which expands array syntax.\n const flat = route\n ? getFlatNodes(route)\n .sort(({ route: a }, { route: b }) => sortRoutes(b, a))\n .reverse()\n : [];\n\n const apiRoutes = uniqueBy(\n flat.filter(({ route }) => route.type === 'api'),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const otherRoutes = uniqueBy(\n flat.filter(\n ({ route }) =>\n route.type === 'route' ||\n (route.type === 'rewrite' && (route.methods === undefined || route.methods.includes('GET')))\n ),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const redirects = uniqueBy(\n flat.filter(({ route }) => route.type === 'redirect'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((redirect) => {\n // TODO(@hassankhan): ENG-16577\n // For external redirects, use `destinationContextKey` as the destination URL\n if (shouldLinkExternally(redirect.route.destinationContextKey!)) {\n redirect.absoluteRoutePath = redirect.route.destinationContextKey!;\n } else {\n redirect.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === redirect.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n }\n\n return redirect;\n })\n .reverse();\n\n const rewrites = uniqueBy(\n flat.filter(({ route }) => route.type === 'rewrite'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((rewrite) => {\n rewrite.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === rewrite.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n\n return rewrite;\n })\n .reverse();\n\n const standardRoutes = otherRoutes.filter(({ route }) => !isNotFoundRoute(route));\n const notFoundRoutes = otherRoutes.filter(({ route }) => isNotFoundRoute(route));\n\n const manifest: RoutesManifest = {\n apiRoutes: getMatchableManifestForPaths(apiRoutes),\n htmlRoutes: getMatchableManifestForPaths(standardRoutes),\n notFoundRoutes: getMatchableManifestForPaths(notFoundRoutes),\n redirects: getMatchableManifestForPaths(redirects),\n rewrites: getMatchableManifestForPaths(rewrites),\n };\n\n if (route?.middleware) {\n manifest.middleware = {\n file: route.middleware.contextKey,\n };\n }\n\n if (options?.headers) {\n manifest.headers = options.headers;\n }\n\n return manifest;\n}\n\nfunction getMatchableManifestForPaths(paths: FlatNode[]): RouteInfo[] {\n return paths.map(({ normalizedContextKey, absoluteRoutePath, route }) => {\n const matcher = getNamedRouteRegex(normalizedContextKey, absoluteRoutePath, route.contextKey);\n\n if (route.generated) {\n matcher.generated = true;\n }\n\n if (route.permanent) {\n matcher.permanent = true;\n }\n\n if (route.methods) {\n matcher.methods = route.methods;\n }\n\n return matcher;\n });\n}\n\nfunction getNamedRouteRegex(\n normalizedRoute: string,\n page: string,\n file: string\n): RouteInfo {\n const result = getNamedParametrizedRoute(normalizedRoute);\n return {\n file,\n page,\n namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,\n routeKeys: result.routeKeys,\n };\n}\n\n/**\n * Builds a function to generate a minimal routeKey using only a-z and minimal\n * number of characters.\n */\nfunction buildGetSafeRouteKey() {\n let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler\n let currentLength = 1;\n\n return () => {\n let result = '';\n let incrementNext = true;\n\n // Iterate from right to left to build the key\n for (let i = 0; i < currentLength; i++) {\n if (incrementNext) {\n currentCharCode++;\n if (currentCharCode > 122) {\n currentCharCode = 97; // Reset to 'a'\n incrementNext = true; // Continue to increment the next character\n } else {\n incrementNext = false;\n }\n }\n result = String.fromCharCode(currentCharCode) + result;\n }\n\n // If all characters are 'z', increase the length of the key\n if (incrementNext) {\n currentLength++;\n currentCharCode = 96; // This will make the next key start with 'a'\n }\n\n return result;\n };\n}\n\nfunction removeTrailingSlash(route: string): string {\n return route.replace(/\\/$/, '') || '/';\n}\n\nfunction getNamedParametrizedRoute(route: string) {\n const segments = removeTrailingSlash(route).slice(1).split('/');\n const getSafeRouteKey = buildGetSafeRouteKey();\n const routeKeys: Record = {};\n return {\n namedParameterizedRoute: segments\n .map((segment, index) => {\n if (segment === '+not-found' && index === segments.length - 1) {\n segment = '[...not-found]';\n }\n if (/^\\[.*\\]$/.test(segment)) {\n const { name, optional, repeat } = parseParameter(segment);\n // replace any non-word characters since they can break\n // the named regex\n let cleanedKey = name.replace(/\\W/g, '');\n let invalidKey = false;\n\n // check if the key is still invalid and fallback to using a known\n // safe key\n if (cleanedKey.length === 0 || cleanedKey.length > 30) {\n invalidKey = true;\n }\n if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) {\n invalidKey = true;\n }\n\n // Prevent duplicates after sanitizing the key\n if (cleanedKey in routeKeys) {\n invalidKey = true;\n }\n\n if (invalidKey) {\n cleanedKey = getSafeRouteKey();\n }\n\n routeKeys[cleanedKey] = name;\n return repeat\n ? optional\n ? `(?:/(?<${cleanedKey}>.+?))?`\n : `/(?<${cleanedKey}>.+?)`\n : `/(?<${cleanedKey}>[^/]+?)`;\n } else if (/^\\(.*\\)$/.test(segment)) {\n const groupName = matchGroupName(segment)!\n .split(',')\n .map((group) => group.trim())\n .filter(Boolean);\n if (groupName.length > 1) {\n const optionalSegment = `\\\\((?:${groupName.map(escapeStringRegexp).join('|')})\\\\)`;\n // Make section optional\n return `(?:/${optionalSegment})?`;\n } else {\n // Use simpler regex for single groups\n return `(?:/${escapeStringRegexp(segment)})?`;\n }\n } else {\n return `/${escapeStringRegexp(segment)}`;\n }\n })\n .join(''),\n routeKeys,\n };\n}\n\n// regexp is based on https://github.com/sindresorhus/escape-string-regexp\nconst reHasRegExp = /[|\\\\{}()[\\]^$+*?.-]/;\nconst reReplaceRegExp = /[|\\\\{}()[\\]^$+*?.-]/g;\n\nfunction escapeStringRegexp(str: string) {\n // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23\n if (reHasRegExp.test(str)) {\n return str.replace(reReplaceRegExp, '\\\\$&');\n }\n return str;\n}\n\nexport function parseParameter(param: string) {\n let repeat = false;\n let optional = false;\n let name = param;\n\n if (/^\\[.*\\]$/.test(name)) {\n optional = true;\n name = name.slice(1, -1);\n }\n\n if (/^\\.\\.\\./.test(name)) {\n repeat = true;\n name = name.slice(3);\n }\n\n return { name, repeat, optional };\n}\n\nfunction getNormalizedContextKey(contextKey: string): string {\n return getContextKey(contextKey).replace(/\\/index$/, '') ?? '/';\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/static/getServerManifest.d.ts.map b/packages/@expo/router-server/build/static/getServerManifest.d.ts.map index e980a366494b83..d9504588106bac 100644 --- a/packages/@expo/router-server/build/static/getServerManifest.d.ts.map +++ b/packages/@expo/router-server/build/static/getServerManifest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../../src/static/getServerManifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAgBjC;AAED,uDAAuD;AACvD,wBAAsB,WAAW,CAAC,OAAO,GAAE,gBAAqB;;;GAgB/D"} \ No newline at end of file +{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../../src/static/getServerManifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAgBjC;AAED,uDAAuD;AACvD,wBAAsB,WAAW,CAAC,OAAO,GAAE,gBAAqB;;;GAkB/D"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/static/getServerManifest.js b/packages/@expo/router-server/build/static/getServerManifest.js index 729fc1c8532027..b0f876ad363ed6 100644 --- a/packages/@expo/router-server/build/static/getServerManifest.js +++ b/packages/@expo/router-server/build/static/getServerManifest.js @@ -24,13 +24,14 @@ async function getBuildTimeServerManifestAsync(options = {}) { platform: 'web', ...options, }); - if (!routeTree) { - throw new Error('No routes found'); - } // Evaluate all static params; skip for SSR mode where routes are matched at runtime - if (!options.skipStaticParams) { + if (routeTree && !options.skipStaticParams) { await (0, loadStaticParamsAsync_1.loadStaticParamsAsync)(routeTree); } + // NOTE(@kitten): The route tree can be `null` and should be accepted if the app + // has no route tree set up. This can happen when we build against a project that + // isn't an expo-router project or not fully set up yet, but has expo-router options + // in the app.json already return (0, getServerManifest_1.getServerManifest)(routeTree, options); } /** Get the linking manifest from a Node.js process. */ @@ -41,11 +42,14 @@ async function getManifest(options = {}) { platform: 'web', ...options, }); - if (!routeTree) { - throw new Error('No routes found'); + if (routeTree) { + // Evaluate all static params + await (0, loadStaticParamsAsync_1.loadStaticParamsAsync)(routeTree); } - // Evaluate all static params - await (0, loadStaticParamsAsync_1.loadStaticParamsAsync)(routeTree); + // NOTE(@kitten): The route tree can be `null` and should be accepted if the app + // has no route tree set up. This can happen when we build against a project that + // isn't an expo-router project or not fully set up yet, but has expo-router options + // in the app.json already return (0, routing_1.getReactNavigationConfig)(routeTree, false); } //# sourceMappingURL=getServerManifest.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/static/getServerManifest.js.map b/packages/@expo/router-server/build/static/getServerManifest.js.map index 3b043c8dc2f31a..bc5a825f64cab6 100644 --- a/packages/@expo/router-server/build/static/getServerManifest.js.map +++ b/packages/@expo/router-server/build/static/getServerManifest.js.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../../src/static/getServerManifest.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAoBH,0EAkBC;AAGD,kCAgBC;AAvDD,2CAAuC;AACvC,0DAIsC;AAGtC,4DAAyD;AACzD,oEAAiE;AAEjE;;;;;;GAMG;AACI,KAAK,UAAU,+BAA+B,CACnD,UAA4B,EAAE;IAE9B,MAAM,SAAS,GAAG,IAAA,mBAAS,EAAC,UAAG,EAAE;QAC/B,QAAQ,EAAE,KAAK;QACf,GAAG,OAAO;KACX,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,oFAAoF;IACpF,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAC9B,MAAM,IAAA,6CAAqB,EAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,IAAA,qCAAiB,EAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,uDAAuD;AAChD,KAAK,UAAU,WAAW,CAAC,UAA4B,EAAE;IAC9D,MAAM,SAAS,GAAG,IAAA,mBAAS,EAAC,UAAG,EAAE;QAC/B,iBAAiB,EAAE,IAAI;QACvB,2BAA2B,EAAE,IAAI;QACjC,QAAQ,EAAE,KAAK;QACf,GAAG,OAAO;KACX,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IAED,6BAA6B;IAC7B,MAAM,IAAA,6CAAqB,EAAC,SAAS,CAAC,CAAC;IAEvC,OAAO,IAAA,kCAAwB,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { ctx } from 'expo-router/_ctx';\nimport {\n getReactNavigationConfig,\n getRoutes,\n type GetRoutesOptions,\n} from 'expo-router/internal/routing';\nimport { type RoutesManifest } from 'expo-server/private';\n\nimport { getServerManifest } from '../getServerManifest';\nimport { loadStaticParamsAsync } from '../loadStaticParamsAsync';\n\n/**\n * Get the server manifest with all dynamic routes loaded with `generateStaticParams`.\n * Unlike the `@expo/router-server/src/routes-manifest.ts` method, this requires loading the entire app in-memory, which\n * takes substantially longer and requires Metro bundling.\n *\n * This is used for the production manifest where we pre-render certain pages and should no longer treat them as dynamic.\n */\nexport async function getBuildTimeServerManifestAsync(\n options: GetRoutesOptions = {}\n): Promise> {\n const routeTree = getRoutes(ctx, {\n platform: 'web',\n ...options,\n });\n\n if (!routeTree) {\n throw new Error('No routes found');\n }\n\n // Evaluate all static params; skip for SSR mode where routes are matched at runtime\n if (!options.skipStaticParams) {\n await loadStaticParamsAsync(routeTree);\n }\n\n return getServerManifest(routeTree, options);\n}\n\n/** Get the linking manifest from a Node.js process. */\nexport async function getManifest(options: GetRoutesOptions = {}) {\n const routeTree = getRoutes(ctx, {\n preserveApiRoutes: true,\n preserveRedirectAndRewrites: true,\n platform: 'web',\n ...options,\n });\n\n if (!routeTree) {\n throw new Error('No routes found');\n }\n\n // Evaluate all static params\n await loadStaticParamsAsync(routeTree);\n\n return getReactNavigationConfig(routeTree, false);\n}\n"]} \ No newline at end of file +{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../../src/static/getServerManifest.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAoBH,0EAkBC;AAGD,kCAkBC;AAzDD,2CAAuC;AACvC,0DAIsC;AAGtC,4DAAyD;AACzD,oEAAiE;AAEjE;;;;;;GAMG;AACI,KAAK,UAAU,+BAA+B,CACnD,UAA4B,EAAE;IAE9B,MAAM,SAAS,GAAG,IAAA,mBAAS,EAAC,UAAG,EAAE;QAC/B,QAAQ,EAAE,KAAK;QACf,GAAG,OAAO;KACX,CAAC,CAAC;IAEH,oFAAoF;IACpF,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAC3C,MAAM,IAAA,6CAAqB,EAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,gFAAgF;IAChF,iFAAiF;IACjF,oFAAoF;IACpF,0BAA0B;IAC1B,OAAO,IAAA,qCAAiB,EAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,uDAAuD;AAChD,KAAK,UAAU,WAAW,CAAC,UAA4B,EAAE;IAC9D,MAAM,SAAS,GAAG,IAAA,mBAAS,EAAC,UAAG,EAAE;QAC/B,iBAAiB,EAAE,IAAI;QACvB,2BAA2B,EAAE,IAAI;QACjC,QAAQ,EAAE,KAAK;QACf,GAAG,OAAO;KACX,CAAC,CAAC;IAEH,IAAI,SAAS,EAAE,CAAC;QACd,6BAA6B;QAC7B,MAAM,IAAA,6CAAqB,EAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,gFAAgF;IAChF,iFAAiF;IACjF,oFAAoF;IACpF,0BAA0B;IAC1B,OAAO,IAAA,kCAAwB,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { ctx } from 'expo-router/_ctx';\nimport {\n getReactNavigationConfig,\n getRoutes,\n type GetRoutesOptions,\n} from 'expo-router/internal/routing';\nimport { type RoutesManifest } from 'expo-server/private';\n\nimport { getServerManifest } from '../getServerManifest';\nimport { loadStaticParamsAsync } from '../loadStaticParamsAsync';\n\n/**\n * Get the server manifest with all dynamic routes loaded with `generateStaticParams`.\n * Unlike the `@expo/router-server/src/routes-manifest.ts` method, this requires loading the entire app in-memory, which\n * takes substantially longer and requires Metro bundling.\n *\n * This is used for the production manifest where we pre-render certain pages and should no longer treat them as dynamic.\n */\nexport async function getBuildTimeServerManifestAsync(\n options: GetRoutesOptions = {}\n): Promise> {\n const routeTree = getRoutes(ctx, {\n platform: 'web',\n ...options,\n });\n\n // Evaluate all static params; skip for SSR mode where routes are matched at runtime\n if (routeTree && !options.skipStaticParams) {\n await loadStaticParamsAsync(routeTree);\n }\n\n // NOTE(@kitten): The route tree can be `null` and should be accepted if the app\n // has no route tree set up. This can happen when we build against a project that\n // isn't an expo-router project or not fully set up yet, but has expo-router options\n // in the app.json already\n return getServerManifest(routeTree, options);\n}\n\n/** Get the linking manifest from a Node.js process. */\nexport async function getManifest(options: GetRoutesOptions = {}) {\n const routeTree = getRoutes(ctx, {\n preserveApiRoutes: true,\n preserveRedirectAndRewrites: true,\n platform: 'web',\n ...options,\n });\n\n if (routeTree) {\n // Evaluate all static params\n await loadStaticParamsAsync(routeTree);\n }\n\n // NOTE(@kitten): The route tree can be `null` and should be accepted if the app\n // has no route tree set up. This can happen when we build against a project that\n // isn't an expo-router project or not fully set up yet, but has expo-router options\n // in the app.json already\n return getReactNavigationConfig(routeTree, false);\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/src/getServerManifest.ts b/packages/@expo/router-server/src/getServerManifest.ts index eca7bcc87441fb..6d7f2a2cd724d3 100644 --- a/packages/@expo/router-server/src/getServerManifest.ts +++ b/packages/@expo/router-server/src/getServerManifest.ts @@ -58,7 +58,7 @@ type GetServerManifestOptions = { // Given a nested route tree, return a flattened array of all routes that can be matched. export function getServerManifest( - route: RouteNode, + route: RouteNode | null, options: GetServerManifestOptions | undefined ): RoutesManifest { function getFlatNodes(route: RouteNode, parentRoute: string = ''): FlatNode[] { @@ -89,9 +89,11 @@ export function getServerManifest( } // Remove duplicates from the runtime manifest which expands array syntax. - const flat = getFlatNodes(route) - .sort(({ route: a }, { route: b }) => sortRoutes(b, a)) - .reverse(); + const flat = route + ? getFlatNodes(route) + .sort(({ route: a }, { route: b }) => sortRoutes(b, a)) + .reverse() + : []; const apiRoutes = uniqueBy( flat.filter(({ route }) => route.type === 'api'), @@ -150,7 +152,7 @@ export function getServerManifest( rewrites: getMatchableManifestForPaths(rewrites), }; - if (route.middleware) { + if (route?.middleware) { manifest.middleware = { file: route.middleware.contextKey, }; diff --git a/packages/@expo/router-server/src/static/getServerManifest.ts b/packages/@expo/router-server/src/static/getServerManifest.ts index e001dde1afdaaf..4a1e964ed18ba8 100644 --- a/packages/@expo/router-server/src/static/getServerManifest.ts +++ b/packages/@expo/router-server/src/static/getServerManifest.ts @@ -31,15 +31,15 @@ export async function getBuildTimeServerManifestAsync( ...options, }); - if (!routeTree) { - throw new Error('No routes found'); - } - // Evaluate all static params; skip for SSR mode where routes are matched at runtime - if (!options.skipStaticParams) { + if (routeTree && !options.skipStaticParams) { await loadStaticParamsAsync(routeTree); } + // NOTE(@kitten): The route tree can be `null` and should be accepted if the app + // has no route tree set up. This can happen when we build against a project that + // isn't an expo-router project or not fully set up yet, but has expo-router options + // in the app.json already return getServerManifest(routeTree, options); } @@ -52,12 +52,14 @@ export async function getManifest(options: GetRoutesOptions = {}) { ...options, }); - if (!routeTree) { - throw new Error('No routes found'); + if (routeTree) { + // Evaluate all static params + await loadStaticParamsAsync(routeTree); } - // Evaluate all static params - await loadStaticParamsAsync(routeTree); - + // NOTE(@kitten): The route tree can be `null` and should be accepted if the app + // has no route tree set up. This can happen when we build against a project that + // isn't an expo-router project or not fully set up yet, but has expo-router options + // in the app.json already return getReactNavigationConfig(routeTree, false); } diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index 34987a66b0f97c..07c4e1a47d16b0 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -14,6 +14,7 @@ - Remove `expo-router/doctor` since this is covered by `expo-doctor`'s duplicates check now ([#43461](https://github.com/expo/expo/pull/43461) by [@kitten](https://github.com/kitten)) - use KVC to provide native API compatible with both react-native-screens 4.24.0 and 4.23.0 ([#43576](https://github.com/expo/expo/pull/43576) by [@Ubax](https://github.com/Ubax)) +- Handle empty routes manifest gracefully ([#43606](https://github.com/expo/expo/pull/43606) by [@kitten](https://github.com/kitten)) ## 55.0.2 — 2026-02-25 diff --git a/packages/expo-router/build/getReactNavigationConfig.d.ts b/packages/expo-router/build/getReactNavigationConfig.d.ts index 1ae2a1e4bc335c..b2b7b8b1d79b1e 100644 --- a/packages/expo-router/build/getReactNavigationConfig.d.ts +++ b/packages/expo-router/build/getReactNavigationConfig.d.ts @@ -7,7 +7,7 @@ export type Screen = string | { }; export declare function parseRouteSegments(segments: string): string; export declare function getReactNavigationScreensConfig(nodes: RouteNode[], metaOnly: boolean): Record; -export declare function getReactNavigationConfig(routes: RouteNode, metaOnly: boolean): { +export declare function getReactNavigationConfig(routeTree: RouteNode | null, metaOnly: boolean): { initialRouteName: undefined; screens: Record; }; diff --git a/packages/expo-router/build/getReactNavigationConfig.d.ts.map b/packages/expo-router/build/getReactNavigationConfig.d.ts.map index 9bb51e3788b8ba..2641d85571764c 100644 --- a/packages/expo-router/build/getReactNavigationConfig.d.ts.map +++ b/packages/expo-router/build/getReactNavigationConfig.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"getReactNavigationConfig.d.ts","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,MAAM,GACd,MAAM,GACN;IACE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBN,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc3D;AAoCD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO;;;EAY5E"} \ No newline at end of file +{"version":3,"file":"getReactNavigationConfig.d.ts","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,MAAM,GACd,MAAM,GACN;IACE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBN,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc3D;AAoCD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO;;;EAatF"} \ No newline at end of file diff --git a/packages/expo-router/build/getReactNavigationConfig.js b/packages/expo-router/build/getReactNavigationConfig.js index 3c025a01391141..e5eb9ffb20c0ed 100644 --- a/packages/expo-router/build/getReactNavigationConfig.js +++ b/packages/expo-router/build/getReactNavigationConfig.js @@ -71,15 +71,15 @@ function convertRouteNodeToScreen(node, metaOnly) { function getReactNavigationScreensConfig(nodes, metaOnly) { return Object.fromEntries(nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)])); } -function getReactNavigationConfig(routes, metaOnly) { +function getReactNavigationConfig(routeTree, metaOnly) { const config = { initialRouteName: undefined, - screens: getReactNavigationScreensConfig(routes.children, metaOnly), + screens: routeTree ? getReactNavigationScreensConfig(routeTree.children, metaOnly) : {}, }; - if (routes.initialRouteName) { + if (routeTree?.initialRouteName) { // We're using LinkingOptions the generic type is `object` instead of a proper ParamList. // So we need to cast the initialRouteName to `any` to avoid type errors. - config.initialRouteName = routes.initialRouteName; + config.initialRouteName = routeTree.initialRouteName; } return config; } diff --git a/packages/expo-router/build/getReactNavigationConfig.js.map b/packages/expo-router/build/getReactNavigationConfig.js.map index 25da9a11a5b468..8e845b8ec24864 100644 --- a/packages/expo-router/build/getReactNavigationConfig.js.map +++ b/packages/expo-router/build/getReactNavigationConfig.js.map @@ -1 +1 @@ -{"version":3,"file":"getReactNavigationConfig.js","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":";;AAgCA,gDAcC;AAoCD,0EAOC;AAED,4DAYC;AAtGD,yCAA8C;AAW9C,sBAAsB;AACtB,mBAAmB;AACnB,SAAS,oCAAoC,CAAC,OAAe;IAC3D,wEAAwE;IACxE,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,MAAM,WAAW,GAAG,IAAA,2BAAgB,EAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;SAAM,IAAI,WAAW,EAAE,IAAI,EAAE,CAAC;QAC7B,OAAO,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAgB,kBAAkB,CAAC,QAAgB;IACjD,OAAO;IACL,gEAAgE;IAChE,yDAAyD;IACzD,qEAAqE;IACrE,QAAQ;SACL,KAAK,CAAC,GAAG,CAAC;QACX,qDAAqD;SACpD,GAAG,CAAC,oCAAoC,CAAC;QAC1C,sDAAsD;SACrD,MAAM,CAAC,OAAO,CAAC;QAChB,4BAA4B;SAC3B,IAAI,CAAC,GAAG,CAAC,CACb,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAe,EAAE,QAAiB;IAClE,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,IAAI;aACb,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,+BAA+B,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEzE,MAAM,MAAM,GAAW;QACrB,IAAI;QACJ,OAAO;KACR,CAAC;IAEF,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,mEAAmE;QACnE,kEAAkE;QAClE,6EAA6E;QAC7E,2CAA2C;QAC3C,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC;IAClD,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAgB,+BAA+B,CAC7C,KAAkB,EAClB,QAAiB;IAEjB,OAAO,MAAM,CAAC,WAAW,CACvB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,wBAAwB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAU,CAAC,CACrF,CAAC;AACJ,CAAC;AAED,SAAgB,wBAAwB,CAAC,MAAiB,EAAE,QAAiB;IAC3E,MAAM,MAAM,GAAG;QACb,gBAAgB,EAAE,SAAS;QAC3B,OAAO,EAAE,+BAA+B,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC;KACpE,CAAC;IAEF,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC5B,yFAAyF;QACzF,yEAAyE;QACzE,MAAM,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAuB,CAAC;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RouteNode } from './Route';\nimport { matchDynamicName } from './matchers';\n\nexport type Screen =\n | string\n | {\n path: string;\n screens: Record;\n _route?: RouteNode;\n initialRouteName?: string;\n };\n\n// `[page]` -> `:page`\n// `page` -> `page`\nfunction convertDynamicRouteToReactNavigation(segment: string): string {\n // NOTE(EvanBacon): To support shared routes we preserve group segments.\n if (segment === 'index') {\n return '';\n }\n if (segment === '+not-found') {\n return '*not-found';\n }\n const dynamicName = matchDynamicName(segment);\n if (dynamicName && !dynamicName.deep) {\n return `:${dynamicName.name}`;\n } else if (dynamicName?.deep) {\n return '*' + dynamicName.name;\n } else {\n return segment;\n }\n}\n\nexport function parseRouteSegments(segments: string): string {\n return (\n // NOTE(EvanBacon): When there are nested routes without layouts\n // the node.route will be something like `app/home/index`\n // this needs to be split to ensure each segment is parsed correctly.\n segments\n .split('/')\n // Convert each segment to a React Navigation format.\n .map(convertDynamicRouteToReactNavigation)\n // Remove any empty paths from groups or index routes.\n .filter(Boolean)\n // Join to return as a path.\n .join('/')\n );\n}\n\nfunction convertRouteNodeToScreen(node: RouteNode, metaOnly: boolean): Screen {\n const path = parseRouteSegments(node.route);\n if (!node.children.length) {\n if (!metaOnly) {\n return {\n path,\n screens: {},\n _route: node,\n };\n }\n return path;\n }\n const screens = getReactNavigationScreensConfig(node.children, metaOnly);\n\n const screen: Screen = {\n path,\n screens,\n };\n\n if (node.initialRouteName) {\n // NOTE(EvanBacon): This is bad because it forces all Layout Routes\n // to be loaded into memory. We should move towards a system where\n // the initial route name is either loaded asynchronously in the Layout Route\n // or defined via a file system convention.\n screen.initialRouteName = node.initialRouteName;\n }\n\n if (!metaOnly) {\n screen._route = node;\n }\n\n return screen;\n}\n\nexport function getReactNavigationScreensConfig(\n nodes: RouteNode[],\n metaOnly: boolean\n): Record {\n return Object.fromEntries(\n nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)] as const)\n );\n}\n\nexport function getReactNavigationConfig(routes: RouteNode, metaOnly: boolean) {\n const config = {\n initialRouteName: undefined,\n screens: getReactNavigationScreensConfig(routes.children, metaOnly),\n };\n\n if (routes.initialRouteName) {\n // We're using LinkingOptions the generic type is `object` instead of a proper ParamList.\n // So we need to cast the initialRouteName to `any` to avoid type errors.\n config.initialRouteName = routes.initialRouteName as any;\n }\n return config;\n}\n"]} \ No newline at end of file +{"version":3,"file":"getReactNavigationConfig.js","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RouteNode } from './Route';\nimport { matchDynamicName } from './matchers';\n\nexport type Screen =\n | string\n | {\n path: string;\n screens: Record;\n _route?: RouteNode;\n initialRouteName?: string;\n };\n\n// `[page]` -> `:page`\n// `page` -> `page`\nfunction convertDynamicRouteToReactNavigation(segment: string): string {\n // NOTE(EvanBacon): To support shared routes we preserve group segments.\n if (segment === 'index') {\n return '';\n }\n if (segment === '+not-found') {\n return '*not-found';\n }\n const dynamicName = matchDynamicName(segment);\n if (dynamicName && !dynamicName.deep) {\n return `:${dynamicName.name}`;\n } else if (dynamicName?.deep) {\n return '*' + dynamicName.name;\n } else {\n return segment;\n }\n}\n\nexport function parseRouteSegments(segments: string): string {\n return (\n // NOTE(EvanBacon): When there are nested routes without layouts\n // the node.route will be something like `app/home/index`\n // this needs to be split to ensure each segment is parsed correctly.\n segments\n .split('/')\n // Convert each segment to a React Navigation format.\n .map(convertDynamicRouteToReactNavigation)\n // Remove any empty paths from groups or index routes.\n .filter(Boolean)\n // Join to return as a path.\n .join('/')\n );\n}\n\nfunction convertRouteNodeToScreen(node: RouteNode, metaOnly: boolean): Screen {\n const path = parseRouteSegments(node.route);\n if (!node.children.length) {\n if (!metaOnly) {\n return {\n path,\n screens: {},\n _route: node,\n };\n }\n return path;\n }\n const screens = getReactNavigationScreensConfig(node.children, metaOnly);\n\n const screen: Screen = {\n path,\n screens,\n };\n\n if (node.initialRouteName) {\n // NOTE(EvanBacon): This is bad because it forces all Layout Routes\n // to be loaded into memory. We should move towards a system where\n // the initial route name is either loaded asynchronously in the Layout Route\n // or defined via a file system convention.\n screen.initialRouteName = node.initialRouteName;\n }\n\n if (!metaOnly) {\n screen._route = node;\n }\n\n return screen;\n}\n\nexport function getReactNavigationScreensConfig(\n nodes: RouteNode[],\n metaOnly: boolean\n): Record {\n return Object.fromEntries(\n nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)] as const)\n );\n}\n\nexport function getReactNavigationConfig(routeTree: RouteNode | null, metaOnly: boolean) {\n const config = {\n initialRouteName: undefined,\n screens: routeTree ? getReactNavigationScreensConfig(routeTree.children, metaOnly) : {},\n };\n\n if (routeTree?.initialRouteName) {\n // We're using LinkingOptions the generic type is `object` instead of a proper ParamList.\n // So we need to cast the initialRouteName to `any` to avoid type errors.\n config.initialRouteName = routeTree.initialRouteName as any;\n }\n\n return config;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/src/getReactNavigationConfig.ts b/packages/expo-router/src/getReactNavigationConfig.ts index d689943a57bf95..de731530ca7fb2 100644 --- a/packages/expo-router/src/getReactNavigationConfig.ts +++ b/packages/expo-router/src/getReactNavigationConfig.ts @@ -89,16 +89,17 @@ export function getReactNavigationScreensConfig( ); } -export function getReactNavigationConfig(routes: RouteNode, metaOnly: boolean) { +export function getReactNavigationConfig(routeTree: RouteNode | null, metaOnly: boolean) { const config = { initialRouteName: undefined, - screens: getReactNavigationScreensConfig(routes.children, metaOnly), + screens: routeTree ? getReactNavigationScreensConfig(routeTree.children, metaOnly) : {}, }; - if (routes.initialRouteName) { + if (routeTree?.initialRouteName) { // We're using LinkingOptions the generic type is `object` instead of a proper ParamList. // So we need to cast the initialRouteName to `any` to avoid type errors. - config.initialRouteName = routes.initialRouteName as any; + config.initialRouteName = routeTree.initialRouteName as any; } + return config; } From e0e0df7cd808c4c53bf1e9a11cd8f568bcf8990e Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:16:54 +0100 Subject: [PATCH 09/13] [sdk-56] Upgrade react-native-screens to 4.24.0 (#43469) # Why Reverts: - https://github.com/expo/expo/commit/261916942287d8626a21cf035d98f161a74ada68 - https://github.com/expo/expo/commit/5b34e1db573af84d0503b1db169a181d5852c896 # How # Test Plan 1. Clean repo `git clean -fdx` 2. Build expo-go & test it with latest SDK-55 example (with screens bumped to 4.24.0) 4. Build and test bare-expo 5. Build router-e2e and test # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- apps/bare-expo/ios/Podfile.lock | 8 ++++---- apps/bare-expo/package.json | 2 +- apps/brownfield-tester/expo-app/package.json | 2 +- apps/expo-go/ios/Podfile.lock | 8 ++++---- apps/expo-go/package.json | 2 +- apps/native-component-list/package.json | 2 +- apps/notification-tester/package.json | 2 +- apps/router-e2e/package.json | 2 +- .../e2e/fixtures/with-monorepo/apps/app-a/package.json | 2 +- .../e2e/fixtures/with-monorepo/apps/app-b/package.json | 2 +- .../e2e/fixtures/with-router-typed-routes/package.json | 2 +- .../@expo/cli/e2e/fixtures/with-router/package.json | 2 +- packages/expo-router/CHANGELOG.md | 2 ++ packages/expo-router/package.json | 3 ++- packages/expo/bundledNativeModules.json | 2 +- templates/expo-template-default/package.json | 2 +- templates/expo-template-tabs/package.json | 2 +- yarn.lock | 10 +++++++++- 18 files changed, 34 insertions(+), 23 deletions(-) diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index a4f3ec64f6ff27..25512c99c031cc 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -2958,7 +2958,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNScreens (4.23.0): + - RNScreens (4.24.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2980,9 +2980,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNScreens/common (= 4.23.0) + - RNScreens/common (= 4.24.0) - Yoga - - RNScreens/common (4.23.0): + - RNScreens/common (4.24.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3995,7 +3995,7 @@ SPEC CHECKSUMS: RNDateTimePicker: 5e0666de98f1edfac67ee7dde6be8a5415e487a0 RNGestureHandler: 6d378fd1aa991c7ab62a4215ee6cc417895a6954 RNReanimated: 752b27ede7d3f8970d5adba71f10258cb7848150 - RNScreens: fb11b7412bcbdc0ffafcaf9174938d998d4e2bc4 + RNScreens: 088d923c4327c63c9f8c942cae17a9d038f47d97 RNSVG: 13970bfde0ea9c9e10e01ab0d7b4a6cde11fca1b RNWorklets: 460112a8b250bcecd1b6b68d39a82fcdb6f8eb24 SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index d21b66cb5be811..2f45b221bc3ccb 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -76,7 +76,7 @@ "react-native-reanimated": "4.2.2", "react-native-safe-area-context": "5.6.2", "react-native-svg": "15.15.3", - "react-native-screens": "4.23.0", + "react-native-screens": "4.24.0", "react-native-view-shot": "4.0.3", "react-native-webview": "13.16.0", "react-native-worklets": "0.7.4", diff --git a/apps/brownfield-tester/expo-app/package.json b/apps/brownfield-tester/expo-app/package.json index 12c7a2189e8147..3663043ac135ac 100644 --- a/apps/brownfield-tester/expo-app/package.json +++ b/apps/brownfield-tester/expo-app/package.json @@ -35,7 +35,7 @@ "react-native-worklets": "0.7.4", "react-native-reanimated": "~4.2.2", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" }, "devDependencies": { diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index ecc873614fced4..79f984880e5dfb 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -3739,7 +3739,7 @@ PODS: - RNWorklets - SocketRocket - Yoga - - RNScreens (4.23.0): + - RNScreens (4.24.0): - boost - DoubleConversion - fast_float @@ -3766,10 +3766,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.23.0) + - RNScreens/common (= 4.24.0) - SocketRocket - Yoga - - RNScreens/common (4.23.0): + - RNScreens/common (4.24.0): - boost - DoubleConversion - fast_float @@ -4868,7 +4868,7 @@ SPEC CHECKSUMS: RNDateTimePicker: e9e210197c267461f70f3f47bec705401ff72077 RNGestureHandler: 0ac42879420bf5353c06174d14af11f6435f3294 RNReanimated: f60cb180f1540dde5e14d1fdc09d20a3552ee3ff - RNScreens: ec8bdc9f024d5828e5adf4f5e8870d5260cff616 + RNScreens: 7179cc1ba31b4e18ed29f10abf20c24a7961cf4c RNSVG: 15c97a81fe80139227609070b1c11a86dce5c9b6 RNWorklets: 7f524b9625dc3d9f1014ae6529fdd25a0141c922 SDWebImage: f29024626962457f3470184232766516dee8dfea diff --git a/apps/expo-go/package.json b/apps/expo-go/package.json index edbb9c61d5cec5..abac5a731903d2 100644 --- a/apps/expo-go/package.json +++ b/apps/expo-go/package.json @@ -78,7 +78,7 @@ "react-native-reanimated": "4.2.2", "react-native-safe-area-context": "5.6.2", "react-native-svg": "15.15.3", - "react-native-screens": "4.23.0", + "react-native-screens": "4.24.0", "react-native-view-shot": "4.0.3", "react-native-webview": "13.16.0", "react-native-worklets": "0.7.4", diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 1b96e3f4a7db11..e589d16623c964 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -153,7 +153,7 @@ "react-native-reanimated": "4.2.2", "react-native-safe-area-context": "5.6.2", "react-native-svg": "15.15.3", - "react-native-screens": "4.23.0", + "react-native-screens": "4.24.0", "react-native-view-shot": "4.0.3", "react-native-web": "~0.21.0", "react-native-webview": "13.16.0", diff --git a/apps/notification-tester/package.json b/apps/notification-tester/package.json index 41ecf691a187d3..120285a4e5aa2c 100644 --- a/apps/notification-tester/package.json +++ b/apps/notification-tester/package.json @@ -36,7 +36,7 @@ "react": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "5.6.2", - "react-native-screens": "4.23.0" + "react-native-screens": "4.24.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/apps/router-e2e/package.json b/apps/router-e2e/package.json index d9626822cfe500..d1de2b19ceb1ca 100644 --- a/apps/router-e2e/package.json +++ b/apps/router-e2e/package.json @@ -73,7 +73,7 @@ "react": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "5.6.2", - "react-native-screens": "4.23.0", + "react-native-screens": "4.24.0", "react-native-webview": "13.16.0" }, "devDependencies": { diff --git a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json index b969269fb6521f..11aace6755d7da 100644 --- a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json +++ b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json @@ -13,7 +13,7 @@ "react-dom": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" } } diff --git a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json index ec586431f277a9..2bcb490a095804 100644 --- a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json +++ b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json @@ -13,7 +13,7 @@ "react-dom": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" } } diff --git a/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json b/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json index f8590b1467b2af..f20863f2573b90 100644 --- a/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json +++ b/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json @@ -13,7 +13,7 @@ "react-dom": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" } } diff --git a/packages/@expo/cli/e2e/fixtures/with-router/package.json b/packages/@expo/cli/e2e/fixtures/with-router/package.json index bc0cf4791c888a..201528654a364a 100644 --- a/packages/@expo/cli/e2e/fixtures/with-router/package.json +++ b/packages/@expo/cli/e2e/fixtures/with-router/package.json @@ -12,7 +12,7 @@ "react-dom": "19.2.3", "react-native": "0.84.1", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" } } diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index 07c4e1a47d16b0..79c2e01a0bc3fb 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -39,6 +39,8 @@ _This version does not introduce any user-facing changes._ ### 💡 Others +- Pin screens to 4.24.0 ([#43379](https://github.com/expo/expo/pull/43379) by [@Ubax](https://github.com/Ubax)) + ## 55.0.0-preview.9 — 2026-02-20 ### 🎉 New features diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json index 2345d9de8713e0..d89b68896c06db 100644 --- a/packages/expo-router/package.json +++ b/packages/expo-router/package.json @@ -94,7 +94,7 @@ "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", - "react-native-screens": "*", + "react-native-screens": "~4.24.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, @@ -151,6 +151,7 @@ "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.2.1", + "react-native-screens": "~4.24.0", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 9e1a0e5393a2b2..da231820b1ebe9 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -105,7 +105,7 @@ "react-native-pager-view": "8.0.0", "react-native-worklets": "0.7.4", "react-native-reanimated": "4.2.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-safe-area-context": "~5.6.2", "react-native-svg": "15.15.3", "react-native-view-shot": "4.0.3", diff --git a/templates/expo-template-default/package.json b/templates/expo-template-default/package.json index 4a29a4796a31d5..5a1ab9623a3393 100644 --- a/templates/expo-template-default/package.json +++ b/templates/expo-template-default/package.json @@ -35,7 +35,7 @@ "react-native-worklets": "0.7.4", "react-native-reanimated": "4.2.2", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" }, "devDependencies": { diff --git a/templates/expo-template-tabs/package.json b/templates/expo-template-tabs/package.json index b4d728a0cecd33..7043863ca394aa 100644 --- a/templates/expo-template-tabs/package.json +++ b/templates/expo-template-tabs/package.json @@ -27,7 +27,7 @@ "react-native-worklets": "0.7.4", "react-native-reanimated": "4.2.2", "react-native-safe-area-context": "~5.6.2", - "react-native-screens": "~4.23.0", + "react-native-screens": "~4.24.0", "react-native-web": "~0.21.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 6502ef09b19e97..f7ffc741b31832 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13311,7 +13311,15 @@ react-native-safe-area-context@5.6.2, react-native-safe-area-context@~5.6.2: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz#283e006f5b434fb247fcb4be0971ad7473d5c560" integrity sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg== -react-native-screens@4.23.0, react-native-screens@~4.23.0: +react-native-screens@4.24.0, react-native-screens@~4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.24.0.tgz#dbe8f610b5d2e31f71425d2ea3caaff1b9a7e2de" + integrity sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA== + dependencies: + react-freeze "^1.0.0" + warn-once "^0.1.0" + +react-native-screens@~4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.23.0.tgz#81574b1b0cc4ac6c9ed63e46eca7126f37affe86" integrity sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw== From 65a28854a4c8acf9439948bb2ea1d93d5c9e75bf Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Fri, 6 Mar 2026 01:55:02 +0530 Subject: [PATCH 10/13] [docs] Fix broken Firebase documentation links (#43695) Co-authored-by: Aman Mittal <10234615+amandeepmittal@users.noreply.github.com> --- .../sending-notifications-custom-fcm-legacy.mdx | 4 ++-- .../push-notifications/sending-notifications-custom.mdx | 4 ++-- docs/pages/push-notifications/what-you-need-to-know.mdx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx b/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx index 459cf2507bda96..0c49f5ff42978b 100644 --- a/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx +++ b/docs/pages/archive/push-notifications/sending-notifications-custom-fcm-legacy.mdx @@ -34,7 +34,7 @@ await fetch('https://fcm.googleapis.com/fcm/send', { **The `experienceId` and `scopeKey` fields are required**. Otherwise, your notifications will not go through to your app. FCM has a list of supported fields in the [notification payload](https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support), and you can see which ones are supported by `expo-notifications` on Android by looking at the [FirebaseRemoteMessage](/versions/latest/sdk/notifications/#firebaseremotemessage). -FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send-message#node.js) you can use instead of raw `fetch` requests. +FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send/admin-sdk) you can use instead of raw `fetch` requests. ### How to find FCM server key @@ -67,7 +67,7 @@ Your FCM server key can be found by making sure you've followed the [configurati ### Firebase notification types -There are two types of Firebase Cloud Messaging messages: [notification and data messages](https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages). +There are two types of Firebase Cloud Messaging messages: [notification and data messages](https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type#notification-messages-with-optional-data-payload). 1. **Notification** messages are only handled (and displayed) by the Firebase library. They don't necessarily wake the app, and `expo-notifications` will not be made aware that your app has received any notification. diff --git a/docs/pages/push-notifications/sending-notifications-custom.mdx b/docs/pages/push-notifications/sending-notifications-custom.mdx index ad89b1a6ffbb4d..7fe9fbc7ae4830 100644 --- a/docs/pages/push-notifications/sending-notifications-custom.mdx +++ b/docs/pages/push-notifications/sending-notifications-custom.mdx @@ -37,7 +37,7 @@ Communicating with FCM is done by sending a POST request. However, before sendin ### Getting an authentication token -FCM requires an Oauth 2.0 access token, which must be obtained via one of the methods described in ["Update authorization of send requests"](https://firebase.google.com/docs/cloud-messaging/migrate-v1#update-authorization-of-send-requests). +FCM requires an Oauth 2.0 access token, which must be obtained via one of the methods described in ["Update authorization of send requests"](https://firebase.google.com/docs/cloud-messaging/send/v1-api#authorize-http-v1-send-requests). For testing purposes, you can use the Google Auth Library and your private key file obtained above, to obtain a short lived token for a single notification, as in this Node example adapted from Firebase documentation: @@ -117,7 +117,7 @@ async function sendFCMv1Notification() { The `experienceId` and `scopeKey` fields are only applicable when using Expo Go (from SDK 53, push notifications support is removed from Expo Go). Otherwise, your notifications will not go through to your app. FCM has a list of supported fields in the [notification payload](https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support), and you can see which ones are supported by `expo-notifications` on Android by looking at the [FirebaseRemoteMessage](/versions/latest/sdk/notifications/#firebaseremotemessage). -FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send-message#node.js) you can use instead of raw `fetch` requests. +FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send/admin-sdk) you can use instead of raw `fetch` requests. ### How to find FCM server key diff --git a/docs/pages/push-notifications/what-you-need-to-know.mdx b/docs/pages/push-notifications/what-you-need-to-know.mdx index 5bfea16d70e342..bc8ec7bb1beee6 100644 --- a/docs/pages/push-notifications/what-you-need-to-know.mdx +++ b/docs/pages/push-notifications/what-you-need-to-know.mdx @@ -69,7 +69,7 @@ The typical use case for a Notification Message is to have it presented to the u ### Notification Message with data payload -This is an Android-only term ([see the official docs](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages)) where a push notification request contains both `data` field and a `notification` field. +This is an Android-only term ([see the official docs](https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type#data-messages)) where a push notification request contains both `data` field and a `notification` field. On iOS, extra data may be part of a regular Notification Message request. Apple doesn't distinguish between Notification Message which does and does not carry data. @@ -77,7 +77,7 @@ On iOS, extra data may be part of a regular Notification Message request. Apple Headless Notification is a remote notification that doesn't directly specify presentational information such as the title or body text. With the exception below\*, headless notifications are not presented to users. Instead, they carry data (JSON) which is processed by a JavaScript task defined in your app via [`registerTaskAsync`](/versions/latest/sdk/notifications/#registertaskasynctaskname). The task may perform arbitrary logic. For example, write to `AsyncStorage`, make an api request, or present a local notification whose content is taken from the push notification's data. -> **info** We use the term "Headless Background Notification" to refer to the [Data Message](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) on Android and the [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) on iOS. Their key similarities are that both of these notification types allow sending only JSON data, and background processing by the app. +> **info** We use the term "Headless Background Notification" to refer to the [Data Message](https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type#data-messages) on Android and the [background notification](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#Create-a-background-notification) on iOS. Their key similarities are that both of these notification types allow sending only JSON data, and background processing by the app. Headless Background Notifications have the ability to run custom JavaScript in response to a notification _even when the app is terminated_. This is powerful but comes with a limitation: even when the notification is delivered to the device, the OS does not guarantee its delivery to your app. This may happen due to a variety of reasons, such as when [Doze mode](https://developer.android.com/training/monitoring-device-state/doze-standby) is enabled on Android, or when you send too many background notifications — Apple recommends not to [send more than two or three per hour](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app#overview). @@ -93,7 +93,7 @@ The rule of thumb is to prefer a regular Notification Message if you don't requi ### Data-only notifications -Android has a concept of [Data Messages](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages). iOS does not have exactly the same concept, but a close equivalent is [Headless Background Notifications](#headless-background-notifications). +Android has a concept of [Data Messages](https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type#data-messages). iOS does not have exactly the same concept, but a close equivalent is [Headless Background Notifications](#headless-background-notifications). You may also come across the term "silent notification", which is yet another name for notifications that don't present anything to the user — we describe these as [Headless Background Notifications](#headless-background-notifications). From 85ee937b5f45b486a0ab8cbb34e1af8191a7e1aa Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Fri, 6 Mar 2026 01:55:12 +0530 Subject: [PATCH 11/13] [docs] Fix misc external links in multiple docs (#43697) Co-authored-by: Aman Mittal <10234615+amandeepmittal@users.noreply.github.com> --- docs/pages/billing/plans.mdx | 2 +- docs/pages/guides/using-firebase.mdx | 2 +- docs/pages/guides/using-push-notifications-services.mdx | 2 +- docs/pages/modules/additional-platform-support.mdx | 2 +- docs/pages/versions/unversioned/sdk/print.mdx | 2 +- docs/pages/versions/v53.0.0/sdk/async-storage.mdx | 4 ++-- docs/pages/versions/v53.0.0/sdk/print.mdx | 2 +- docs/pages/versions/v54.0.0/sdk/async-storage.mdx | 4 ++-- docs/pages/versions/v54.0.0/sdk/print.mdx | 2 +- docs/pages/versions/v55.0.0/sdk/print.mdx | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/pages/billing/plans.mdx b/docs/pages/billing/plans.mdx index 2ad80bab399f8a..9bd485e469310c 100644 --- a/docs/pages/billing/plans.mdx +++ b/docs/pages/billing/plans.mdx @@ -92,6 +92,6 @@ The Enterprise Support add-on is only available for new Enterprise plan subscrib diff --git a/docs/pages/guides/using-firebase.mdx b/docs/pages/guides/using-firebase.mdx index 478937fc4b6ec3..d0d2824c80d0ae 100644 --- a/docs/pages/guides/using-firebase.mdx +++ b/docs/pages/guides/using-firebase.mdx @@ -157,7 +157,7 @@ Each Firebase service is available as a module that can be added as a dependency You can consider using React Native Firebase when: -- Your app requires access to Firebase services not supported by the Firebase JS SDK, such as [Dynamic Links](https://rnfirebase.io/dynamic-links/usage), [Crashlytics](https://rnfirebase.io/crashlytics/usage), and so on. +- Your app requires access to Firebase services not supported by the Firebase JS SDK, such as [Dynamic Links](https://rnfirebase.io/screencasts/dynamic-links-overview), [Crashlytics](https://rnfirebase.io/crashlytics/usage), and so on. For more information on the additional capabilities offered by the native SDK's, see [React Native Firebase documentation](https://rnfirebase.io/faqs-and-tips#why-react-native-firebase-over-firebase-js-sdk). - You want to use native SDKs in your app. - You have a bare React Native app with React Native Firebase already configured but are migrating to use Expo SDK. diff --git a/docs/pages/guides/using-push-notifications-services.mdx b/docs/pages/guides/using-push-notifications-services.mdx index b3631f01426b1f..a243f726093af0 100644 --- a/docs/pages/guides/using-push-notifications-services.mdx +++ b/docs/pages/guides/using-push-notifications-services.mdx @@ -61,7 +61,7 @@ For implementation details, see the following guides: diff --git a/docs/pages/modules/additional-platform-support.mdx b/docs/pages/modules/additional-platform-support.mdx index 873d41cd24da6f..fb46a78170d1be 100644 --- a/docs/pages/modules/additional-platform-support.mdx +++ b/docs/pages/modules/additional-platform-support.mdx @@ -65,7 +65,7 @@ Any changes in the podspec require running `pod install` to have an effect. If you are writing a local module and your app is already set up, you can skip this step. Otherwise, you will need to set up your app or the example app if you are writing a standalone (non-local) module. -- **For macOS**: follow the official [Install React Native for macOS](https://microsoft.github.io/react-native-windows/docs/rnm-getting-started#install-the-macos-extension) guide from `react-native-macos` documentation. +- **For macOS**: follow the official [Install React Native for macOS](https://microsoft.github.io/react-native-macos/docs/getting-started) guide from `react-native-macos` documentation. - **For tvOS**: follow the instructions in the [`react-native-tvos`](https://github.com/react-native-tvos/react-native-tvos) repository. If you are building an Expo app, you should also follow the instructions in the [Build Expo apps for TV guide](/guides/building-for-tv/). diff --git a/docs/pages/versions/unversioned/sdk/print.mdx b/docs/pages/versions/unversioned/sdk/print.mdx index aaa4fbdb486db2..4e698bd2d9db53 100644 --- a/docs/pages/versions/unversioned/sdk/print.mdx +++ b/docs/pages/versions/unversioned/sdk/print.mdx @@ -37,7 +37,7 @@ const html = ` Hello Expo! diff --git a/docs/pages/versions/v53.0.0/sdk/async-storage.mdx b/docs/pages/versions/v53.0.0/sdk/async-storage.mdx index 2afe9fd1592ac8..96bc9e3c199c17 100644 --- a/docs/pages/versions/v53.0.0/sdk/async-storage.mdx +++ b/docs/pages/versions/v53.0.0/sdk/async-storage.mdx @@ -16,7 +16,7 @@ Async Storage is asynchronous, unencrypted, persistent, key-value storage soluti ## Installation - + ## Learn more @@ -24,5 +24,5 @@ Async Storage is asynchronous, unencrypted, persistent, key-value storage soluti title="Visit official documentation" description="Get full information on API and its usage." Icon={BookOpen02Icon} - href="https://react-native-async-storage.github.io/async-storage/docs/usage" + href="https://react-native-async-storage.github.io/3.0/api/usage/" /> diff --git a/docs/pages/versions/v53.0.0/sdk/print.mdx b/docs/pages/versions/v53.0.0/sdk/print.mdx index 9eeeaba4fc8ab3..5550d71c001984 100644 --- a/docs/pages/versions/v53.0.0/sdk/print.mdx +++ b/docs/pages/versions/v53.0.0/sdk/print.mdx @@ -37,7 +37,7 @@ const html = ` Hello Expo! diff --git a/docs/pages/versions/v54.0.0/sdk/async-storage.mdx b/docs/pages/versions/v54.0.0/sdk/async-storage.mdx index 2afe9fd1592ac8..96bc9e3c199c17 100644 --- a/docs/pages/versions/v54.0.0/sdk/async-storage.mdx +++ b/docs/pages/versions/v54.0.0/sdk/async-storage.mdx @@ -16,7 +16,7 @@ Async Storage is asynchronous, unencrypted, persistent, key-value storage soluti ## Installation - + ## Learn more @@ -24,5 +24,5 @@ Async Storage is asynchronous, unencrypted, persistent, key-value storage soluti title="Visit official documentation" description="Get full information on API and its usage." Icon={BookOpen02Icon} - href="https://react-native-async-storage.github.io/async-storage/docs/usage" + href="https://react-native-async-storage.github.io/3.0/api/usage/" /> diff --git a/docs/pages/versions/v54.0.0/sdk/print.mdx b/docs/pages/versions/v54.0.0/sdk/print.mdx index 1c33ffa2f179b4..4a173da02784ab 100644 --- a/docs/pages/versions/v54.0.0/sdk/print.mdx +++ b/docs/pages/versions/v54.0.0/sdk/print.mdx @@ -37,7 +37,7 @@ const html = ` Hello Expo! diff --git a/docs/pages/versions/v55.0.0/sdk/print.mdx b/docs/pages/versions/v55.0.0/sdk/print.mdx index aaa4fbdb486db2..4e698bd2d9db53 100644 --- a/docs/pages/versions/v55.0.0/sdk/print.mdx +++ b/docs/pages/versions/v55.0.0/sdk/print.mdx @@ -37,7 +37,7 @@ const html = ` Hello Expo! From 944aea76f9774a74bb2fe75d5c56d5cd45cb593a Mon Sep 17 00:00:00 2001 From: Aman Mittal Date: Fri, 6 Mar 2026 01:55:22 +0530 Subject: [PATCH 12/13] [docs] Fix broken Android developer docs links in Jetpack Compose (#43696) Co-authored-by: Aman Mittal <10234615+amandeepmittal@users.noreply.github.com> --- .../unversioned/sdk/ui/jetpack-compose/dockedsearchbar.mdx | 2 +- .../sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx | 2 +- .../versions/unversioned/sdk/ui/jetpack-compose/listitem.mdx | 2 +- .../versions/unversioned/sdk/ui/jetpack-compose/searchbar.mdx | 2 +- .../versions/unversioned/sdk/ui/jetpack-compose/surface.mdx | 2 +- .../versions/v55.0.0/sdk/ui/jetpack-compose/dockedsearchbar.mdx | 2 +- .../sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx | 2 +- docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/listitem.mdx | 2 +- .../pages/versions/v55.0.0/sdk/ui/jetpack-compose/searchbar.mdx | 2 +- docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/surface.mdx | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dockedsearchbar.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dockedsearchbar.mdx index 78de7aafad86fb..0f639afc1408a3 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dockedsearchbar.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dockedsearchbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI DockedSearchBar matches the official Jetpack Compose [SearchBar API](https://developer.android.com/develop/ui/compose/components/search) and displays a search input that remains anchored in its parent layout rather than expanding to full screen. +Expo UI DockedSearchBar matches the official Jetpack Compose [SearchBar API](https://developer.android.com/develop/ui/compose/components/search-bar) and displays a search input that remains anchored in its parent layout rather than expanding to full screen. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx index 9aca4fd622bf06..fe3b1338f26726 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI HorizontalFloatingToolbar matches the official Jetpack Compose [FloatingActionButton API](https://developer.android.com/develop/ui/compose/components/floating-action-button) and displays a horizontal toolbar that floats above content, containing action buttons. +Expo UI HorizontalFloatingToolbar matches the official Jetpack Compose [FloatingActionButton API](https://developer.android.com/develop/ui/compose/components/fab) and displays a horizontal toolbar that floats above content, containing action buttons. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/listitem.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/listitem.mdx index bdc4307573cd13..68933f86ca6c01 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/listitem.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/listitem.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -A list item component that follows Material Design 3 guidelines for structured list entries with headline, supporting text, and leading/trailing slots. See the [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/lists) for more information. +A list item component that follows Material Design 3 guidelines for structured list entries with headline, supporting text, and leading/trailing slots. See the [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/lists) for more information. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/searchbar.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/searchbar.mdx index e62c2248d751dc..526bc7349f537c 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/searchbar.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/searchbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI SearchBar matches the official Jetpack Compose [Search](https://developer.android.com/develop/ui/compose/components/search) API and provides a search input with support for placeholder text and expanded full-screen search. +Expo UI SearchBar matches the official Jetpack Compose [Search](https://developer.android.com/develop/ui/compose/components/search-bar) API and provides a search input with support for placeholder text and expanded full-screen search. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/surface.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/surface.mdx index baafb447d46aec..75a4a053dc5fb4 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/surface.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/surface.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI Surface matches the official Jetpack Compose [Surface](https://developer.android.com/develop/ui/compose/components/surfaces) API and provides a container that applies Material Design surface styling including color, elevation, and content color. +Expo UI Surface matches the official Jetpack Compose [Surface](https://developer.android.com/develop/ui/compose/designsystems/material3) API and provides a container that applies Material Design surface styling including color, elevation, and content color. ## Installation diff --git a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/dockedsearchbar.mdx b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/dockedsearchbar.mdx index 78de7aafad86fb..0f639afc1408a3 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/dockedsearchbar.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/dockedsearchbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI DockedSearchBar matches the official Jetpack Compose [SearchBar API](https://developer.android.com/develop/ui/compose/components/search) and displays a search input that remains anchored in its parent layout rather than expanding to full screen. +Expo UI DockedSearchBar matches the official Jetpack Compose [SearchBar API](https://developer.android.com/develop/ui/compose/components/search-bar) and displays a search input that remains anchored in its parent layout rather than expanding to full screen. ## Installation diff --git a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx index 9aca4fd622bf06..fe3b1338f26726 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/horizontalfloatingtoolbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI HorizontalFloatingToolbar matches the official Jetpack Compose [FloatingActionButton API](https://developer.android.com/develop/ui/compose/components/floating-action-button) and displays a horizontal toolbar that floats above content, containing action buttons. +Expo UI HorizontalFloatingToolbar matches the official Jetpack Compose [FloatingActionButton API](https://developer.android.com/develop/ui/compose/components/fab) and displays a horizontal toolbar that floats above content, containing action buttons. ## Installation diff --git a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/listitem.mdx b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/listitem.mdx index bdc4307573cd13..68933f86ca6c01 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/listitem.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/listitem.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -A list item component that follows Material Design 3 guidelines for structured list entries with headline, supporting text, and leading/trailing slots. See the [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/lists) for more information. +A list item component that follows Material Design 3 guidelines for structured list entries with headline, supporting text, and leading/trailing slots. See the [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/lists) for more information. ## Installation diff --git a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/searchbar.mdx b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/searchbar.mdx index e62c2248d751dc..526bc7349f537c 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/searchbar.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/searchbar.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI SearchBar matches the official Jetpack Compose [Search](https://developer.android.com/develop/ui/compose/components/search) API and provides a search input with support for placeholder text and expanded full-screen search. +Expo UI SearchBar matches the official Jetpack Compose [Search](https://developer.android.com/develop/ui/compose/components/search-bar) API and provides a search input with support for placeholder text and expanded full-screen search. ## Installation diff --git a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/surface.mdx b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/surface.mdx index baafb447d46aec..75a4a053dc5fb4 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/surface.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/jetpack-compose/surface.mdx @@ -9,7 +9,7 @@ platforms: ['android'] import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; -Expo UI Surface matches the official Jetpack Compose [Surface](https://developer.android.com/develop/ui/compose/components/surfaces) API and provides a container that applies Material Design surface styling including color, elevation, and content color. +Expo UI Surface matches the official Jetpack Compose [Surface](https://developer.android.com/develop/ui/compose/designsystems/material3) API and provides a container that applies Material Design surface styling including color, elevation, and content color. ## Installation From 8323d01efe91edc9f38d198ecefad4543041d26c Mon Sep 17 00:00:00 2001 From: Patryk Mleczek <67064618+pmleczek@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:25:40 +0100 Subject: [PATCH 13/13] [brownfield][test] add maestro e2e tests for dev menu (#43421) # Why - We should test that dev menu displays correctly in brownfield (as it requires some modifications in brownfield in order to work) and we can do it using Maestro - Tests and workflows for brownfield are a bit duplicated and have leftover filters, etc. - we can refine that # How `test-suite-brownfield-isolated.yml` To avoid using matrices and using additional 2 runners: - Extended existing build jobs with building & uploading of debug apps - Extended existing test jobs with additional execution of Maestro tests against the debug apps `brownfield.yml` - Removed unused cli and plugin path filters - Removed `packages/expo-brownfield/**` filter for compilation tests - this is now covered by Maestro E2Es workflow - Extended filters for iOS compilation test with `'packages/expo-modules-core/ios/**'` - commits there aren't very frequent, but it affects `expo-brownfield` and now with ccache and prebuilts we should be able to run it more often **E2Es** - Unified common test flows (e.g. for launching the app or skipping initial dev menu) - Added `dev-menu.yml` workflow - Removed `android/basic.yml` and `ios/basic.yml` as all they do is anyway covered by other test suites - Unified workflows `navigation.yml` and `communication.yml` - Updated test apps # Test Plan Ensured that tests pass locally and on CI # Checklist - [X] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). --------- Co-authored-by: Gabriel Donadel --- .github/workflows/brownfield.yml | 18 +--- .../test-suite-brownfield-isolated.yml | 74 +++++++++---- .../BrownfieldTestActivity.kt | 15 ++- .../expo-app/src/app/apis/dev-menu.tsx | 21 ++++ .../expo-app/src/app/apis/index.tsx | 12 ++- .../expo-app/src/components/header/Header.tsx | 4 +- .../expo-app/src/components/header/types.ts | 1 + packages/expo-brownfield/CHANGELOG.md | 2 + .../_nested-flows/launch-brownfield.yml | 14 --- .../e2e/maestro/__tests__/android/basic.yml | 33 ------ .../__tests__/android/communication.yml | 20 ---- .../maestro/__tests__/android/navigation.yml | 46 -------- .../_nested-flows/launch-brownfield.yml | 10 +- .../common/_nested-flows/skip-dev-menu.yml | 33 ++++++ .../__tests__/common/communication.yml | 32 ++++++ .../e2e/maestro/__tests__/common/dev-menu.yml | 76 +++++++++++++ .../maestro/__tests__/common/navigation.yml | 102 ++++++++++++++++++ .../_nested-flows/maybe-close-dev-menu.yml | 10 -- .../e2e/maestro/__tests__/ios/basic.yml | 47 -------- .../maestro/__tests__/ios/communication.yml | 21 ---- .../e2e/maestro/__tests__/ios/navigation.yml | 61 ----------- .../e2e/scripts/install-app-ios.sh | 4 +- .../e2e/scripts/run-e2e-android.sh | 21 ++++ .../e2e/scripts/run-e2e-ios.sh | 13 ++- 24 files changed, 387 insertions(+), 303 deletions(-) create mode 100644 apps/brownfield-tester/expo-app/src/app/apis/dev-menu.tsx delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/android/_nested-flows/launch-brownfield.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/android/basic.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/android/communication.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/android/navigation.yml rename packages/expo-brownfield/e2e/maestro/__tests__/{ios => common}/_nested-flows/launch-brownfield.yml (55%) create mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/skip-dev-menu.yml create mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/common/communication.yml create mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/common/dev-menu.yml create mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/common/navigation.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/maybe-close-dev-menu.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/ios/basic.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/ios/communication.yml delete mode 100644 packages/expo-brownfield/e2e/maestro/__tests__/ios/navigation.yml create mode 100755 packages/expo-brownfield/e2e/scripts/run-e2e-android.sh diff --git a/.github/workflows/brownfield.yml b/.github/workflows/brownfield.yml index b773b508ad3106..f362dec2cac40d 100644 --- a/.github/workflows/brownfield.yml +++ b/.github/workflows/brownfield.yml @@ -39,8 +39,6 @@ jobs: runs-on: ubuntu-24.04 outputs: all_tests: ${{ steps.filter.outputs.all_tests }} - cli_e2e: ${{ steps.filter.outputs.cli_e2e }} - plugin_e2e: ${{ steps.filter.outputs.plugin_e2e }} compilation_test_android: ${{ steps.filter.outputs.compilation_test_android }} compilation_test_ios: ${{ steps.filter.outputs.compilation_test_ios }} steps: @@ -48,27 +46,13 @@ jobs: uses: actions/checkout@v5 - uses: dorny/paths-filter@v3 id: filter - # TODO(pmleczek): Update paths once we wrap up Maestro tests (remove expo-brownfield/**) # TODO(pmleczek): Add "- 'packages/expo-updates/**'" to the compilation test filters with: filters: | all_tests: - '.github/workflows/brownfield.yml' - 'yarn.lock' - cli_e2e: - - 'packages/expo-brownfield/bin/**' - - 'packages/expo-brownfield/cli/**' - - 'packages/expo-brownfield/e2e/cli/**' - - 'packages/expo-brownfield/e2e/utils/**' - - 'packages/expo-brownfield/package.json' - plugin_e2e: - - 'packages/expo-brownfield/plugin/**' - - 'packages/expo-brownfield/e2e/plugin/**' - - 'packages/expo-brownfield/e2e/utils/**' - - 'packages/expo-brownfield/app.plugin.js' - - 'packages/expo-brownfield/package.json' compilation_test_android: - - 'packages/expo-brownfield/**' - 'packages/expo/android/src/main/jave/expo/modules/**' - 'packages/expo/android/build.gradle' - 'packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/**' @@ -76,10 +60,10 @@ jobs: - 'packages/expo-manifests/android/src/main/java/expo/modules/manifests/**' - 'packages/expo-manifests/android/build.gradle' compilation_test_ios: - - 'packages/expo-brownfield/**' - 'packages/expo/ios/AppDelegates/**' - 'packages/expo-dev-menu/ios/**' - 'packages/expo-manifests/ios/EXManifests/**' + - 'packages/expo-modules-core/ios/**' compilation-test-android: runs-on: ubuntu-24.04 diff --git a/.github/workflows/test-suite-brownfield-isolated.yml b/.github/workflows/test-suite-brownfield-isolated.yml index 9f61f006ac0bfb..b6026ebed87579 100644 --- a/.github/workflows/test-suite-brownfield-isolated.yml +++ b/.github/workflows/test-suite-brownfield-isolated.yml @@ -94,12 +94,21 @@ jobs: npx expo prebuild --clean -p android npx expo-brownfield build:android --repo MavenLocal --verbose working-directory: apps/brownfield-tester/expo-app - - name: 🏗️ Build APK + - name: 🏗️ Build Debug APK + run: | + ./gradlew clean assembleDebug --refresh-dependencies + working-directory: apps/brownfield-tester/android-integrated + - name: 💾 Store Debug APK artifact + uses: actions/upload-artifact@v4 + with: + name: expo-brownfield-android-debug + path: apps/brownfield-tester/android-integrated/app/build/outputs/apk/debug/app-debug.apk + - name: 🏗️ Build Release APK run: | ./gradlew clean ./gradlew assembleRelease --refresh-dependencies working-directory: apps/brownfield-tester/android-integrated - - name: 💾 Store APK artifact + - name: 💾 Store Release APK artifact uses: actions/upload-artifact@v4 with: name: expo-brownfield-android-release @@ -122,23 +131,43 @@ jobs: # Version `1.x` fails due to https://github.com/oven-sh/setup-bun/issues/37 # TODO(cedric): swap `latest` back once the issue is resolved bun-version: latest - - name: 🌠 Download builds + - name: 🌠 Download build (Release) uses: actions/download-artifact@v4 with: name: expo-brownfield-android-release path: apps/brownfield-tester/android-integrated/apk + - name: 🌠 Download build (Debug) + uses: actions/download-artifact@v4 + with: + name: expo-brownfield-android-debug + path: apps/brownfield-tester/android-integrated/apk - name: 🍺 Install Maestro run: | curl -Ls "https://get.maestro.mobile.dev" | bash echo "${HOME}/.maestro/bin" >> $GITHUB_PATH - - name: 🧪 Run e2e tests (isolated brownfield) + - name: 🧪 Run e2e tests (Release) + uses: ./.github/actions/use-android-emulator + with: + avd-api: ${{ matrix.api-level }} + avd-name: avd-${{ matrix.api-level }} + script: + ./packages/expo-brownfield/e2e/scripts/run-e2e-android.sh + - name: 🧶 Install node modules in root dir + run: yarn install --frozen-lockfile + - name: 👷 Build Expo CLI + run: yarn workspace @expo/cli prepare + - name: 🚇 Start Metro server + run: | + yarn start --clear & + npx wait-on http://localhost:8081 --timeout 60000 + working-directory: apps/brownfield-tester/expo-app + - name: 🧪 Run e2e tests (Debug) uses: ./.github/actions/use-android-emulator with: avd-api: ${{ matrix.api-level }} avd-name: avd-${{ matrix.api-level }} script: | - adb install -r apps/brownfield-tester/android-integrated/apk/*.apk - cd packages/expo-brownfield/e2e && maestro test maestro/__tests__/android + CONFIGURATION=Debug ./packages/expo-brownfield/e2e/scripts/run-e2e-android.sh - name: 🔔 Notify on Slack uses: ./.github/actions/slack-notify if: failure() && (github.event.ref == 'refs/heads/main' || startsWith(github.event.ref, 'refs/heads/sdk-')) @@ -147,16 +176,10 @@ jobs: channel: '#expo-android' author_name: E2E Test Suite for Expo Brownfield - ios-e2e: + ios-build-and-e2e: runs-on: macos-15 needs: detect-platform-for-e2e if: needs.detect-platform-for-e2e.outputs.should_run_ios == 'true' - strategy: - fail-fast: false - matrix: - build-type: [release] - # TODO(pmleczek): Add dev menu tests for iOS and Android - # build-type: [debug, release] steps: - name: 👀 Checkout uses: actions/checkout@v5 @@ -203,25 +226,30 @@ jobs: run: yarn install --frozen-lockfile - name: 👷 Build Expo CLI run: yarn workspace @expo/cli prepare - - name: 🍏 Build iOS artifacts (apps/brownfield-tester/expo-app) + - name: 🍏 Build iOS artifacts (Release) run: | npx expo prebuild --clean -p ios - npx expo-brownfield build:ios --${{ matrix.build-type }} --verbose -a ../../../artifacts -p BrownfieldPackage + npx expo-brownfield build:ios -r --verbose -a ../../../artifacts -p BrownfieldPackage working-directory: apps/brownfield-tester/expo-app - - name: 💾 Save ccache - if: always() - uses: actions/cache/save@v4 - with: - path: ${{ runner.temp }}/.ccache - key: ${{ steps.ccache-restore.outputs.cache-primary-key }} - - name: 🔨 Add brownfield Swift Package to the app + - name: 🔨 Add XCFrameworks to SwiftUI project run: ruby packages/expo-brownfield/e2e/scripts/add_xcframeworks.rb - name: 🍺 Install Maestro run: | curl -Ls "https://get.maestro.mobile.dev" | bash echo "${HOME}/.maestro/bin" >> $GITHUB_PATH - - name: 🧪 Run e2e tests (isolated brownfield) + - name: 🧪 Run e2e tests (Release) run: ./packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh + - name: 🍏 Build iOS artifacts (Debug) + run: | + npx expo-brownfield build:ios -d --verbose -a ../../../artifacts -p BrownfieldPackage + - name: 🧪 Run e2e tests (Debug) + run: CONFIGURATION=Debug ./packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh + - name: 💾 Save ccache + if: always() + uses: actions/cache/save@v4 + with: + path: ${{ runner.temp }}/.ccache + key: ${{ steps.ccache-restore.outputs.cache-primary-key }} - name: Show ccache stats run: ccache -s -v - name: 🔔 Notify on Slack diff --git a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt index 377e683e2a7a96..37602728c6c8a2 100644 --- a/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt +++ b/apps/brownfield-tester/android-integrated/app/src/main/java/dev/expo/brownfieldintegratedtester/BrownfieldTestActivity.kt @@ -2,6 +2,7 @@ package dev.expo.brownfieldintegratedtester import android.util.Log import android.widget.Toast +import androidx.appcompat.app.AlertDialog import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler import expo.modules.brownfield.BrownfieldMessage import expo.modules.brownfield.BrownfieldMessaging @@ -26,7 +27,7 @@ open class BrownfieldTestActivity : BrownfieldActivity(), DefaultHardwareBackBtn BrownfieldMessaging.addListener { message -> Log.i("BrownfieldTestActivity", "Message from React Native received:") Log.i("BrownfieldTestActivity", message.toString()) - showToast(message) + showDialog(message) } setupStateListeners() @@ -97,12 +98,20 @@ open class BrownfieldTestActivity : BrownfieldActivity(), DefaultHardwareBackBtn messageTimer = null } - private fun showToast(message: BrownfieldMessage) { + private fun showDialog(message: BrownfieldMessage) { val sender = message["sender"] as? String val nested = message["source"] as? Map<*, *> val platform = nested?.get("platform") as? String if (sender != null && platform != null) { - Toast.makeText(this, "$platform($sender)", Toast.LENGTH_LONG).show() + runOnUiThread { + AlertDialog.Builder(this) + .setTitle("Message Received") // Optional: give it a title + .setMessage("$platform($sender)") + .setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() // Closes the dialog when clicked + } + .show() + } } } diff --git a/apps/brownfield-tester/expo-app/src/app/apis/dev-menu.tsx b/apps/brownfield-tester/expo-app/src/app/apis/dev-menu.tsx new file mode 100644 index 00000000000000..0d756d82b7baa0 --- /dev/null +++ b/apps/brownfield-tester/expo-app/src/app/apis/dev-menu.tsx @@ -0,0 +1,21 @@ +import * as ExpoDevMenu from 'expo-dev-menu'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import { ActionButton, Header } from '@/components'; + +const Navigation = () => { + return ( + +
+ ExpoDevMenu.openMenu()} + testID="dev-menu-open" + /> + + ); +}; + +export default Navigation; diff --git a/apps/brownfield-tester/expo-app/src/app/apis/index.tsx b/apps/brownfield-tester/expo-app/src/app/apis/index.tsx index 4ccf1ae7f730d2..64776f7e49b9d0 100644 --- a/apps/brownfield-tester/expo-app/src/app/apis/index.tsx +++ b/apps/brownfield-tester/expo-app/src/app/apis/index.tsx @@ -4,10 +4,12 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { ActionButton } from '@/components'; +type Screen = 'communication' | 'dev-menu' | 'navigation' | 'state'; + const Index = () => { const router = useRouter(); - const navigateToScreen = (screen: 'communication' | 'navigation' | 'state') => { + const navigateToScreen = (screen: Screen) => { router.navigate(`/apis/${screen}`); }; @@ -37,6 +39,14 @@ const Index = () => { onPress={() => navigateToScreen('state')} testID="apis-state" /> + navigateToScreen('dev-menu')} + testID="apis-dev-menu" + /> ); }; diff --git a/apps/brownfield-tester/expo-app/src/components/header/Header.tsx b/apps/brownfield-tester/expo-app/src/components/header/Header.tsx index 751752f5f56913..48470a81d1eaa0 100644 --- a/apps/brownfield-tester/expo-app/src/components/header/Header.tsx +++ b/apps/brownfield-tester/expo-app/src/components/header/Header.tsx @@ -4,7 +4,7 @@ import { View, Text, Pressable, StyleSheet } from 'react-native'; import type { HeaderProps } from './types'; -const Header = ({ title }: HeaderProps) => { +const Header = ({ title, testID }: HeaderProps) => { const router = useRouter(); return ( @@ -15,7 +15,7 @@ const Header = ({ title }: HeaderProps) => { testID="header-back-button"> - {title} + {title} ); }; diff --git a/apps/brownfield-tester/expo-app/src/components/header/types.ts b/apps/brownfield-tester/expo-app/src/components/header/types.ts index 5def2174bf21c6..a70aa4a571e5f6 100644 --- a/apps/brownfield-tester/expo-app/src/components/header/types.ts +++ b/apps/brownfield-tester/expo-app/src/components/header/types.ts @@ -1,3 +1,4 @@ export interface HeaderProps { title: string; + testID?: string; } diff --git a/packages/expo-brownfield/CHANGELOG.md b/packages/expo-brownfield/CHANGELOG.md index 547550c5543472..a554140c9577b6 100644 --- a/packages/expo-brownfield/CHANGELOG.md +++ b/packages/expo-brownfield/CHANGELOG.md @@ -16,6 +16,8 @@ ### 💡 Others +- [test] add maestro e2e tests for dev menu ([#43421](https://github.com/expo/expo/pull/43421) by [@pmleczek](https://github.com/pmleczek)) + ## 55.0.11 — 2026-02-25 ### 💡 Others diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/android/_nested-flows/launch-brownfield.yml b/packages/expo-brownfield/e2e/maestro/__tests__/android/_nested-flows/launch-brownfield.yml deleted file mode 100644 index 44bbe6955040a4..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/android/_nested-flows/launch-brownfield.yml +++ /dev/null @@ -1,14 +0,0 @@ -appId: dev.expo.brownfieldintegratedtester -name: Launch and verify the brownfield app ---- -- launchApp -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- assertVisible: Expo + Brownfield = 🖤 -- assertVisible: - id: home-tab -- assertVisible: - id: apis-tab diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/android/basic.yml b/packages/expo-brownfield/e2e/maestro/__tests__/android/basic.yml deleted file mode 100644 index 71efc346e6fa77..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/android/basic.yml +++ /dev/null @@ -1,33 +0,0 @@ -appId: dev.expo.brownfieldintegratedtester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab -- assertVisible: Communication -- assertVisible: Navigation API - -- tapOn: - id: apis-navigation -- assertVisible: Navigation -- assertVisible: - id: navigation-pop-to-native -- assertVisible: - id: navigation-disable-native-back - -- back -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- assertVisible: Navigation -- assertVisible: - id: navigation-pop-to-native - -- assertVisible: - id: header-back-button -- tapOn: - id: header-back-button -- assertVisible: Communication -- assertVisible: Navigation API diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/android/communication.yml b/packages/expo-brownfield/e2e/maestro/__tests__/android/communication.yml deleted file mode 100644 index a6b0bc33f14331..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/android/communication.yml +++ /dev/null @@ -1,20 +0,0 @@ -appId: dev.expo.brownfieldintegratedtester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab -- tapOn: - id: apis-communication -- assertVisible: - id: communication-send-message - -- tapOn: - id: communication-send-message -- assertVisible: React Native(brownfield-tester/expo-app) - -- assertNotVisible: - id: communication-received-message - text: "{}" - -- assertVisible: "Received message (multiple: true)" diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/android/navigation.yml b/packages/expo-brownfield/e2e/maestro/__tests__/android/navigation.yml deleted file mode 100644 index 1585b05d5eb238..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/android/navigation.yml +++ /dev/null @@ -1,46 +0,0 @@ -appId: dev.expo.brownfieldintegratedtester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab -- tapOn: - id: apis-navigation -- assertVisible: - id: navigation-enable-native-back - -- tapOn: - id: navigation-pop-to-native -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- back -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- tapOn: - id: navigation-disable-native-back -- back -- assertVisible: Communication -- assertVisible: Bi-directional communication API -- assertVisible: Navigation -- assertVisible: Navigation API - -- tapOn: - id: apis-navigation -- tapOn: - id: navigation-pop-to-native -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- tapOn: - id: navigation-enable-native-back -- back -- assertVisible: - id: openReactNativeButton diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/launch-brownfield.yml b/packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/launch-brownfield.yml similarity index 55% rename from packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/launch-brownfield.yml rename to packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/launch-brownfield.yml index eb24a01a018d1a..d2d00f2a1bb972 100644 --- a/packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/launch-brownfield.yml +++ b/packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/launch-brownfield.yml @@ -1,14 +1,20 @@ -appId: dev.expo.BrownfieldIntegratedTester +appId: ${APP_ID} name: Launch and verify the brownfield app --- +# Launch the app - launchApp + +# Verify that native part has launched +# And launch the React Native part - assertVisible: id: openReactNativeButton - tapOn: id: openReactNativeButton -- runFlow: ./maybe-close-dev-menu.yml +# Close the dev menu if shown +- runFlow: ./skip-dev-menu.yml +# Verify that the RN part has launched - assertVisible: Expo + Brownfield = 🖤 - assertVisible: id: home-tab diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/skip-dev-menu.yml b/packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/skip-dev-menu.yml new file mode 100644 index 00000000000000..30a65fb78d8903 --- /dev/null +++ b/packages/expo-brownfield/e2e/maestro/__tests__/common/_nested-flows/skip-dev-menu.yml @@ -0,0 +1,33 @@ +appId: ${APP_ID} +name: Close dev menu if visible +--- +# Wait for the dev menu to be visible +- waitForAnimationToEnd: + timeout: 5000 + +# Common - press 'Continue' +- runFlow: + when: + visible: + text: "Continue" + commands: + - tapOn: + text: "Continue" + +# Android — close button with text "Close" +- runFlow: + when: + visible: + text: "Close" + commands: + - tapOn: + text: "Close" + +# iOS — close button with testID "xmark" +- runFlow: + when: + visible: + id: xmark + commands: + - tapOn: + id: xmark diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/common/communication.yml b/packages/expo-brownfield/e2e/maestro/__tests__/common/communication.yml new file mode 100644 index 00000000000000..b88d834050a79d --- /dev/null +++ b/packages/expo-brownfield/e2e/maestro/__tests__/common/communication.yml @@ -0,0 +1,32 @@ +appId: ${APP_ID} +--- +# Launch the app +- runFlow: ../common/_nested-flows/launch-brownfield.yml + +# Navigate to the communication screen +- tapOn: + id: apis-tab +- tapOn: + id: apis-communication +- assertVisible: + id: communication-send-message + +# Assert message can be sent from RN +# to the native app +- repeat: + times: 2 + commands: + - tapOn: + id: communication-send-message + - assertVisible: React Native(brownfield-tester/expo-app) + - tapOn: OK + +# Assert messages can be sent from the +# native app to the RN app +- assertVisible: '.*"platform": "(Android|iOS)".*' +- assertVisible: '.*"array": \[.*' +- assertVisible: '.*"ab".*' +- assertVisible: '.*2\.45.*' + +# Assert JS receives multiple messages +- assertVisible: "Received message (multiple: true)" diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/common/dev-menu.yml b/packages/expo-brownfield/e2e/maestro/__tests__/common/dev-menu.yml new file mode 100644 index 00000000000000..2791c861f8ac51 --- /dev/null +++ b/packages/expo-brownfield/e2e/maestro/__tests__/common/dev-menu.yml @@ -0,0 +1,76 @@ +appId: ${APP_ID} +--- +# Launch the app +- runFlow: ../common/_nested-flows/launch-brownfield.yml + +# Navigate to the dev menu screen +- tapOn: + id: apis-tab +- tapOn: + id: apis-dev-menu +- assertVisible: + id: dev-menu-header + +# Open the dev menu +- tapOn: + id: dev-menu-open + +# Assert that buttons are rendered correctly +- assertVisible: "Reload" +- assertVisible: "Toggle performance monitor" +- assertVisible: + text: "Toggle element inspector" + enabled: true +- assertVisible: "Open DevTools" + +# Assert that information about the app +# is correctly loaded +- assertVisible: "expo-app" +- assertVisible: "Runtime version: exposdk:55.0.0" + +- scrollUntilVisible: + element: + text: "Copy system info" + +- assertVisible: "Version" +- assertVisible: "1.0.0" + +- assertVisible: "Runtime version" +- assertVisible: "exposdk:55.0.0" + +- runFlow: + when: + platform: iOS + commands: + - assertVisible: http://localhost:8081 +- runFlow: + when: + platform: Android + commands: + - assertVisible: http://10.0.2.2:8081 + +# Assert that dev menu actions work +- scrollUntilVisible: + element: + text: "Reload" + direction: UP + +# Android Performance Monitor is not detectable +# by Maestro +- runFlow: + when: + platform: iOS + commands: + - tapOn: "Toggle Performance Monitor" + - assertVisible: 'RAM .* MB' + - assertVisible: 'JSC .* MB' + - tapOn: + id: dev-menu-open + - tapOn: "Toggle Performance Monitor" + - assertNotVisible: 'RAM .* MB' + - assertNotVisible: 'JSC .* MB' + - tapOn: + id: dev-menu-open + +- tapOn: "Reload" +- assertVisible: "Expo + Brownfield = 🖤" diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/common/navigation.yml b/packages/expo-brownfield/e2e/maestro/__tests__/common/navigation.yml new file mode 100644 index 00000000000000..a626ba8c2dac9b --- /dev/null +++ b/packages/expo-brownfield/e2e/maestro/__tests__/common/navigation.yml @@ -0,0 +1,102 @@ +appId: ${APP_ID} +--- +# Launch the app +- runFlow: ../common/_nested-flows/launch-brownfield.yml + +# Validate that brownfield app +# can be opened multiple times +- runFlow: + when: + platform: Android + commands: + - repeat: + times: 2 + commands: + - back + - assertVisible: + id: openReactNativeButton + - tapOn: + id: openReactNativeButton + - assertVisible: Expo + Brownfield = 🖤 +- runFlow: + when: + platform: iOS + commands: + - repeat: + times: 2 + commands: + - swipe: + direction: RIGHT + - assertVisible: + id: openReactNativeButton + - tapOn: + id: openReactNativeButton + - assertVisible: Expo + Brownfield = 🖤 + +# Verify popToNative() method +- tapOn: + id: apis-tab +- tapOn: + id: apis-navigation +- tapOn: + id: navigation-pop-to-native +- assertVisible: + id: openReactNativeButton +- tapOn: + id: openReactNativeButton + +# Disable native back gesture/button +- tapOn: + id: apis-tab +- tapOn: + id: apis-navigation +- tapOn: + id: navigation-disable-native-back + +# Verify that back action now works within RN app +- runFlow: + when: + platform: Android + commands: + - back + - assertVisible: + id: apis-navigation + - assertVisible: + id: apis-dev-menu +# On iOS the collision between native and RN back button +# is not so simple as on Android and doesn't seem to be testable +# automatically + +# Enable native back gesture/button +- tapOn: + id: apis-tab +- tapOn: + id: apis-navigation +- tapOn: + id: navigation-enable-native-back + +# Verify that back action now works within +# the host app +- runFlow: + when: + platform: Android + commands: + - back + - assertVisible: + id: openReactNativeButton + - tapOn: + id: openReactNativeButton +# TODO(pmleczek): iOS flow? + +# Verify that popToNative works regardless +# of native back button/gesture state +- tapOn: + id: apis-tab +- tapOn: + id: apis-navigation +- tapOn: + id: navigation-disable-native-back +- tapOn: + id: navigation-pop-to-native +- assertVisible: + id: openReactNativeButton diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/maybe-close-dev-menu.yml b/packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/maybe-close-dev-menu.yml deleted file mode 100644 index 3e062b7ce51a41..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/ios/_nested-flows/maybe-close-dev-menu.yml +++ /dev/null @@ -1,10 +0,0 @@ -appId: dev.expo.BrownfieldIntegratedTester -name: Ckose dev menu if visible ---- -- runFlow: - when: - visible: - id: xmark - commands: - - tapOn: - id: xmark diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/ios/basic.yml b/packages/expo-brownfield/e2e/maestro/__tests__/ios/basic.yml deleted file mode 100644 index 13ac2648e994db..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/ios/basic.yml +++ /dev/null @@ -1,47 +0,0 @@ -appId: dev.expo.BrownfieldIntegratedTester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab - -- assertVisible: - id: apis-communication -- assertVisible: - id: apis-navigation - -- tapOn: - id: apis-navigation -- assertVisible: Navigation -- assertVisible: - id: navigation-pop-to-native -- assertVisible: - id: navigation-disable-native-back - -- swipe: - direction: RIGHT -- swipe: - direction: RIGHT - -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- runFlow: ./_nested-flows/maybe-close-dev-menu.yml -- assertVisible: Expo + Brownfield = 🖤 -- tapOn: - id: apis-tab -- tapOn: - id: apis-navigation -- assertVisible: - id: navigation-pop-to-native - -- assertVisible: - id: header-back-button -- tapOn: - id: header-back-button -- assertVisible: - id: apis-communication -- assertVisible: - id: apis-navigation diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/ios/communication.yml b/packages/expo-brownfield/e2e/maestro/__tests__/ios/communication.yml deleted file mode 100644 index d4ce56e57fa5ee..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/ios/communication.yml +++ /dev/null @@ -1,21 +0,0 @@ -appId: dev.expo.BrownfieldIntegratedTester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab -- tapOn: - id: apis-communication -- assertVisible: - id: communication-send-message - -- tapOn: - id: communication-send-message -- assertVisible: React Native(brownfield-tester/expo-app) -- tapOn: OK - -- assertNotVisible: - id: communication-received-message - text: "{}" - -- assertVisible: "Received message (multiple: true)" diff --git a/packages/expo-brownfield/e2e/maestro/__tests__/ios/navigation.yml b/packages/expo-brownfield/e2e/maestro/__tests__/ios/navigation.yml deleted file mode 100644 index 884dec1742d0c9..00000000000000 --- a/packages/expo-brownfield/e2e/maestro/__tests__/ios/navigation.yml +++ /dev/null @@ -1,61 +0,0 @@ -appId: dev.expo.BrownfieldIntegratedTester ---- -- runFlow: ./_nested-flows/launch-brownfield.yml - -- tapOn: - id: apis-tab -- tapOn: - id: apis-navigation -- assertVisible: - id: navigation-enable-native-back - -- tapOn: - id: navigation-pop-to-native -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- swipe: - direction: RIGHT -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- runFlow: ./_nested-flows/maybe-close-dev-menu.yml -- tapOn: - id: apis-tab -- tapOn: - id: apis-navigation -- tapOn: - id: navigation-disable-native-back -- swipe: - direction: RIGHT -- assertVisible: - id: apis-communication -- assertVisible: - id: apis-navigation - -- tapOn: - id: apis-navigation -- tapOn: - id: navigation-pop-to-native -- assertVisible: - id: openReactNativeButton - -- tapOn: - id: openReactNativeButton -- runFlow: ./_nested-flows/maybe-close-dev-menu.yml -- tapOn: - id: apis-tab -- tapOn: - id: apis-navigation -- tapOn: - id: navigation-enable-native-back -- swipe: - direction: RIGHT -- swipe: - direction: RIGHT -- assertVisible: - id: openReactNativeButton diff --git a/packages/expo-brownfield/e2e/scripts/install-app-ios.sh b/packages/expo-brownfield/e2e/scripts/install-app-ios.sh index 714724230ced5c..5c21914a9a4d10 100755 --- a/packages/expo-brownfield/e2e/scripts/install-app-ios.sh +++ b/packages/expo-brownfield/e2e/scripts/install-app-ios.sh @@ -4,7 +4,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ARCH="arm64" BUNDLE_ID="dev.expo.BrownfieldIntegratedTester" -CONFIGURATION="Release" +CONFIGURATION=${CONFIGURATION:-Release} DERIVED_DATA_PATH="build" DEVICE_ID=$1 PROJECT="BrownfieldIntegratedTester.xcodeproj" @@ -20,7 +20,7 @@ xcodebuild build \ -arch $ARCH \ -derivedDataPath $DERIVED_DATA_PATH -APP_BINARY_PATH=build/Build/Products/Release-iphonesimulator/BrownfieldIntegratedTester.app +APP_BINARY_PATH=build/Build/Products/${CONFIGURATION}-iphonesimulator/BrownfieldIntegratedTester.app echo " 🔌 Installing UIKit App - deviceId[${DEVICE_ID}] appBinaryPath[${APP_BINARY_PATH}]" xcrun simctl install $DEVICE_ID $APP_BINARY_PATH xcrun simctl launch $DEVICE_ID $BUNDLE_ID diff --git a/packages/expo-brownfield/e2e/scripts/run-e2e-android.sh b/packages/expo-brownfield/e2e/scripts/run-e2e-android.sh new file mode 100755 index 00000000000000..e77b8af7f7ea3b --- /dev/null +++ b/packages/expo-brownfield/e2e/scripts/run-e2e-android.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +APP_ID=${APP_ID:-dev.expo.brownfieldintegratedtester} +CONFIGURATION=${CONFIGURATION:-Release} +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Install app +CONFIGURATION_LOWER=$(echo $CONFIGURATION | tr '[:upper:]' '[:lower:]') +adb install -r $GITHUB_WORKSPACE/apps/brownfield-tester/android-integrated/apk/app-$CONFIGURATION_LOWER.apk + +# Run the tests +maestro test \ + -e APP_ID=$APP_ID \ + $DIR/../maestro/__tests__/common/communication.yml \ + $DIR/../maestro/__tests__/common/navigation.yml + +if [ "$CONFIGURATION" = "Debug" ]; then + maestro test \ + -e APP_ID=$APP_ID \ + $DIR/../maestro/__tests__/common/dev-menu.yml +fi \ No newline at end of file diff --git a/packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh b/packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh index 028b0a22bd1941..6da19534862c99 100755 --- a/packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh +++ b/packages/expo-brownfield/e2e/scripts/run-e2e-ios.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +APP_ID=${APP_ID:-dev.expo.BrownfieldIntegratedTester} +CONFIGURATION=${CONFIGURATION:-Release} DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Setup simulator @@ -10,4 +12,13 @@ start_simulator source $DIR/install-app-ios.sh $DEVICE_ID # Run the tests -maestro test $DIR/../maestro/__tests__/ios +maestro test \ + -e APP_ID=$APP_ID \ + $DIR/../maestro/__tests__/common/communication.yml \ + $DIR/../maestro/__tests__/common/navigation.yml + +if [ "$CONFIGURATION" = "Debug" ]; then + maestro test \ + -e APP_ID=$APP_ID \ + $DIR/../maestro/__tests__/common/dev-menu.yml +fi