rn-iso 0.5.0 → 0.6.0
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/README.md +1 -0
- package/bin/cli.js +2 -0
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
- package/src/commands/prune.js +44 -0
- package/src/config.js +20 -0
- package/src/ports.js +5 -0
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ All commands below take the same `npx rn-iso` prefix.
|
|
|
51
51
|
| `start` | Start Metro detached, no platform action |
|
|
52
52
|
| `stop [<port>\|<shortcut>\|<path>]` | Kill Metro. No arg = current project; pass a port (e.g. 8083), a project shortcut (label or unique basename), or an absolute path. |
|
|
53
53
|
| `logs [<port>\|<shortcut>\|<path>] [-n <lines>] [--follow]` | Print the managed Metro log (bundle progress, resolution errors, client logs). |
|
|
54
|
+
| `prune` | Remove entries for deleted project directories, freeing their devices and ports. |
|
|
54
55
|
| `device [--platform ios\|android] [--json]` | Print the assigned device target |
|
|
55
56
|
| `status` | Show all projects' state |
|
|
56
57
|
| `reserve [ios\|android]` | Lock a manually-started sim/emulator to the current project (no build) |
|
package/bin/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import androidCommand from '../src/commands/android.js';
|
|
|
7
7
|
import startCommand from '../src/commands/start.js';
|
|
8
8
|
import stopCommand from '../src/commands/stop.js';
|
|
9
9
|
import logsCommand from '../src/commands/logs.js';
|
|
10
|
+
import pruneCommand from '../src/commands/prune.js';
|
|
10
11
|
import statusCommand from '../src/commands/status.js';
|
|
11
12
|
import releaseCommand from '../src/commands/release.js';
|
|
12
13
|
import reserveCommand from '../src/commands/reserve.js';
|
|
@@ -28,6 +29,7 @@ androidCommand(program);
|
|
|
28
29
|
startCommand(program);
|
|
29
30
|
stopCommand(program);
|
|
30
31
|
logsCommand(program);
|
|
32
|
+
pruneCommand(program);
|
|
31
33
|
statusCommand(program);
|
|
32
34
|
releaseCommand(program);
|
|
33
35
|
reserveCommand(program);
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -77,7 +77,7 @@ Reserve binds the sim to the current project the same way `ios` does, but skips
|
|
|
77
77
|
## When things go wrong
|
|
78
78
|
|
|
79
79
|
- **"No rn-iso assignment for project"** — run `npx rn-iso ios` (or android) first.
|
|
80
|
-
- **"All iOS simulators are claimed by other rn-iso projects"** (under `--auto`) — every existing sim is held by another project. Options: free another project (`npx rn-iso release` from there), pass `--device-type "iPhone 17 Pro"` to create a new sim, or re-run without `--auto` (in a real TTY) and ask the user before confirming the take-over prompt.
|
|
80
|
+
- **"All iOS simulators are claimed by other rn-iso projects"** (under `--auto`) — every existing sim is held by another project. Claims from deleted worktrees don't count (they're auto-reclaimed), so these are all live projects. Options: `npx rn-iso prune` if you suspect stale state, free another project (`npx rn-iso release` from there), pass `--device-type "iPhone 17 Pro"` to create a new sim, or re-run without `--auto` (in a real TTY) and ask the user before confirming the take-over prompt.
|
|
81
81
|
- **"All Android AVDs are claimed by other rn-iso projects"** — same situation on Android. Free another project or re-run interactively to take one over.
|
|
82
82
|
- **Wrong sim got the app** — older `@expo/cli` (< 54.0.24) had a bug where the launch ignored `--device`. Bump expo to 54.0.34+ if on SDK 54.
|
|
83
83
|
- **Blank screen / app installed but nothing renders** — check `npx rn-iso status`. Metro `stopped` almost always means the build ran WITHOUT `--managed-metro`, so Metro died with the shell that ran it: recover with `npx rn-iso start`, then relaunch the app (`xcrun simctl launch <UDID> <bundleId>`), and pass the flag next time. If Metro IS running, read `npx rn-iso logs -n 50` for bundle/resolution errors (a stale `node_modules` after a branch switch is a classic — reinstall deps, then `npx rn-iso stop` + `start`).
|
|
@@ -89,6 +89,7 @@ Reserve binds the sim to the current project the same way `ios` does, but skips
|
|
|
89
89
|
|
|
90
90
|
- `npx rn-iso status` — show all projects, their assignments, and Metro state.
|
|
91
91
|
- `npx rn-iso logs [<port>|<shortcut>|<path>] [-n <lines>] [--follow]` — print the managed Metro log (default: last 50 lines of the current project's). This is where bundle progress, module-resolution errors, and client console logs land. **Check this first on a blank screen or red box** — it's faster than screenshots.
|
|
92
|
+
- `npx rn-iso prune` — remove entries for projects whose directory no longer exists (deleted worktrees), freeing their sims/emulators and ports, and killing any orphaned Metro. Live projects are never touched. Claims from deleted worktrees are also ignored automatically during device selection, so prune is housekeeping, not a prerequisite.
|
|
92
93
|
- `npx rn-iso start [--reset-cache] [-- <extras...>]` — start Metro detached on the project's assigned port WITHOUT building/installing. `--reset-cache` clears Metro's transform cache. Other extras after `--` are forwarded to `expo start` / `react-native start`.
|
|
93
94
|
- `npx rn-iso stop [<port>|<shortcut>|<path>]` — kill Metro. No arg = current project. Passing a port (e.g. `8083`) kills whatever is on it; a project shortcut (label or unique basename) or absolute path targets that project. Finds the process by port, so it works whether Metro was started by `npx rn-iso start` or by the build CLI.
|
|
94
95
|
- `npx rn-iso release [<port>|<shortcut>|<path>] [--platform <p>] [--shutdown]` — free a project's sim assignment. Defaults to the current project. Target can also be a Metro port (`8083`) or a shortcut (label / unique basename). `--shutdown` also stops the sim/emulator.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/commands/prune.js
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { pruneDeadProjects } from '../config.js';
|
|
4
|
+
import { findPidListeningOnPort } from '../metro.js';
|
|
5
|
+
|
|
6
|
+
export default function pruneCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('prune')
|
|
9
|
+
.description('Remove entries for projects whose directory no longer exists (deleted worktrees), freeing their sims/emulators and Metro ports. Live projects are never touched.')
|
|
10
|
+
.action(() => {
|
|
11
|
+
const removed = pruneDeadProjects();
|
|
12
|
+
if (removed.length === 0) {
|
|
13
|
+
console.log(chalk.dim('Nothing to prune: every registered project path still exists.'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const { path, project } of removed) {
|
|
18
|
+
const freed = [];
|
|
19
|
+
const ios = project.platforms?.ios;
|
|
20
|
+
if (ios?.deviceUdid) freed.push(`ios sim ${ios.deviceUdid}`);
|
|
21
|
+
const android = project.platforms?.android;
|
|
22
|
+
if (android?.avdName) freed.push(`android avd ${android.avdName}`);
|
|
23
|
+
else if (android?.serial) freed.push(`android device ${android.serial}`);
|
|
24
|
+
|
|
25
|
+
console.log(chalk.green(`Pruned ${path}`));
|
|
26
|
+
if (freed.length) console.log(chalk.dim(` freed: ${freed.join(', ')}`));
|
|
27
|
+
|
|
28
|
+
// A Metro started from the deleted directory can outlive it and squat
|
|
29
|
+
// on the port; kill it so the port is genuinely free.
|
|
30
|
+
if (typeof project.metroPort === 'number') {
|
|
31
|
+
const pid = findPidListeningOnPort(project.metroPort);
|
|
32
|
+
if (pid) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 'SIGTERM');
|
|
35
|
+
console.log(chalk.dim(` killed orphaned Metro pid ${pid} on port ${project.metroPort}`));
|
|
36
|
+
} catch {
|
|
37
|
+
console.log(chalk.dim(` could not kill pid ${pid} on port ${project.metroPort}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log(chalk.dim(`\n${removed.length} project entr${removed.length === 1 ? 'y' : 'ies'} removed.`));
|
|
43
|
+
});
|
|
44
|
+
}
|
package/src/config.js
CHANGED
|
@@ -187,6 +187,10 @@ export function allClaimedDevices() {
|
|
|
187
187
|
};
|
|
188
188
|
if (!cfg) return result;
|
|
189
189
|
for (const [path, proj] of Object.entries(cfg.projects || {})) {
|
|
190
|
+
// Claims from project paths that no longer exist on disk are orphaned --
|
|
191
|
+
// nothing can ever run from a deleted worktree again -- so pickers treat
|
|
192
|
+
// those devices as free. `prune` removes the dead entries themselves.
|
|
193
|
+
if (!existsSync(path)) continue;
|
|
190
194
|
const label = path.split('/').pop() || path;
|
|
191
195
|
const ios = proj.platforms?.ios;
|
|
192
196
|
if (ios?.deviceUdid) {
|
|
@@ -218,6 +222,22 @@ export function allClaimedDevices() {
|
|
|
218
222
|
return result;
|
|
219
223
|
}
|
|
220
224
|
|
|
225
|
+
// Remove project entries whose path no longer exists on disk (deleted
|
|
226
|
+
// worktrees). Returns the removed entries so callers can report what was
|
|
227
|
+
// freed and clean up any process still bound to their Metro ports.
|
|
228
|
+
export function pruneDeadProjects() {
|
|
229
|
+
const cfg = loadConfig();
|
|
230
|
+
if (!cfg?.projects) return [];
|
|
231
|
+
const removed = [];
|
|
232
|
+
for (const [path, proj] of Object.entries(cfg.projects)) {
|
|
233
|
+
if (existsSync(path)) continue;
|
|
234
|
+
removed.push({ path, project: proj });
|
|
235
|
+
delete cfg.projects[path];
|
|
236
|
+
}
|
|
237
|
+
if (removed.length) saveConfig(cfg);
|
|
238
|
+
return removed;
|
|
239
|
+
}
|
|
240
|
+
|
|
221
241
|
export function recordSimUsage(platform, identifier) {
|
|
222
242
|
if (platform !== 'ios' && platform !== 'android') return;
|
|
223
243
|
const cfg = ensureConfig();
|
package/src/ports.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { request } from 'http';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
2
3
|
import { loadConfig, allMetroPorts, removeProject } from './config.js';
|
|
3
4
|
|
|
4
5
|
export function isMetroRunning(port) {
|
|
@@ -29,6 +30,10 @@ export async function findReclaimablePort(excludeProjectPath, probe = isMetroRun
|
|
|
29
30
|
const candidates = [];
|
|
30
31
|
for (const [path, proj] of Object.entries(cfg.projects)) {
|
|
31
32
|
if (path === excludeProjectPath) continue;
|
|
33
|
+
// Only projects whose path no longer exists are reclaimable: reclaiming
|
|
34
|
+
// removes the whole entry, and doing that to a live project would also
|
|
35
|
+
// drop its device claim out from under it.
|
|
36
|
+
if (existsSync(path)) continue;
|
|
32
37
|
if (typeof proj.metroPort === 'number') {
|
|
33
38
|
candidates.push({ port: proj.metroPort, ownerPath: path });
|
|
34
39
|
}
|