@nx/expo 23.1.0-beta.1 → 23.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/plugins/metro-resolver.js +14 -4
  2. package/dist/plugins/with-nx-metro.js +32 -7
  3. package/dist/src/executors/export/export.impl.js +2 -1
  4. package/dist/src/executors/prebuild/prebuild.impl.js +2 -1
  5. package/dist/src/executors/run/run.impl.js +2 -1
  6. package/dist/src/executors/serve/serve.impl.js +2 -1
  7. package/dist/src/executors/start/start.impl.js +2 -1
  8. package/dist/src/generators/application/files/base/metro.config.js.template +5 -2
  9. package/dist/src/generators/application/files/base/package.json.template +5 -3
  10. package/dist/src/generators/application/lib/create-application-files.js +7 -0
  11. package/dist/src/generators/init/init.js +13 -2
  12. package/dist/src/migrations/update-23-1-0/ai-instructions-for-expo-56.md +148 -0
  13. package/dist/src/migrations/update-23-1-0/lib/expo-apps.d.ts +7 -0
  14. package/dist/src/migrations/update-23-1-0/lib/expo-apps.js +41 -0
  15. package/dist/src/migrations/update-23-1-0/replace-standalone-metro-for-expo-56.d.ts +10 -0
  16. package/dist/src/migrations/update-23-1-0/replace-standalone-metro-for-expo-56.js +46 -0
  17. package/dist/src/migrations/update-23-1-0/update-jest-winter-runtime-for-expo-56.d.ts +8 -0
  18. package/dist/src/migrations/update-23-1-0/update-jest-winter-runtime-for-expo-56.js +61 -0
  19. package/dist/src/migrations/update-23-1-0/update-metro-config-for-expo-56.d.ts +9 -0
  20. package/dist/src/migrations/update-23-1-0/update-metro-config-for-expo-56.js +44 -0
  21. package/dist/src/utils/ensure-dependencies.js +17 -1
  22. package/dist/src/utils/jest/add-jest.js +23 -0
  23. package/dist/src/utils/resolve-expo-cli.d.ts +11 -0
  24. package/dist/src/utils/resolve-expo-cli.js +21 -0
  25. package/dist/src/utils/version-utils.d.ts +9 -1
  26. package/dist/src/utils/version-utils.js +28 -5
  27. package/dist/src/utils/versions.d.ts +30 -11
  28. package/dist/src/utils/versions.js +42 -20
  29. package/migrations.json +116 -0
  30. package/package.json +18 -8
@@ -12,15 +12,25 @@ const devkit_1 = require("@nx/devkit");
12
12
  // Cache for metro-resolver module
13
13
  let metroResolver = null;
14
14
  /**
15
- * Lazily require metro-resolver to handle cases where it might not be installed
15
+ * Lazily require the Metro resolver.
16
+ *
17
+ * Expo SDK 55+ ships Metro through `@expo/metro`, so the resolver must come
18
+ * from the same Metro instance Expo uses. Older SDKs (53/54) use the standalone
19
+ * `metro-resolver` package. We prefer `@expo/metro` and fall back to the
20
+ * standalone package to stay compatible with both.
16
21
  */
