Skip to content

Commit 02abeb7

Browse files
committed
feat: add standalone Reactotron server and update dependencies
- Introduced a bundled standalone Reactotron server that starts automatically with the macOS app. - Added a new script to bundle the server code using esbuild. - Updated package.json to include esbuild as a dependency. - Enhanced README with instructions for running the server and details about the embedded server functionality. - Updated Podfile.lock to reflect changes in dependencies. - Minor adjustments to existing scripts and configurations for better integration.
1 parent cdb6c7f commit 02abeb7

File tree

8 files changed

+5148
-12
lines changed

8 files changed

+5148
-12
lines changed

README.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ npm install
3535
npm run pod
3636
npm run start
3737
npm run macos
38-
# for release builds
38+
# for release builds (automatically bundles the server)
3939
npm run macos-release
4040
```
4141

42-
### Windows Development
42+
**Note:** The Reactotron server is now embedded in the macOS app and starts automatically on port 9292. No need to run a separate server process!
43+
44+
### Windows Development
4345

4446
#### System Requirements
4547

@@ -69,14 +71,26 @@ Both platforms use unified commands for native module development:
6971

7072
See [Making a TurboModule](./docs/Making-a-TurboModule.md) for detailed native development instructions.
7173

74+
### Server Bundle
75+
76+
If you modify the standalone server code (`standalone-server.js`), rebuild the bundle:
77+
78+
```sh
79+
npm run bundle-server
80+
```
81+
82+
The bundle is automatically generated during release builds (`npm run macos-release`).
83+
7284
## Enabling Reactotron in your app
7385

7486
> [!NOTE]
7587
> We don't have a simple way to integrate the new Reactotron-macOS into your app yet, but that will be coming at some point. This assumes you've cloned down Reactotron-macOS.
7688
77-
1. From the root of Reactotron-macOS, start the standalone relay server:
78-
`node -e "require('./standalone-server').startReactotronServer({ port: 9292 })"`
79-
2. In your app, add the following to your app.tsx:
89+
The Reactotron server is now embedded in the macOS app and starts automatically when you launch it. Simply:
90+
91+
1. Run the Reactotron macOS app (via `npm run macos` or the built .app)
92+
2. The server will automatically start on port 9292
93+
3. In your app, add the following to your app.tsx:
8094

8195
```tsx
8296
if (__DEV__) {
@@ -92,7 +106,26 @@ if (__DEV__) {
92106
}
93107
```
94108

95-
3. Start your app and Reactotron-macOS. You should see logs appear.
109+
4. Start your app and you should see logs appear in Reactotron.
110+
111+
### Running the Server Standalone (Optional)
112+
113+
If you need to run the server without the GUI (for CI/CD or headless environments), you can still run:
114+
115+
```sh
116+
node -e "require('./standalone-server').startReactotronServer({ port: 9292 })"
117+
```
118+
119+
### Server Implementation Details
120+
121+
The embedded server:
122+
123+
- Starts automatically when the app launches
124+
- Stops automatically when the app quits
125+
- Runs on port 9292 by default (configurable in `AppDelegate.mm`)
126+
- Is bundled as a single file with all dependencies
127+
- Requires Node.js to be installed on the system
128+
- Supports nvm, asdf, fnm, and other Node version managers
96129

97130
## Get Help
98131

macos/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1779,7 +1779,7 @@ SPEC CHECKSUMS:
17791779
glog: b7594b792ee4e02ed1f44b01d046ca25fa713e3d
17801780
hermes-engine: b5c9cfbe6415f1b0b24759f2942c8f33e9af6347
17811781
IRNativeModules: 2fa316ab0ca91ec3e7bd4ba7ab2fc1f642fb5542
1782-
RCT-Folly: e8b53d8c0d2d9df4a6a8b0a368a1a91fc62a88cb
1782+
RCT-Folly: abec2d7f4af402b4957c44e86ceff8725b23c1b4
17831783
RCTDeprecation: 9da6c2d8a3b1802142718283260fb06d702ddb07
17841784
RCTRequired: 574f9d55bda1d50676530b6c36bab4605612dfb6
17851785
RCTTypeSafety: 7de929c405e619c023116e7747118a2c5d5b2320

macos/Reactotron-macOS/AppDelegate.mm

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,121 @@
55
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
66
#import "WindowSetup.h"
77

8-
@implementation AppDelegate
8+
@implementation AppDelegate {
9+
NSTask *_reactotronTask;
10+
}
11+
12+
#pragma mark - Reactotron Server Management
13+
14+
- (NSString *)findNodeBinary
15+
{
16+
// Use login shell approach to honor nvm/asdf/etc.
17+
NSTask *which = [[NSTask alloc] init];
18+
which.launchPath = @"/usr/bin/env";
19+
which.arguments = @[ @"bash", @"-lc", @"command -v node || true" ];
20+
21+
NSPipe *pipe = [NSPipe pipe];
22+
which.standardOutput = pipe;
23+
24+
[which launch];
25+
[which waitUntilExit];
26+
27+
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
28+
NSString *path = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
29+
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
30+
31+
if (path.length > 0) {
32+
NSLog(@"Found node at: %@", path);
33+
return path;
34+
}
35+
36+
NSLog(@"Node not found in shell path, using /usr/bin/env as fallback");
37+
return @"/usr/bin/env"; // fallback; will run "env node" below
38+
}
39+
40+
- (void)startReactotronServerWithPort:(NSString *)port
41+
{
42+
if (_reactotronTask) {
43+
NSLog(@"Reactotron server already running");
44+
return;
45+
}
46+
47+
NSString *node = [self findNodeBinary];
48+
49+
NSURL *scriptURL =
50+
[[NSBundle mainBundle] URLForResource:@"standalone-server.bundle" withExtension:@"js"];
51+
if (!scriptURL) {
52+
NSLog(@"⚠️ standalone-server.bundle.js not found in bundle. Run 'npm run bundle-server' to generate it.");
53+
return;
54+
}
55+
56+
NSLog(@"Found server script at: %@", scriptURL.path);
57+
58+
_reactotronTask = [[NSTask alloc] init];
59+
60+
// Launch node directly with arguments (avoid shell -c for security)
61+
if ([node isEqualToString:@"/usr/bin/env"]) {
62+
_reactotronTask.launchPath = node;
63+
_reactotronTask.arguments = @[ @"node", scriptURL.path, @"--port", port ?: @"9292" ];
64+
} else {
65+
_reactotronTask.launchPath = node;
66+
_reactotronTask.arguments = @[ scriptURL.path, @"--port", port ?: @"9292" ];
67+
}
68+
69+
// Give the script a sane CWD (app bundle directory)
70+
_reactotronTask.currentDirectoryPath = [[[NSBundle mainBundle] bundleURL] path];
71+
72+
// Capture output for debugging
73+
NSPipe *outputPipe = [NSPipe pipe];
74+
NSPipe *errorPipe = [NSPipe pipe];
75+
_reactotronTask.standardOutput = outputPipe;
76+
_reactotronTask.standardError = errorPipe;
77+
78+
// Log server output
79+
outputPipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *file) {
80+
NSData *data = [file availableData];
81+
if (data.length > 0) {
82+
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
83+
NSLog(@"[Reactotron Server] %@", [output stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]);
84+
}
85+
};
86+
87+
errorPipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *file) {
88+
NSData *data = [file availableData];
89+
if (data.length > 0) {
90+
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
91+
NSLog(@"[Reactotron Server Error] %@", [output stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]);
92+
}
93+
};
94+
95+
__weak __typeof__(self) weakSelf = self;
96+
_reactotronTask.terminationHandler = ^(NSTask *task) {
97+
NSLog(@"Reactotron server exited with status: %d", task.terminationStatus);
98+
__typeof__(self) strongSelf = weakSelf;
99+
if (strongSelf) {
100+
strongSelf->_reactotronTask = nil;
101+
}
102+
};
103+
104+
@try {
105+
[_reactotronTask launch];
106+
NSLog(@"✅ Reactotron server started on port %@", port ?: @"9292");
107+
} @catch (NSException *exception) {
108+
NSLog(@"❌ Failed to start Reactotron server: %@", exception.reason);
109+
_reactotronTask = nil;
110+
}
111+
}
112+
113+
- (void)stopReactotronServer
114+
{
115+
if (_reactotronTask && _reactotronTask.isRunning) {
116+
NSLog(@"Stopping Reactotron server...");
117+
[_reactotronTask terminate];
118+
}
119+
_reactotronTask = nil;
120+
}
121+
122+
#pragma mark - Application Lifecycle
9123

10124
- (void)applicationDidFinishLaunching:(NSNotification *)notification
11125
{
@@ -25,6 +139,15 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
25139

26140
// Configure window chrome before RN mounts
27141
IRConfigureWindow(self.window);
142+
143+
// Start the bundled Reactotron server
144+
// You can change the port here or make it a user preference
145+
[self startReactotronServerWithPort:@"9292"];
146+
}
147+
148+
- (void)applicationWillTerminate:(NSNotification *)notification
149+
{
150+
[self stopReactotronServer];
28151
}
29152

30153
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge

0 commit comments

Comments
 (0)