From 9aefcaf404511d2a1ac155cf0ec2febfe6f30e02 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Thu, 7 May 2026 23:18:37 +0100 Subject: [PATCH 1/6] chore: Remove invalid pins (#45520) # Summary Some of these have likely been left over by the bump, or changed accidentally. None of the remaining version pins seem reasonable or necessary to me. The `@expo/log-box` dependencies are curious. `expo-router` has a non-optional peer, but then `expo` has a dependency, but also a transitive one via `@expo/metro-runtime`. We should likely make a peer+dep combo consistent in `@expo/metro-runtime` and `expo-router` until we resolve the circular/dual-use nature of `@expo/log-box`. # Test Plan # 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/cli/CHANGELOG.md | 2 + packages/@expo/cli/package.json | 6 +- packages/@expo/metro-runtime/CHANGELOG.md | 2 + packages/@expo/metro-runtime/package.json | 3 +- packages/expo-dev-client/CHANGELOG.md | 2 + packages/expo-dev-client/package.json | 6 +- packages/expo-dev-launcher/CHANGELOG.md | 2 + packages/expo-dev-launcher/package.json | 2 +- packages/expo-dev-menu/CHANGELOG.md | 2 + packages/expo-dev-menu/package.json | 2 +- packages/expo-processing/package.json | 2 +- packages/expo-router/CHANGELOG.md | 2 + packages/expo-router/package.json | 3 +- packages/expo/CHANGELOG.md | 2 + packages/expo/package.json | 16 +-- pnpm-lock.yaml | 129 ++++++---------------- 16 files changed, 67 insertions(+), 116 deletions(-) diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index e9c89433e73abe..bf330e30420f2d 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -13,6 +13,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.6 — 2026-05-07 ### 🐛 Bug fixes diff --git a/packages/@expo/cli/package.json b/packages/@expo/cli/package.json index 614b391f195c89..2642ed9625845b 100644 --- a/packages/@expo/cli/package.json +++ b/packages/@expo/cli/package.json @@ -55,12 +55,12 @@ "@expo/devcert": "^1.2.1", "@expo/env": "workspace:~2.2.0", "@expo/image-utils": "workspace:^0.9.0", - "@expo/inline-modules": "workspace:0.0.4", + "@expo/inline-modules": "workspace:^0.0.4", "@expo/json-file": "workspace:^10.1.0", - "@expo/log-box": "workspace:56.0.5", + "@expo/log-box": "workspace:^56.0.5", "@expo/metro": "~56.0.0", "@expo/metro-config": "workspace:~56.0.4", - "@expo/metro-file-map": "workspace:56.0.0-2", + "@expo/metro-file-map": "workspace:^56.0.0-2", "@expo/osascript": "workspace:^2.5.0", "@expo/package-manager": "workspace:^1.11.0", "@expo/plist": "workspace:^0.6.0", diff --git a/packages/@expo/metro-runtime/CHANGELOG.md b/packages/@expo/metro-runtime/CHANGELOG.md index ec6b54132e2de2..73c93a8398d0ca 100644 --- a/packages/@expo/metro-runtime/CHANGELOG.md +++ b/packages/@expo/metro-runtime/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.4 — 2026-05-06 _This version does not introduce any user-facing changes._ diff --git a/packages/@expo/metro-runtime/package.json b/packages/@expo/metro-runtime/package.json index 24dc106efc8ec4..33c75c4000ab1f 100644 --- a/packages/@expo/metro-runtime/package.json +++ b/packages/@expo/metro-runtime/package.json @@ -38,6 +38,7 @@ "url": "https://github.com/expo/expo.git" }, "peerDependencies": { + "@expo/log-box": "workspace:^56.0.5", "expo": "*", "react": "*", "react-dom": "*", @@ -49,7 +50,7 @@ } }, "dependencies": { - "@expo/log-box": "workspace:56.0.5", + "@expo/log-box": "workspace:^56.0.5", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10", "pretty-format": "^29.7.0", diff --git a/packages/expo-dev-client/CHANGELOG.md b/packages/expo-dev-client/CHANGELOG.md index cca0ec71a7cafa..125e2871264586 100644 --- a/packages/expo-dev-client/CHANGELOG.md +++ b/packages/expo-dev-client/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.4 — 2026-05-07 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-dev-client/package.json b/packages/expo-dev-client/package.json index 297b3d0e1a90b2..7b9473c12c1118 100644 --- a/packages/expo-dev-client/package.json +++ b/packages/expo-dev-client/package.json @@ -31,9 +31,9 @@ "license": "MIT", "homepage": "https://docs.expo.dev/versions/latest/sdk/dev-client/", "dependencies": { - "expo-dev-launcher": "workspace:56.0.4", - "expo-dev-menu": "workspace:56.0.4", - "expo-dev-menu-interface": "workspace:56.0.0", + "expo-dev-launcher": "workspace:~56.0.4", + "expo-dev-menu": "workspace:~56.0.4", + "expo-dev-menu-interface": "workspace:~56.0.0", "expo-manifests": "workspace:~56.0.1", "expo-updates-interface": "workspace:~56.0.1" }, diff --git a/packages/expo-dev-launcher/CHANGELOG.md b/packages/expo-dev-launcher/CHANGELOG.md index a3835da13bf996..55de66b48a52a1 100644 --- a/packages/expo-dev-launcher/CHANGELOG.md +++ b/packages/expo-dev-launcher/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.4 — 2026-05-07 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-dev-launcher/package.json b/packages/expo-dev-launcher/package.json index 8b0b30d9227064..7d2d1638d27069 100644 --- a/packages/expo-dev-launcher/package.json +++ b/packages/expo-dev-launcher/package.json @@ -20,7 +20,7 @@ "homepage": "https://docs.expo.dev", "dependencies": { "@expo/schema-utils": "workspace:^56.0.0", - "expo-dev-menu": "workspace:56.0.4", + "expo-dev-menu": "workspace:~56.0.4", "expo-manifests": "workspace:~56.0.1" }, "peerDependencies": { diff --git a/packages/expo-dev-menu/CHANGELOG.md b/packages/expo-dev-menu/CHANGELOG.md index cd06250a0f8aa1..8473c0017a17fa 100644 --- a/packages/expo-dev-menu/CHANGELOG.md +++ b/packages/expo-dev-menu/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.4 — 2026-05-07 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-dev-menu/package.json b/packages/expo-dev-menu/package.json index c1b0efa8ba431b..08798bafc16698 100644 --- a/packages/expo-dev-menu/package.json +++ b/packages/expo-dev-menu/package.json @@ -32,7 +32,7 @@ "license": "MIT", "homepage": "https://docs.expo.dev", "dependencies": { - "expo-dev-menu-interface": "workspace:56.0.0" + "expo-dev-menu-interface": "workspace:~56.0.0" }, "devDependencies": { "@babel/preset-typescript": "^7.7.4", diff --git a/packages/expo-processing/package.json b/packages/expo-processing/package.json index c56181e4e0f1d4..5e3bb09470a711 100644 --- a/packages/expo-processing/package.json +++ b/packages/expo-processing/package.json @@ -22,7 +22,7 @@ "license": "MIT", "homepage": "https://github.com/expo/expo/tree/main/packages/expo-processing", "dependencies": { - "expo-gl": "workspace:56.0.3" + "expo-gl": "workspace:~56.0.3" }, "peerDependencies": { "expo": "*", diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index de687fdd59e090..aeefdcbdacd7dc 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.1.0 — 2026-05-07 ### 🎉 New features diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json index 87a7920a5e65f6..7441f250fbd4b9 100644 --- a/packages/expo-router/package.json +++ b/packages/expo-router/package.json @@ -88,7 +88,7 @@ "expo" ], "peerDependencies": { - "@expo/log-box": "workspace:56.0.5", + "@expo/log-box": "workspace:^56.0.5", "@expo/metro-runtime": "workspace:^56.0.4", "@testing-library/react-native": ">= 13.2.0", "expo": "*", @@ -149,6 +149,7 @@ "tsd": "^0.33.0" }, "dependencies": { + "@expo/log-box": "workspace:^56.0.5", "@expo/metro-runtime": "workspace:^56.0.4", "@expo/schema-utils": "workspace:^56.0.0", "@expo/ui": "workspace:^56.0.3", diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 0b14a9ac6fb048..71d3c8adbd56f4 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) + ## 56.0.0-preview.6 — 2026-05-07 _This version does not introduce any user-facing changes._ diff --git a/packages/expo/package.json b/packages/expo/package.json index 5404970745e246..d1970972e89389 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -75,16 +75,16 @@ "homepage": "https://github.com/expo/expo/tree/main/packages/expo", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "workspace:56.0.6", + "@expo/cli": "workspace:^56.0.6", "@expo/config": "workspace:~56.0.2", "@expo/config-plugins": "workspace:~56.0.2", - "@expo/devtools": "workspace:56.0.2", + "@expo/devtools": "workspace:~56.0.2", "@expo/dom-webview": "workspace:~56.0.4", - "@expo/fingerprint": "workspace:0.17.3", - "@expo/local-build-cache-provider": "workspace:56.0.2", - "@expo/log-box": "workspace:56.0.5", + "@expo/fingerprint": "workspace:^0.17.3", + "@expo/local-build-cache-provider": "workspace:^56.0.2", + "@expo/log-box": "workspace:^56.0.5", "@expo/metro": "~56.0.0", - "@expo/metro-config": "workspace:56.0.4", + "@expo/metro-config": "workspace:~56.0.4", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "workspace:~56.0.4", @@ -93,8 +93,8 @@ "expo-file-system": "workspace:~56.0.3", "expo-font": "workspace:~56.0.3", "expo-keep-awake": "workspace:~56.0.3", - "expo-modules-autolinking": "workspace:56.0.2", - "expo-modules-core": "workspace:56.0.4", + "expo-modules-autolinking": "workspace:~56.0.2", + "expo-modules-core": "workspace:~56.0.4", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62226c5a5de6ea..2829e1146ae3b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -268,7 +268,7 @@ importers: version: link:../../packages/expo-module-scripts jest: specifier: ^29.3.1 - version: 29.7.0(@types/node@25.5.0)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) + version: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)) apps/bare-expo/e2e/image-comparison: dependencies: @@ -1792,13 +1792,13 @@ importers: specifier: workspace:^0.9.0 version: link:../image-utils '@expo/inline-modules': - specifier: workspace:0.0.4 + specifier: workspace:^0.0.4 version: link:../inline-modules '@expo/json-file': specifier: workspace:^10.1.0 version: link:../json-file '@expo/log-box': - specifier: workspace:56.0.5 + specifier: workspace:^56.0.5 version: link:../log-box '@expo/metro': specifier: ~56.0.0 @@ -1807,7 +1807,7 @@ importers: specifier: workspace:~56.0.4 version: link:../metro-config '@expo/metro-file-map': - specifier: workspace:56.0.0-2 + specifier: workspace:^56.0.0-2 version: link:../metro-file-map '@expo/osascript': specifier: workspace:^2.5.0 @@ -2655,7 +2655,7 @@ importers: packages/@expo/metro-runtime: dependencies: '@expo/log-box': - specifier: workspace:56.0.5 + specifier: workspace:^56.0.5 version: link:../log-box anser: specifier: ^1.4.9 @@ -3122,7 +3122,7 @@ importers: version: link:../expo-splash-screen jest: specifier: ^29.2.1 - version: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) + version: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)) react-refresh: specifier: ^0.14.2 version: 0.14.2 @@ -3428,7 +3428,7 @@ importers: specifier: ^7.20.0 version: 7.29.2 '@expo/cli': - specifier: workspace:56.0.6 + specifier: workspace:^56.0.6 version: link:../@expo/cli '@expo/config': specifier: workspace:~56.0.2 @@ -3437,25 +3437,25 @@ importers: specifier: workspace:~56.0.2 version: link:../@expo/config-plugins '@expo/devtools': - specifier: workspace:56.0.2 + specifier: workspace:~56.0.2 version: link:../@expo/devtools '@expo/dom-webview': specifier: workspace:~56.0.4 version: link:../@expo/dom-webview '@expo/fingerprint': - specifier: workspace:0.17.3 + specifier: workspace:^0.17.3 version: link:../@expo/fingerprint '@expo/local-build-cache-provider': - specifier: workspace:56.0.2 + specifier: workspace:^56.0.2 version: link:../@expo/local-build-cache-provider '@expo/log-box': - specifier: workspace:56.0.5 + specifier: workspace:^56.0.5 version: link:../@expo/log-box '@expo/metro': specifier: ~56.0.0 version: 56.0.0 '@expo/metro-config': - specifier: workspace:56.0.4 + specifier: workspace:~56.0.4 version: link:../@expo/metro-config '@expo/vector-icons': specifier: ^15.0.2 @@ -3482,10 +3482,10 @@ importers: specifier: workspace:~56.0.3 version: link:../expo-keep-awake expo-modules-autolinking: - specifier: workspace:56.0.2 + specifier: workspace:~56.0.2 version: link:../expo-modules-autolinking expo-modules-core: - specifier: workspace:56.0.4 + specifier: workspace:~56.0.4 version: link:../expo-modules-core pretty-format: specifier: ^29.7.0 @@ -3630,7 +3630,7 @@ importers: devDependencies: '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/node': specifier: ^22.14.0 version: 22.19.15 @@ -4089,13 +4089,13 @@ importers: packages/expo-dev-client: dependencies: expo-dev-launcher: - specifier: workspace:56.0.4 + specifier: workspace:~56.0.4 version: link:../expo-dev-launcher expo-dev-menu: - specifier: workspace:56.0.4 + specifier: workspace:~56.0.4 version: link:../expo-dev-menu expo-dev-menu-interface: - specifier: workspace:56.0.0 + specifier: workspace:~56.0.0 version: link:../expo-dev-menu-interface expo-manifests: specifier: workspace:~56.0.1 @@ -4123,7 +4123,7 @@ importers: specifier: workspace:^56.0.0 version: link:../@expo/schema-utils expo-dev-menu: - specifier: workspace:56.0.4 + specifier: workspace:~56.0.4 version: link:../expo-dev-menu expo-manifests: specifier: workspace:~56.0.1 @@ -4142,7 +4142,7 @@ importers: packages/expo-dev-menu: dependencies: expo-dev-menu-interface: - specifier: workspace:56.0.0 + specifier: workspace:~56.0.0 version: link:../expo-dev-menu-interface devDependencies: '@babel/preset-typescript': @@ -4150,7 +4150,7 @@ importers: version: 7.28.5(@babel/core@7.29.0) '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/node': specifier: ^22.14.0 version: 22.19.15 @@ -4331,7 +4331,7 @@ importers: devDependencies: '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/fontfaceobserver': specifier: ^2.1.3 version: 2.1.3 @@ -5047,7 +5047,7 @@ importers: packages/expo-processing: dependencies: expo-gl: - specifier: workspace:56.0.3 + specifier: workspace:~56.0.3 version: link:../expo-gl processing-js: specifier: ^1.6.6 @@ -5060,7 +5060,7 @@ importers: packages/expo-router: dependencies: '@expo/log-box': - specifier: workspace:56.0.5 + specifier: workspace:^56.0.5 version: link:../@expo/log-box '@expo/metro-runtime': specifier: workspace:^56.0.4 @@ -5173,7 +5173,7 @@ importers: version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@tsd/typescript': specifier: ^5.9.3 version: 5.9.3 @@ -5407,7 +5407,7 @@ importers: devDependencies: '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/better-sqlite3': specifier: ^7.6.6 version: 7.6.13 @@ -5459,7 +5459,7 @@ importers: version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/node': specifier: ^22.14.0 version: 22.19.15 @@ -5654,10 +5654,10 @@ importers: version: link:../expo-module-scripts jest: specifier: ^29.3.1 - version: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) + version: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)) ts-jest: specifier: ^29.1.2 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(typescript@6.0.3) + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(typescript@6.0.3) packages/expo-ui: dependencies: @@ -5682,7 +5682,7 @@ importers: version: 7.29.0 '@testing-library/react-native': specifier: ^13.3.0 - version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) + version: 13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/babel__core': specifier: ^7.20.5 version: 7.20.5 @@ -18003,7 +18003,6 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3))': dependencies: @@ -18877,9 +18876,7 @@ snapshots: metro-runtime: 0.84.4 transitivePeerDependencies: - '@babel/core' - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-colors@0.74.89': {} @@ -19184,18 +19181,6 @@ snapshots: optionalDependencies: jest: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)) - '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - jest-matcher-utils: 30.3.0 - picocolors: 1.1.1 - pretty-format: 30.3.0 - react: 19.2.3 - react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-test-renderer: 19.2.3(react@19.2.3) - redent: 3.0.0 - optionalDependencies: - jest: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@25.5.0)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: jest-matcher-utils: 30.3.0 @@ -20910,22 +20895,6 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - - create-jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node create-jest@29.7.0(@types/node@25.5.0)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): dependencies: @@ -23174,26 +23143,6 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - - jest-cli@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node jest-cli@29.7.0(@types/node@25.5.0)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): dependencies: @@ -23244,7 +23193,6 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color - optional: true jest-config@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): dependencies: @@ -23597,19 +23545,6 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - - jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node jest@29.7.0(@types/node@25.5.0)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)): dependencies: @@ -26608,12 +26543,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)))(typescript@6.0.3): + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)))(typescript@6.0.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@25.5.0)(typescript@6.0.3)) + jest: 29.7.0(@types/node@22.19.15)(ts-node@10.9.2(@swc/core@1.15.18)(@types/node@22.19.15)(typescript@6.0.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 From 94c1aae30d07bb367f9b760b91088d49c3b13bc7 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 7 May 2026 15:41:35 -0700 Subject: [PATCH 2/6] Remove unnecessary no-icon warning in prebuild (#45515) # Why The `setIconsAsync` function emits a warning when no icon is defined in the Expo config, but this is a perfectly valid state -- not every project needs a custom icon during development. The warning adds noise without actionable value. # How Removed the `WarningAggregator.addWarningIOS` call that fires when no icon is configured, and removed the corresponding test assertion. # Test Plan Updated the existing test to no longer expect the warning. Existing icon tests continue to pass. --- packages/@expo/prebuild-config/CHANGELOG.md | 2 ++ .../prebuild-config/build/plugins/icons/withIosIcons.js | 3 --- .../build/plugins/icons/withIosIcons.js.map | 2 +- .../src/plugins/icons/__tests__/withIosIcons-test.ts | 5 ----- .../prebuild-config/src/plugins/icons/withIosIcons.ts | 8 -------- 5 files changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/@expo/prebuild-config/CHANGELOG.md b/packages/@expo/prebuild-config/CHANGELOG.md index 5128d34d02d1de..d0c040770a72e1 100644 --- a/packages/@expo/prebuild-config/CHANGELOG.md +++ b/packages/@expo/prebuild-config/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Remove unnecessary warning when no icon is defined in the Expo config. ([#45515](https://github.com/expo/expo/pull/45515) by [@EvanBacon](https://github.com/EvanBacon)) + ### 💡 Others ## 56.0.3 — 2026-05-06 diff --git a/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js b/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js index cc56c0418448f7..4bb832b78ce3b6 100644 --- a/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js +++ b/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js @@ -107,9 +107,6 @@ function getIcons(config) { } async function setIconsAsync(config, projectRoot) { const icon = getIcons(config); - if (!icon || typeof icon === 'string' && !icon || typeof icon === 'object' && !icon?.light && !icon?.dark && !icon?.tinted) { - _configPlugins().WarningAggregator.addWarningIOS('icon', 'No icon is defined in the Expo config.'); - } // Something like projectRoot/ios/MyApp/ const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot); diff --git a/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js.map b/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js.map index d6ce0f0d50332c..f80481b0076d77 100644 --- a/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js.map +++ b/packages/@expo/prebuild-config/build/plugins/icons/withIosIcons.js.map @@ -1 +1 @@ -{"version":3,"file":"withIosIcons.js","names":["_configPlugins","data","require","_Target","_imageUtils","_fs","_interopRequireDefault","_path","_AssetContents","e","__esModule","default","getProjectName","IOSConfig","XcodeUtils","IMAGE_CACHE_NAME","IMAGESET_PATH","withIosIcons","config","withDangerousMod","setIconsAsync","modRequest","projectRoot","withXcodeProject","icon","getIcons","projectName","path","extname","iconName","basename","setIconName","modResults","addIconFileToProject","exports","iosSpecificIcons","ios","paths","light","dark","tinted","filter","Boolean","iconPath","WarningAggregator","addWarningIOS","iosNamedProjectRoot","getIosNamedProjectPath","addLiquidGlassIcon","fs","promises","mkdir","join","recursive","imagesJson","baseIconPath","baseIcon","generateUniversalIconAsync","cacheKey","platform","push","darkIcon","appearance","tintedIcon","writeContentsJsonAsync","images","getAppleIconName","size","scale","name","filename","source","generateImageAsync","cacheType","src","width","height","removeTransparency","resizeMode","backgroundColor","undefined","createSquareAsync","assetPath","writeFile","idiom","appearances","value","sourceIconPath","targetIconPath","existsSync","cp","project","target","findNativeTargetByName","configurations","getBuildConfigurationsForListId","buildConfigurationList","buildSettings","ASSETCATALOG_COMPILER_APPICON_NAME","addResourceFileToGroup","filepath","groupName","isBuildFile","verbose"],"sources":["../../../src/plugins/icons/withIosIcons.ts"],"sourcesContent":["import type { ConfigPlugin } from '@expo/config-plugins';\nimport {\n IOSConfig,\n WarningAggregator,\n withDangerousMod,\n withXcodeProject,\n} from '@expo/config-plugins';\nimport { findNativeTargetByName } from '@expo/config-plugins/build/ios/Target';\nimport type { ExpoConfig, IOSIcons } from '@expo/config-types';\nimport { createSquareAsync, generateImageAsync } from '@expo/image-utils';\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { ContentsJson, ContentsJsonImage } from './AssetContents';\nimport { writeContentsJsonAsync } from './AssetContents';\n\nconst { getProjectName } = IOSConfig.XcodeUtils;\n\nconst IMAGE_CACHE_NAME = 'icons';\nconst IMAGESET_PATH = 'Images.xcassets/AppIcon.appiconset';\n\nexport const withIosIcons: ConfigPlugin = (config) => {\n config = withDangerousMod(config, [\n 'ios',\n async (config) => {\n await setIconsAsync(config, config.modRequest.projectRoot);\n return config;\n },\n ]);\n\n config = withXcodeProject(config, (config) => {\n const icon = getIcons(config);\n const projectName = config.modRequest.projectName;\n\n if (icon && typeof icon === 'string' && path.extname(icon) === '.icon' && projectName) {\n const iconName = path.basename(icon, '.icon');\n setIconName(config.modResults, projectName, iconName);\n addIconFileToProject(config.modResults, projectName, iconName);\n }\n return config;\n });\n\n return config;\n};\n\nexport function getIcons(config: Pick): IOSIcons | string | null {\n const iosSpecificIcons = config.ios?.icon;\n\n if (iosSpecificIcons) {\n // For backwards compatibility, the icon can be a string\n if (typeof iosSpecificIcons === 'string') {\n return iosSpecificIcons || config.icon || null;\n }\n\n if (typeof iosSpecificIcons === 'object') {\n const paths = [iosSpecificIcons.light, iosSpecificIcons.dark, iosSpecificIcons.tinted].filter(\n Boolean\n );\n for (const iconPath of paths) {\n if (typeof iconPath === 'string' && path.extname(iconPath) === '.icon') {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icons (.icon) should be provided as a string to the \"ios.icon\" property, not as an object. Found: \"${iconPath}\"`\n );\n }\n }\n }\n\n // in iOS 18 introduced the ability to specify dark and tinted icons, which users can specify as an object\n if (!iosSpecificIcons.light && !iosSpecificIcons.dark && !iosSpecificIcons.tinted) {\n return config.icon || null;\n }\n\n return iosSpecificIcons;\n }\n\n // Top level icon property should not be used to specify a `.icon` folder\n if (config.icon && typeof config.icon === 'string' && path.extname(config.icon) === '.icon') {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icons (.icon) should be provided via the \"ios.icon\" property, not the root \"icon\" property. Found: \"${config.icon}\"`\n );\n }\n\n if (config.icon) {\n return config.icon;\n }\n\n return null;\n}\n\nexport async function setIconsAsync(config: ExpoConfig, projectRoot: string) {\n const icon = getIcons(config);\n\n if (\n !icon ||\n (typeof icon === 'string' && !icon) ||\n (typeof icon === 'object' && !icon?.light && !icon?.dark && !icon?.tinted)\n ) {\n WarningAggregator.addWarningIOS('icon', 'No icon is defined in the Expo config.');\n }\n\n // Something like projectRoot/ios/MyApp/\n const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot);\n\n if (typeof icon === 'string' && path.extname(icon) === '.icon') {\n return await addLiquidGlassIcon(icon, projectRoot, iosNamedProjectRoot);\n }\n\n // Ensure the Images.xcassets/AppIcon.appiconset path exists\n await fs.promises.mkdir(path.join(iosNamedProjectRoot, IMAGESET_PATH), { recursive: true });\n\n const imagesJson: ContentsJson['images'] = [];\n\n const baseIconPath = typeof icon === 'object' ? icon?.light || icon?.dark || icon?.tinted : icon;\n\n // Store the image JSON data for assigning via the Contents.json\n const baseIcon = await generateUniversalIconAsync(projectRoot, {\n icon: baseIconPath,\n cacheKey: 'universal-icon',\n iosNamedProjectRoot,\n platform: 'ios',\n });\n\n imagesJson.push(baseIcon);\n\n if (typeof icon === 'object') {\n if (icon?.dark) {\n const darkIcon = await generateUniversalIconAsync(projectRoot, {\n icon: icon.dark,\n cacheKey: 'universal-icon-dark',\n iosNamedProjectRoot,\n platform: 'ios',\n appearance: 'dark',\n });\n\n imagesJson.push(darkIcon);\n }\n\n if (icon?.tinted) {\n const tintedIcon = await generateUniversalIconAsync(projectRoot, {\n icon: icon.tinted,\n cacheKey: 'universal-icon-tinted',\n iosNamedProjectRoot,\n platform: 'ios',\n appearance: 'tinted',\n });\n\n imagesJson.push(tintedIcon);\n }\n }\n\n // Finally, write the Contents.json\n await writeContentsJsonAsync(path.join(iosNamedProjectRoot, IMAGESET_PATH), {\n images: imagesJson,\n });\n}\n\n/**\n * Return the project's named iOS path: ios/MyProject/\n *\n * @param projectRoot Expo project root path.\n */\nfunction getIosNamedProjectPath(projectRoot: string): string {\n const projectName = getProjectName(projectRoot);\n return path.join(projectRoot, 'ios', projectName);\n}\n\nfunction getAppleIconName(size: number, scale: number, appearance?: 'dark' | 'tinted'): string {\n let name = 'App-Icon';\n\n if (appearance) {\n name = `${name}-${appearance}`;\n }\n\n name = `${name}-${size}x${size}@${scale}x.png`;\n\n return name;\n}\n\nexport async function generateUniversalIconAsync(\n projectRoot: string,\n {\n icon,\n cacheKey,\n iosNamedProjectRoot,\n platform,\n appearance,\n }: {\n platform: 'watchos' | 'ios';\n icon?: string | null;\n appearance?: 'dark' | 'tinted';\n iosNamedProjectRoot: string;\n cacheKey: string;\n }\n): Promise {\n const size = 1024;\n const filename = getAppleIconName(size, 1, appearance);\n\n let source: Buffer;\n\n if (icon) {\n // Using this method will cache the images in `.expo` based on the properties used to generate them.\n // this method also supports remote URLs and using the global sharp instance.\n source = (\n await generateImageAsync(\n { projectRoot, cacheType: IMAGE_CACHE_NAME + cacheKey },\n {\n src: icon,\n name: filename,\n width: size,\n height: size,\n // Transparency needs to be preserved in dark variant, but can safely be removed in \"light\" and \"tinted\" variants.\n removeTransparency: appearance !== 'dark',\n // The icon should be square, but if it's not then it will be cropped.\n resizeMode: 'cover',\n // Force the background color to solid white to prevent any transparency. (for \"any\" and \"tinted\" variants)\n // TODO: Maybe use a more adaptive option based on the icon color?\n backgroundColor: appearance !== 'dark' ? '#ffffff' : undefined,\n }\n )\n ).source;\n } else {\n // Create a white square image if no icon exists to mitigate the chance of a submission failure to the app store.\n source = await createSquareAsync({ size });\n }\n // Write image buffer to the file system.\n const assetPath = path.join(iosNamedProjectRoot, IMAGESET_PATH, filename);\n await fs.promises.writeFile(assetPath, source);\n\n return {\n filename,\n idiom: 'universal',\n platform,\n size: `${size}x${size}`,\n ...(appearance ? { appearances: [{ appearance: 'luminosity', value: appearance }] } : {}),\n };\n}\n\nasync function addLiquidGlassIcon(\n iconPath: string,\n projectRoot: string,\n iosNamedProjectRoot: string\n): Promise {\n const iconName = path.basename(iconPath, '.icon');\n const sourceIconPath = path.join(projectRoot, iconPath);\n const targetIconPath = path.join(iosNamedProjectRoot, `${iconName}.icon`);\n\n if (!fs.existsSync(sourceIconPath)) {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icon file not found at path: ${iconPath}`\n );\n return;\n }\n\n await fs.promises.cp(sourceIconPath, targetIconPath, { recursive: true });\n}\n\n/**\n * Adds the .icons name to the project\n */\nfunction setIconName(project: any, projectName: string, iconName: string): void {\n const [, target] = findNativeTargetByName(project, projectName);\n const configurations = IOSConfig.XcodeUtils.getBuildConfigurationsForListId(\n project,\n target.buildConfigurationList\n );\n\n for (const [, config] of configurations) {\n if ((config as any)?.buildSettings) {\n (config as any).buildSettings.ASSETCATALOG_COMPILER_APPICON_NAME = iconName;\n }\n }\n}\n\n/**\n * Adds the .icon file to the project\n */\nfunction addIconFileToProject(project: any, projectName: string, iconName: string): void {\n const iconPath = `${iconName}.icon`;\n\n IOSConfig.XcodeUtils.addResourceFileToGroup({\n filepath: `${projectName}/${iconPath}`,\n groupName: projectName,\n project,\n isBuildFile: true,\n verbose: true,\n });\n}\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file +{"version":3,"file":"withIosIcons.js","names":["_configPlugins","data","require","_Target","_imageUtils","_fs","_interopRequireDefault","_path","_AssetContents","e","__esModule","default","getProjectName","IOSConfig","XcodeUtils","IMAGE_CACHE_NAME","IMAGESET_PATH","withIosIcons","config","withDangerousMod","setIconsAsync","modRequest","projectRoot","withXcodeProject","icon","getIcons","projectName","path","extname","iconName","basename","setIconName","modResults","addIconFileToProject","exports","iosSpecificIcons","ios","paths","light","dark","tinted","filter","Boolean","iconPath","WarningAggregator","addWarningIOS","iosNamedProjectRoot","getIosNamedProjectPath","addLiquidGlassIcon","fs","promises","mkdir","join","recursive","imagesJson","baseIconPath","baseIcon","generateUniversalIconAsync","cacheKey","platform","push","darkIcon","appearance","tintedIcon","writeContentsJsonAsync","images","getAppleIconName","size","scale","name","filename","source","generateImageAsync","cacheType","src","width","height","removeTransparency","resizeMode","backgroundColor","undefined","createSquareAsync","assetPath","writeFile","idiom","appearances","value","sourceIconPath","targetIconPath","existsSync","cp","project","target","findNativeTargetByName","configurations","getBuildConfigurationsForListId","buildConfigurationList","buildSettings","ASSETCATALOG_COMPILER_APPICON_NAME","addResourceFileToGroup","filepath","groupName","isBuildFile","verbose"],"sources":["../../../src/plugins/icons/withIosIcons.ts"],"sourcesContent":["import type { ConfigPlugin } from '@expo/config-plugins';\nimport {\n IOSConfig,\n WarningAggregator,\n withDangerousMod,\n withXcodeProject,\n} from '@expo/config-plugins';\nimport { findNativeTargetByName } from '@expo/config-plugins/build/ios/Target';\nimport type { ExpoConfig, IOSIcons } from '@expo/config-types';\nimport { createSquareAsync, generateImageAsync } from '@expo/image-utils';\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { ContentsJson, ContentsJsonImage } from './AssetContents';\nimport { writeContentsJsonAsync } from './AssetContents';\n\nconst { getProjectName } = IOSConfig.XcodeUtils;\n\nconst IMAGE_CACHE_NAME = 'icons';\nconst IMAGESET_PATH = 'Images.xcassets/AppIcon.appiconset';\n\nexport const withIosIcons: ConfigPlugin = (config) => {\n config = withDangerousMod(config, [\n 'ios',\n async (config) => {\n await setIconsAsync(config, config.modRequest.projectRoot);\n return config;\n },\n ]);\n\n config = withXcodeProject(config, (config) => {\n const icon = getIcons(config);\n const projectName = config.modRequest.projectName;\n\n if (icon && typeof icon === 'string' && path.extname(icon) === '.icon' && projectName) {\n const iconName = path.basename(icon, '.icon');\n setIconName(config.modResults, projectName, iconName);\n addIconFileToProject(config.modResults, projectName, iconName);\n }\n return config;\n });\n\n return config;\n};\n\nexport function getIcons(config: Pick): IOSIcons | string | null {\n const iosSpecificIcons = config.ios?.icon;\n\n if (iosSpecificIcons) {\n // For backwards compatibility, the icon can be a string\n if (typeof iosSpecificIcons === 'string') {\n return iosSpecificIcons || config.icon || null;\n }\n\n if (typeof iosSpecificIcons === 'object') {\n const paths = [iosSpecificIcons.light, iosSpecificIcons.dark, iosSpecificIcons.tinted].filter(\n Boolean\n );\n for (const iconPath of paths) {\n if (typeof iconPath === 'string' && path.extname(iconPath) === '.icon') {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icons (.icon) should be provided as a string to the \"ios.icon\" property, not as an object. Found: \"${iconPath}\"`\n );\n }\n }\n }\n\n // in iOS 18 introduced the ability to specify dark and tinted icons, which users can specify as an object\n if (!iosSpecificIcons.light && !iosSpecificIcons.dark && !iosSpecificIcons.tinted) {\n return config.icon || null;\n }\n\n return iosSpecificIcons;\n }\n\n // Top level icon property should not be used to specify a `.icon` folder\n if (config.icon && typeof config.icon === 'string' && path.extname(config.icon) === '.icon') {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icons (.icon) should be provided via the \"ios.icon\" property, not the root \"icon\" property. Found: \"${config.icon}\"`\n );\n }\n\n if (config.icon) {\n return config.icon;\n }\n\n return null;\n}\n\nexport async function setIconsAsync(config: ExpoConfig, projectRoot: string) {\n const icon = getIcons(config);\n\n // Something like projectRoot/ios/MyApp/\n const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot);\n\n if (typeof icon === 'string' && path.extname(icon) === '.icon') {\n return await addLiquidGlassIcon(icon, projectRoot, iosNamedProjectRoot);\n }\n\n // Ensure the Images.xcassets/AppIcon.appiconset path exists\n await fs.promises.mkdir(path.join(iosNamedProjectRoot, IMAGESET_PATH), { recursive: true });\n\n const imagesJson: ContentsJson['images'] = [];\n\n const baseIconPath = typeof icon === 'object' ? icon?.light || icon?.dark || icon?.tinted : icon;\n\n // Store the image JSON data for assigning via the Contents.json\n const baseIcon = await generateUniversalIconAsync(projectRoot, {\n icon: baseIconPath,\n cacheKey: 'universal-icon',\n iosNamedProjectRoot,\n platform: 'ios',\n });\n\n imagesJson.push(baseIcon);\n\n if (typeof icon === 'object') {\n if (icon?.dark) {\n const darkIcon = await generateUniversalIconAsync(projectRoot, {\n icon: icon.dark,\n cacheKey: 'universal-icon-dark',\n iosNamedProjectRoot,\n platform: 'ios',\n appearance: 'dark',\n });\n\n imagesJson.push(darkIcon);\n }\n\n if (icon?.tinted) {\n const tintedIcon = await generateUniversalIconAsync(projectRoot, {\n icon: icon.tinted,\n cacheKey: 'universal-icon-tinted',\n iosNamedProjectRoot,\n platform: 'ios',\n appearance: 'tinted',\n });\n\n imagesJson.push(tintedIcon);\n }\n }\n\n // Finally, write the Contents.json\n await writeContentsJsonAsync(path.join(iosNamedProjectRoot, IMAGESET_PATH), {\n images: imagesJson,\n });\n}\n\n/**\n * Return the project's named iOS path: ios/MyProject/\n *\n * @param projectRoot Expo project root path.\n */\nfunction getIosNamedProjectPath(projectRoot: string): string {\n const projectName = getProjectName(projectRoot);\n return path.join(projectRoot, 'ios', projectName);\n}\n\nfunction getAppleIconName(size: number, scale: number, appearance?: 'dark' | 'tinted'): string {\n let name = 'App-Icon';\n\n if (appearance) {\n name = `${name}-${appearance}`;\n }\n\n name = `${name}-${size}x${size}@${scale}x.png`;\n\n return name;\n}\n\nexport async function generateUniversalIconAsync(\n projectRoot: string,\n {\n icon,\n cacheKey,\n iosNamedProjectRoot,\n platform,\n appearance,\n }: {\n platform: 'watchos' | 'ios';\n icon?: string | null;\n appearance?: 'dark' | 'tinted';\n iosNamedProjectRoot: string;\n cacheKey: string;\n }\n): Promise {\n const size = 1024;\n const filename = getAppleIconName(size, 1, appearance);\n\n let source: Buffer;\n\n if (icon) {\n // Using this method will cache the images in `.expo` based on the properties used to generate them.\n // this method also supports remote URLs and using the global sharp instance.\n source = (\n await generateImageAsync(\n { projectRoot, cacheType: IMAGE_CACHE_NAME + cacheKey },\n {\n src: icon,\n name: filename,\n width: size,\n height: size,\n // Transparency needs to be preserved in dark variant, but can safely be removed in \"light\" and \"tinted\" variants.\n removeTransparency: appearance !== 'dark',\n // The icon should be square, but if it's not then it will be cropped.\n resizeMode: 'cover',\n // Force the background color to solid white to prevent any transparency. (for \"any\" and \"tinted\" variants)\n // TODO: Maybe use a more adaptive option based on the icon color?\n backgroundColor: appearance !== 'dark' ? '#ffffff' : undefined,\n }\n )\n ).source;\n } else {\n // Create a white square image if no icon exists to mitigate the chance of a submission failure to the app store.\n source = await createSquareAsync({ size });\n }\n // Write image buffer to the file system.\n const assetPath = path.join(iosNamedProjectRoot, IMAGESET_PATH, filename);\n await fs.promises.writeFile(assetPath, source);\n\n return {\n filename,\n idiom: 'universal',\n platform,\n size: `${size}x${size}`,\n ...(appearance ? { appearances: [{ appearance: 'luminosity', value: appearance }] } : {}),\n };\n}\n\nasync function addLiquidGlassIcon(\n iconPath: string,\n projectRoot: string,\n iosNamedProjectRoot: string\n): Promise {\n const iconName = path.basename(iconPath, '.icon');\n const sourceIconPath = path.join(projectRoot, iconPath);\n const targetIconPath = path.join(iosNamedProjectRoot, `${iconName}.icon`);\n\n if (!fs.existsSync(sourceIconPath)) {\n WarningAggregator.addWarningIOS(\n 'icon',\n `Liquid glass icon file not found at path: ${iconPath}`\n );\n return;\n }\n\n await fs.promises.cp(sourceIconPath, targetIconPath, { recursive: true });\n}\n\n/**\n * Adds the .icons name to the project\n */\nfunction setIconName(project: any, projectName: string, iconName: string): void {\n const [, target] = findNativeTargetByName(project, projectName);\n const configurations = IOSConfig.XcodeUtils.getBuildConfigurationsForListId(\n project,\n target.buildConfigurationList\n );\n\n for (const [, config] of configurations) {\n if ((config as any)?.buildSettings) {\n (config as any).buildSettings.ASSETCATALOG_COMPILER_APPICON_NAME = iconName;\n }\n }\n}\n\n/**\n * Adds the .icon file to the project\n */\nfunction addIconFileToProject(project: any, projectName: string, iconName: string): void {\n const iconPath = `${iconName}.icon`;\n\n IOSConfig.XcodeUtils.addResourceFileToGroup({\n filepath: `${projectName}/${iconPath}`,\n groupName: projectName,\n project,\n isBuildFile: true,\n verbose: true,\n });\n}\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file diff --git a/packages/@expo/prebuild-config/src/plugins/icons/__tests__/withIosIcons-test.ts b/packages/@expo/prebuild-config/src/plugins/icons/__tests__/withIosIcons-test.ts index a5be22bdbca56d..e62ff56b20d426 100644 --- a/packages/@expo/prebuild-config/src/plugins/icons/__tests__/withIosIcons-test.ts +++ b/packages/@expo/prebuild-config/src/plugins/icons/__tests__/withIosIcons-test.ts @@ -198,11 +198,6 @@ describe('e2e: iOS icons with fallback image', () => { value.startsWith('ios/HelloWorld/Images.xcassets/AppIcon.appiconset/App-Icon') ); - expect(WarningAggregator.addWarningIOS).toHaveBeenCalledTimes(1); - expect(WarningAggregator.addWarningIOS).toHaveBeenCalledWith( - 'icon', - 'No icon is defined in the Expo config.' - ); expect(icons.length).toBe(1); // Test the Contents.json file diff --git a/packages/@expo/prebuild-config/src/plugins/icons/withIosIcons.ts b/packages/@expo/prebuild-config/src/plugins/icons/withIosIcons.ts index 1efc28a50448df..4a6cfbcc81f1f1 100644 --- a/packages/@expo/prebuild-config/src/plugins/icons/withIosIcons.ts +++ b/packages/@expo/prebuild-config/src/plugins/icons/withIosIcons.ts @@ -92,14 +92,6 @@ export function getIcons(config: Pick): IOSIcons | s export async function setIconsAsync(config: ExpoConfig, projectRoot: string) { const icon = getIcons(config); - if ( - !icon || - (typeof icon === 'string' && !icon) || - (typeof icon === 'object' && !icon?.light && !icon?.dark && !icon?.tinted) - ) { - WarningAggregator.addWarningIOS('icon', 'No icon is defined in the Expo config.'); - } - // Something like projectRoot/ios/MyApp/ const iosNamedProjectRoot = getIosNamedProjectPath(projectRoot); From 39d6083559ed067dff2edf8d0125265b9004b5c5 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 7 May 2026 16:11:49 -0700 Subject: [PATCH 3/6] fix(cli): prevent Metro progress bars from corrupting terminal output (#45523) # Why Metro's Terminal class manages progress bar status lines on stdout using cursor movement. Two issues caused progress bars to appear as permanent, stuck output: 1. **Stale status snapshots**: Terminal's `#update()` is async and captures `#nextStatusStr` at the start. When `_log()` calls `terminal.log()`, the async update captures whatever status is currently set (often a stale progress bar) before `terminal.status()` can update it. 2. **Stderr cursor corruption**: `console.warn()` and `console.error()` during server bundle evaluation write directly to stderr, which shifts the terminal cursor without Terminal's knowledge. When Terminal tries to clear status lines via cursor movement on stdout, the positions are wrong. # How **Status clearing** (`TerminalReporter.update()`): Clears `terminal.status('')` before `_log()` runs for non-progress events. This way Terminal's async `#update()` captures an empty status and writes no progress bars alongside log lines. The correct status is restored by `terminal.status()` at the end of `update()`. **Non-interactive suppression** (`MetroTerminalReporter._getStatusMessage()`): Returns empty string in non-TTY mode since status lines can't be overwritten and just create noise. "Bundled Xms" completion messages still appear (they use `terminal.log()`). **Stderr coordination** (`LogRespectingTerminal.logStderr()`): Intercepts `console.warn`/`console.error` and routes them through a `logStderr()` method that uses Terminal's public API (`status()` + `flush()`) to clear progress bars before the stderr write and restore them after. # Test Plan - Unit tests for status clearing behavior, non-interactive suppression, and progress event passthrough - Manual testing with `expo start` on a project that triggers server bundle warnings during bundling - Verified with debug logging that all event types flow through the `update()` override --- packages/@expo/cli/CHANGELOG.md | 1 + .../start/server/metro/TerminalReporter.ts | 23 +++ .../__tests__/MetroTerminalReporter-test.ts | 152 ++++++++++++++++++ .../start/server/metro/instantiateMetro.ts | 55 ++++++- 4 files changed, 230 insertions(+), 1 deletion(-) diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index bf330e30420f2d..2fd7c4603be450 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -8,6 +8,7 @@ ### 🐛 Bug fixes +- Fix Metro progress bars appearing as permanent output due to cursor corruption from stderr writes and stale status snapshots. ([#45523](https://github.com/expo/expo/pull/45523) by [@EvanBacon](https://github.com/EvanBacon)) - Prevent Metro loading indicator from showing broken states in headless runs. ([#45513](https://github.com/expo/expo/pull/45513) by [@EvanBacon](https://github.com/EvanBacon)) - Fix `--port 0` exiting silently in `expo start` when the port is busy. ([#45513](https://github.com/expo/expo/pull/45513) by [@EvanBacon](https://github.com/EvanBacon)) diff --git a/packages/@expo/cli/src/start/server/metro/TerminalReporter.ts b/packages/@expo/cli/src/start/server/metro/TerminalReporter.ts index 7702944c58600d..fc477b8955e9c9 100644 --- a/packages/@expo/cli/src/start/server/metro/TerminalReporter.ts +++ b/packages/@expo/cli/src/start/server/metro/TerminalReporter.ts @@ -59,6 +59,29 @@ export class TerminalReporter extends XTerminalReporter implements TerminalRepor /** Keep track of bundle processes that should not be logged. */ _hiddenBundleEvents: Set = new Set(); + /** + * Override Metro's update() to clear the status before _log() runs. + * + * Metro's Terminal.#update() is async and snapshots #nextStatusStr at the start. + * When _log() calls terminal.log(), Terminal starts #update() which captures + * whatever status is currently set — often a stale progress bar. This progress bar + * gets written as permanent output between log lines because the next #update() + * cycle (which would clear it) runs 33ms later. + * + * By clearing the status to empty before _log(), Terminal's #update() captures + * an empty status and doesn't write any progress bars alongside log lines. + * The correct status is then restored by terminal.status() at the end. + */ + update(event: TerminalReportableEvent): void { + if ( + event.type !== 'bundle_transform_progressed' && + event.type !== ('bundle_transform_progressed_throttled' as string) + ) { + this.terminal.status(''); + } + super.update(event); + } + _log(event: TerminalReportableEvent): void { switch (event.type) { case 'transform_cache_reset': diff --git a/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts b/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts index c4826811b8ca34..e34e3053f0fb78 100644 --- a/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts +++ b/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts @@ -27,6 +27,11 @@ const LIGHT_BLOCK_CHAR = '\u2591'; jest.useFakeTimers(); +const mockIsInteractive = jest.fn(() => false); +jest.mock('../../../../utils/interactive', () => ({ + isInteractive: () => mockIsInteractive(), +})); + jest.mock('../../serverLogLikeMetro', () => { const original = jest.requireActual('../../serverLogLikeMetro'); return { @@ -582,6 +587,153 @@ describe('non-interactive terminal output', () => { }); }); +describe('status cleared before log lines', () => { + beforeEach(() => mockIsInteractive.mockReturnValue(true)); + afterEach(() => mockIsInteractive.mockReturnValue(false)); + + /** + * Metro's Terminal.#update() is async and snapshots #nextStatusStr when it starts. + * When _log() calls terminal.log(), Terminal starts #update() which captures + * whatever status is currently set. If it's a progress bar, that progress bar + * gets written as permanent output between log lines (it can't be reliably + * cleared before more output arrives). + * + * The fix: update() clears the status to empty before _log() runs, so Terminal's + * #update() captures an empty status and writes no progress bars alongside log lines. + */ + + const buildID1 = 'build-1'; + const buildID2 = 'build-2'; + const entryFile = 'node_modules/expo-router/entry.js'; + const serverEntry = 'packages/@expo/router-server/node/render.js'; + const webDetails = asBundleDetails({ + entryFile, + platform: 'web', + bundleType: 'bundle', + }); + const serverDetails = asBundleDetails({ + entryFile: serverEntry, + platform: 'web', + bundleType: 'bundle', + customTransformOptions: { environment: 'node' }, + }); + + function createReporter() { + const callOrder: { type: 'log' | 'status'; content: string }[] = []; + + const terminal = { + log: jest.fn((...args: any[]) => { + const content = stripAnsi(args.join(' ')); + callOrder.push({ type: 'log', content }); + }), + persistStatus: jest.fn(), + status: jest.fn((...args: any[]) => { + const content = stripAnsi(args.join(' ')); + callOrder.push({ type: 'status', content }); + }), + flush: jest.fn(), + } satisfies Partial; + + const reporter = new MetroTerminalReporter('/', terminal as any); + reporter._getElapsedTime = jest.fn(() => BigInt(100_000_000)); + return { reporter, terminal, callOrder }; + } + + it('clears status before any log call during bundle_build_done', () => { + const { reporter, callOrder } = createReporter(); + + // Start a bundle and add progress + reporter.update({ + type: 'bundle_build_started', + buildID: buildID1, + bundleDetails: { ...webDetails, buildID: buildID1 }, + isPrefetch: false, + } as any); + reporter.update({ + type: 'bundle_transform_progressed_throttled', + buildID: buildID1, + transformedFileCount: 50, + totalFileCount: 100, + } as any); + + callOrder.length = 0; + + // Bundle completes + reporter.update({ + type: 'bundle_build_done', + buildID: buildID1, + bundleDetails: { ...webDetails, buildID: buildID1 }, + } as any); + + // The FIRST call should be status('') to clear progress bars + expect(callOrder[0]).toEqual({ type: 'status', content: '' }); + + // The log should come after the clear + const bundledLog = callOrder.find((c) => c.type === 'log' && c.content.includes('Bundled')); + expect(bundledLog).toBeDefined(); + }); + + it('clears status before server log warnings', () => { + const { reporter, callOrder } = createReporter(); + + // Start a bundle (web) and add progress + reporter.update({ + type: 'bundle_build_started', + buildID: buildID1, + bundleDetails: { ...webDetails, buildID: buildID1 }, + isPrefetch: false, + } as any); + reporter.update({ + type: 'bundle_transform_progressed_throttled', + buildID: buildID1, + transformedFileCount: 883, + totalFileCount: 886, + } as any); + + callOrder.length = 0; + + // A server log (WARN) comes in while web bundle is still active + reporter.update({ + type: 'unstable_server_log', + level: 'warn', + data: ['Deep imports from react-native are deprecated'], + } as any); + + // The FIRST call should be status('') to clear the web progress bar + expect(callOrder[0]).toEqual({ type: 'status', content: '' }); + + // Status should be restored at the end (web bundle still active) + const lastStatus = [...callOrder].reverse().find((c) => c.type === 'status'); + expect(lastStatus?.content).toContain(entryFile); + }); + + it('does not clear status for progress events', () => { + const { reporter, callOrder } = createReporter(); + + // Start a bundle + reporter.update({ + type: 'bundle_build_started', + buildID: buildID1, + bundleDetails: { ...webDetails, buildID: buildID1 }, + isPrefetch: false, + } as any); + + callOrder.length = 0; + + // Progress event should NOT clear the status + reporter.update({ + type: 'bundle_transform_progressed_throttled', + buildID: buildID1, + transformedFileCount: 50, + totalFileCount: 100, + } as any); + + // Should not start with a clear + const firstStatus = callOrder.find((c) => c.type === 'status'); + expect(firstStatus?.content).not.toBe(''); + }); +}); + describe('extractCodeFrame', () => { it('extracts code frame from a message', () => { const inputMessage = ` diff --git a/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts b/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts index 041211d213b84e..f92432d932f672 100644 --- a/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts +++ b/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts @@ -84,8 +84,20 @@ function asWritable(input: T): { -readonly [K in keyof T]: T[K] } { return input; } -// Wrap terminal and polyfill console.log so we can log during bundling without breaking the indicator. +/** + * Extends Metro's Terminal to intercept all console methods so they don't + * corrupt the progress bar status lines. + * + * console.log/info are routed through terminal.log() (stdout, managed). + * console.warn/error are routed through logStderr() which clears the + * status from stdout before writing to stderr, then restores it. + * Without this, unmanaged stderr writes shift the cursor and cause + * progress bars to get stuck as permanent output. + */ class LogRespectingTerminal extends Terminal { + #stderrQueue: string[] = []; + #drainingStderr = false; + constructor(stream: import('node:net').Socket | import('node:stream').Writable) { super(stream, { ttyPrint: true }); @@ -100,8 +112,49 @@ class LogRespectingTerminal extends Terminal { this.flush(); }; + const sendStderr = (...msg: any[]) => { + if (!msg.length) { + this.logStderr(''); + } else { + const [format, ...args] = msg; + this.logStderr(require('util').format(format, ...args)); + } + }; + console.log = sendLog; console.info = sendLog; + console.warn = sendStderr; + console.error = sendStderr; + } + + /** Write to stderr without corrupting Terminal's cursor tracking. */ + logStderr(line: string): void { + if (!(process.stdout as any).isTTY) { + process.stderr.write(line + '\n'); + return; + } + this.#stderrQueue.push(line); + this.#drainStderr(); + } + + async #drainStderr(): Promise { + if (this.#drainingStderr) return; + this.#drainingStderr = true; + + while (this.#stderrQueue.length > 0) { + // Clear status, flush to ensure it's removed from screen + const prev = this.status(''); + await this.flush(); + + // Write to stderr while status is cleared + const lines = this.#stderrQueue.splice(0); + process.stderr.write(lines.join('\n') + '\n'); + + // Restore status + this.status(prev); + } + + this.#drainingStderr = false; } } From 22dd20f95aca598edf6f45239ec13d5699d3db98 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 7 May 2026 16:12:56 -0700 Subject: [PATCH 4/6] fix(cli): replace deprecated url.parse() with WHATWG URL API (#45524) # Why Node.js emits a `DEP0169` deprecation warning for `url.parse()` during Metro dev server upgrade handling. The warning recommends using the WHATWG `URL` API instead. # How Replaced the `url.parse(request.url!)` call in `runServer-fork.ts` with `new URL(request.url!, 'http://localhost')`. Since only `pathname` is extracted and `request.url` is a relative path, the base URL value is irrelevant. Also removed the unused `import { parse } from 'url'`. # Test Plan Verified the dev server starts without the `DEP0169` deprecation warning. WebSocket upgrade handling (HMR) continues to work as before since the pathname extraction behavior is equivalent. --- packages/@expo/cli/CHANGELOG.md | 1 + packages/@expo/cli/src/start/server/metro/runServer-fork.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index 2fd7c4603be450..b2c131ded71d56 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -14,6 +14,7 @@ ### 💡 Others +- Replace deprecated `url.parse()` with WHATWG `URL` API in Metro dev server. ([#45524](https://github.com/expo/expo/pull/45524) by [@EvanBacon](https://github.com/EvanBacon)) - Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) ## 56.0.6 — 2026-05-07 diff --git a/packages/@expo/cli/src/start/server/metro/runServer-fork.ts b/packages/@expo/cli/src/start/server/metro/runServer-fork.ts index ec738fe621ca6f..96b1000013f0e3 100644 --- a/packages/@expo/cli/src/start/server/metro/runServer-fork.ts +++ b/packages/@expo/cli/src/start/server/metro/runServer-fork.ts @@ -13,7 +13,6 @@ import assert from 'assert'; import http from 'http'; import https from 'https'; import type { AddressInfo } from 'net'; -import { parse } from 'url'; import type { WebSocketServer } from 'ws'; import type { MetroBundlerDevServer } from './MetroBundlerDevServer'; @@ -171,7 +170,7 @@ export const runServer = async ( }); httpServer.on('upgrade', (request, socket, head) => { - const { pathname } = parse(request.url!); + const { pathname } = new URL(request.url!, 'http://localhost'); if (pathname != null && websocketEndpoints[pathname]) { websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => { websocketEndpoints[pathname]?.emit('connection', ws, request); From 0a9c420dd9f1af6950c28e5c20f7e8fa0d0534fb Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 7 May 2026 16:49:47 -0700 Subject: [PATCH 5/6] feat(cli): prefix web client logs with platform name in Metro terminal (#45516) # Why Web client logs in the Metro terminal have no platform indicator, making it hard to distinguish them from native logs when running multiple platforms simultaneously. API route logs already show a prefix (the lambda symbol), but web client logs are unlabeled. Additionally, React's browser console calls use printf-style format strings (e.g., `console.warn('%s\n\n%s', msg1, msg2)`) which the browser substitutes natively, but when forwarded to Metro's terminal logger the raw `%s` placeholders are printed literally. # How - Changed the web HMR client to send `mode: 'web'` instead of `mode: 'BRIDGE'` with a separate `platform` field. The `mode` field is the only custom field Metro's HmrServer forwards to the reporter event, so encoding the platform there is the only way to pass it through without patching Metro. - Added `getPlatformTagForClientLog()` in `MetroTerminalReporter` that maps known mode values (`web`, `ios`, `android`) to formatted names (`Web`, `iOS`, `Android`) and passes them to `logLikeMetro` as the prefix. - Added `applyConsoleFormatting()` that applies `util.format` to log data when the first item contains printf-style specifiers, matching browser console behavior. - Removed the `$$EXPO_INITIAL_PROPS` guard and redundant setup log messages from `setupHMR.ts`. # Test Plan - Added unit tests for platform prefixing (web, NOBRIDGE, no mode) and format string substitution. - Tested manually with `expo start --web` and verified `Web LOG`, `Web WARN` prefixes appear in the terminal. --- packages/@expo/cli/CHANGELOG.md | 4 +- .../server/metro/MetroTerminalReporter.ts | 61 +++++++++++-- .../__tests__/MetroTerminalReporter-test.ts | 86 +++++++++++++++++++ .../src/start/server/serverLogLikeMetro.ts | 2 +- packages/expo/CHANGELOG.md | 3 +- .../expo/build/async-require/hmr.d.ts.map | 2 +- packages/expo/src/async-require/hmr.ts | 3 +- packages/expo/src/async-require/setupHMR.ts | 5 +- 8 files changed, 150 insertions(+), 16 deletions(-) diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index b2c131ded71d56..d18a6bfe6d30de 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -6,12 +6,14 @@ ### 🎉 New features +- Prefix web client logs with platform name in Metro terminal output. ([#45516](https://github.com/expo/expo/pull/45516) by [@EvanBacon](https://github.com/EvanBacon)) + ### 🐛 Bug fixes - Fix Metro progress bars appearing as permanent output due to cursor corruption from stderr writes and stale status snapshots. ([#45523](https://github.com/expo/expo/pull/45523) by [@EvanBacon](https://github.com/EvanBacon)) - Prevent Metro loading indicator from showing broken states in headless runs. ([#45513](https://github.com/expo/expo/pull/45513) by [@EvanBacon](https://github.com/EvanBacon)) - Fix `--port 0` exiting silently in `expo start` when the port is busy. ([#45513](https://github.com/expo/expo/pull/45513) by [@EvanBacon](https://github.com/EvanBacon)) - +- Apply printf-style format substitution for web client logs forwarded from the browser. ([#45516](https://github.com/expo/expo/pull/45516) by [@EvanBacon](https://github.com/EvanBacon)) ### 💡 Others - Replace deprecated `url.parse()` with WHATWG `URL` API in Metro dev server. ([#45524](https://github.com/expo/expo/pull/45524) by [@EvanBacon](https://github.com/EvanBacon)) diff --git a/packages/@expo/cli/src/start/server/metro/MetroTerminalReporter.ts b/packages/@expo/cli/src/start/server/metro/MetroTerminalReporter.ts index 58dfdfd32c8b0a..3966a0f5d6f3df 100644 --- a/packages/@expo/cli/src/start/server/metro/MetroTerminalReporter.ts +++ b/packages/@expo/cli/src/start/server/metro/MetroTerminalReporter.ts @@ -1,7 +1,7 @@ import type { Terminal } from '@expo/metro/metro-core'; import chalk from 'chalk'; import path from 'path'; -import { stripVTControlCharacters } from 'util'; +import { format as utilFormat, stripVTControlCharacters } from 'util'; import { logWarning, TerminalReporter } from './TerminalReporter'; import type { @@ -335,8 +335,17 @@ export class MetroTerminalReporter extends TerminalReporter { } } - #onClientLog(evt: { type: 'client_log'; level?: ClientLogLevel; data: unknown[] }) { - const { level = 'log', data } = evt; + #onClientLog(evt: { + type: 'client_log'; + level?: ClientLogLevel; + data: unknown[]; + mode?: string; + }) { + const { level = 'log' } = evt; + // Apply printf-style format substitution (e.g. %s, %d) that browsers handle + // natively in console methods but Node/Metro terminal logging does not. + const data = applyConsoleFormatting(evt.data); + const platformTag = getPlatformTagForClientLog(evt.mode); if (level === 'warn' || (level as string) === 'error') { let hasStack = false; const parsed = data.map((msg) => { @@ -399,7 +408,7 @@ export class MetroTerminalReporter extends TerminalReporter { : symbolicated; event('client_log', { level, data: symbolicated }); - logLikeMetro(this.terminal.log.bind(this.terminal), level, null, ...filtered); + logLikeMetro(this.terminal.log.bind(this.terminal), level, platformTag, ...filtered); })(); return; } @@ -407,7 +416,7 @@ export class MetroTerminalReporter extends TerminalReporter { event('client_log', { level, data }); // Overwrite the Metro terminal logging so we can improve the warnings, symbolicate stacks, and inject extra info. - logLikeMetro(this.terminal.log.bind(this.terminal), level, null, ...data); + logLikeMetro(this.terminal.log.bind(this.terminal), level, platformTag, ...data); } #captureLog(evt: TerminalReportableEvent) { @@ -552,11 +561,51 @@ function isAppRegistryStartupMessage(body: any[]): boolean { ); } +/** Apply printf-style format substitutions (%s, %d, %i, %f, %o, %O) that browsers handle natively */ +function applyConsoleFormatting(data: unknown[]): unknown[] { + if (data.length <= 1 || typeof data[0] !== 'string' || !/%[sdifoO%]/.test(data[0])) { + return data; + } + return [utilFormat(...(data as [string, ...unknown[]]))]; +} + +/** @returns formatted platform name for a client log event, or null if no prefix should be shown */ +function getPlatformTagForClientLog(mode?: string): string | null { + switch (mode) { + case 'ios': + return 'iOS'; + case 'android': + return 'Android'; + case 'web': + return 'Web'; + case 'dom': + return 'DOM'; + default: + return null; + } +} + /** @returns platform specific tag for a `BundleDetails` object */ function getPlatformTagForBuildDetails(bundleDetails?: BundleDetails | null): string { const platform = bundleDetails?.platform ?? null; if (platform) { - const formatted = { ios: 'iOS', android: 'Android', web: 'Web' }[platform] || platform; + let formatted: string; + switch (platform) { + case 'ios': + formatted = 'iOS'; + break; + case 'android': + formatted = 'Android'; + break; + case 'web': + formatted = 'Web'; + break; + case 'dom': + formatted = 'DOM'; + break; + default: + formatted = platform; + } return `${chalk.bold(formatted)} `; } diff --git a/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts b/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts index e34e3053f0fb78..14536d62343180 100644 --- a/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts +++ b/packages/@expo/cli/src/start/server/metro/__tests__/MetroTerminalReporter-test.ts @@ -204,6 +204,92 @@ describe('symbolicate React stacks', () => { }); }); +describe('client log platform prefix and format substitution', () => { + const terminal = { + log: jest.fn(), + persistStatus: jest.fn(), + status: jest.fn(), + flush: jest.fn(), + _update: jest.fn(), + } satisfies Partial; + + const reporter = new MetroTerminalReporter('/', terminal as any); + + beforeEach(() => { + terminal.log.mockReset(); + }); + + it(`prefixes web client logs with "Web"`, () => { + reporter._log({ + type: 'client_log', + level: 'log', + data: ['hello world'], + mode: 'web', + } as any); + + expect(terminal.log).toHaveBeenCalledTimes(1); + const output = stripVTControlCharacters(terminal.log.mock.calls[0].join('')); + expect(output).toMatch(/^Web /); + expect(output).toContain('LOG'); + expect(output).toContain('hello world'); + }); + + it(`does not prefix native client logs (NOBRIDGE)`, () => { + reporter._log({ + type: 'client_log', + level: 'log', + data: ['hello world'], + mode: 'NOBRIDGE', + } as any); + + expect(terminal.log).toHaveBeenCalledTimes(1); + const output = stripVTControlCharacters(terminal.log.mock.calls[0].join('')); + expect(output).not.toMatch(/^(Web|iOS|Android) /); + expect(output).toContain('LOG'); + }); + + it(`does not prefix client logs without mode`, () => { + reporter._log({ + type: 'client_log', + level: 'log', + data: ['hello world'], + } as any); + + expect(terminal.log).toHaveBeenCalledTimes(1); + const output = stripVTControlCharacters(terminal.log.mock.calls[0].join('')); + expect(output).not.toMatch(/^(Web|iOS|Android) /); + }); + + it(`applies printf-style %s format substitution`, () => { + reporter._log({ + type: 'client_log', + level: 'warn', + data: ['%s\n\n%s', 'An error occurred.', 'Visit https://react.dev for more info.'], + mode: 'web', + } as any); + + expect(terminal.log).toHaveBeenCalledTimes(1); + const output = stripVTControlCharacters(terminal.log.mock.calls[0].join('')); + expect(output).not.toContain('%s'); + expect(output).toContain('An error occurred.'); + expect(output).toContain('Visit https://react.dev for more info.'); + }); + + it(`does not apply format substitution when first arg has no specifiers`, () => { + reporter._log({ + type: 'client_log', + level: 'log', + data: ['plain message', 'extra arg'], + } as any); + + expect(terminal.log).toHaveBeenCalledTimes(1); + const args = terminal.log.mock.calls[0]; + // Both items should be passed through as separate arguments + expect(args).toContain('plain message'); + expect(args).toContain('extra arg'); + }); +}); + describe('_getBundleStatusMessage', () => { const buildID = '1'; const reporter = new MetroTerminalReporter('/', { diff --git a/packages/@expo/cli/src/start/server/serverLogLikeMetro.ts b/packages/@expo/cli/src/start/server/serverLogLikeMetro.ts index 2a702a3a5bd34f..1280288fe181de 100644 --- a/packages/@expo/cli/src/start/server/serverLogLikeMetro.ts +++ b/packages/@expo/cli/src/start/server/serverLogLikeMetro.ts @@ -40,7 +40,7 @@ let collapsedGuardTimer: ReturnType | undefined; export function logLikeMetro( originalLogFunction: (...args: any[]) => void, level: ConsoleMethod, - platform: 'BRIDGE' | 'NOBRIDGE' | 'λ' | null, + platform: string | null, ...data: any[] ) { const logFunction = console[level] && level !== 'trace' ? level : 'log'; diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 71d3c8adbd56f4..1d54e398eca095 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -11,7 +11,8 @@ ### 💡 Others - Remove pinned dependencies ([#45520](https://github.com/expo/expo/pull/45520) by [@kitten](https://githun.com/kitten)) - +- Send platform as HMR log mode for terminal log prefixing. ([#45516](https://github.com/expo/expo/pull/45516) by [@EvanBacon](https://github.com/EvanBacon)) +- Remove redundant log messages from web HMR setup. ([#45516](https://github.com/expo/expo/pull/45516) by [@EvanBacon](https://github.com/EvanBacon)) ## 56.0.0-preview.6 — 2026-05-07 _This version does not introduce any user-facing changes._ diff --git a/packages/expo/build/async-require/hmr.d.ts.map b/packages/expo/build/async-require/hmr.d.ts.map index 557d8d9f2cd5ef..24f633b1a82c38 100644 --- a/packages/expo/build/async-require/hmr.d.ts.map +++ b/packages/expo/build/async-require/hmr.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../src/async-require/hmr.ts"],"names":[],"mappings":"AAiCA,KAAK,QAAQ,GACT,OAAO,GACP,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,OAAO,CAAC;AAMZ;;;GAGG;AACH,QAAA,MAAM,SAAS;;;+BAyCc,MAAM;eAMtB,QAAQ,QAAQ,GAAG,EAAE;6BA8CX,MAAM,GAAG;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,gBACpC,MAAM,SACb,MAAM,SACN,MAAM,GAAG,MAAM,yBACC,OAAO,WACtB,MAAM;yBAmHK,OAAO;CA4B7B,CAAC;AA8DF,eAAe,SAAS,CAAC"} \ No newline at end of file +{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../src/async-require/hmr.ts"],"names":[],"mappings":"AAiCA,KAAK,QAAQ,GACT,OAAO,GACP,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,OAAO,CAAC;AAMZ;;;GAGG;AACH,QAAA,MAAM,SAAS;;;+BAyCc,MAAM;eAMtB,QAAQ,QAAQ,GAAG,EAAE;6BA6CX,MAAM,GAAG;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,gBACpC,MAAM,SACb,MAAM,SACN,MAAM,GAAG,MAAM,yBACC,OAAO,WACtB,MAAM;yBAmHK,OAAO;CA4B7B,CAAC;AA8DF,eAAe,SAAS,CAAC"} \ No newline at end of file diff --git a/packages/expo/src/async-require/hmr.ts b/packages/expo/src/async-require/hmr.ts index c0ea71e36a0bbc..bd226af36bb1c8 100644 --- a/packages/expo/src/async-require/hmr.ts +++ b/packages/expo/src/async-require/hmr.ts @@ -111,8 +111,7 @@ const HMRClient = { const webMetadata = process.env.EXPO_OS === 'web' ? { - platform: 'web', - mode: 'BRIDGE', + mode: typeof window.$$EXPO_INITIAL_PROPS !== 'undefined' ? 'dom' : 'web', } : undefined; hmrClient.send( diff --git a/packages/expo/src/async-require/setupHMR.ts b/packages/expo/src/async-require/setupHMR.ts index 72bfcaca7ea472..aee847525fa67a 100644 --- a/packages/expo/src/async-require/setupHMR.ts +++ b/packages/expo/src/async-require/setupHMR.ts @@ -1,6 +1,6 @@ import HMRClient from './hmr'; -if (typeof window !== 'undefined' && typeof window.$$EXPO_INITIAL_PROPS !== 'undefined') { +if (typeof window !== 'undefined') { // Sets up developer tools for web platforms when running in a webview. This ensures that logs are visible in the terminal. // We assume full control over the console and send JavaScript logs to Metro. const LEVELS = [ @@ -21,9 +21,6 @@ if (typeof window !== 'undefined' && typeof window.$$EXPO_INITIAL_PROPS !== 'und originalFunction.apply(console, args); }; }); - HMRClient.log('log', [`[webview] Logs will also appear in the Safari/Chrome debug console`]); -} else { - HMRClient.log('log', [`[web] Logs will appear in the browser console`]); } // This is called native on native platforms From 299ae831d09f50995fe3850910a8c96c858bbf29 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 7 May 2026 19:03:29 -0700 Subject: [PATCH 6/6] fix(log-box): fix error overlay footer overlapping content (#45526) # Why The error overlay footer (with reload/dismiss buttons) uses `position: absolute`, which causes it to overlap the code frame content when scrolling. Users can't see the bottom of the code frames because the footer covers them. # How - Changed the footer from `position: absolute` to `position: fixed` so it stays anchored to the viewport bottom regardless of scroll position - Added `3.5rem` bottom padding to the code frames container to prevent content from being hidden behind the fixed footer # Test Plan - Open an app with a runtime error that shows a code frame in the error overlay - Verify the footer stays at the bottom of the screen - Verify the code frame content is fully visible and not obscured by the footer - Verify scrolling behavior works correctly --- packages/@expo/log-box/CHANGELOG.md | 2 ++ packages/@expo/log-box/src/overlay/Overlay.module.css | 2 +- packages/@expo/log-box/src/overlay/Overlay.tsx | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@expo/log-box/CHANGELOG.md b/packages/@expo/log-box/CHANGELOG.md index f7fbb232846afc..7c531ecf87e40f 100644 --- a/packages/@expo/log-box/CHANGELOG.md +++ b/packages/@expo/log-box/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Fix error overlay footer overlapping content by using fixed positioning and adding bottom padding. ([#45526](https://github.com/expo/expo/pull/45526) by [@EvanBacon](https://github.com/EvanBacon)) + ### 💡 Others ## 56.0.5 — 2026-05-07 diff --git a/packages/@expo/log-box/src/overlay/Overlay.module.css b/packages/@expo/log-box/src/overlay/Overlay.module.css index d5f524ad67bd56..668d1f639aff35 100644 --- a/packages/@expo/log-box/src/overlay/Overlay.module.css +++ b/packages/@expo/log-box/src/overlay/Overlay.module.css @@ -150,7 +150,7 @@ } .footer { - position: absolute; + position: fixed; bottom: 0; left: 0; right: 0; diff --git a/packages/@expo/log-box/src/overlay/Overlay.tsx b/packages/@expo/log-box/src/overlay/Overlay.tsx index 988fdc21aea5e8..ef8e76f8bf4b5c 100644 --- a/packages/@expo/log-box/src/overlay/Overlay.tsx +++ b/packages/@expo/log-box/src/overlay/Overlay.tsx @@ -293,7 +293,8 @@ function LogBoxContent({ /> { -
+
{codeFrames.map(([key, codeFrame]) => { // If no frame from a stack is expanded, likely no frame is from user code, let's not show the code snippet. // This avoid cluttering the overlay with irrelevant code frames of node_modules and internals.