17
22
  function getMetroResolver() {
18
23
  if (!metroResolver) {
19
24
  try {
20
- metroResolver = require('metro-resolver');
25
+ metroResolver = require('@expo/metro/metro-resolver');
21
26
  }
22
- catch (error) {
23
- throw new Error('metro-resolver is required but not installed. Please install metro-resolver >= 0.82.0');
27
+ catch {
28
+ try {
29
+ metroResolver = require('metro-resolver');
30
+ }
31
+ catch (error) {
32
+ throw new Error('Unable to load Metro resolver. Install `@expo/metro` (Expo SDK 55+) or `metro-resolver` (>= 0.82.0).');
33
+ }
24
34
  }
25
35
  }
26
36
  return metroResolver;
@@ -7,15 +7,26 @@ const path_1 = require("path");
7
7
  // Cache for metro-config module
8
8
  let metroConfig = null;
9
9
  /**
10
- * Lazily require metro-config to handle cases where it might not be installed
10
+ * Lazily require the Metro config helpers.
11
+ *
12
+ * Expo SDK 55+ ships Metro through the `@expo/metro` package family, so the
13
+ * `mergeConfig` used here must come from the same Metro instance that
14
+ * `@expo/metro-config`'s `getDefaultConfig` is built against. Older SDKs
15
+ * (53/54) use the standalone `metro-config` package. We prefer `@expo/metro`
16
+ * and fall back to the standalone package to stay compatible with both.
11
17
  */
12
18
  function getMetroConfig() {
13
19
  if (!metroConfig) {
14
20
  try {
15
- metroConfig = require('metro-config');
21
+ metroConfig = require('@expo/metro/metro-config');
16
22
  }
17
- catch (error) {
18
- throw new Error('metro-config is required but not installed. Please install metro-config >= 0.82.0');
23
+ catch {
24
+ try {
25
+ metroConfig = require('metro-config');
26
+ }
27
+ catch (error) {
28
+ throw new Error('Unable to load Metro config. Install `@expo/metro` (Expo SDK 55+) or `metro-config` (>= 0.82.0).');
29
+ }
19
30
  }
20
31
  }
21
32
  return metroConfig;
@@ -35,10 +46,24 @@ function withNxMetro(userConfig, opts = {}) {
35
46
  watchFolders = watchFolders.concat(opts.watchFolders);
36
47
  }
37
48
  watchFolders = [...new Set(watchFolders)].filter((folder) => (0, node_fs_1.existsSync)(folder));
49
+ // Expo SDK 55+ ships Metro via `@expo/metro` and resolves the project's
50
+ // Babel config relative to `projectRoot`. Forcing `projectRoot` to the
51
+ // workspace root breaks that lookup, because the app's `.babelrc.js` lives in
52
+ // the project directory, not the workspace root (Metro's babel transformer
53
+ // does `path.resolve(projectRoot, '.babelrc.js')`). So only override
54
+ // `projectRoot` for older SDKs (53/54). Workspace libraries remain resolvable
55
+ // via `watchFolders`, `nodeModulesPaths`, and the custom `resolveRequest`.
56
+ // `@expo/metro` has no root entry point (only subpath exports), so resolve a
57
+ // known subpath to detect it — `require.resolve('@expo/metro')` would throw
58
+ // ERR_PACKAGE_PATH_NOT_EXPORTED even when the package is installed.
59
+ let usesExpoMetro = false;
60
+ try {
61
+ require.resolve('@expo/metro/metro-config');
62
+ usesExpoMetro = true;
63
+ }
64
+ catch { }
38
65
  const nxConfig = {
39
- // Set projectRoot to workspaceRoot to ensure originModulePath is
40
- // workspace-relative. This is required for Expo SDK 54+ compatibility.
41
- projectRoot: devkit_1.workspaceRoot,
66
+ ...(usesExpoMetro ? {} : { projectRoot: devkit_1.workspaceRoot }),
42
67
  resolver: {
43
68
  resolveRequest: (0, metro_resolver_1.getResolveRequest)(extensions, opts.exportsConditionNames, opts.mainFields),
44
69
  nodeModulesPaths: [(0, path_1.join)(devkit_1.workspaceRoot, 'node_modules')],
@@ -7,6 +7,7 @@ const internal_1 = require("@nx/devkit/internal");
7
7
  const child_process_1 = require("child_process");
8
8
  const path_1 = require("path");
9
9
  const deprecation_1 = require("../../utils/deprecation");
10
+ const resolve_expo_cli_1 = require("../../utils/resolve-expo-cli");
10
11
  let childProcess;
11
12
  async function* exportExecutor(options, context) {
12
13
  (0, deprecation_1.warnExpoExecutorDeprecation)('export');
@@ -25,7 +26,7 @@ async function* exportExecutor(options, context) {
25
26
  }
26
27
  function exportAsync(workspaceRoot, projectRoot, options) {
27
28
  return new Promise((resolve, reject) => {
28
- childProcess = (0, child_process_1.fork)(require.resolve('@expo/cli/build/bin/cli'), [`export`, ...createExportOptions(options, projectRoot)], {
29
+ childProcess = (0, child_process_1.fork)((0, resolve_expo_cli_1.resolveExpoCliPath)(), [`export`, ...createExportOptions(options, projectRoot)], {
29
30
  cwd: (0, path_1.resolve)(workspaceRoot, projectRoot),
30
31
  env: process.env,
31
32
  });
@@ -5,6 +5,7 @@ exports.prebuildAsync = prebuildAsync;
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const internal_1 = require("@nx/devkit/internal");
7
7
  const child_process_1 = require("child_process");
8
+ const resolve_expo_cli_1 = require("../../utils/resolve-expo-cli");
8
9
  const path_1 = require("path");
9
10
  const pod_install_task_1 = require("../../utils/pod-install-task");
10
11
  const deprecation_1 = require("../../utils/deprecation");
@@ -33,7 +34,7 @@ async function* prebuildExecutor(options, context) {
33
34
  }
34
35
  function prebuildAsync(workspaceRoot, projectRoot, options) {
35
36
  return new Promise((resolve, reject) => {
36
- childProcess = (0, child_process_1.fork)(require.resolve('@expo/cli/build/bin/cli'), ['prebuild', ...createPrebuildOptions(options), '--no-install'], {
37
+ childProcess = (0, child_process_1.fork)((0, resolve_expo_cli_1.resolveExpoCliPath)(), ['prebuild', ...createPrebuildOptions(options), '--no-install'], {
37
38
  cwd: (0, path_1.join)(workspaceRoot, projectRoot),
38
39
  env: process.env,
39
40
  });
@@ -4,6 +4,7 @@ exports.default = runExecutor;
4
4
  const devkit_1 = require("@nx/devkit");
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  const child_process_1 = require("child_process");
7
+ const resolve_expo_cli_1 = require("../../utils/resolve-expo-cli");
7
8
  const node_fs_1 = require("node:fs");
8
9
  const os_1 = require("os");
9
10
  const path_1 = require("path");
@@ -43,7 +44,7 @@ async function* runExecutor(options, context) {
43
44
  }
44
45
  function runCliRun(workspaceRoot, projectRoot, options) {
45
46
  return new Promise((resolve, reject) => {
46
- childProcess = (0, child_process_1.fork)(require.resolve('@expo/cli/build/bin/cli'), ['run:' + options.platform, ...createRunOptions(options), '--no-install'], // pass in no-install to prevent node_modules install
47
+ childProcess = (0, child_process_1.fork)((0, resolve_expo_cli_1.resolveExpoCliPath)(), ['run:' + options.platform, ...createRunOptions(options), '--no-install'], // pass in no-install to prevent node_modules install
47
48
  {
48
49
  cwd: (0, path_1.resolve)(workspaceRoot, projectRoot),
49
50
  env: process.env,
@@ -5,6 +5,7 @@ exports.runCliServe = runCliServe;
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const internal_1 = require("@nx/devkit/internal");
7
7
  const child_process_1 = require("child_process");
8
+ const resolve_expo_cli_1 = require("../../utils/resolve-expo-cli");
8
9
  const path_1 = require("path");
9
10
  const is_packager_running_1 = require("./lib/is-packager-running");
10
11
  const deprecation_1 = require("../../utils/deprecation");
@@ -54,7 +55,7 @@ async function runCliServe(workspaceRoot, projectRoot, options) {
54
55
  }
55
56
  function serveAsync(workspaceRoot, projectRoot, options) {
56
57
  return new Promise((resolve, reject) => {
57
- const childProcess = (0, child_process_1.fork)(require.resolve('@expo/cli/build/bin/cli'), ['start', '--web', ...createServeOptions(options)], {
58
+ const childProcess = (0, child_process_1.fork)((0, resolve_expo_cli_1.resolveExpoCliPath)(), ['start', '--web', ...createServeOptions(options)], {
58
59
  cwd: (0, path_1.resolve)(workspaceRoot, projectRoot),
59
60
  env: process.env,
60
61
  stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
@@ -6,6 +6,7 @@ const pc = tslib_1.__importStar(require("picocolors"));
6
6
  const devkit_1 = require("@nx/devkit");
7
7
  const internal_1 = require("@nx/devkit/internal");
8
8
  const child_process_1 = require("child_process");
9
+ const resolve_expo_cli_1 = require("../../utils/resolve-expo-cli");
9
10
  const path_1 = require("path");
10
11
  const deprecation_1 = require("../../utils/deprecation");
11
12
  let childProcess;
@@ -29,7 +30,7 @@ async function* startExecutor(options, context) {
29
30
  }
30
31
  function startAsync(workspaceRoot, projectRoot, options) {
31
32
  return new Promise((resolve, reject) => {
32
- childProcess = (0, child_process_1.fork)(require.resolve('@expo/cli/build/bin/cli'), ['start', ...createStartOptions(options)], {
33
+ childProcess = (0, child_process_1.fork)((0, resolve_expo_cli_1.resolveExpoCliPath)(), ['start', ...createStartOptions(options)], {
33
34
  cwd: (0, path_1.resolve)(workspaceRoot, projectRoot),
34
35
  env: {
35
36
  RCT_METRO_PORT: options.port.toString(),
@@ -1,6 +1,9 @@
1
1
  const { withNxMetro } = require('@nx/expo');
2
- const { getDefaultConfig } = require('@expo/metro-config');
3
- const { mergeConfig } = require('metro-config');
2
+ <% if (usesExpoMetro) { %>// Expo SDK 55+ ships Metro via `@expo/metro`. `getDefaultConfig` and
3
+ // `mergeConfig` must come from the Expo-provided Metro instance.
4
+ const { getDefaultConfig } = require('expo/metro-config');
5
+ const { mergeConfig } = require('@expo/metro/metro-config');<% } else { %>const { getDefaultConfig } = require('@expo/metro-config');
6
+ const { mergeConfig } = require('metro-config');<% } %>
4
7
 
5
8
  const defaultConfig = getDefaultConfig(__dirname);
6
9
  const { assetExts, sourceExts } = defaultConfig.resolver;
@@ -3,15 +3,17 @@
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
5
  "dependencies": {
6
- "@expo/metro-config": "*",
6
+ "@expo/vector-icons": "*",<% if (usesExpoMetro) { %>
7
+ "@expo/metro": "*",<% } else { %>
8
+ "@expo/metro-config": "*",<% } %>
7
9
  "@testing-library/react-native": "*",
8
10
  "expo": "*",
9
11
  "expo-splash-screen": "*",
10
12
  "expo-status-bar": "*",
11
13
  "expo-system-ui": "*",
12
- "jest-expo": "*",
14
+ "jest-expo": "*",<% if (!usesExpoMetro) { %>
13
15
  "metro-config": "*",
14
- "metro-resolver": "*",
16
+ "metro-resolver": "*",<% } %>
15
17
  "react": "*",
16
18
  "react-native": "*",
17
19
  "react-native-svg": "*",
@@ -4,8 +4,14 @@ exports.createApplicationFiles = createApplicationFiles;
4
4
  const devkit_1 = require("@nx/devkit");
5
5
  const onboarding_1 = require("nx/src/nx-cloud/utilities/onboarding");
6
6
  const path_1 = require("path");
7
+ const version_utils_1 = require("../../../utils/version-utils");
7
8
  async function createApplicationFiles(host, options) {
8
9
  const packageManager = (0, devkit_1.detectPackageManager)(host.root);
10
+ // Expo SDK 55+ ships Metro via `@expo/metro` instead of the standalone
11
+ // `metro`/`metro-config`/`metro-resolver` packages. When null (no Expo
12
+ // installed yet), default to the latest behavior (v56).
13
+ const installedExpoMajor = await (0, version_utils_1.getInstalledExpoMajor)(host);
14
+ const usesExpoMetro = installedExpoMajor === null || installedExpoMajor >= 55;
9
15
  const onBoardingStatus = await (0, onboarding_1.createNxCloudOnboardingURLForWelcomeApp)(host, options.nxCloudToken);
10
16
  const connectCloudUrl = onBoardingStatus === 'unclaimed' &&
11
17
  (await (0, onboarding_1.getNxCloudAppOnBoardingUrl)(options.nxCloudToken));
@@ -13,6 +19,7 @@ async function createApplicationFiles(host, options) {
13
19
  ...options,
14
20
  offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.appProjectRoot),
15
21
  packageManager,
22
+ usesExpoMetro,
16
23
  });
17
24
  (0, devkit_1.generateFiles)(host, (0, path_1.join)(__dirname, `../files/nx-welcome/${onBoardingStatus}`), options.appProjectRoot, {
18
25
  ...options,
@@ -5,6 +5,7 @@ exports.expoInitGeneratorInternal = expoInitGeneratorInternal;
5
5
  exports.updateDependencies = updateDependencies;
6
6
  const internal_1 = require("@nx/devkit/internal");
7
7
  const devkit_1 = require("@nx/devkit");
8
+ const semver_1 = require("semver");
8
9
  const plugin_1 = require("../../../plugins/plugin");
9
10
  const versions_1 = require("../../utils/versions");
10
11
  const version_utils_1 = require("../../utils/version-utils");
@@ -58,6 +59,12 @@ async function expoInitGeneratorInternal(host, schema) {
58
59
  }
59
60
  async function updateDependencies(host, schema) {
60
61
  const versions = await (0, version_utils_1.getExpoDependenciesVersionsToInstall)(host);
62
+ // Expo SDK 55+ provides Metro through `@expo/metro` (a transitive dependency
63
+ // of `expo`). Installing the standalone `metro-config`/`metro-resolver`
64
+ // packages alongside it pulls in a second, incompatible Metro instance and
65
+ // breaks bundling, so only add them for older SDKs (53/54).
66
+ const expoMajor = (0, semver_1.major)((0, semver_1.coerce)(versions.expo) ?? '0.0.0');
67
+ const usesExpoMetro = expoMajor >= 55;
61
68
  return (0, devkit_1.addDependenciesToPackageJson)(host, {
62
69
  react: versions.react,
63
70
  'react-dom': versions.reactDom,
@@ -66,8 +73,12 @@ async function updateDependencies(host, schema) {
66
73
  }, {
67
74
  '@nx/expo': versions_1.nxVersion,
68
75
  '@expo/cli': versions.expoCli,
69
- 'metro-config': versions.metro,
70
- 'metro-resolver': versions.metro,
76
+ ...(usesExpoMetro
77
+ ? {}
78
+ : {
79
+ 'metro-config': versions.metro,
80
+ 'metro-resolver': versions.metro,
81
+ }),
71
82
  }, undefined, schema.keepExistingVersions ?? true);
72
83
  }
73
84
  function moveDependency(host) {
@@ -0,0 +1,148 @@
1
+ # Expo SDK 56 Migration Instructions for LLM
2
+
3
+ ## Overview
4
+
5
+ These instructions guide you through migrating an Nx workspace containing Expo projects to Expo SDK 56. The most common starting points are Expo SDK 54 and SDK 55. Work systematically through each breaking change category and validate after every step.
6
+
7
+ ## Pre-Migration Checklist
8
+
9
+ 1. **Identify all Expo projects**:
10
+
11
+ ```bash
12
+ nx show projects --with-target start
13
+ ```
14
+
15
+ 2. **Locate all Expo configuration files**:
16
+ - Search for `app.json` or `app.config.{js,ts}`
17
+ - Search for `metro.config.{js,ts}`
18
+ - Search for `babel.config.{js,ts}`
19
+ - Check `project.json` files for Expo-related configuration
20
+
21
+ 3. **Record the current SDK** for each project (read `expo` in each app's `package.json`).
22
+
23
+ 4. **Check for Detox E2E testing projects**:
24
+ - Search for `detox` in package.json dependencies
25
+ - Look for `detox.config.js` or `.detoxrc.js` files
26
+
27
+ ## Target Versions (Expo SDK 56)
28
+
29
+ When updating an app's `package.json`, align to these versions:
30
+
31
+ - `expo`: `~56.0.0`
32
+ - `@expo/cli`: `~56.1.14`
33
+ - `react`: `^19.2.0`
34
+ - `react-dom`: `^19.2.0`
35
+ - `@types/react`: `^19.2.0`
36
+ - `react-native`: `0.85.3`
37
+ - `react-native-web`: `~0.21.0`
38
+ - `@expo/metro`: `~56.0.0`
39
+ - `@expo/metro-runtime`: `~56.0.14`
40
+ - `babel-preset-expo`: `~56.0.14`
41
+ - `jest-expo`: `~56.0.4`
42
+ - `expo-splash-screen`: `~56.0.10`
43
+ - `expo-status-bar`: `~56.0.4`
44
+ - `expo-system-ui`: `~56.0.5`
45
+
46
+ > Prefer `npx expo install --fix` inside each project to let Expo resolve the exact compatible native module versions for SDK 56, then reconcile the workspace root `package.json`.
47
+
48
+ ## Migration Steps by Category
49
+
50
+ ### 1. React 19.2 and React Native 0.85
51
+
52
+ Expo SDK 56 uses React 19.2 and React Native 0.85.
53
+
54
+ **Action Items**:
55
+
56
+ - [ ] Update `@types/react` to `^19.2.0`
57
+ - [ ] Verify third-party libraries are compatible with React 19.2 / RN 0.85
58
+ - [ ] Re-run typecheck: `nx run-many -t typecheck`
59
+
60
+ ### 2. Metro is now provided through `@expo/metro`
61
+
62
+ Starting with SDK 55, Expo bundles Metro through the `@expo/metro` package family rather than depending on the standalone `metro`, `metro-config`, and `metro-resolver` packages directly.
63
+
64
+ **Search Pattern**: `metro.config.{js,ts}`, direct imports of `metro-config` / `metro-resolver`
65
+
66
+ **Action Items**:
67
+
68
+ - [ ] Add `@expo/metro` (`~56.0.0`) as a direct dependency — the generated `metro.config.js` and `withNxMetro` require `@expo/metro/metro-config`, which package managers like pnpm won't resolve from a transitive dependency
69
+ - [ ] Source the Metro config from the Expo-provided instance: `getDefaultConfig` from `expo/metro-config` and `mergeConfig` from `@expo/metro/metro-config`
70
+ - [ ] Remove direct dependencies on standalone `metro` / `metro-config` / `metro-resolver`, and do not install `@expo/metro-config` directly (expo-doctor flags it) — `@expo/metro` replaces them
71
+ - [ ] If you wrap config with `withNxMetro` from `@nx/expo`, leave it in place — it continues to merge Nx workspace resolution
72
+ - [ ] Clear the Metro cache after upgrading: `npx expo start --clear`
73
+
74
+ ### 3. Babel preset
75
+
76
+ SDK 56 updates `babel-preset-expo` to `~56.0.14`.
77
+
78
+ **Action Items**:
79
+
80
+ - [ ] Update `babel-preset-expo` to `~56.0.14`
81
+ - [ ] Remove deprecated Babel plugins
82
+ - [ ] Clear Metro cache after Babel changes: `npx expo start --clear`
83
+
84
+ ### 4. Jest configuration
85
+
86
+ Expo v54+ (including SDK 56) relies on the winter runtime. New projects generated by `@nx/expo` no longer use the custom `jest.resolver.js`; instead `src/test-setup.ts` mocks `expo/src/winter/ImportMetaRegistry` and polyfills `structuredClone`.
87
+
88
+ **Action Items**:
89
+
90
+ - [ ] If migrating from SDK 53, remove `jest.resolver.js` and the `resolver:` line from `jest.config.ts`
91
+ - [ ] Ensure `src/test-setup.ts` mocks `ImportMetaRegistry` and polyfills `structuredClone`
92
+ - [ ] Update `jest-expo` to `~56.0.4`
93
+
94
+ ### 5. Detox E2E Testing
95
+
96
+ **Verify Detox supports Expo SDK 56 before migrating.** If your workspace includes Detox projects and Detox has not yet published SDK 56 support, ask the user:
97
+
98
+ > "Your workspace contains Detox E2E tests. Confirm Detox supports Expo SDK 56 before proceeding, or these tests may not run after migrating."
99
+
100
+ Consider [Maestro](https://maestro.mobile.dev/) as an alternative if Detox support lags.
101
+
102
+ ## Post-Migration Validation
103
+
104
+ ### 1. Clear All Caches
105
+
106
+ ```bash
107
+ npx expo start --clear
108
+ rm -rf node_modules/.cache/metro-*
109
+ nx reset
110
+ ```
111
+
112
+ ### 2. Run Tests Per Project
113
+
114
+ ```bash
115
+ nx run-many -t test -p PROJECT_NAME
116
+ ```
117
+
118
+ ### 3. Run All Affected Tests
119
+
120
+ ```bash
121
+ nx affected -t test,lint,typecheck
122
+ ```
123
+
124
+ ### 4. Test on Device/Simulator
125
+
126
+ ```bash
127
+ nx run PROJECT_NAME:run-ios
128
+ nx run PROJECT_NAME:run-android
129
+ ```
130
+
131
+ ### 5. Review Migration Checklist
132
+
133
+ - [ ] All Expo apps pinned to SDK 56 versions
134
+ - [ ] React 19.2 / React Native 0.85 compatibility verified
135
+ - [ ] Metro provided via `@expo/metro`; caches cleared
136
+ - [ ] Babel and Jest configurations updated
137
+ - [ ] All tests pass
138
+ - [ ] App runs on iOS simulator/device
139
+ - [ ] App runs on Android emulator/device
140
+
141
+ ## Notes for LLM Execution
142
+
143
+ 1. **Work systematically**: Complete one category before moving to the next.
144
+ 2. **Test after each change**: Don't batch all changes without validation.
145
+ 3. **Prefer `npx expo install --fix`** per project to resolve native module versions.
146
+ 4. **Keep the user informed** and create meaningful, grouped commits.
147
+ 5. **Use the TodoWrite tool** to track migration progress.
148
+ 6. **Test on both platforms** — Expo changes often affect iOS and Android differently.
@@ -0,0 +1,7 @@
1
+ import { Tree } from '@nx/devkit';
2
+ /**
3
+ * Returns the project roots of every Expo application in the workspace.
4
+ * An Expo project is detected by an `expo` entry in its (or the root)
5
+ * package.json.
6
+ */
7
+ export declare function getExpoAppRoots(tree: Tree): string[];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExpoAppRoots = getExpoAppRoots;
4
+ const devkit_1 = require("@nx/devkit");
5
+ /**
6
+ * Returns the project roots of every Expo application in the workspace.
7
+ * An Expo project is detected by an `expo` entry in its (or the root)
8
+ * package.json.
9
+ */
10
+ function getExpoAppRoots(tree) {
11
+ const roots = [];
12
+ for (const [, config] of (0, devkit_1.getProjects)(tree)) {
13
+ if (config.projectType !== 'application') {
14
+ continue;
15
+ }
16
+ if (isExpoProject(tree, config.root)) {
17
+ roots.push(config.root);
18
+ }
19
+ }
20
+ return roots;
21
+ }
22
+ function isExpoProject(tree, projectRoot) {
23
+ // Newer Expo apps declare deps as `*` and pin them at the workspace root, so
24
+ // check both the project package.json and the root package.json.
25
+ for (const dir of [projectRoot, '']) {
26
+ const packageJsonPath = (0, devkit_1.joinPathFragments)(dir, 'package.json');
27
+ if (!tree.exists(packageJsonPath)) {
28
+ continue;
29
+ }
30
+ try {
31
+ const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
32
+ if (packageJson.dependencies?.expo || packageJson.devDependencies?.expo) {
33
+ return true;
34
+ }
35
+ }
36
+ catch {
37
+ // ignore malformed package.json
38
+ }
39
+ }
40
+ return false;
41
+ }
@@ -0,0 +1,10 @@
1
+ import { Tree } from '@nx/devkit';
2
+ /**
3
+ * Expo SDK 55+ provides Metro through `@expo/metro`. Installing the standalone
4
+ * `metro-config`/`metro-resolver` packages alongside it pulls in a second,
5
+ * incompatible Metro instance that breaks bundling. Replace them with
6
+ * `@expo/metro` (a direct dependency is required so `withNxMetro` and the
7
+ * generated `metro.config.js` can `require('@expo/metro/metro-config')` under
8
+ * pnpm).
9
+ */
10
+ export default function update(tree: Tree): void;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const versions_1 = require("../../utils/versions");
6
+ const expo_apps_1 = require("./lib/expo-apps");
7
+ /**
8
+ * Expo SDK 55+ provides Metro through `@expo/metro`. Installing the standalone
9
+ * `metro-config`/`metro-resolver` packages alongside it pulls in a second,
10
+ * incompatible Metro instance that breaks bundling. Replace them with
11
+ * `@expo/metro` (a direct dependency is required so `withNxMetro` and the
12
+ * generated `metro.config.js` can `require('@expo/metro/metro-config')` under
13
+ * pnpm).
14
+ */
15
+ function update(tree) {
16
+ const appRoots = (0, expo_apps_1.getExpoAppRoots)(tree);
17
+ if (appRoots.length === 0) {
18
+ return;
19
+ }
20
+ // Root package.json holds the pinned versions in the integrated layout.
21
+ swapMetro(tree, 'package.json', versions_1.expoV56MetroVersion);
22
+ // App package.jsons declare deps as `*` (resolved from the root).
23
+ for (const projectRoot of appRoots) {
24
+ swapMetro(tree, (0, devkit_1.joinPathFragments)(projectRoot, 'package.json'), '*');
25
+ }
26
+ }
27
+ function swapMetro(tree, packageJsonPath, expoMetroVersion) {
28
+ if (!tree.exists(packageJsonPath)) {
29
+ return;
30
+ }
31
+ const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
32
+ const hasStandaloneMetro = packageJson.dependencies?.['metro-config'] ||
33
+ packageJson.dependencies?.['metro-resolver'] ||
34
+ packageJson.devDependencies?.['metro-config'] ||
35
+ packageJson.devDependencies?.['metro-resolver'];
36
+ // Only touch workspaces/apps that still carry the standalone packages.
37
+ if (!hasStandaloneMetro) {
38
+ return;
39
+ }
40
+ (0, devkit_1.removeDependenciesFromPackageJson)(tree, ['metro-config', 'metro-resolver'], ['metro-config', 'metro-resolver'], packageJsonPath);
41
+ const alreadyHasExpoMetro = packageJson.dependencies?.['@expo/metro'] ||
42
+ packageJson.devDependencies?.['@expo/metro'];
43
+ if (!alreadyHasExpoMetro) {
44
+ (0, devkit_1.addDependenciesToPackageJson)(tree, { '@expo/metro': expoMetroVersion }, {}, packageJsonPath);
45
+ }
46
+ }
@@ -0,0 +1,8 @@
1
+ import { Tree } from '@nx/devkit';
2
+ /**
3
+ * Expo SDK 56's winter runtime installs lazy globals that Jest rejects as
4
+ * "outside of the scope of the test code" in a monorepo. Add the winter-globals
5
+ * neutralization block to the generated test-setup files that mock
6
+ * `ImportMetaRegistry` but don't yet have it.
7
+ */
8
+ export default function update(tree: Tree): void;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const expo_apps_1 = require("./lib/expo-apps");
6
+ const WINTER_GLOBALS_BLOCK = `
7
+ // Expo SDK 55+ installs lazy winter-runtime globals (fetch, URL, etc.) that
8
+ // require files Jest treats as "outside of the scope of the test code" in a
9
+ // monorepo. Replace them with the runtime's own globals so the lazy getters
10
+ // never fire during tests.
11
+ const defineGlobal = (name, value) => {
12
+ try {
13
+ Object.defineProperty(global, name, {
14
+ value,
15
+ configurable: true,
16
+ writable: true,
17
+ });
18
+ } catch {
19
+ // Ignore environments that don't allow redefining these globals.
20
+ }
21
+ };
22
+ defineGlobal('fetch', globalThis.fetch);
23
+ defineGlobal('Headers', globalThis.Headers);
24
+ defineGlobal('Request', globalThis.Request);
25
+ defineGlobal('Response', globalThis.Response);
26
+ defineGlobal('FormData', globalThis.FormData);
27
+ defineGlobal('URL', globalThis.URL);
28
+ defineGlobal('URLSearchParams', globalThis.URLSearchParams);
29
+ `;
30
+ /**
31
+ * Expo SDK 56's winter runtime installs lazy globals that Jest rejects as
32
+ * "outside of the scope of the test code" in a monorepo. Add the winter-globals
33
+ * neutralization block to the generated test-setup files that mock
34
+ * `ImportMetaRegistry` but don't yet have it.
35
+ */
36
+ function update(tree) {
37
+ for (const projectRoot of (0, expo_apps_1.getExpoAppRoots)(tree)) {
38
+ for (const ext of ['ts', 'js']) {
39
+ const setupPath = (0, devkit_1.joinPathFragments)(projectRoot, `src/test-setup.${ext}`);
40
+ if (!tree.exists(setupPath)) {
41
+ continue;
42
+ }
43
+ const content = tree.read(setupPath, 'utf-8');
44
+ // Only update the generated winter-runtime setup, and only once.
45
+ if (!content ||
46
+ !content.includes('expo/src/winter/ImportMetaRegistry') ||
47
+ content.includes('defineGlobal(')) {
48
+ continue;
49
+ }
50
+ const anchor = '}));\n';
51
+ const idx = content.indexOf(anchor);
52
+ const updated = idx === -1
53
+ ? `${content}\n${WINTER_GLOBALS_BLOCK}`
54
+ : content.slice(0, idx + anchor.length) +
55
+ WINTER_GLOBALS_BLOCK +
56
+ content.slice(idx + anchor.length);
57
+ tree.write(setupPath, updated);
58
+ devkit_1.logger.info(`Updated ${setupPath} for the Expo SDK 56 winter runtime in Jest.`);
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,9 @@
1
+ import { Tree } from '@nx/devkit';
2
+ /**
3
+ * Expo SDK 55+ ships Metro via the `@expo/metro` package family. Update the
4
+ * generated `metro.config.js` to source `getDefaultConfig`/`mergeConfig` from
5
+ * `expo/metro-config` and `@expo/metro/metro-config` instead of the standalone
6
+ * `metro-config`. Only files matching the generated form are rewritten;
7
+ * customized configs are left untouched.
8
+ */
9
+ export default function update(tree: Tree): void;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const expo_apps_1 = require("./lib/expo-apps");
6
+ const REPLACEMENTS = [
7
+ [
8
+ "const { getDefaultConfig } = require('@expo/metro-config');",
9
+ "const { getDefaultConfig } = require('expo/metro-config');",
10
+ ],
11
+ [
12
+ "const { mergeConfig } = require('metro-config');",
13
+ "const { mergeConfig } = require('@expo/metro/metro-config');",
14
+ ],
15
+ ];
16
+ /**
17
+ * Expo SDK 55+ ships Metro via the `@expo/metro` package family. Update the
18
+ * generated `metro.config.js` to source `getDefaultConfig`/`mergeConfig` from
19
+ * `expo/metro-config` and `@expo/metro/metro-config` instead of the standalone
20
+ * `metro-config`. Only files matching the generated form are rewritten;
21
+ * customized configs are left untouched.
22
+ */
23
+ function update(tree) {
24
+ for (const projectRoot of (0, expo_apps_1.getExpoAppRoots)(tree)) {
25
+ const metroConfigPath = (0, devkit_1.joinPathFragments)(projectRoot, 'metro.config.js');
26
+ if (!tree.exists(metroConfigPath)) {
27
+ continue;
28
+ }
29
+ let content = tree.read(metroConfigPath, 'utf-8');
30
+ if (!content) {
31
+ continue;
32
+ }
33
+ const original = content;
34
+ for (const [from, to] of REPLACEMENTS) {
35
+ if (content.includes(from)) {
36
+ content = content.replace(from, to);
37
+ }
38
+ }
39
+ if (content !== original) {
40
+ tree.write(metroConfigPath, content);
41
+ devkit_1.logger.info(`Updated ${metroConfigPath} to use @expo/metro for Expo SDK 56.`);
42
+ }
43
+ }
44
+ }
@@ -2,9 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureDependencies = ensureDependencies;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const semver_1 = require("semver");
5
6
  const version_utils_1 = require("./version-utils");
7
+ const versions_1 = require("./versions");
6
8
  async function ensureDependencies(host, unitTestRunner) {
7
9
  const versions = await (0, version_utils_1.getExpoDependenciesVersionsToInstall)(host);
10
+ // Expo SDK 55+ ships Metro via `@expo/metro` and exposes its config through
11
+ // the `expo/metro-config` sub-export, so `@expo/metro-config` should not be
12
+ // installed directly (expo-doctor flags it). Instead `@expo/metro` must be a
13
+ // direct dependency, because the generated `metro.config.js` and `withNxMetro`
14
+ // require `@expo/metro/metro-config` — package managers like pnpm don't allow
15
+ // requiring into transitive dependencies. Older SDKs (53/54) keep the
16
+ // standalone `@expo/metro-config`.
17
+ const usesExpoMetro = (0, semver_1.major)((0, semver_1.coerce)(versions.expo) ?? '0.0.0') >= 55;
18
+ const metroDeps = usesExpoMetro
19
+ ? versions.expoMetro
20
+ ? { '@expo/metro': versions.expoMetro }
21
+ : {}
22
+ : { '@expo/metro-config': versions.expoMetroConfig };
8
23
  const devDependencies = {
9
24
  '@types/react': versions.typesReact,
10
25
  'babel-preset-expo': versions.babelPresetExpo,
@@ -20,11 +35,12 @@ async function ensureDependencies(host, unitTestRunner) {
20
35
  devDependencies['jest-expo'] = versions.jestExpo;
21
36
  }
22
37
  return (0, devkit_1.addDependenciesToPackageJson)(host, {
38
+ '@expo/vector-icons': versions_1.expoVectorIconsVersion,
23
39
  'expo-splash-screen': versions.expoSplashScreen,
24
40
  'expo-status-bar': versions.expoStatusBar,
25
41
  'expo-system-ui': versions.expoSystemUi,
26
42
  'react-native-web': versions.reactNativeWeb,
27
- '@expo/metro-config': versions.expoMetroConfig,
43
+ ...metroDeps,
28
44
  '@expo/metro-runtime': versions.expoMetroRuntime,
29
45
  'react-native-svg-transformer': versions.reactNativeSvgTransformer,
30
46
  'react-native-svg': versions.reactNativeSvg,
@@ -68,6 +68,29 @@ module.exports = {
68
68
  },
69
69
  }));
70
70
 
71
+ // Expo SDK 55+ installs lazy winter-runtime globals (fetch, URL, etc.) that
72
+ // require files Jest treats as "outside of the scope of the test code" in a
73
+ // monorepo. Replace them with the runtime's own globals so the lazy getters
74
+ // never fire during tests.
75
+ const defineGlobal = (name, value) => {
76
+ try {
77
+ Object.defineProperty(global, name, {
78
+ value,
79
+ configurable: true,
80
+ writable: true,
81
+ });
82
+ } catch {
83
+ // Ignore environments that don't allow redefining these globals.
84
+ }
85
+ };
86
+ defineGlobal('fetch', globalThis.fetch);
87
+ defineGlobal('Headers', globalThis.Headers);
88
+ defineGlobal('Request', globalThis.Request);
89
+ defineGlobal('Response', globalThis.Response);
90
+ defineGlobal('FormData', globalThis.FormData);
91
+ defineGlobal('URL', globalThis.URL);
92
+ defineGlobal('URLSearchParams', globalThis.URLSearchParams);
93
+
71
94
  if (typeof global.structuredClone === 'undefined') {
72
95
  global.structuredClone = (object) => JSON.parse(JSON.stringify(object));
73
96
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolve the path to the Expo CLI entry point.
3
+ *
4
+ * Expo SDK 55+ ships `@expo/cli` with an `exports` map where subpaths resolve
5
+ * to `*.js` (`"./*": "./*.js"`). The CLI bin file is `build/bin/cli` (with no
6
+ * extension), so `require.resolve('@expo/cli/build/bin/cli')` now fails with
7
+ * MODULE_NOT_FOUND. The `expo` package's `bin/cli` is the stable entry point
8
+ * (the same one `npx expo` uses) across all supported SDK versions, so prefer
9
+ * it and fall back to the legacy `@expo/cli` path for older setups.
10
+ */
11
+ export declare function resolveExpoCliPath(): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveExpoCliPath = resolveExpoCliPath;
4
+ /**
5
+ * Resolve the path to the Expo CLI entry point.
6
+ *
7
+ * Expo SDK 55+ ships `@expo/cli` with an `exports` map where subpaths resolve
8
+ * to `*.js` (`"./*": "./*.js"`). The CLI bin file is `build/bin/cli` (with no
9
+ * extension), so `require.resolve('@expo/cli/build/bin/cli')` now fails with
10
+ * MODULE_NOT_FOUND. The `expo` package's `bin/cli` is the stable entry point
11
+ * (the same one `npx expo` uses) across all supported SDK versions, so prefer
12
+ * it and fall back to the legacy `@expo/cli` path for older setups.
13
+ */
14
+ function resolveExpoCliPath() {
15
+ try {
16
+ return require.resolve('expo/bin/cli');
17
+ }
18
+ catch {
19
+ return require.resolve('@expo/cli/build/bin/cli');
20
+ }
21
+ }
@@ -6,6 +6,7 @@ export type ExpoDependenciesVersions = {
6
6
  expoSystemUi: string;
7
7
  expoCli: string;
8
8
  babelPresetExpo: string;
9
+ expoMetro?: string;
9
10
  expoMetroConfig: string;
10
11
  expoMetroRuntime: string;
11
12
  jestExpo: string;
@@ -23,9 +24,16 @@ export type ExpoDependenciesVersions = {
23
24
  };
24
25
  /**
25
26
  * Get the appropriate dependency versions based on the installed Expo version.
26
- * Returns v53 or v54 versions when those are detected, otherwise v55 (latest).
27
+ * Returns the matching lane for v53/v54/v55 when detected, otherwise v56
28
+ * (latest).
27
29
  */
28
30
  export declare function getExpoDependenciesVersionsToInstall(tree: Tree): Promise<ExpoDependenciesVersions>;
31
+ /**
32
+ * Resolve the installed Expo major version. Prefers the resolved version from
33
+ * the project graph, falling back to the declared package.json version.
34
+ * Returns null when Expo isn't installed.
35
+ */
36
+ export declare function getInstalledExpoMajor(tree: Tree): Promise<number | null>;
29
37
  /**
30
38
  * Check if the workspace is using Expo v53 — the only SDK that still needs the
31
39
  * legacy Jest resolver. SDK 54+ use the winter-runtime mock instead.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getExpoDependenciesVersionsToInstall = getExpoDependenciesVersionsToInstall;
4
+ exports.getInstalledExpoMajor = getInstalledExpoMajor;
4
5
  exports.isExpoV53 = isExpoV53;
5
6
  exports.getInstalledExpoVersion = getInstalledExpoVersion;
6
7
  exports.getInstalledExpoVersionFromGraph = getInstalledExpoVersionFromGraph;
@@ -14,8 +15,8 @@ const sharedVersions = {
14
15
  testingLibraryReactNative: versions_1.testingLibraryReactNativeVersion,
15
16
  babelRuntime: versions_1.babelRuntimeVersion,
16
17
  };
17
- // Per-major Expo install lanes. 55 is the latest/default; 53 and 54 are kept
18
- // for existing workspaces. Anything outside this window routes to 55 — the
18
+ // Per-major Expo install lanes. 56 is the latest/default; 53, 54 and 55 are
19
+ // kept for existing workspaces. Anything outside this window routes to 56 — the
19
20
  // below-floor throw is assertSupportedExpoVersion's job, not the router's.
20
21
  const expoVersionsByMajor = {
21
22
  53: {
@@ -63,6 +64,7 @@ const expoVersionsByMajor = {
63
64
  expoSystemUi: versions_1.expoV55SystemUiVersion,
64
65
  expoCli: versions_1.expoV55CliVersion,
65
66
  babelPresetExpo: versions_1.babelPresetExpoV55Version,
67
+ expoMetro: versions_1.expoV55MetroVersion,
66
68
  expoMetroConfig: versions_1.expoV55MetroConfigVersion,
67
69
  expoMetroRuntime: versions_1.expoV55MetroRuntimeVersion,
68
70
  jestExpo: versions_1.jestExpoV55Version,
@@ -75,17 +77,38 @@ const expoVersionsByMajor = {
75
77
  reactTestRenderer: versions_1.reactTestRendererV55Version,
76
78
  ...sharedVersions,
77
79
  },
80
+ 56: {
81
+ expo: versions_1.expoV56Version,
82
+ expoSplashScreen: versions_1.expoV56SplashScreenVersion,
83
+ expoStatusBar: versions_1.expoV56StatusBarVersion,
84
+ expoSystemUi: versions_1.expoV56SystemUiVersion,
85
+ expoCli: versions_1.expoV56CliVersion,
86
+ babelPresetExpo: versions_1.babelPresetExpoV56Version,
87
+ expoMetro: versions_1.expoV56MetroVersion,
88
+ expoMetroConfig: versions_1.expoV56MetroConfigVersion,
89
+ expoMetroRuntime: versions_1.expoV56MetroRuntimeVersion,
90
+ jestExpo: versions_1.jestExpoV56Version,
91
+ react: versions_1.reactV56Version,
92
+ reactDom: versions_1.reactDomV56Version,
93
+ typesReact: versions_1.typesReactV56Version,
94
+ reactNative: versions_1.reactNativeV56Version,
95
+ metro: versions_1.metroV56Version,
96
+ reactNativeWeb: versions_1.reactNativeWebV56Version,
97
+ reactTestRenderer: versions_1.reactTestRendererV56Version,
98
+ ...sharedVersions,
99
+ },
78
100
  };
79
101
  /**
80
102
  * Get the appropriate dependency versions based on the installed Expo version.
81
- * Returns v53 or v54 versions when those are detected, otherwise v55 (latest).
103
+ * Returns the matching lane for v53/v54/v55 when detected, otherwise v56
104
+ * (latest).
82
105
  */
83
106
  async function getExpoDependenciesVersionsToInstall(tree) {
84
107
  const installedMajor = await getInstalledExpoMajor(tree);
85
- if (installedMajor === 53 || installedMajor === 54) {
108
+ if (installedMajor === 53 || installedMajor === 54 || installedMajor === 55) {
86
109
  return expoVersionsByMajor[installedMajor];
87
110
  }
88
- return expoVersionsByMajor[55];
111
+ return expoVersionsByMajor[56];
89
112
  }
90
113
  /**
91
114
  * Resolve the installed Expo major version. Prefers the resolved version from
@@ -2,12 +2,30 @@ import { type Tree } from '@nx/devkit';
2
2
  export declare const nxVersion: any;
3
3
  export declare const minSupportedExpoVersion = "53.0.0";
4
4
  export declare function assertSupportedExpoVersion(tree: Tree): void;
5
+ export declare const expoV56Version = "~56.0.0";
6
+ export declare const expoV56SplashScreenVersion = "~56.0.10";
7
+ export declare const expoV56StatusBarVersion = "~56.0.4";
8
+ export declare const expoV56SystemUiVersion = "~56.0.5";
9
+ export declare const expoV56CliVersion = "~56.1.14";
10
+ export declare const babelPresetExpoV56Version = "~56.0.14";
11
+ export declare const expoV56MetroVersion = "~56.0.0";
12
+ export declare const expoV56MetroConfigVersion = "~56.0.13";
13
+ export declare const expoV56MetroRuntimeVersion = "~56.0.14";
14
+ export declare const jestExpoV56Version = "~56.0.4";
15
+ export declare const reactV56Version = "^19.2.0";
16
+ export declare const reactDomV56Version = "^19.2.0";
17
+ export declare const typesReactV56Version = "^19.2.0";
18
+ export declare const reactNativeV56Version = "0.85.3";
19
+ export declare const metroV56Version = "~0.84.3";
20
+ export declare const reactNativeWebV56Version = "~0.21.0";
21
+ export declare const reactTestRendererV56Version = "^19.2.0";
5
22
  export declare const expoV55Version = "~55.0.26";
6
23
  export declare const expoV55SplashScreenVersion = "~55.0.21";
7
24
  export declare const expoV55StatusBarVersion = "~55.0.6";
8
25
  export declare const expoV55SystemUiVersion = "~55.0.18";
9
26
  export declare const expoV55CliVersion = "~55.0.11";
10
27
  export declare const babelPresetExpoV55Version = "~55.0.7";
28
+ export declare const expoV55MetroVersion = "~55.0.0";
11
29
  export declare const expoV55MetroConfigVersion = "~55.0.8";
12
30
  export declare const expoV55MetroRuntimeVersion = "~55.0.11";
13
31
  export declare const jestExpoV55Version = "~55.0.18";
@@ -50,22 +68,23 @@ export declare const reactNativeV53Version = "0.79.3";
50
68
  export declare const metroV53Version = "~0.82.4";
51
69
  export declare const reactNativeWebV53Version = "~0.20.0";
52
70
  export declare const reactTestRendererV53Version = "^19.0.0";
53
- export declare const expoVersion = "~55.0.26";
54
- export declare const expoSplashScreenVersion = "~55.0.21";
55
- export declare const expoStatusBarVersion = "~55.0.6";
56
- export declare const expoSystemUiVersion = "~55.0.18";
57
- export declare const expoCliVersion = "~55.0.11";
58
- export declare const babelPresetExpoVersion = "~55.0.7";
59
- export declare const expoMetroConfigVersion = "~55.0.8";
60
- export declare const expoMetroRuntimeVersion = "~55.0.11";
61
- export declare const jestExpoVersion = "~55.0.18";
71
+ export declare const expoVersion = "~56.0.0";
72
+ export declare const expoSplashScreenVersion = "~56.0.10";
73
+ export declare const expoStatusBarVersion = "~56.0.4";
74
+ export declare const expoSystemUiVersion = "~56.0.5";
75
+ export declare const expoCliVersion = "~56.1.14";
76
+ export declare const babelPresetExpoVersion = "~56.0.14";
77
+ export declare const expoMetroConfigVersion = "~56.0.13";
78
+ export declare const expoMetroRuntimeVersion = "~56.0.14";
79
+ export declare const jestExpoVersion = "~56.0.4";
62
80
  export declare const reactVersion = "^19.2.0";
63
81
  export declare const reactDomVersion = "^19.2.0";
64
82
  export declare const typesReactVersion = "^19.2.0";
65
- export declare const reactNativeVersion = "0.83.6";
66
- export declare const metroVersion = "~0.83.0";
83
+ export declare const reactNativeVersion = "0.85.3";
84
+ export declare const metroVersion = "~0.84.3";
67
85
  export declare const reactNativeWebVersion = "~0.21.0";
68
86
  export declare const reactNativeSvgTransformerVersion = "~1.5.1";
69
87
  export declare const reactNativeSvgVersion = "15.12.1";
70
88
  export declare const testingLibraryReactNativeVersion = "~13.2.0";
71
89
  export declare const babelRuntimeVersion = "~7.27.6";
90
+ export declare const expoVectorIconsVersion = "~15.1.0";
@@ -1,25 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.reactTestRendererV53Version = exports.reactNativeWebV53Version = exports.metroV53Version = exports.reactNativeV53Version = exports.typesReactV53Version = exports.reactDomV53Version = exports.reactV53Version = exports.jestExpoV53Version = exports.expoV53MetroRuntimeVersion = exports.expoV53MetroConfigVersion = exports.babelPresetExpoV53Version = exports.expoV53CliVersion = exports.expoV53SystemUiVersion = exports.expoV53StatusBarVersion = exports.expoV53SplashScreenVersion = exports.expoV53Version = exports.reactTestRendererV54Version = exports.reactNativeWebV54Version = exports.metroV54Version = exports.reactNativeV54Version = exports.typesReactV54Version = exports.reactDomV54Version = exports.reactV54Version = exports.jestExpoV54Version = exports.expoV54MetroRuntimeVersion = exports.expoV54MetroConfigVersion = exports.babelPresetExpoV54Version = exports.expoV54CliVersion = exports.expoV54SystemUiVersion = exports.expoV54StatusBarVersion = exports.expoV54SplashScreenVersion = exports.expoV54Version = exports.reactTestRendererV55Version = exports.reactNativeWebV55Version = exports.metroV55Version = exports.reactNativeV55Version = exports.typesReactV55Version = exports.reactDomV55Version = exports.reactV55Version = exports.jestExpoV55Version = exports.expoV55MetroRuntimeVersion = exports.expoV55MetroConfigVersion = exports.babelPresetExpoV55Version = exports.expoV55CliVersion = exports.expoV55SystemUiVersion = exports.expoV55StatusBarVersion = exports.expoV55SplashScreenVersion = exports.expoV55Version = exports.minSupportedExpoVersion = exports.nxVersion = void 0;
4
- exports.babelRuntimeVersion = exports.testingLibraryReactNativeVersion = exports.reactNativeSvgVersion = exports.reactNativeSvgTransformerVersion = exports.reactNativeWebVersion = exports.metroVersion = exports.reactNativeVersion = exports.typesReactVersion = exports.reactDomVersion = exports.reactVersion = exports.jestExpoVersion = exports.expoMetroRuntimeVersion = exports.expoMetroConfigVersion = exports.babelPresetExpoVersion = exports.expoCliVersion = exports.expoSystemUiVersion = exports.expoStatusBarVersion = exports.expoSplashScreenVersion = exports.expoVersion = void 0;
3
+ exports.metroV54Version = exports.reactNativeV54Version = exports.typesReactV54Version = exports.reactDomV54Version = exports.reactV54Version = exports.jestExpoV54Version = exports.expoV54MetroRuntimeVersion = exports.expoV54MetroConfigVersion = exports.babelPresetExpoV54Version = exports.expoV54CliVersion = exports.expoV54SystemUiVersion = exports.expoV54StatusBarVersion = exports.expoV54SplashScreenVersion = exports.expoV54Version = exports.reactTestRendererV55Version = exports.reactNativeWebV55Version = exports.metroV55Version = exports.reactNativeV55Version = exports.typesReactV55Version = exports.reactDomV55Version = exports.reactV55Version = exports.jestExpoV55Version = exports.expoV55MetroRuntimeVersion = exports.expoV55MetroConfigVersion = exports.expoV55MetroVersion = exports.babelPresetExpoV55Version = exports.expoV55CliVersion = exports.expoV55SystemUiVersion = exports.expoV55StatusBarVersion = exports.expoV55SplashScreenVersion = exports.expoV55Version = exports.reactTestRendererV56Version = exports.reactNativeWebV56Version = exports.metroV56Version = exports.reactNativeV56Version = exports.typesReactV56Version = exports.reactDomV56Version = exports.reactV56Version = exports.jestExpoV56Version = exports.expoV56MetroRuntimeVersion = exports.expoV56MetroConfigVersion = exports.expoV56MetroVersion = exports.babelPresetExpoV56Version = exports.expoV56CliVersion = exports.expoV56SystemUiVersion = exports.expoV56StatusBarVersion = exports.expoV56SplashScreenVersion = exports.expoV56Version = exports.minSupportedExpoVersion = exports.nxVersion = void 0;
4
+ exports.expoVectorIconsVersion = exports.babelRuntimeVersion = exports.testingLibraryReactNativeVersion = exports.reactNativeSvgVersion = exports.reactNativeSvgTransformerVersion = exports.reactNativeWebVersion = exports.metroVersion = exports.reactNativeVersion = exports.typesReactVersion = exports.reactDomVersion = exports.reactVersion = exports.jestExpoVersion = exports.expoMetroRuntimeVersion = exports.expoMetroConfigVersion = exports.babelPresetExpoVersion = exports.expoCliVersion = exports.expoSystemUiVersion = exports.expoStatusBarVersion = exports.expoSplashScreenVersion = exports.expoVersion = exports.reactTestRendererV53Version = exports.reactNativeWebV53Version = exports.metroV53Version = exports.reactNativeV53Version = exports.typesReactV53Version = exports.reactDomV53Version = exports.reactV53Version = exports.jestExpoV53Version = exports.expoV53MetroRuntimeVersion = exports.expoV53MetroConfigVersion = exports.babelPresetExpoV53Version = exports.expoV53CliVersion = exports.expoV53SystemUiVersion = exports.expoV53StatusBarVersion = exports.expoV53SplashScreenVersion = exports.expoV53Version = exports.reactTestRendererV54Version = exports.reactNativeWebV54Version = void 0;
5
5
  exports.assertSupportedExpoVersion = assertSupportedExpoVersion;
6
6
  const path_1 = require("path");
7
7
  const internal_1 = require("@nx/devkit/internal");
8
8
  exports.nxVersion = require((0, path_1.join)('@nx/expo', 'package.json')).version;
9
9
  // Lowest supported Expo SDK. Below this, generators throw via
10
10
  // assertSupportedExpoVersion. Expo Go only supports the latest SDK, but the
11
- // plugin keeps install lanes for the recent SDKs it has constants for (53–55).
11
+ // plugin keeps install lanes for the recent SDKs it has constants for (53–56).
12
12
  exports.minSupportedExpoVersion = '53.0.0';
13
13
  function assertSupportedExpoVersion(tree) {
14
14
  (0, internal_1.assertSupportedPackageVersion)(tree, 'expo', exports.minSupportedExpoVersion);
15
15
  }
16
- // Expo v55 versions (default for new projects) — RN 0.83, React 19.2
16
+ // Expo v56 versions (default for new projects) — RN 0.85, React 19.2
17
+ exports.expoV56Version = '~56.0.0';
18
+ exports.expoV56SplashScreenVersion = '~56.0.10';
19
+ exports.expoV56StatusBarVersion = '~56.0.4';
20
+ exports.expoV56SystemUiVersion = '~56.0.5';
21
+ exports.expoV56CliVersion = '~56.1.14'; // @expo/cli
22
+ exports.babelPresetExpoV56Version = '~56.0.14';
23
+ exports.expoV56MetroVersion = '~56.0.0'; // @expo/metro (SDK 55+ Metro)
24
+ exports.expoV56MetroConfigVersion = '~56.0.13'; // @expo/metro-config
25
+ exports.expoV56MetroRuntimeVersion = '~56.0.14';
26
+ exports.jestExpoV56Version = '~56.0.4';
27
+ exports.reactV56Version = '^19.2.0';
28
+ exports.reactDomV56Version = '^19.2.0';
29
+ exports.typesReactV56Version = '^19.2.0';
30
+ exports.reactNativeV56Version = '0.85.3';
31
+ exports.metroV56Version = '~0.84.3';
32
+ exports.reactNativeWebV56Version = '~0.21.0';
33
+ exports.reactTestRendererV56Version = '^19.2.0';
34
+ // Expo v55 versions (for existing workspaces) — RN 0.83, React 19.2
17
35
  exports.expoV55Version = '~55.0.26';
18
36
  exports.expoV55SplashScreenVersion = '~55.0.21';
19
37
  exports.expoV55StatusBarVersion = '~55.0.6';
20
38
  exports.expoV55SystemUiVersion = '~55.0.18';
21
39
  exports.expoV55CliVersion = '~55.0.11';
22
40
  exports.babelPresetExpoV55Version = '~55.0.7';
41
+ exports.expoV55MetroVersion = '~55.0.0'; // @expo/metro (SDK 55+ Metro)
23
42
  exports.expoV55MetroConfigVersion = '~55.0.8';
24
43
  exports.expoV55MetroRuntimeVersion = '~55.0.11';
25
44
  exports.jestExpoV55Version = '~55.0.18';
@@ -64,24 +83,27 @@ exports.reactNativeV53Version = '0.79.3';
64
83
  exports.metroV53Version = '~0.82.4';
65
84
  exports.reactNativeWebV53Version = '~0.20.0';
66
85
  exports.reactTestRendererV53Version = '^19.0.0';
67
- // Default exports point to v55 (latest)
68
- exports.expoVersion = exports.expoV55Version;
69
- exports.expoSplashScreenVersion = exports.expoV55SplashScreenVersion;
70
- exports.expoStatusBarVersion = exports.expoV55StatusBarVersion;
71
- exports.expoSystemUiVersion = exports.expoV55SystemUiVersion;
72
- exports.expoCliVersion = exports.expoV55CliVersion;
73
- exports.babelPresetExpoVersion = exports.babelPresetExpoV55Version;
74
- exports.expoMetroConfigVersion = exports.expoV55MetroConfigVersion;
75
- exports.expoMetroRuntimeVersion = exports.expoV55MetroRuntimeVersion;
76
- exports.jestExpoVersion = exports.jestExpoV55Version;
77
- exports.reactVersion = exports.reactV55Version;
78
- exports.reactDomVersion = exports.reactDomV55Version;
79
- exports.typesReactVersion = exports.typesReactV55Version;
80
- exports.reactNativeVersion = exports.reactNativeV55Version;
81
- exports.metroVersion = exports.metroV55Version;
82
- exports.reactNativeWebVersion = exports.reactNativeWebV55Version;
86
+ // Default exports point to v56 (latest)
87
+ exports.expoVersion = exports.expoV56Version;
88
+ exports.expoSplashScreenVersion = exports.expoV56SplashScreenVersion;
89
+ exports.expoStatusBarVersion = exports.expoV56StatusBarVersion;
90
+ exports.expoSystemUiVersion = exports.expoV56SystemUiVersion;
91
+ exports.expoCliVersion = exports.expoV56CliVersion;
92
+ exports.babelPresetExpoVersion = exports.babelPresetExpoV56Version;
93
+ exports.expoMetroConfigVersion = exports.expoV56MetroConfigVersion;
94
+ exports.expoMetroRuntimeVersion = exports.expoV56MetroRuntimeVersion;
95
+ exports.jestExpoVersion = exports.jestExpoV56Version;
96
+ exports.reactVersion = exports.reactV56Version;
97
+ exports.reactDomVersion = exports.reactDomV56Version;
98
+ exports.typesReactVersion = exports.typesReactV56Version;
99
+ exports.reactNativeVersion = exports.reactNativeV56Version;
100
+ exports.metroVersion = exports.metroV56Version;
101
+ exports.reactNativeWebVersion = exports.reactNativeWebV56Version;
83
102
  // Shared versions (version-independent)
84
103
  exports.reactNativeSvgTransformerVersion = '~1.5.1';
85
104
  exports.reactNativeSvgVersion = '15.12.1';
86
105
  exports.testingLibraryReactNativeVersion = '~13.2.0';
87
106
  exports.babelRuntimeVersion = '~7.27.6';
107
+ // jest-expo's preset maps `react-native-vector-icons` to `@expo/vector-icons`,
108
+ // so it must be installed for the Jest preset to resolve.
109
+ exports.expoVectorIconsVersion = '~15.1.0';
package/migrations.json CHANGED
@@ -56,6 +56,34 @@
56
56
  "description": "Rename imports of `createNodesV2` from `@nx/expo/plugin` to the canonical `createNodes` export.",
57
57
  "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes",
58
58
  "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md"
59
+ },
60
+ "update-23-1-0-update-metro-config-for-expo-56": {
61
+ "version": "23.1.0-beta.1",
62
+ "cli": "nx",
63
+ "requires": {
64
+ "expo": ">= 56.0.0"
65
+ },
66
+ "description": "Update generated metro.config.js to use @expo/metro for Expo SDK 56",
67
+ "factory": "./dist/src/migrations/update-23-1-0/update-metro-config-for-expo-56",
68
+ "prompt": "./dist/src/migrations/update-23-1-0/ai-instructions-for-expo-56.md"
69
+ },
70
+ "update-23-1-0-replace-standalone-metro-for-expo-56": {
71
+ "version": "23.1.0-beta.1",
72
+ "cli": "nx",
73
+ "requires": {
74
+ "expo": ">= 56.0.0"
75
+ },
76
+ "description": "Replace standalone metro-config/metro-resolver with @expo/metro for Expo SDK 56",
77
+ "factory": "./dist/src/migrations/update-23-1-0/replace-standalone-metro-for-expo-56"
78
+ },
79
+ "update-23-1-0-update-jest-winter-runtime-for-expo-56": {
80
+ "version": "23.1.0-beta.1",
81
+ "cli": "nx",
82
+ "requires": {
83
+ "expo": ">= 56.0.0"
84
+ },
85
+ "description": "Update Jest test-setup for the Expo SDK 56 winter runtime",
86
+ "factory": "./dist/src/migrations/update-23-1-0/update-jest-winter-runtime-for-expo-56"
59
87
  }
60
88
  },
61
89
  "packageJsonUpdates": {
@@ -310,6 +338,94 @@
310
338
  "alwaysAddToPackageJson": false
311
339
  }
312
340
  }
341
+ },
342
+ "23.1.0": {
343
+ "version": "23.1.0-beta.1",
344
+ "requires": {
345
+ "expo": ">=55.0.0 <56.0.0"
346
+ },
347
+ "packages": {
348
+ "expo": {
349
+ "version": "~56.0.0",
350
+ "alwaysAddToPackageJson": false
351
+ },
352
+ "expo-splash-screen": {
353
+ "version": "~56.0.10",
354
+ "alwaysAddToPackageJson": false
355
+ },
356
+ "expo-status-bar": {
357
+ "version": "~56.0.4",
358
+ "alwaysAddToPackageJson": false
359
+ },
360
+ "expo-system-ui": {
361
+ "version": "~56.0.5",
362
+ "alwaysAddToPackageJson": false
363
+ },
364
+ "@expo/cli": {
365
+ "version": "~56.1.14",
366
+ "alwaysAddToPackageJson": false
367
+ },
368
+ "babel-preset-expo": {
369
+ "version": "~56.0.14",
370
+ "alwaysAddToPackageJson": false
371
+ },
372
+ "react": {
373
+ "version": "^19.2.0",
374
+ "alwaysAddToPackageJson": false
375
+ },
376
+ "react-dom": {
377
+ "version": "^19.2.0",
378
+ "alwaysAddToPackageJson": false
379
+ },
380
+ "@types/react": {
381
+ "version": "^19.2.0",
382
+ "alwaysAddToPackageJson": false
383
+ },
384
+ "react-native": {
385
+ "version": "0.85.3",
386
+ "alwaysAddToPackageJson": false
387
+ },
388
+ "react-native-web": {
389
+ "version": "~0.21.0",
390
+ "alwaysAddToPackageJson": false
391
+ },
392
+ "@expo/metro": {
393
+ "version": "~56.0.0",
394
+ "alwaysAddToPackageJson": false
395
+ },
396
+ "@expo/metro-config": {
397
+ "version": "~56.0.13",
398
+ "alwaysAddToPackageJson": false
399
+ },
400
+ "@expo/metro-runtime": {
401
+ "version": "~56.0.14",
402
+ "alwaysAddToPackageJson": false
403
+ },
404
+ "@expo/vector-icons": {
405
+ "version": "~15.1.0",
406
+ "alwaysAddToPackageJson": false
407
+ },
408
+ "react-native-svg-transformer": {
409
+ "version": "~1.5.1",
410
+ "alwaysAddToPackageJson": false
411
+ },
412
+ "react-native-svg": {
413
+ "version": "15.12.1",
414
+ "alwaysAddToPackageJson": false
415
+ },
416
+ "@testing-library/react-native": {
417
+ "version": "~13.2.0",
418
+ "alwaysAddToPackageJson": false
419
+ },
420
+ "jest-expo": {
421
+ "version": "~56.0.4",
422
+ "alwaysAddToPackageJson": false
423
+ },
424
+ "@babel/runtime": {
425
+ "version": "~7.27.6",
426
+ "alwaysAddToPackageJson": false
427
+ }
428
+ }
313
429
  }
314
430
  }
315
431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/expo",
3
- "version": "23.1.0-beta.1",
3
+ "version": "23.1.0-beta.3",
4
4
  "private": false,
5
5
  "description": "The Expo Plugin for Nx contains executors and generators for managing and developing an expo application within your workspace. For example, you can directly build for different target platforms as well as generate projects and publish your code.",
6
6
  "keywords": [
@@ -61,27 +61,37 @@
61
61
  "semver": "^7.6.3",
62
62
  "tsconfig-paths": "^4.1.2",
63
63
  "tslib": "^2.3.0",
64
- "@nx/devkit": "23.1.0-beta.1",
65
- "@nx/eslint": "23.1.0-beta.1",
66
- "@nx/js": "23.1.0-beta.1",
67
- "@nx/react": "23.1.0-beta.1"
64
+ "@nx/devkit": "23.1.0-beta.3",
65
+ "@nx/eslint": "23.1.0-beta.3",
66
+ "@nx/js": "23.1.0-beta.3",
67
+ "@nx/react": "23.1.0-beta.3"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "expo": ">=53.0.0",
71
+ "@expo/metro": ">= 55.0.0",
71
72
  "metro-config": ">= 0.82.0",
72
73
  "metro-resolver": ">= 0.82.0"
73
74
  },
74
75
  "peerDependenciesMeta": {
75
76
  "expo": {
76
77
  "optional": true
78
+ },
79
+ "@expo/metro": {
80
+ "optional": true
81
+ },
82
+ "metro-config": {
83
+ "optional": true
84
+ },
85
+ "metro-resolver": {
86
+ "optional": true
77
87
  }
78
88
  },
79
89
  "devDependencies": {
80
- "nx": "23.1.0-beta.1"
90
+ "nx": "23.1.0-beta.3"
81
91
  },
82
92
  "optionalDependencies": {
83
- "@nx/detox": "23.1.0-beta.1",
84
- "@nx/rollup": "23.1.0-beta.1"
93
+ "@nx/detox": "23.1.0-beta.3",
94
+ "@nx/rollup": "23.1.0-beta.3"
85
95
  },
86
96
  "executors": "./executors.json",
87
97
  "ng-update": {