Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/e2e-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ jobs:
strategy:
fail-fast: false # keeps matrix running if one fails
matrix:
rn-version: ['0.71.19', '0.83.0']
rn-version: ['0.71.19', '0.84.0']
rn-architecture: ['legacy', 'new']
platform: ['android', 'ios']
build-type: ['production']
Expand All @@ -202,13 +202,13 @@ jobs:
runs-on: ["ghcr.io/cirruslabs/macos-sequoia-xcode:16.4", "runner_group_id:12"]
# Use Xcode 26 for newer RN versions (0.83.0)
- platform: ios
rn-version: '0.83.0'
rn-version: '0.84.0'
runs-on: ["ghcr.io/cirruslabs/macos-tahoe-xcode:26.2.0", "runner_group_id:12"]
- platform: android
runs-on: ["ghcr.io/cirruslabs/ubuntu-runner-amd64:22.04", "runner_group_id:12"]
exclude:
# exclude JSC for new RN versions (keeping the matrix manageable)
- rn-version: '0.83.0'
- rn-version: '0.84.0'
engine: 'jsc'
# exclude all rn versions lower than 0.80.0 for new architecture
- rn-version: '0.71.19'
Expand Down Expand Up @@ -323,15 +323,15 @@ jobs:
strategy:
fail-fast: false # keeps matrix running if one fails
matrix:
rn-version: ['0.83.0']
rn-version: ['0.84.0']
rn-architecture: ['legacy', 'new']
platform: ['android', 'ios']
build-type: ['production']
ios-use-frameworks: ['no'] # test only no frameworks
engine: ['hermes']
include:
- platform: ios
rn-version: '0.83.0'
rn-version: '0.84.0'
runs-on: macos-26
- platform: android
runs-on: ["ghcr.io/cirruslabs/ubuntu-runner-amd64:22.04", "runner_group_id:12"]
Expand Down
10 changes: 9 additions & 1 deletion dev-packages/e2e-tests/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ if (actions.includes('create')) {
env: Object.assign(env, { YARN_ENABLE_IMMUTABLE_INSTALLS: false }),
});

