rn-iso 0.3.0 → 0.3.3
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 +1 -1
- package/src/commands/android.js +32 -16
- package/src/commands/ios.js +6 -5
- package/src/commands/release.js +3 -3
- package/src/config.js +12 -3
- package/src/runner.js +9 -7
- package/src/sim/android.js +23 -15
- package/src/sim/ios.js +11 -1
package/package.json
CHANGED
package/src/commands/android.js
CHANGED
|
@@ -7,6 +7,7 @@ import { allocatePort, isMetroRunning } from '../ports.js';
|
|
|
7
7
|
import {
|
|
8
8
|
selectAndroidDevice,
|
|
9
9
|
sortAndroidCandidates,
|
|
10
|
+
enumerateAndroidCandidates,
|
|
10
11
|
bootAndroidEmulator,
|
|
11
12
|
waitForBoot,
|
|
12
13
|
adbReverse,
|
|
@@ -77,16 +78,16 @@ export default function androidCommand(program) {
|
|
|
77
78
|
if (selection.kind === 'reuse') {
|
|
78
79
|
({ avdName, consolePort, isRunning } = selection);
|
|
79
80
|
if (isRunning) {
|
|
80
|
-
console.log(chalk.dim(`Reusing running
|
|
81
|
+
console.log(chalk.dim(`Reusing running ${avdName} (emulator-${consolePort})`));
|
|
81
82
|
} else {
|
|
82
|
-
console.log(chalk.dim(`Booting assigned
|
|
83
|
+
console.log(chalk.dim(`Booting assigned ${avdName} (emulator-${consolePort})...`));
|
|
83
84
|
}
|
|
84
85
|
} else if (selection.kind === 'allocate') {
|
|
85
86
|
const picked = (selection.candidates.length === 1 || auto)
|
|
86
87
|
? { c: selection.candidates[0], prevClaim: null }
|
|
87
88
|
: await pickAvd({
|
|
88
89
|
candidates: selection.candidates,
|
|
89
|
-
|
|
90
|
+
androidClaimsByAvd: claimed.androidClaimsByAvd,
|
|
90
91
|
});
|
|
91
92
|
await releasePriorClaim(picked.prevClaim);
|
|
92
93
|
({ avdName, isRunning, consolePort } = picked.c);
|
|
@@ -94,8 +95,8 @@ export default function androidCommand(program) {
|
|
|
94
95
|
consolePort = nextConsolePort(claimedPorts);
|
|
95
96
|
}
|
|
96
97
|
console.log(isRunning
|
|
97
|
-
? chalk.green(`Picked ${avdName} (
|
|
98
|
-
: chalk.dim(`Booting ${avdName}
|
|
98
|
+
? chalk.green(`Picked ${avdName} (emulator-${consolePort}, running)`)
|
|
99
|
+
: chalk.dim(`Booting ${avdName} (emulator-${consolePort})...`));
|
|
99
100
|
} else if (selection.kind === 'allClaimed') {
|
|
100
101
|
if (auto) {
|
|
101
102
|
console.error(chalk.red('All Android AVDs are claimed by other rn-iso projects.'));
|
|
@@ -104,7 +105,7 @@ export default function androidCommand(program) {
|
|
|
104
105
|
}
|
|
105
106
|
const picked = await pickAvd({
|
|
106
107
|
candidates: selection.candidates,
|
|
107
|
-
|
|
108
|
+
androidClaimsByAvd: claimed.androidClaimsByAvd,
|
|
108
109
|
allClaimed: true,
|
|
109
110
|
});
|
|
110
111
|
await releasePriorClaim(picked.prevClaim);
|
|
@@ -115,8 +116,8 @@ export default function androidCommand(program) {
|
|
|
115
116
|
consolePort = nextConsolePort(fresh);
|
|
116
117
|
}
|
|
117
118
|
console.log(isRunning
|
|
118
|
-
? chalk.green(`Took over ${avdName} (
|
|
119
|
-
: chalk.dim(`Booting ${avdName}
|
|
119
|
+
? chalk.green(`Took over ${avdName} (emulator-${consolePort}, running)`)
|
|
120
|
+
: chalk.dim(`Booting ${avdName} (emulator-${consolePort})...`));
|
|
120
121
|
} else {
|
|
121
122
|
console.error(chalk.red(
|
|
122
123
|
'No AVDs available. Create one via Android Studio (Tools -> Device Manager).'
|
|
@@ -149,6 +150,7 @@ export default function androidCommand(program) {
|
|
|
149
150
|
packageManager,
|
|
150
151
|
scriptName,
|
|
151
152
|
isExpo,
|
|
153
|
+
avdName,
|
|
152
154
|
serial,
|
|
153
155
|
port: proj.metroPort,
|
|
154
156
|
useScript,
|
|
@@ -161,20 +163,34 @@ export default function androidCommand(program) {
|
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
console.log(chalk.green(`\nAndroid ready on ${serial}, Metro port ${proj.metroPort}`));
|
|
166
|
+
console.log(chalk.green(`\nAndroid ready on ${avdName} (${serial}), Metro port ${proj.metroPort}`));
|
|
165
167
|
});
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
async function pickAvd({ candidates,
|
|
169
|
-
|
|
170
|
+
async function pickAvd({ candidates, androidClaimsByAvd = {}, allClaimed = false }) {
|
|
171
|
+
// Show every AVD on disk (parallel to the iOS picker), so the user can
|
|
172
|
+
// see what's claimed and optionally take it over. `candidates` is the
|
|
173
|
+
// unclaimed set passed in by selectAndroidDevice; AVDs outside it are
|
|
174
|
+
// claimed and will require a confirm prompt on selection.
|
|
175
|
+
const allAvds = enumerateAndroidCandidates();
|
|
176
|
+
const candidateAvds = new Set(candidates.map(c => c.avdName));
|
|
177
|
+
const sorted = sortAndroidCandidates(allAvds);
|
|
178
|
+
|
|
170
179
|
const nameWidth = Math.max(...sorted.map(c => c.avdName.length), 18);
|
|
171
180
|
const choices = sorted.map(c => {
|
|
172
|
-
const claim =
|
|
173
|
-
const
|
|
174
|
-
const runTag = c.isRunning ? chalk.green(`
|
|
181
|
+
const claim = androidClaimsByAvd[c.avdName];
|
|
182
|
+
const isCandidate = candidateAvds.has(c.avdName);
|
|
183
|
+
const runTag = c.isRunning ? chalk.green(` [emulator-${c.consolePort}, running]`) : '';
|
|
184
|
+
if (claim || !isCandidate) {
|
|
185
|
+
const tag = claim ? chalk.yellow(` [claimed by ${claim.label}]`) : '';
|
|
186
|
+
return {
|
|
187
|
+
title: chalk.yellow(`${c.avdName.padEnd(nameWidth)}${tag}${runTag}`),
|
|
188
|
+
value: { c, claim: claim || null },
|
|
189
|
+
};
|
|
190
|
+
}
|
|
175
191
|
return {
|
|
176
|
-
title: `${c.avdName.padEnd(nameWidth)}${runTag}
|
|
177
|
-
value: { c, claim:
|
|
192
|
+
title: `${c.avdName.padEnd(nameWidth)}${runTag}`,
|
|
193
|
+
value: { c, claim: null },
|
|
178
194
|
};
|
|
179
195
|
});
|
|
180
196
|
const message = allClaimed
|
package/src/commands/ios.js
CHANGED
|
@@ -4,7 +4,7 @@ import prompts from 'prompts';
|
|
|
4
4
|
import { findProjectRoot, detectIsExpo, detectBundleId, detectAndroidPackage } from '../project.js';
|
|
5
5
|
import { getProject, upsertProject, setMetro, setDevice, clearDevice, allClaimedDevices, recordSimUsage, getSimUsage } from '../config.js';
|
|
6
6
|
import { allocatePort, isMetroRunning } from '../ports.js';
|
|
7
|
-
import { selectIosDevice, bootIosSim, listIosRuntimes, createIosSim, parseRuntimeVersion, listAllIosSims, sortSims } from '../sim/ios.js';
|
|
7
|
+
import { selectIosDevice, bootIosSim, listIosRuntimes, createIosSim, parseRuntimeVersion, listAllIosSims, sortSims, formatIosLabel } from '../sim/ios.js';
|
|
8
8
|
import { buildIosCommand, detectPackageManager } from '../runner.js';
|
|
9
9
|
import { getExecutor } from '../exec.js';
|
|
10
10
|
import { resolveLabel } from '../labels.js';
|
|
@@ -70,11 +70,12 @@ export default function iosCommand(program) {
|
|
|
70
70
|
let udid;
|
|
71
71
|
if (selection.kind === 'reuse') {
|
|
72
72
|
udid = selection.udid;
|
|
73
|
+
const label = `${selection.name} (${udid})`;
|
|
73
74
|
if (selection.state !== 'Booted') {
|
|
74
|
-
console.log(chalk.dim(`Booting assigned sim ${
|
|
75
|
+
console.log(chalk.dim(`Booting assigned sim ${label}...`));
|
|
75
76
|
bootIosSim(udid);
|
|
76
77
|
} else {
|
|
77
|
-
console.log(chalk.dim(`Reusing assigned sim ${
|
|
78
|
+
console.log(chalk.dim(`Reusing assigned sim ${label} (already booted)`));
|
|
78
79
|
}
|
|
79
80
|
} else if (selection.kind === 'allocate') {
|
|
80
81
|
const picked = (selection.candidates.length === 1 || auto)
|
|
@@ -99,7 +100,7 @@ export default function iosCommand(program) {
|
|
|
99
100
|
if (auto) {
|
|
100
101
|
if (opts.deviceType) {
|
|
101
102
|
udid = createNewSim({ deviceType: opts.deviceType, runtimeVersion: opts.runtime });
|
|
102
|
-
console.log(chalk.green(`Created and booted new sim ${udid}`));
|
|
103
|
+
console.log(chalk.green(`Created and booted new sim ${formatIosLabel(udid)}`));
|
|
103
104
|
} else {
|
|
104
105
|
console.error(chalk.red('All iOS simulators are claimed by other rn-iso projects.'));
|
|
105
106
|
console.error(chalk.dim('Re-run without --auto to confirm taking one over, or pass --device-type to create a new sim.'));
|
|
@@ -164,7 +165,7 @@ export default function iosCommand(program) {
|
|
|
164
165
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
console.log(chalk.green(`\nOK: iOS ready on sim ${udid}, Metro port ${proj.metroPort}`));
|
|
168
|
+
console.log(chalk.green(`\nOK: iOS ready on sim ${formatIosLabel(udid)}, Metro port ${proj.metroPort}`));
|
|
168
169
|
});
|
|
169
170
|
}
|
|
170
171
|
|
package/src/commands/release.js
CHANGED
|
@@ -4,7 +4,7 @@ import prompts from 'prompts';
|
|
|
4
4
|
import { resolveRegisteredProject } from '../project.js';
|
|
5
5
|
import { getProject, clearDevice, findProjectByMetroPort } from '../config.js';
|
|
6
6
|
import { findPidListeningOnPort } from '../metro.js';
|
|
7
|
-
import { shutdownIosSim } from '../sim/ios.js';
|
|
7
|
+
import { shutdownIosSim, formatIosLabel } from '../sim/ios.js';
|
|
8
8
|
import { shutdownAndroidEmulator } from '../sim/android.js';
|
|
9
9
|
|
|
10
10
|
export default function releaseCommand(program) {
|
|
@@ -45,10 +45,10 @@ export default function releaseCommand(program) {
|
|
|
45
45
|
if (opts.shutdown) {
|
|
46
46
|
if (p === 'ios') {
|
|
47
47
|
shutdownIosSim(entry.deviceUdid);
|
|
48
|
-
console.log(chalk.green(`Shut down iOS sim ${entry.deviceUdid}`));
|
|
48
|
+
console.log(chalk.green(`Shut down iOS sim ${formatIosLabel(entry.deviceUdid)}`));
|
|
49
49
|
} else {
|
|
50
50
|
shutdownAndroidEmulator(`emulator-${entry.consolePort}`);
|
|
51
|
-
console.log(chalk.green(`Shut down emulator-${entry.consolePort}`));
|
|
51
|
+
console.log(chalk.green(`Shut down ${entry.avdName} (emulator-${entry.consolePort})`));
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
clearDevice(found, p);
|
package/src/config.js
CHANGED
|
@@ -111,10 +111,12 @@ export function allClaimedDevices() {
|
|
|
111
111
|
androidAvds: [],
|
|
112
112
|
androidConsolePorts: [],
|
|
113
113
|
// iosClaims: udid -> { label, path }. androidClaims: consolePort ->
|
|
114
|
-
// { label, path, avdName }.
|
|
115
|
-
//
|
|
114
|
+
// { label, path, avdName }. androidClaimsByAvd: avdName -> { label,
|
|
115
|
+
// path, consolePort }. `path` is the absolute project path so take-
|
|
116
|
+
// over flows can call clearDevice on the owning project.
|
|
116
117
|
iosClaims: {},
|
|
117
118
|
androidClaims: {},
|
|
119
|
+
androidClaimsByAvd: {},
|
|
118
120
|
};
|
|
119
121
|
if (!cfg) return result;
|
|
120
122
|
for (const [path, proj] of Object.entries(cfg.projects || {})) {
|
|
@@ -125,7 +127,14 @@ export function allClaimedDevices() {
|
|
|
125
127
|
result.iosClaims[ios.deviceUdid] = { label, path };
|
|
126
128
|
}
|
|
127
129
|
const android = proj.platforms?.android;
|
|
128
|
-
if (android?.avdName)
|
|
130
|
+
if (android?.avdName) {
|
|
131
|
+
result.androidAvds.push(android.avdName);
|
|
132
|
+
result.androidClaimsByAvd[android.avdName] = {
|
|
133
|
+
label,
|
|
134
|
+
path,
|
|
135
|
+
consolePort: android.consolePort,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
129
138
|
if (typeof android?.consolePort === 'number') {
|
|
130
139
|
result.androidConsolePorts.push(android.consolePort);
|
|
131
140
|
result.androidClaims[android.consolePort] = {
|
package/src/runner.js
CHANGED
|
@@ -64,9 +64,11 @@ export function buildScriptCommand(packageManager, scriptName, extraArgs = []) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// Decide which CLI a script invokes. Affects flag names:
|
|
68
|
-
//
|
|
69
|
-
//
|
|
67
|
+
// Decide which CLI a script invokes. Affects flag names:
|
|
68
|
+
// iOS: Expo `--device <UDID>` | RN `--udid <UDID>`
|
|
69
|
+
// Android: Expo `--device <AVD-name>` | RN `--deviceId <serial>`
|
|
70
|
+
// Expo's run:android resolves --device by name (not by serial), so we pass
|
|
71
|
+
// the AVD name there even though we boot/track by serial.
|
|
70
72
|
export function detectScriptCli(scriptBody) {
|
|
71
73
|
if (typeof scriptBody !== 'string') return 'unknown';
|
|
72
74
|
if (/\bexpo\s+(run:ios|run:android|start)\b/.test(scriptBody)) return 'expo';
|
|
@@ -96,13 +98,13 @@ export function buildIosCommand({ projectRoot, packageManager, scriptName, isExp
|
|
|
96
98
|
return `npx react-native run-ios --udid ${udid} --port ${port}`;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
export function buildAndroidCommand({ projectRoot, packageManager, scriptName, isExpo, serial, port, useScript = true }) {
|
|
101
|
+
export function buildAndroidCommand({ projectRoot, packageManager, scriptName, isExpo, avdName, serial, port, useScript = true }) {
|
|
100
102
|
if (useScript && scriptName) {
|
|
101
103
|
const script = getProjectScript(projectRoot, scriptName);
|
|
102
104
|
if (script) {
|
|
103
105
|
const cli = detectScriptCli(script);
|
|
104
|
-
// Expo: --device <
|
|
105
|
-
const deviceFlag = cli === 'expo' ? `--device ${
|
|
106
|
+
// Expo: --device <AVD name>; RN: --deviceId <serial>.
|
|
107
|
+
const deviceFlag = cli === 'expo' ? `--device "${avdName}"` : `--deviceId ${serial}`;
|
|
106
108
|
return buildScriptCommand(packageManager, scriptName, [
|
|
107
109
|
deviceFlag,
|
|
108
110
|
`--port ${port}`,
|
|
@@ -110,7 +112,7 @@ export function buildAndroidCommand({ projectRoot, packageManager, scriptName, i
|
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
if (isExpo) {
|
|
113
|
-
return `npx expo run:android --device ${
|
|
115
|
+
return `npx expo run:android --device "${avdName}" --port ${port}`;
|
|
114
116
|
}
|
|
115
117
|
return `RCT_METRO_PORT=${port} npx react-native run-android --deviceId ${serial}`;
|
|
116
118
|
}
|
package/src/sim/android.js
CHANGED
|
@@ -35,37 +35,45 @@ export function nextConsolePort(claimedPorts) {
|
|
|
35
35
|
return max + 2; // emulator console ports are even
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// Build the full candidate list: every AVD on disk, paired with whether it
|
|
39
|
+
// currently has a running emulator and (if so) on which console port.
|
|
40
|
+
// Emulators that fail to respond to `adb emu avd name` are dropped from
|
|
41
|
+
// the running map. Returns [] when no AVDs are installed.
|
|
42
|
+
export function enumerateAndroidCandidates() {
|
|
39
43
|
const avds = listAvds();
|
|
40
|
-
if (avds.length === 0) return
|
|
44
|
+
if (avds.length === 0) return [];
|
|
41
45
|
|
|
42
46
|
const adbDevices = listAdbDevices();
|
|
43
|
-
// Resolve which AVD each running emulator is. Emulators that don't respond
|
|
44
|
-
// to `adb emu avd name` are dropped from the running map.
|
|
45
47
|
const runningByAvd = {};
|
|
46
48
|
for (const e of adbDevices.emulators) {
|
|
47
49
|
const avdName = getAvdNameForSerial(e.serial);
|
|
48
50
|
if (avdName) runningByAvd[avdName] = e.consolePort;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
return avds.map(avdName => ({
|
|
52
54
|
avdName,
|
|
53
55
|
isRunning: avdName in runningByAvd,
|
|
54
56
|
consolePort: runningByAvd[avdName] ?? null,
|
|
55
|
-
});
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
export function selectAndroidDevice({ existingAvd, existingConsolePort, claimedAvds, claimedConsolePorts }) {
|
|
61
|
+
const all = enumerateAndroidCandidates();
|
|
62
|
+
if (all.length === 0) return { kind: 'noAvd' };
|
|
63
|
+
|
|
64
|
+
if (existingAvd) {
|
|
65
|
+
const found = all.find(c => c.avdName === existingAvd);
|
|
66
|
+
if (found) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'reuse',
|
|
69
|
+
avdName: existingAvd,
|
|
70
|
+
consolePort: found.consolePort ?? existingConsolePort ?? nextConsolePort(claimedConsolePorts),
|
|
71
|
+
isRunning: found.isRunning,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
const claimedAvdSet = new Set(claimedAvds);
|
|
68
|
-
const all = avds.map(buildCandidate);
|
|
69
77
|
const unclaimed = all.filter(c => !claimedAvdSet.has(c.avdName));
|
|
70
78
|
|
|
71
79
|
if (unclaimed.length === 0) {
|
package/src/sim/ios.js
CHANGED
|
@@ -31,6 +31,16 @@ export function listBootedIosSims() {
|
|
|
31
31
|
return listAllIosSims().filter(s => s.state === 'Booted');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// "iPhone 16 Pro (ABC-123-...)" if simctl knows about the UDID; the bare
|
|
35
|
+
// UDID otherwise (deleted sim, or simctl unavailable).
|
|
36
|
+
export function formatIosLabel(udid) {
|
|
37
|
+
try {
|
|
38
|
+
const sim = listAllIosSims().find(s => s.udid === udid);
|
|
39
|
+
if (sim) return `${sim.name} (${udid})`;
|
|
40
|
+
} catch { /* simctl not available */ }
|
|
41
|
+
return udid;
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
export function deviceFamilyRank(name) {
|
|
35
45
|
if (/^iPhone/i.test(name)) return 0;
|
|
36
46
|
if (/^iPad/i.test(name)) return 1;
|
|
@@ -62,7 +72,7 @@ export function selectIosDevice({ existingUdid, claimedUdids, usage = {} }) {
|
|
|
62
72
|
if (existingUdid) {
|
|
63
73
|
const found = sims.find(s => s.udid === existingUdid);
|
|
64
74
|
if (found) {
|
|
65
|
-
return { kind: 'reuse', udid: found.udid, state: found.state };
|
|
75
|
+
return { kind: 'reuse', udid: found.udid, name: found.name, state: found.state };
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
|