rn-iso 0.6.0 → 0.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-iso",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Isolated React Native dev environments per project/worktree",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,10 +60,11 @@ export default function androidCommand(program) {
60
60
  }
61
61
  // With --managed-metro, rn-iso owns Metro: detached so it survives the
62
62
  // invoking shell (agents run builds from finite shells), output to the
63
- // per-project log file, and the build CLI gets --no-packager /
64
- // --no-bundler so it cannot start a second Metro on the same port.
65
- // Without the flag, the build CLI owns Metro as usual (interactive
66
- // bundler UX for humans).
63
+ // per-project log file. The build CLI is then kept from starting its own:
64
+ // bare RN gets --no-packager; expo gets --port and reuses the Metro
65
+ // already listening on that port. ensureMetro waits for /status so the
66
+ // port is bound before the build's reuse check runs. Without the flag,
67
+ // the build CLI owns Metro as usual (interactive bundler UX for humans).
67
68
  if (opts.managedMetro) {
68
69
  const metro = await ensureMetro({ projectPath: root, isExpo, port: proj.metroPort });
69
70
  if (metro.alreadyRunning) {
@@ -72,6 +73,9 @@ export default function androidCommand(program) {
72
73
  setMetro(root, proj.metroPort, metro.pid);
73
74
  console.log(chalk.dim(`Metro started detached (pid ${metro.pid}, port ${proj.metroPort})`));
74
75
  console.log(chalk.dim(`Metro log: ${logFileFor(root)}`));
76
+ if (!metro.ready) {
77
+ console.log(chalk.yellow(`Warning: Metro on port ${proj.metroPort} did not report ready; the build may start its own.`));
78
+ }
75
79
  }
76
80
  } else {
77
81
  const metroAlreadyUp = await isMetroRunning(proj.metroPort);
@@ -56,10 +56,11 @@ export default function iosCommand(program) {
56
56
  }
57
57
  // With --managed-metro, rn-iso owns Metro: detached so it survives the
58
58
  // invoking shell (agents run builds from finite shells), output to the
59
- // per-project log file, and the build CLI gets --no-packager /
60
- // --no-bundler so it cannot start a second Metro on the same port.
61
- // Without the flag, the build CLI owns Metro as usual (interactive
62
- // bundler UX for humans).
59
+ // per-project log file. The build CLI is then kept from starting its own:
60
+ // bare RN gets --no-packager; expo gets --port and reuses the Metro
61
+ // already listening on that port. ensureMetro waits for /status so the
62
+ // port is bound before the build's reuse check runs. Without the flag,
63
+ // the build CLI owns Metro as usual (interactive bundler UX for humans).
63
64
  if (opts.managedMetro) {
64
65
  const metro = await ensureMetro({ projectPath: root, isExpo, port: proj.metroPort });
65
66
  if (metro.alreadyRunning) {
@@ -68,6 +69,9 @@ export default function iosCommand(program) {
68
69
  setMetro(root, proj.metroPort, metro.pid);
69
70
  console.log(chalk.dim(`Metro started detached (pid ${metro.pid}, port ${proj.metroPort})`));
70
71
  console.log(chalk.dim(`Metro log: ${logFileFor(root)}`));
72
+ if (!metro.ready) {
73
+ console.log(chalk.yellow(`Warning: Metro on port ${proj.metroPort} did not report ready; the build may start its own.`));
74
+ }
71
75
  }
72
76
  } else {
73
77
  const metroAlreadyUp = await isMetroRunning(proj.metroPort);
package/src/metro.js CHANGED
@@ -25,8 +25,8 @@ export function buildMetroSpawnArgs({ isExpo, port, extras = [] }) {
25
25
  };
26
26
  }
27
27
 
28
- export async function ensureMetro({ projectPath, isExpo, port, extras = [], detach = true }) {
29
- if (await isMetroRunning(port)) return { alreadyRunning: true, pid: null };
28
+ export async function ensureMetro({ projectPath, isExpo, port, extras = [], detach = true, readyTimeoutMs = 30000 }) {
29
+ if (await isMetroRunning(port)) return { alreadyRunning: true, pid: null, ready: true };
30
30
 
31
31
  const log = logFileFor(projectPath);
32
32
  const fd = openSync(log, 'a');
@@ -40,7 +40,22 @@ export async function ensureMetro({ projectPath, isExpo, port, extras = [], deta
40
40
  env: { ...process.env, RCT_METRO_PORT: String(port) },
41
41
  });
42
42
  if (detach) child.unref();
43
- return { alreadyRunning: false, pid: child.pid };
43
+
44
+ // Wait until the dev server answers /status. The build CLI's "a Metro is
45
+ // already on this port, reuse it" detection only fires once the port is
46
+ // bound, so returning before that races the build into spawning a second
47
+ // Metro. readyTimeoutMs <= 0 skips the wait.
48
+ const ready = readyTimeoutMs > 0 ? await waitForMetroReady(port, readyTimeoutMs) : false;
49
+ return { alreadyRunning: false, pid: child.pid, ready };
50
+ }
51
+
52
+ export async function waitForMetroReady(port, timeoutMs = 30000, intervalMs = 500) {
53
+ const deadline = Date.now() + timeoutMs;
54
+ while (Date.now() < deadline) {
55
+ if (await isMetroRunning(port)) return true;
56
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
57
+ }
58
+ return false;
44
59
  }
45
60
 
46
61
  export function killMetroByPid(pid) {
package/src/runner.js CHANGED
@@ -76,11 +76,17 @@ export function detectScriptCli(scriptBody) {
76
76
  return 'unknown';
77
77
  }
78
78
 
79
- // Flag that stops the build CLI from spawning its own packager. rn-iso owns
80
- // Metro (detached, log file, pid tracked), so the build must not start a
81
- // second one on the same port.
82
- function skipPackagerFlag(cli) {
83
- return cli === 'expo' ? '--no-bundler' : '--no-packager';
79
+ // Flags that stop the build CLI from spawning its own packager when rn-iso owns
80
+ // Metro (detached, log file, pid tracked). Bare RN takes --no-packager. Expo
81
+ // gets NO flag: `expo run` rejects --port together with --no-bundler, and
82
+ // --no-bundler also pins the dev server to 8081 with no override. Instead we
83
+ // pass --port <managed port> and rely on `expo run` detecting the Metro already
84
+ // listening on that port and reusing it (reuseExistingPort) rather than
85
+ // spawning its own. Callers must have the managed Metro listening before the
86
+ // build runs (ensureMetro waits for /status).
87
+ function noPackagerFlags(cli, noPackager) {
88
+ if (!noPackager) return [];
89
+ return cli === 'expo' ? [] : ['--no-packager'];
84
90
  }
85
91
 
86
92
  // iOS run command. Prefers the project's `ios` script if present (the most
@@ -99,15 +105,14 @@ export function buildIosCommand({ projectRoot, packageManager, scriptName, isExp
99
105
  return buildScriptCommand(packageManager, scriptName, [
100
106
  deviceFlag,
101
107
  `--port ${port}`,
102
- ...(noPackager ? [skipPackagerFlag(cli)] : []),
108
+ ...noPackagerFlags(cli, noPackager),
103
109
  ...tail,
104
110
  ]);
105
111
  }
106
112
  }
107
113
  const tailStr = tail.length ? ' ' + tail.join(' ') : '';
108
114
  if (isExpo) {
109
- const skip = noPackager ? ' --no-bundler' : '';
110
- return `npx expo run:ios --device ${udid} --port ${port}${skip}${tailStr}`;
115
+ return `npx expo run:ios --device ${udid} --port ${port}${tailStr}`;
111
116
  }
112
117
  const skip = noPackager ? ' --no-packager' : '';
113
118
  return `npx react-native run-ios --udid ${udid} --port ${port}${skip}${tailStr}`;
@@ -128,15 +133,14 @@ export function buildAndroidCommand({ projectRoot, packageManager, scriptName, i
128
133
  return buildScriptCommand(packageManager, scriptName, [
129
134
  deviceFlag,
130
135
  `--port ${port}`,
131
- ...(noPackager ? [skipPackagerFlag(cli)] : []),
136
+ ...noPackagerFlags(cli, noPackager),
132
137
  ...tail,
133
138
  ]);
134
139
  }
135
140
  }
136
141
  const tailStr = tail.length ? ' ' + tail.join(' ') : '';
137
142
  if (isExpo) {
138
- const skip = noPackager ? ' --no-bundler' : '';
139
- return `npx expo run:android --device "${expoDeviceArg}" --port ${port}${skip}${tailStr}`;
143
+ return `npx expo run:android --device "${expoDeviceArg}" --port ${port}${tailStr}`;
140
144
  }
141
145
  const skip = noPackager ? ' --no-packager' : '';
142
146
  return `RCT_METRO_PORT=${port} npx react-native run-android --device ${serial}${skip}${tailStr}`;