// Patch react-native-launch-arguments for Gradle 9+ compatibility
// Patch react-native-launch-arguments for Gradle 9+ compatibility (Android) and React Native 0.84+ compatibility (iOS)
execSync(`${patchScriptsDir}/rn.patch.launch-arguments.js --app-dir .`, {
stdio: 'inherit',
cwd: appDir,
Expand Down Expand Up @@ -189,6 +189,14 @@ if (actions.includes('create')) {
env: env,
});

// Clean Pods to ensure CocoaPods reads the patched podspec
if (fs.existsSync(`${appDir}/ios/Pods`)) {
fs.rmSync(`${appDir}/ios/Pods`, { recursive: true });
}
if (fs.existsSync(`${appDir}/ios/Podfile.lock`)) {
fs.rmSync(`${appDir}/ios/Podfile.lock`);
}

if (fs.existsSync(`${appDir}/Gemfile`)) {
execSync(`bundle install`, { stdio: 'inherit', cwd: appDir, env: env });
execSync('bundle exec pod install --repo-update', { stdio: 'inherit', cwd: `${appDir}/ios`, env: env });
Expand Down
81 changes: 69 additions & 12 deletions dev-packages/e2e-tests/patch-scripts/rn.patch.launch-arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,79 @@ debug.log('Patching react-native-launch-arguments build.gradle', buildGradlePath

if (!fs.existsSync(buildGradlePath)) {
debug.log('build.gradle not found, skipping patch');
return;
} else {
const buildGradle = fs.readFileSync(buildGradlePath, 'utf8');

// Replace destinationDir with destinationDirectory.get() for Gradle 9+ compatibility
const isPatched = buildGradle.includes('destinationDirectory.get()');
if (!isPatched) {
const patched = buildGradle.replace(
/\.destinationDir\b/g,
'.destinationDirectory.get()'
);

fs.writeFileSync(buildGradlePath, patched);
debug.log('Patched react-native-launch-arguments build.gradle successfully!');
} else {
debug.log('react-native-launch-arguments build.gradle is already patched!');
}
}

const buildGradle = fs.readFileSync(buildGradlePath, 'utf8');
// Patch iOS podspec for React Native 0.71+ compatibility
// Replace 'React' with install_modules_dependencies() to fix RCTRegisterModule undefined symbol error in RN 0.84+ with dynamic frameworks
const podspecPath = path.join(
args['app-dir'],
'node_modules',
'react-native-launch-arguments',
'react-native-launch-arguments.podspec'
);

debug.log('Patching react-native-launch-arguments podspec', podspecPath);

if (fs.existsSync(podspecPath)) {
const podspec = fs.readFileSync(podspecPath, 'utf8');
debug.log('Found podspec, checking for React dependency...');

// Check if already patched with install_modules_dependencies
const isPatched = podspec.includes('install_modules_dependencies');
const hasReactDep = /s\.dependency\s+['"]React['"]/.test(podspec);
const hasReactCoreDep = /s\.dependency\s+['"]React\/Core['"]/.test(podspec);
const hasReactCoreDepOnly = podspec.includes("s.dependency 'React-Core'") || podspec.includes('s.dependency "React-Core"');

debug.log(`Podspec status: isPatched=${isPatched}, hasReactDep=${hasReactDep}, hasReactCoreDep=${hasReactCoreDep}, hasReactCoreDepOnly=${hasReactCoreDepOnly}`);

if (!isPatched && (hasReactDep || hasReactCoreDep || hasReactCoreDepOnly)) {
let patched = podspec;

// Replace any React dependency with install_modules_dependencies(s)
// This is the modern approach that works with all framework configurations (static, dynamic, etc.)
// and automatically includes the correct React Native dependencies
const installModulesDepsBlock = `
if defined? install_modules_dependencies
install_modules_dependencies(s)
else
s.dependency "React-Core"
end`;

// Replace destinationDir with destinationDirectory.get() for Gradle 9+ compatibility
const isPatched = buildGradle.includes('destinationDirectory.get()');
if (!isPatched) {
const patched = buildGradle.replace(
/\.destinationDir\b/g,
'.destinationDirectory.get()'
);
if (hasReactDep) {
debug.log("Replacing s.dependency 'React' with install_modules_dependencies(s)");
patched = patched.replace(/\s+s\.dependency\s+['"]React['"]\s*\n/g, installModulesDepsBlock + '\n');
} else if (hasReactCoreDep) {
debug.log("Replacing s.dependency 'React/Core' with install_modules_dependencies(s)");
patched = patched.replace(/\s+s\.dependency\s+['"]React\/Core['"]\s*\n/g, installModulesDepsBlock + '\n');
} else if (hasReactCoreDepOnly) {
debug.log("Replacing s.dependency 'React-Core' with install_modules_dependencies(s)");
patched = patched.replace(/\s+s\.dependency\s+['"]React-Core['"]\s*\n/g, installModulesDepsBlock + '\n');
}

fs.writeFileSync(buildGradlePath, patched);
debug.log('Patched react-native-launch-arguments build.gradle successfully!');
fs.writeFileSync(podspecPath, patched);
debug.log('Patched react-native-launch-arguments podspec successfully!');
Comment on lines +84 to +94
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The regex for detecting a dependency is more permissive than the regex for replacing it, which can cause the patch to fail silently while reporting success.
Severity: MEDIUM

Suggested Fix

Unify the detection and replacement logic. Either make the replacement regex more flexible to not require a trailing newline, or make the detection regex stricter to match the replacement's requirements. Additionally, verify that the file content has changed after the replacement operation before reporting success.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: dev-packages/e2e-tests/patch-scripts/rn.patch.launch-arguments.js#L82-L94

Potential issue: The patch script uses different regular expressions for detecting and
replacing dependency lines in a podspec file. The detection regex, for example
`/s\.dependency\s+['"]React['"]/`, will match a line even if it is not followed by a
newline. However, the corresponding replacement regex, `
/\s+s\.dependency\s+['"]React['"]\s*\n/g`, requires a newline `\n` at the end. If a
podspec file has a dependency declaration without a trailing newline, the script will
detect the dependency, but the replacement will fail. The script does not verify that
the replacement was successful, leading to a silent failure where it reports success but
the file remains unpatched, which can cause subsequent build failures.

} else if (isPatched) {
debug.log('react-native-launch-arguments podspec is already patched!');
} else {
debug.log('Podspec does not contain React dependency - may already use install_modules_dependencies');
}
} else {
debug.log('react-native-launch-arguments build.gradle is already patched!');
debug.log('podspec not found, skipping iOS patch');
}

Loading