Skip to content

eas/repack in custom EAS build fails to load metro.config.js on iOS but works on Android #3267

@yonitou

Description

@yonitou

Build/Submit details page URL

https://expo.dev/accounts/alvie1/projects/hygo/workflows/019ac60c-89aa-7f4b-85c1-1c8888b18456

Summary

When using eas/repack in EAS Build workflows, the repack process fails on iOS but succeeds on Android. The error occurs during the expo export:embed step when Metro tries to load metro.config.js.

Error Message

Error: Found config at /Users/expo/workingdir/build/apps/mobile/metro.config.js that could not be
  loaded with Node.js. at loadConfigFile (/Users/expo/workingdir/build/apps/mobile/node_modules/@expo/metro/node_modules/metro-config/src/loadConfig.js:263:15)

Environment

  • EAS Build Profile: e2e-tests-repack
  • Platform: iOS (fails) vs Android (works)
  • Node version: 22.12.0 (configured in eas.json)
  • Metro config: TypeScript file (metro.config.ts) loaded via JavaScript wrapper (metro.config.js)

Reproduction

metro.config.js

  require("tsx/cjs");
  module.exports = require("./metro.config.ts");

metro.config.ts

import { workspaceRoot } from "@nx/devkit";
import { getResolveRequest } from "@nx/expo/plugins/metro-resolver";
import { getSentryExpoConfig } from "@sentry/react-native/metro";
import { mergeConfig, type MetroConfig } from "metro-config";
import { existsSync, readdirSync, statSync } from "node:fs";
import path from "node:path";

const sentryConfig = getSentryExpoConfig(__dirname, { annotateReactComponents: true });
const { assetExts: assetExtensions = [], sourceExts: sourceExtensions = [] } = sentryConfig.resolver ?? {};

const customConfig: MetroConfig = {
	resolver: {
		assetExts: assetExtensions.filter((extension) => extension !== "svg"),
		sourceExts: [...sourceExtensions, "svg"],
		unstable_enablePackageExports: false
	},
	transformer: {
		babelTransformerPath: require.resolve("react-native-svg-transformer")
	}
};

const watchFolders = [
	...new Set(
		readdirSync(workspaceRoot)
			.filter((fileName) => !["dist", "e2e"].includes(fileName) && !fileName.startsWith("."))
			.map((fileName) => path.join(workspaceRoot, fileName))
			.filter((filePath) => statSync(filePath).isDirectory())
	)
].filter((folder) => existsSync(folder));

const nxConfig: MetroConfig = {
	resolver: {
		nodeModulesPaths: [path.join(workspaceRoot, "node_modules")],
		resolveRequest: getResolveRequest(["", "ts", "tsx", "js", "jsx", "json"], [], [])
	},
	watchFolders
};

module.exports = mergeConfig(mergeConfig(sentryConfig, customConfig), nxConfig);

Observations

I tried everything to reproduce the error locally but I can't :

Managed or bare?

managed

Environment

 expo-env-info 1.2.1 environment info:
    System:
      OS: macOS 26.1
      Shell: 5.9 - /bin/zsh
    Binaries:
      Node: 24.11.1 - ~/.nvm/versions/node/v24.11.1/bin/node
      Yarn: 1.22.22 - /opt/homebrew/bin/yarn
      npm: 8.19.4 - ~/code/yonitou/hygo/node_modules/.bin/npm
      Watchman: 2025.03.10.00 - /opt/homebrew/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: DriverKit 25.1, iOS 26.1, macOS 26.1, tvOS 26.1, visionOS 26.1, watchOS 26.1
    IDEs:
      Android Studio: 2025.1 AI-251.26094.121.2513.14007798
      Xcode: 26.1.1/17B100 - /usr/bin/xcodebuild
    npmPackages:
      babel-preset-expo: ~54.0.0 => 54.0.7 
      expo: ^54.0.0 => 54.0.25 
      react: 19.1.0 => 19.1.0 
      react-dom: 19.1.0 => 19.1.0 
      react-native: 0.81.5 => 0.81.5 
    npmGlobalPackages:
      eas-cli: 16.28.0
    Expo Workflow: managed

Error output

 Error: Found config at /Users/expo/workingdir/build/apps/mobile/metro.config.js that could not be
  loaded with Node.js.
      at loadConfigFile (/Users/expo/workingdir/build/apps/mobile/node_modules/@expo/metro/node_modu
  les/metro-config/src/loadConfig.js:263:15)
      at resolveConfig (/Users/expo/workingdir/build/apps/mobile/node_modules/@expo/metro/node_modul
  es/metro-config/src/loadConfig.js:94:10)
      at loadMetroConfigAsync (/Users/expo/workingdir/build/apps/mobile/node_modules/@expo/cli/src/s
  tart/server/metro/instantiateMetro.ts:97:21)

Reproducible demo or steps to reproduce from a blank project

  • Create a new expo app
  • Create this custom build configuration
build:
  name: Run tests
  steps:
    - eas/checkout
    - eas/download_build:
        id: download_build
        inputs:
          build_id: ${{ env.BUILD_ID }}
    - eas/repack:
        id: repack
        inputs:
          platform: ${{ env.PLATFORM }}
          source_app_path: ${{ steps.download_build.outputs.artifact_path }}
    - eas/upload_artifact:
        id: upload_artifact
        inputs:
          path: ${{ steps.repack.outputs.output_path }}

  • Create this EAS Workflows config file :
name: e2e-test

on:
  pull_request:

defaults:
  tools:
    node: 22.12.0
jobs:
  ios_repack:
    id: ios_repack
    type: build
    params:
      platform: ios
      profile: e2e-tests-repack
    env:
      PLATFORM: ios
      BUILD_ID: PUT_BUILD_ID_HERE
  • Create this metro.config.ts file:
import { getSentryExpoConfig } from "@sentry/react-native/metro";
import { mergeConfig, type MetroConfig } from "metro-config";
import { existsSync, readdirSync, statSync } from "node:fs";
import path from "node:path";

const sentryConfig = getSentryExpoConfig(__dirname, { annotateReactComponents: true });
const { assetExts: assetExtensions = [], sourceExts: sourceExtensions = [] } = sentryConfig.resolver ?? {};

const customConfig: MetroConfig = {
	resolver: {
		assetExts: assetExtensions.filter((extension) => extension !== "svg"),
		sourceExts: [...sourceExtensions, "svg"],
		unstable_enablePackageExports: false
	},
	transformer: {
		babelTransformerPath: require.resolve("react-native-svg-transformer")
	}
};

module.exports = mergeConfig(sentryConfig, customConfig);

  • Create this metro.config.js file
require("tsx/cjs");

module.exports = require("./metro.config.ts");
  • Create a pull request and look at the logs

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs reviewIssue is ready to be reviewed by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions