Skip to content

Commit ff2a8a7

Browse files
committed
fix(cli): embed debug JS bundle in simulator xcframeworks
1 parent f1b5447 commit ff2a8a7

11 files changed

Lines changed: 299 additions & 15 deletions

File tree

apps/AppleApp/Brownfield Apple App/components/ContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ struct ContentView: View {
1919
NavigationView {
2020

2121
VStack(spacing: 16) {
22-
GreetingCard(name: "iOS Expo")
22+
GreetingCard(name: "iOS Vanilla")
2323

2424
MessagesView()
2525

2626
ReactNativeView(
27-
moduleName: "main",
27+
moduleName: "RNApp",
2828
initialProperties: [
2929
"nativeOsVersionLabel":
3030
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"

apps/RNApp/ios/RNApp.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@
4747
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RNApp/Images.xcassets; sourceTree = "<group>"; };
4848
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RNApp/Info.plist; sourceTree = "<group>"; };
4949
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = RNApp/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
50+
2065C22D9167CC092D4BB5F7 /* Pods_RNApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RNApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5051
3B4392A12AC88292D35C810B /* Pods-RNApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNApp.debug.xcconfig"; path = "Target Support Files/Pods-RNApp/Pods-RNApp.debug.xcconfig"; sourceTree = "<group>"; };
5152
5709B34CF0A7D63546082F79 /* Pods-RNApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNApp.release.xcconfig"; path = "Target Support Files/Pods-RNApp/Pods-RNApp.release.xcconfig"; sourceTree = "<group>"; };
5253
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = RNApp/AppDelegate.swift; sourceTree = "<group>"; };
5354
79BD1EE32EEBFB76003AA29F /* BrownfieldLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BrownfieldLib.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5455
79F35E8A2EEC1D4500E64860 /* BrownfieldLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrownfieldLib.swift; sourceTree = "<group>"; };
55-
2065C22D9167CC092D4BB5F7 /* Pods_RNApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RNApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5656
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RNApp/LaunchScreen.storyboard; sourceTree = "<group>"; };
5757
8A02E03D9F74B585B0A8F7F7 /* Pods-RNApp-BrownfieldLib.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNApp-BrownfieldLib.debug.xcconfig"; path = "Target Support Files/Pods-RNApp-BrownfieldLib/Pods-RNApp-BrownfieldLib.debug.xcconfig"; sourceTree = "<group>"; };
5858
D8C030F60E402FD6CFBB3904 /* Pods-RNApp-BrownfieldLib.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNApp-BrownfieldLib.release.xcconfig"; path = "Target Support Files/Pods-RNApp-BrownfieldLib/Pods-RNApp-BrownfieldLib.release.xcconfig"; sourceTree = "<group>"; };
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
package com.callstack.nativebrownfieldnavigation
22

3-
interface BrownfieldNavigationDelegate
3+
interface BrownfieldNavigationDelegate {
4+
fun navigateToSettings()
5+
fun navigateToReferrals(userId: String)
6+
}

packages/brownfield-navigation/android/src/main/java/com/callstack/nativebrownfieldnavigation/NativeBrownfieldNavigationModule.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package com.callstack.nativebrownfieldnavigation
22

3-
import android.util.Log
43
import com.facebook.react.bridge.ReactApplicationContext
54
import com.facebook.react.bridge.ReactMethod
65

76
class NativeBrownfieldNavigationModule(
87
reactContext: ReactApplicationContext
98
) : NativeBrownfieldNavigationSpec(reactContext) {
109
@ReactMethod
11-
override fun temporary() {
12-
Log.d(NAME, "temporary")
10+
override fun navigateToSettings() {
11+
BrownfieldNavigationManager.getDelegate().navigateToSettings()
12+
}
13+
14+
@ReactMethod
15+
override fun navigateToReferrals(userId: String) {
16+
BrownfieldNavigationManager.getDelegate().navigateToReferrals(userId)
1317
}
1418

1519
companion object {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22

33
@objc public protocol BrownfieldNavigationDelegate: AnyObject {
4-
4+
@objc func navigateToSettings()
5+
@objc func navigateToReferrals(_ userId: String)
56
}

packages/brownfield-navigation/ios/NativeBrownfieldNavigation.mm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99
@implementation NativeBrownfieldNavigation
1010

11-
- (void)temporary {
12-
NSLog(@"temporary");
11+
- (void)navigateToSettings {
12+
[[[BrownfieldNavigationManager shared] getDelegate] navigateToSettings];
13+
}
14+
15+
- (void)navigateToReferrals:(NSString *)userId {
16+
[[[BrownfieldNavigationManager shared] getDelegate] navigateToReferrals:userId];
1317
}
1418

1519
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:

packages/brownfield-navigation/src/NativeBrownfieldNavigation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { TurboModuleRegistry, type TurboModule } from 'react-native';
22

33
export interface Spec extends TurboModule {
4-
temporary(): void;
4+
navigateToSettings(): void;
5+
navigateToReferrals(userId: string): void;
56
}
67

78
export default TurboModuleRegistry.getEnforcing<Spec>(

packages/brownfield-navigation/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import NativeBrownfieldNavigation from './NativeBrownfieldNavigation';
22

33
const BrownfieldNavigation = {
4-
temporary: () => {
5-
NativeBrownfieldNavigation.temporary();
4+
navigateToSettings: () => {
5+
NativeBrownfieldNavigation.navigateToSettings();
6+
},
7+
navigateToReferrals: (userId: string) => {
8+
NativeBrownfieldNavigation.navigateToReferrals(userId);
69
},
710
};
811

packages/cli/src/brownfield/commands/packageIos.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
2727
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
2828
import { stripFrameworkBinary } from '../utils/stripFrameworkBinary.js';
29+
import { copyDebugBundleToSimulatorSlice } from '../utils/copyDebugBundleToSimulatorSlice.js';
2930

3031
export const packageIosCommand = curryOptions(
3132
new Command('package:ios').description('Build iOS XCFramework'),
@@ -96,6 +97,40 @@ export const packageIosCommand = curryOptions(
9697
platformConfig
9798
);
9899

100+
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
101+
const frameworkName = options.scheme;
102+
103+
if (frameworkName) {
104+
copyDebugBundleToSimulatorSlice({
105+
productsPath,
106+
configuration,
107+
frameworkName,
108+
});
109+
110+
if (configuration.includes('Debug')) {
111+
await mergeFrameworks({
112+
sourceDir: userConfig.project.ios.sourceDir,
113+
frameworkPaths: [
114+
path.join(
115+
productsPath,
116+
`${configuration}-iphoneos`,
117+
`${frameworkName}.framework`
118+
),
119+
path.join(
120+
productsPath,
121+
`${configuration}-iphonesimulator`,
122+
`${frameworkName}.framework`
123+
),
124+
],
125+
outputPath: path.join(packageDir, `${frameworkName}.xcframework`),
126+
});
127+
}
128+
} else if (configuration.includes('Debug')) {
129+
logger.warn(
130+
'Skipping Debug simulator JS bundle copy: scheme is required to locate the framework output'
131+
);
132+
}
133+
99134
const reactBrownfieldXcframeworkPath = path.join(
100135
packageDir,
101136
'ReactBrownfield.xcframework'
@@ -108,7 +143,6 @@ export const packageIosCommand = curryOptions(
108143
}
109144

110145
if (hasBrownie) {
111-
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
112146
const brownieOutputPath = path.join(packageDir, 'Brownie.xcframework');
113147

114148
await mergeFrameworks({
@@ -141,7 +175,6 @@ export const packageIosCommand = curryOptions(
141175
}
142176

143177
if (hasNavigation) {
144-
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
145178
const brownfieldNavigationOutputPath = path.join(packageDir, 'BrownfieldNavigation.xcframework');
146179

147180
await mergeFrameworks({
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import fs from 'node:fs';
2+
import os from 'node:os';
3+
import path from 'node:path';
4+
5+
import * as rockTools from '@rock-js/tools';
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7+
8+
import { copyDebugBundleToSimulatorSlice } from '../copyDebugBundleToSimulatorSlice.js';
9+
10+
vi.mock('@rock-js/tools', async (importOriginal) => {
11+
const actual = await importOriginal<typeof rockTools>();
12+
return {
13+
...actual,
14+
logger: {
15+
...actual.logger,
16+
error: vi.fn(),
17+
warn: vi.fn(),
18+
info: vi.fn(),
19+
success: vi.fn(),
20+
debug: vi.fn(),
21+
},
22+
};
23+
});
24+
25+
const mockLoggerWarn = rockTools.logger.warn as ReturnType<typeof vi.fn>;
26+
const mockLoggerSuccess = rockTools.logger.success as ReturnType<typeof vi.fn>;
27+
28+
function createFramework(pathname: string) {
29+
fs.mkdirSync(pathname, { recursive: true });
30+
fs.writeFileSync(path.join(pathname, 'BrownfieldLib'), 'fake binary');
31+
}
32+
33+
describe('copyDebugBundleToSimulatorSlice', () => {
34+
let tempDir: string;
35+
36+
beforeEach(() => {
37+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-debug-bundle-test-'));
38+
vi.clearAllMocks();
39+
});
40+
41+
afterEach(() => {
42+
fs.rmSync(tempDir, { recursive: true, force: true });
43+
});
44+
45+
it('copies main.jsbundle into the Debug simulator slice when it is missing', () => {
46+
const productsPath = path.join(tempDir, 'Build', 'Products');
47+
48+
const deviceFrameworkPath = path.join(
49+
productsPath,
50+
'Debug-iphoneos',
51+
'BrownfieldLib.framework'
52+
);
53+
const simulatorFrameworkPath = path.join(
54+
productsPath,
55+
'Debug-iphonesimulator',
56+
'BrownfieldLib.framework'
57+
);
58+
59+
createFramework(deviceFrameworkPath);
60+
createFramework(simulatorFrameworkPath);
61+
62+
fs.writeFileSync(
63+
path.join(deviceFrameworkPath, 'main.jsbundle'),
64+
'debug bundled output'
65+
);
66+
67+
copyDebugBundleToSimulatorSlice({
68+
productsPath,
69+
configuration: 'Debug',
70+
frameworkName: 'BrownfieldLib',
71+
});
72+
73+
const simulatorBundlePath = path.join(
74+
simulatorFrameworkPath,
75+
'main.jsbundle'
76+
);
77+
78+
expect(fs.readFileSync(simulatorBundlePath, 'utf8')).toBe(
79+
'debug bundled output'
80+
);
81+
expect(mockLoggerSuccess).toHaveBeenCalledWith(
82+
expect.stringContaining('Copied Debug JS bundle to simulator slice')
83+
);
84+
});
85+
86+
it('does nothing for non-Debug configurations', () => {
87+
const productsPath = path.join(tempDir, 'Build', 'Products');
88+
89+
const deviceFrameworkPath = path.join(
90+
productsPath,
91+
'Release-iphoneos',
92+
'BrownfieldLib.framework'
93+
);
94+
const simulatorFrameworkPath = path.join(
95+
productsPath,
96+
'Release-iphonesimulator',
97+
'BrownfieldLib.framework'
98+
);
99+
100+
createFramework(deviceFrameworkPath);
101+
createFramework(simulatorFrameworkPath);
102+
fs.writeFileSync(
103+
path.join(deviceFrameworkPath, 'main.jsbundle'),
104+
'release bundle'
105+
);
106+
107+
copyDebugBundleToSimulatorSlice({
108+
productsPath,
109+
configuration: 'Release',
110+
frameworkName: 'BrownfieldLib',
111+
});
112+
113+
expect(
114+
fs.existsSync(path.join(simulatorFrameworkPath, 'main.jsbundle'))
115+
).toBe(false);
116+
expect(mockLoggerSuccess).not.toHaveBeenCalled();
117+
});
118+
119+
it('warns and skips when the device bundle is missing', () => {
120+
const productsPath = path.join(tempDir, 'Build', 'Products');
121+
122+
const simulatorFrameworkPath = path.join(
123+
productsPath,
124+
'Debug-iphonesimulator',
125+
'BrownfieldLib.framework'
126+
);
127+
128+
createFramework(simulatorFrameworkPath);
129+
130+
copyDebugBundleToSimulatorSlice({
131+
productsPath,
132+
configuration: 'Debug',
133+
frameworkName: 'BrownfieldLib',
134+
});
135+
136+
expect(mockLoggerWarn).toHaveBeenCalledWith(
137+
expect.stringContaining('Skipping simulator JS bundle copy')
138+
);
139+
});
140+
141+
it('overwrites an existing simulator bundle with the Debug device bundle', () => {
142+
const productsPath = path.join(tempDir, 'Build', 'Products');
143+
144+
const deviceFrameworkPath = path.join(
145+
productsPath,
146+
'Debug-iphoneos',
147+
'BrownfieldLib.framework'
148+
);
149+
const simulatorFrameworkPath = path.join(
150+
productsPath,
151+
'Debug-iphonesimulator',
152+
'BrownfieldLib.framework'
153+
);
154+
155+
createFramework(deviceFrameworkPath);
156+
createFramework(simulatorFrameworkPath);
157+
158+
fs.writeFileSync(
159+
path.join(deviceFrameworkPath, 'main.jsbundle'),
160+
'fresh debug bundle'
161+
);
162+
fs.writeFileSync(
163+
path.join(simulatorFrameworkPath, 'main.jsbundle'),
164+
'stale simulator bundle'
165+
);
166+
167+
copyDebugBundleToSimulatorSlice({
168+
productsPath,
169+
configuration: 'Debug',
170+
frameworkName: 'BrownfieldLib',
171+
});
172+
173+
expect(
174+
fs.readFileSync(path.join(simulatorFrameworkPath, 'main.jsbundle'), 'utf8')
175+
).toBe('fresh debug bundle');
176+
});
177+
});

0 commit comments

Comments
 (0)