@absolutejs/absolute 0.20.0-beta.98 → 0.20.0-beta.99
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/CHANGELOG.md +7 -0
- package/changelog.json +17 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/{dev-r8dztrp8.js → dev-r4vwdz6b.js} +22 -11
- package/dist/cli/{expoDevController-a0jk8d43.js → expoDevController-8wyrcy6a.js} +98 -37
- package/dist/cli/index.js +1 -1
- package/dist/mobile/index.js +99 -38
- package/dist/mobile/index.js.map +3 -3
- package/dist/mobile/remoteMacAgentEntry.js +214 -214
- package/dist/src/mobile/expoDevController.d.ts +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.20.0-beta.99 — 2026-09-15
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Expo development recovers from Metro startup port races** (`AbsoluteExpoDevPhaseTiming`, `AbsoluteExpoDevSession`, `StartAbsoluteExpoDevOptions`, `startAbsoluteExpoDevSession`)
|
|
14
|
+
AbsoluteJS retries a newly allocated loopback port without prompting, propagates the final port through local, WSL, and Remote Mac native targets, preserves it across rebuilds, and emits only a categorical reason plus bounded retry count.
|
|
15
|
+
|
|
9
16
|
## 0.20.0-beta.98 — 2026-09-15
|
|
10
17
|
|
|
11
18
|
### Added
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/absolute",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"detail": "AbsoluteJS retries a newly allocated loopback port without prompting, propagates the final port through local, WSL, and Remote Mac native targets, preserves it across rebuilds, and emits only a categorical reason plus bounded retry count.",
|
|
9
|
+
"kind": "fixed",
|
|
10
|
+
"summary": "Expo development recovers from Metro startup port races",
|
|
11
|
+
"symbols": [
|
|
12
|
+
"AbsoluteExpoDevPhaseTiming",
|
|
13
|
+
"AbsoluteExpoDevSession",
|
|
14
|
+
"StartAbsoluteExpoDevOptions",
|
|
15
|
+
"startAbsoluteExpoDevSession"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"date": "2026-09-15",
|
|
20
|
+
"version": "0.20.0-beta.99"
|
|
21
|
+
},
|
|
5
22
|
{
|
|
6
23
|
"changes": [
|
|
7
24
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
|
-
// .angular-partial-tmp-
|
|
4
|
+
// .angular-partial-tmp-lbMe6j/src/core/streamingSlotRegistrar.ts
|
|
5
5
|
var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
|
|
6
6
|
var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
|
|
7
7
|
var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
|
-
// .angular-partial-tmp-
|
|
4
|
+
// .angular-partial-tmp-lbMe6j/src/core/streamingSlotRegistrar.ts
|
|
5
5
|
var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
|
|
6
6
|
var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
|
|
7
7
|
var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
|
|
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
|
|
|
48
48
|
getWarningController()?.maybeWarn(primitiveName);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
// .angular-partial-tmp-
|
|
51
|
+
// .angular-partial-tmp-lbMe6j/src/core/streamingSlotRegistry.ts
|
|
52
52
|
var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
|
|
53
53
|
var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
|
|
54
54
|
var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
|
|
@@ -1287,7 +1287,7 @@ var loadAbsoluteMobileDevModules = async () => {
|
|
|
1287
1287
|
] = await Promise.all([
|
|
1288
1288
|
import("./config-vy04mqce.js"),
|
|
1289
1289
|
import("./expoProject-ceey4cfg.js"),
|
|
1290
|
-
import("./expoDevController-
|
|
1290
|
+
import("./expoDevController-8wyrcy6a.js"),
|
|
1291
1291
|
import("./nativeAuth-zx4tsb1f.js"),
|
|
1292
1292
|
import("./androidEmulatorController-nybhzc6c.js"),
|
|
1293
1293
|
import("./emulatorDoctor-y2nqmaha.js"),
|
|
@@ -2174,13 +2174,15 @@ var dev = async (serverEntry, configPath, options = {}) => {
|
|
|
2174
2174
|
platforms: [],
|
|
2175
2175
|
signal: expoDevAbort.signal,
|
|
2176
2176
|
log: (message) => printNativeOutput(cliTag("\x1B[35m", `Expo ${message}`)),
|
|
2177
|
-
onPhaseTiming: ({ durationMs, phase }) => {
|
|
2177
|
+
onPhaseTiming: ({ durationMs, phase, reason, retryCount }) => {
|
|
2178
2178
|
sendTelemetryEvent("mobile:expo-dev-phase", {
|
|
2179
2179
|
durationMs: Math.round(durationMs),
|
|
2180
2180
|
host: mobile.detectAbsoluteMobileHost(),
|
|
2181
2181
|
phase,
|
|
2182
2182
|
platform: expoTelemetryPlatform(phase),
|
|
2183
|
-
provider: "expo"
|
|
2183
|
+
provider: "expo",
|
|
2184
|
+
reason,
|
|
2185
|
+
retryCount
|
|
2184
2186
|
});
|
|
2185
2187
|
},
|
|
2186
2188
|
onStateChange: (state) => {
|
|
@@ -2193,7 +2195,7 @@ var dev = async (serverEntry, configPath, options = {}) => {
|
|
|
2193
2195
|
metroSession = session;
|
|
2194
2196
|
return session;
|
|
2195
2197
|
});
|
|
2196
|
-
const localNativeStart = metroStart.then(async () => {
|
|
2198
|
+
const localNativeStart = metroStart.then(async (metro) => {
|
|
2197
2199
|
if (project.platforms.length === 0)
|
|
2198
2200
|
return;
|
|
2199
2201
|
const session = await mobile.startAbsoluteExpoDevSession({
|
|
@@ -2205,17 +2207,19 @@ var dev = async (serverEntry, configPath, options = {}) => {
|
|
|
2205
2207
|
iosDevice: options.iosDevice,
|
|
2206
2208
|
iosOrigin,
|
|
2207
2209
|
metro: "external",
|
|
2208
|
-
metroPort:
|
|
2210
|
+
metroPort: metro.metroPort,
|
|
2209
2211
|
platforms: project.platforms,
|
|
2210
2212
|
signal: expoDevAbort.signal,
|
|
2211
2213
|
log: (message) => printNativeOutput(cliTag("\x1B[35m", `Expo ${message}`)),
|
|
2212
|
-
onPhaseTiming: ({ durationMs, phase }) => {
|
|
2214
|
+
onPhaseTiming: ({ durationMs, phase, reason, retryCount }) => {
|
|
2213
2215
|
sendTelemetryEvent("mobile:expo-dev-phase", {
|
|
2214
2216
|
durationMs: Math.round(durationMs),
|
|
2215
2217
|
host: mobile.detectAbsoluteMobileHost(),
|
|
2216
2218
|
phase,
|
|
2217
2219
|
platform: expoTelemetryPlatform(phase),
|
|
2218
|
-
provider: "expo"
|
|
2220
|
+
provider: "expo",
|
|
2221
|
+
reason,
|
|
2222
|
+
retryCount
|
|
2219
2223
|
});
|
|
2220
2224
|
},
|
|
2221
2225
|
onStateChange: (state) => {
|
|
@@ -2229,24 +2233,31 @@ var dev = async (serverEntry, configPath, options = {}) => {
|
|
|
2229
2233
|
return session;
|
|
2230
2234
|
});
|
|
2231
2235
|
const remoteIosProject = project.remoteIos;
|
|
2232
|
-
const remoteStart = remoteIosProject ? metroStart.then(async () => {
|
|
2236
|
+
const remoteStart = remoteIosProject ? metroStart.then(async (metro) => {
|
|
2233
2237
|
const session = await mobile.startAbsoluteRemoteExpoIosDevSession({
|
|
2234
2238
|
certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
|
|
2235
2239
|
deviceIdentifier: options.iosDevice,
|
|
2236
2240
|
https: httpsEnabled,
|
|
2237
|
-
metroPort:
|
|
2241
|
+
metroPort: metro.metroPort,
|
|
2238
2242
|
port,
|
|
2239
2243
|
project: remoteIosProject,
|
|
2240
2244
|
serverHost: options.iosDevice ? iosPhysicalServerHost : undefined,
|
|
2241
2245
|
signal: expoDevAbort.signal,
|
|
2242
2246
|
log: (message) => printNativeOutput(cliTag("\x1B[35m", `Expo [remote-ios] ${message}`)),
|
|
2243
|
-
onPhaseTiming: ({
|
|
2247
|
+
onPhaseTiming: ({
|
|
2248
|
+
durationMs,
|
|
2249
|
+
phase,
|
|
2250
|
+
reason,
|
|
2251
|
+
retryCount
|
|
2252
|
+
}) => {
|
|
2244
2253
|
sendTelemetryEvent("mobile:expo-dev-phase", {
|
|
2245
2254
|
durationMs: Math.round(durationMs),
|
|
2246
2255
|
host: "remote-macos",
|
|
2247
2256
|
phase,
|
|
2248
2257
|
platform: expoTelemetryPlatform(phase),
|
|
2249
|
-
provider: "expo"
|
|
2258
|
+
provider: "expo",
|
|
2259
|
+
reason,
|
|
2260
|
+
retryCount
|
|
2250
2261
|
});
|
|
2251
2262
|
},
|
|
2252
2263
|
onStateChange: (state) => {
|
|
@@ -15,9 +15,39 @@ import"./index-mywsk201.js";
|
|
|
15
15
|
// src/mobile/expoDevController.ts
|
|
16
16
|
import { spawn } from "child_process";
|
|
17
17
|
import { access } from "fs/promises";
|
|
18
|
+
import { createServer } from "net";
|
|
18
19
|
import { join, resolve as resolvePath } from "path";
|
|
19
20
|
var METRO_READY_TIMEOUT_MS = 120000;
|
|
21
|
+
var METRO_START_MAX_ATTEMPTS = 4;
|
|
22
|
+
var MAX_TCP_PORT = 65535;
|
|
20
23
|
var PROCESS_CLOSE_TIMEOUT_MS = 2000;
|
|
24
|
+
var allocateAvailableMetroPort = () => new Promise((resolve, reject) => {
|
|
25
|
+
const server = createServer();
|
|
26
|
+
server.unref();
|
|
27
|
+
server.once("error", reject);
|
|
28
|
+
server.listen(0, "127.0.0.1", () => {
|
|
29
|
+
const address = server.address();
|
|
30
|
+
if (!address || typeof address === "string") {
|
|
31
|
+
server.close();
|
|
32
|
+
reject(new Error("Failed to allocate a replacement Metro port."));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
server.close((error) => {
|
|
36
|
+
if (error)
|
|
37
|
+
reject(error);
|
|
38
|
+
else
|
|
39
|
+
resolve(address.port);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
var metroPortConflict = (line) => /(?:EADDRINUSE|address already in use|port \d+ is being used by another process)/iu.test(line);
|
|
44
|
+
|
|
45
|
+
class MetroStartupError extends Error {
|
|
46
|
+
constructor(exitCode, conflict) {
|
|
47
|
+
super(`Expo Metro exited with status ${exitCode}.`);
|
|
48
|
+
this.conflict = conflict;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
21
51
|
var preserveWindowsSubstRealpaths = `
|
|
22
52
|
const fs = require('node:fs');
|
|
23
53
|
const path = require('node:path');
|
|
@@ -512,7 +542,7 @@ var parseBootedAbsoluteExpoIosSimulators = (source) => {
|
|
|
512
542
|
});
|
|
513
543
|
};
|
|
514
544
|
var startAbsoluteExpoDevSession = async (options) => {
|
|
515
|
-
|
|
545
|
+
let plan = planAbsoluteExpoDevSession(options.config, options);
|
|
516
546
|
const executable = options.executable ?? await absoluteExpoExecutable(plan.project);
|
|
517
547
|
const run = options.spawnProcess ?? spawn;
|
|
518
548
|
const host = options.host ?? detectAbsoluteMobileHost();
|
|
@@ -527,9 +557,9 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
527
557
|
caEnrollmentServer = null;
|
|
528
558
|
await server?.close();
|
|
529
559
|
};
|
|
530
|
-
const publishTiming = (phase, durationMs) => {
|
|
560
|
+
const publishTiming = (phase, durationMs, details = {}) => {
|
|
531
561
|
timings[phase] = (timings[phase] ?? 0) + durationMs;
|
|
532
|
-
options.onPhaseTiming?.({ durationMs, phase });
|
|
562
|
+
options.onPhaseTiming?.({ durationMs, phase, ...details });
|
|
533
563
|
};
|
|
534
564
|
const setState = (state) => {
|
|
535
565
|
options.onStateChange?.(state);
|
|
@@ -537,8 +567,8 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
537
567
|
if (options.signal?.aborted)
|
|
538
568
|
throw abortError();
|
|
539
569
|
const prepareCommand = plan.commands.find((command) => command.role === "native-prepare");
|
|
540
|
-
|
|
541
|
-
|
|
570
|
+
let metroCommand = plan.commands.find((command) => command.role === "metro");
|
|
571
|
+
let nativeCommands = plan.commands.filter((command) => command.role === "native-build");
|
|
542
572
|
const runPrepareCommand = async (command) => {
|
|
543
573
|
setState("preparing-native");
|
|
544
574
|
const prepareStarted = performance.now();
|
|
@@ -569,41 +599,67 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
569
599
|
if (managedMetro)
|
|
570
600
|
setState("starting-metro");
|
|
571
601
|
const metroStarted = performance.now();
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
let resolveMetro;
|
|
579
|
-
const metroPromise = new Promise((resolve, reject) => {
|
|
580
|
-
if (!metro) {
|
|
581
|
-
resolve();
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
602
|
+
let metro;
|
|
603
|
+
let metroRetryCount = 0;
|
|
604
|
+
const attemptedMetroPorts = [plan.metroPort];
|
|
605
|
+
const waitForMetroReady = (child) => new Promise((resolve, reject) => {
|
|
606
|
+
let ready = false;
|
|
607
|
+
let conflict = false;
|
|
584
608
|
const timeout = setTimeout(() => {
|
|
585
609
|
reject(new Error("Expo Metro did not become ready within 120 seconds."));
|
|
586
610
|
}, METRO_READY_TIMEOUT_MS);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (!
|
|
611
|
+
forwardLines(child, (line) => {
|
|
612
|
+
if (line)
|
|
613
|
+
log(`[metro] ${line}`);
|
|
614
|
+
if (metroPortConflict(line))
|
|
615
|
+
conflict = true;
|
|
616
|
+
if (!ready && /(?:Waiting on|Metro waiting on|Dev server ready)/iu.test(line)) {
|
|
617
|
+
ready = true;
|
|
593
618
|
clearTimeout(timeout);
|
|
594
|
-
|
|
619
|
+
resolve();
|
|
595
620
|
}
|
|
596
621
|
});
|
|
622
|
+
child.once("exit", (code) => {
|
|
623
|
+
if (ready)
|
|
624
|
+
return;
|
|
625
|
+
clearTimeout(timeout);
|
|
626
|
+
reject(new MetroStartupError(code ?? 1, conflict));
|
|
627
|
+
});
|
|
597
628
|
});
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
629
|
+
const startManagedMetro = async () => {
|
|
630
|
+
if (!metroCommand)
|
|
631
|
+
return;
|
|
632
|
+
if (options.signal?.aborted)
|
|
633
|
+
throw abortError();
|
|
634
|
+
metro = run(executable, metroCommand.args, {
|
|
635
|
+
cwd: plan.project,
|
|
636
|
+
env: { ...process.env, ...metroCommand.env },
|
|
637
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
606
638
|
});
|
|
639
|
+
try {
|
|
640
|
+
await waitForMetroReady(metro);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
if (!(error instanceof MetroStartupError) || !error.conflict || attemptedMetroPorts.length >= METRO_START_MAX_ATTEMPTS) {
|
|
643
|
+
throw error;
|
|
644
|
+
}
|
|
645
|
+
await stopProcess(metro);
|
|
646
|
+
const allocate = options.allocateMetroPort ?? allocateAvailableMetroPort;
|
|
647
|
+
const replacementPort = await allocate(attemptedMetroPorts);
|
|
648
|
+
if (!Number.isSafeInteger(replacementPort) || replacementPort < 1 || replacementPort > MAX_TCP_PORT || attemptedMetroPorts.includes(replacementPort)) {
|
|
649
|
+
throw new Error("Could not allocate a distinct replacement Metro port.", { cause: error });
|
|
650
|
+
}
|
|
651
|
+
attemptedMetroPorts.push(replacementPort);
|
|
652
|
+
metroRetryCount += 1;
|
|
653
|
+
log("Metro port was claimed during startup; retrying automatically on a new port.");
|
|
654
|
+
plan = planAbsoluteExpoDevSession(options.config, {
|
|
655
|
+
...options,
|
|
656
|
+
metroPort: replacementPort
|
|
657
|
+
});
|
|
658
|
+
metroCommand = plan.commands.find((command) => command.role === "metro");
|
|
659
|
+
nativeCommands = plan.commands.filter((command) => command.role === "native-build");
|
|
660
|
+
await startManagedMetro();
|
|
661
|
+
}
|
|
662
|
+
};
|
|
607
663
|
const abort = () => {
|
|
608
664
|
if (metro)
|
|
609
665
|
stopProcess(metro);
|
|
@@ -618,7 +674,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
618
674
|
const state = platform === "android" ? "building-android" : "building-ios";
|
|
619
675
|
setState(state);
|
|
620
676
|
const started = performance.now();
|
|
621
|
-
const invocation = platform === "android" && host === "wsl" ? encodedWindowsExpoAndroidCommand(plan.project, options.androidRoot ?? absoluteManagedAndroidSdkRoot(host), options.config.appId, command.args, capture,
|
|
677
|
+
const invocation = platform === "android" && host === "wsl" ? encodedWindowsExpoAndroidCommand(plan.project, options.androidRoot ?? absoluteManagedAndroidSdkRoot(host), options.config.appId, command.args, capture, plan.metroPort, options.androidDevice) : [executable, ...command.args];
|
|
622
678
|
if (platform === "android" && host === "wsl") {
|
|
623
679
|
log("[android] Mirroring native inputs to the Windows host for accelerated Expo build and launch.");
|
|
624
680
|
}
|
|
@@ -646,7 +702,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
646
702
|
if (platform === "android" && host !== "wsl") {
|
|
647
703
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
|
|
648
704
|
const adb = options.androidAdb ?? join(androidRoot, "platform-tools", host === "windows" ? "adb.exe" : "adb");
|
|
649
|
-
connectLocalExpoAndroid(adb, options.config.appId,
|
|
705
|
+
connectLocalExpoAndroid(adb, options.config.appId, plan.metroPort, capture, options.androidDevice);
|
|
650
706
|
log("Connected the Expo Android development client to Metro.");
|
|
651
707
|
}
|
|
652
708
|
if (platform === "ios" && options.certificateAuthorityPath && options.iosOrigin && new URL(options.iosOrigin).protocol === "https:" && !options.iosDevice) {
|
|
@@ -684,7 +740,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
684
740
|
"simctl",
|
|
685
741
|
"openurl",
|
|
686
742
|
"booted",
|
|
687
|
-
expoDevelopmentClientUrl(options.config.appId, options.metroHost ?? "localhost",
|
|
743
|
+
expoDevelopmentClientUrl(options.config.appId, options.metroHost ?? "localhost", plan.metroPort)
|
|
688
744
|
], utilityOptions);
|
|
689
745
|
if (connectExit !== 0)
|
|
690
746
|
throw new Error(`Expo iOS Simulator Metro connection exited with status ${connectExit}.`);
|
|
@@ -717,9 +773,14 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
717
773
|
};
|
|
718
774
|
try {
|
|
719
775
|
await startPhysicalIosEnrollment();
|
|
720
|
-
await
|
|
776
|
+
await startManagedMetro();
|
|
721
777
|
if (managedMetro)
|
|
722
|
-
publishTiming("starting-metro", performance.now() - metroStarted
|
|
778
|
+
publishTiming("starting-metro", performance.now() - metroStarted, {
|
|
779
|
+
...metroRetryCount > 0 ? {
|
|
780
|
+
reason: "metro-port-conflict",
|
|
781
|
+
retryCount: metroRetryCount
|
|
782
|
+
} : {}
|
|
783
|
+
});
|
|
723
784
|
await runNativeCommands(nativeCommands);
|
|
724
785
|
setState("ready");
|
|
725
786
|
let closed = false;
|
package/dist/cli/index.js
CHANGED
|
@@ -58,7 +58,7 @@ if (command === "dev") {
|
|
|
58
58
|
}
|
|
59
59
|
const positionalArgs = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile" && arg !== "--eager");
|
|
60
60
|
const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
|
|
61
|
-
const { dev } = await import("./dev-
|
|
61
|
+
const { dev } = await import("./dev-r4vwdz6b.js");
|
|
62
62
|
await dev(serverEntry, configPath, {
|
|
63
63
|
androidDevice,
|
|
64
64
|
eager: args.includes("--eager"),
|
package/dist/mobile/index.js
CHANGED
|
@@ -30584,9 +30584,39 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
30584
30584
|
// src/mobile/expoDevController.ts
|
|
30585
30585
|
import { spawn } from "child_process";
|
|
30586
30586
|
import { access as access11 } from "fs/promises";
|
|
30587
|
+
import { createServer as createServer2 } from "net";
|
|
30587
30588
|
import { join as join18, resolve as resolvePath4 } from "path";
|
|
30588
30589
|
var METRO_READY_TIMEOUT_MS = 120000;
|
|
30590
|
+
var METRO_START_MAX_ATTEMPTS = 4;
|
|
30591
|
+
var MAX_TCP_PORT = 65535;
|
|
30589
30592
|
var PROCESS_CLOSE_TIMEOUT_MS = 2000;
|
|
30593
|
+
var allocateAvailableMetroPort = () => new Promise((resolve15, reject) => {
|
|
30594
|
+
const server = createServer2();
|
|
30595
|
+
server.unref();
|
|
30596
|
+
server.once("error", reject);
|
|
30597
|
+
server.listen(0, "127.0.0.1", () => {
|
|
30598
|
+
const address = server.address();
|
|
30599
|
+
if (!address || typeof address === "string") {
|
|
30600
|
+
server.close();
|
|
30601
|
+
reject(new Error("Failed to allocate a replacement Metro port."));
|
|
30602
|
+
return;
|
|
30603
|
+
}
|
|
30604
|
+
server.close((error) => {
|
|
30605
|
+
if (error)
|
|
30606
|
+
reject(error);
|
|
30607
|
+
else
|
|
30608
|
+
resolve15(address.port);
|
|
30609
|
+
});
|
|
30610
|
+
});
|
|
30611
|
+
});
|
|
30612
|
+
var metroPortConflict = (line) => /(?:EADDRINUSE|address already in use|port \d+ is being used by another process)/iu.test(line);
|
|
30613
|
+
|
|
30614
|
+
class MetroStartupError extends Error {
|
|
30615
|
+
constructor(exitCode, conflict) {
|
|
30616
|
+
super(`Expo Metro exited with status ${exitCode}.`);
|
|
30617
|
+
this.conflict = conflict;
|
|
30618
|
+
}
|
|
30619
|
+
}
|
|
30590
30620
|
var preserveWindowsSubstRealpaths = `
|
|
30591
30621
|
const fs = require('node:fs');
|
|
30592
30622
|
const path = require('node:path');
|
|
@@ -31081,7 +31111,7 @@ var parseBootedAbsoluteExpoIosSimulators = (source) => {
|
|
|
31081
31111
|
});
|
|
31082
31112
|
};
|
|
31083
31113
|
var startAbsoluteExpoDevSession = async (options) => {
|
|
31084
|
-
|
|
31114
|
+
let plan = planAbsoluteExpoDevSession(options.config, options);
|
|
31085
31115
|
const executable = options.executable ?? await absoluteExpoExecutable(plan.project);
|
|
31086
31116
|
const run = options.spawnProcess ?? spawn;
|
|
31087
31117
|
const host3 = options.host ?? detectAbsoluteMobileHost();
|
|
@@ -31096,9 +31126,9 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31096
31126
|
caEnrollmentServer = null;
|
|
31097
31127
|
await server?.close();
|
|
31098
31128
|
};
|
|
31099
|
-
const publishTiming = (phase, durationMs) => {
|
|
31129
|
+
const publishTiming = (phase, durationMs, details = {}) => {
|
|
31100
31130
|
timings[phase] = (timings[phase] ?? 0) + durationMs;
|
|
31101
|
-
options.onPhaseTiming?.({ durationMs, phase });
|
|
31131
|
+
options.onPhaseTiming?.({ durationMs, phase, ...details });
|
|
31102
31132
|
};
|
|
31103
31133
|
const setState = (state) => {
|
|
31104
31134
|
options.onStateChange?.(state);
|
|
@@ -31106,8 +31136,8 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31106
31136
|
if (options.signal?.aborted)
|
|
31107
31137
|
throw abortError();
|
|
31108
31138
|
const prepareCommand = plan.commands.find((command) => command.role === "native-prepare");
|
|
31109
|
-
|
|
31110
|
-
|
|
31139
|
+
let metroCommand = plan.commands.find((command) => command.role === "metro");
|
|
31140
|
+
let nativeCommands = plan.commands.filter((command) => command.role === "native-build");
|
|
31111
31141
|
const runPrepareCommand = async (command) => {
|
|
31112
31142
|
setState("preparing-native");
|
|
31113
31143
|
const prepareStarted = performance.now();
|
|
@@ -31138,41 +31168,67 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31138
31168
|
if (managedMetro)
|
|
31139
31169
|
setState("starting-metro");
|
|
31140
31170
|
const metroStarted = performance.now();
|
|
31141
|
-
|
|
31142
|
-
|
|
31143
|
-
|
|
31144
|
-
|
|
31145
|
-
|
|
31146
|
-
|
|
31147
|
-
let resolveMetro;
|
|
31148
|
-
const metroPromise = new Promise((resolve15, reject) => {
|
|
31149
|
-
if (!metro) {
|
|
31150
|
-
resolve15();
|
|
31151
|
-
return;
|
|
31152
|
-
}
|
|
31171
|
+
let metro;
|
|
31172
|
+
let metroRetryCount = 0;
|
|
31173
|
+
const attemptedMetroPorts = [plan.metroPort];
|
|
31174
|
+
const waitForMetroReady = (child) => new Promise((resolve15, reject) => {
|
|
31175
|
+
let ready = false;
|
|
31176
|
+
let conflict = false;
|
|
31153
31177
|
const timeout = setTimeout(() => {
|
|
31154
31178
|
reject(new Error("Expo Metro did not become ready within 120 seconds."));
|
|
31155
31179
|
}, METRO_READY_TIMEOUT_MS);
|
|
31156
|
-
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
if (!
|
|
31180
|
+
forwardLines(child, (line) => {
|
|
31181
|
+
if (line)
|
|
31182
|
+
log(`[metro] ${line}`);
|
|
31183
|
+
if (metroPortConflict(line))
|
|
31184
|
+
conflict = true;
|
|
31185
|
+
if (!ready && /(?:Waiting on|Metro waiting on|Dev server ready)/iu.test(line)) {
|
|
31186
|
+
ready = true;
|
|
31162
31187
|
clearTimeout(timeout);
|
|
31163
|
-
|
|
31188
|
+
resolve15();
|
|
31164
31189
|
}
|
|
31165
31190
|
});
|
|
31191
|
+
child.once("exit", (code) => {
|
|
31192
|
+
if (ready)
|
|
31193
|
+
return;
|
|
31194
|
+
clearTimeout(timeout);
|
|
31195
|
+
reject(new MetroStartupError(code ?? 1, conflict));
|
|
31196
|
+
});
|
|
31166
31197
|
});
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31170
|
-
|
|
31171
|
-
|
|
31172
|
-
|
|
31173
|
-
|
|
31174
|
-
}
|
|
31198
|
+
const startManagedMetro = async () => {
|
|
31199
|
+
if (!metroCommand)
|
|
31200
|
+
return;
|
|
31201
|
+
if (options.signal?.aborted)
|
|
31202
|
+
throw abortError();
|
|
31203
|
+
metro = run(executable, metroCommand.args, {
|
|
31204
|
+
cwd: plan.project,
|
|
31205
|
+
env: { ...process.env, ...metroCommand.env },
|
|
31206
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
31175
31207
|
});
|
|
31208
|
+
try {
|
|
31209
|
+
await waitForMetroReady(metro);
|
|
31210
|
+
} catch (error) {
|
|
31211
|
+
if (!(error instanceof MetroStartupError) || !error.conflict || attemptedMetroPorts.length >= METRO_START_MAX_ATTEMPTS) {
|
|
31212
|
+
throw error;
|
|
31213
|
+
}
|
|
31214
|
+
await stopProcess(metro);
|
|
31215
|
+
const allocate = options.allocateMetroPort ?? allocateAvailableMetroPort;
|
|
31216
|
+
const replacementPort = await allocate(attemptedMetroPorts);
|
|
31217
|
+
if (!Number.isSafeInteger(replacementPort) || replacementPort < 1 || replacementPort > MAX_TCP_PORT || attemptedMetroPorts.includes(replacementPort)) {
|
|
31218
|
+
throw new Error("Could not allocate a distinct replacement Metro port.", { cause: error });
|
|
31219
|
+
}
|
|
31220
|
+
attemptedMetroPorts.push(replacementPort);
|
|
31221
|
+
metroRetryCount += 1;
|
|
31222
|
+
log("Metro port was claimed during startup; retrying automatically on a new port.");
|
|
31223
|
+
plan = planAbsoluteExpoDevSession(options.config, {
|
|
31224
|
+
...options,
|
|
31225
|
+
metroPort: replacementPort
|
|
31226
|
+
});
|
|
31227
|
+
metroCommand = plan.commands.find((command) => command.role === "metro");
|
|
31228
|
+
nativeCommands = plan.commands.filter((command) => command.role === "native-build");
|
|
31229
|
+
await startManagedMetro();
|
|
31230
|
+
}
|
|
31231
|
+
};
|
|
31176
31232
|
const abort = () => {
|
|
31177
31233
|
if (metro)
|
|
31178
31234
|
stopProcess(metro);
|
|
@@ -31187,7 +31243,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31187
31243
|
const state = platform2 === "android" ? "building-android" : "building-ios";
|
|
31188
31244
|
setState(state);
|
|
31189
31245
|
const started = performance.now();
|
|
31190
|
-
const invocation = platform2 === "android" && host3 === "wsl" ? encodedWindowsExpoAndroidCommand(plan.project, options.androidRoot ?? absoluteManagedAndroidSdkRoot(host3), options.config.appId, command.args, capture,
|
|
31246
|
+
const invocation = platform2 === "android" && host3 === "wsl" ? encodedWindowsExpoAndroidCommand(plan.project, options.androidRoot ?? absoluteManagedAndroidSdkRoot(host3), options.config.appId, command.args, capture, plan.metroPort, options.androidDevice) : [executable, ...command.args];
|
|
31191
31247
|
if (platform2 === "android" && host3 === "wsl") {
|
|
31192
31248
|
log("[android] Mirroring native inputs to the Windows host for accelerated Expo build and launch.");
|
|
31193
31249
|
}
|
|
@@ -31215,7 +31271,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31215
31271
|
if (platform2 === "android" && host3 !== "wsl") {
|
|
31216
31272
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host3);
|
|
31217
31273
|
const adb = options.androidAdb ?? join18(androidRoot, "platform-tools", host3 === "windows" ? "adb.exe" : "adb");
|
|
31218
|
-
connectLocalExpoAndroid(adb, options.config.appId,
|
|
31274
|
+
connectLocalExpoAndroid(adb, options.config.appId, plan.metroPort, capture, options.androidDevice);
|
|
31219
31275
|
log("Connected the Expo Android development client to Metro.");
|
|
31220
31276
|
}
|
|
31221
31277
|
if (platform2 === "ios" && options.certificateAuthorityPath && options.iosOrigin && new URL(options.iosOrigin).protocol === "https:" && !options.iosDevice) {
|
|
@@ -31253,7 +31309,7 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31253
31309
|
"simctl",
|
|
31254
31310
|
"openurl",
|
|
31255
31311
|
"booted",
|
|
31256
|
-
expoDevelopmentClientUrl(options.config.appId, options.metroHost ?? "localhost",
|
|
31312
|
+
expoDevelopmentClientUrl(options.config.appId, options.metroHost ?? "localhost", plan.metroPort)
|
|
31257
31313
|
], utilityOptions);
|
|
31258
31314
|
if (connectExit !== 0)
|
|
31259
31315
|
throw new Error(`Expo iOS Simulator Metro connection exited with status ${connectExit}.`);
|
|
@@ -31286,9 +31342,14 @@ var startAbsoluteExpoDevSession = async (options) => {
|
|
|
31286
31342
|
};
|
|
31287
31343
|
try {
|
|
31288
31344
|
await startPhysicalIosEnrollment();
|
|
31289
|
-
await
|
|
31345
|
+
await startManagedMetro();
|
|
31290
31346
|
if (managedMetro)
|
|
31291
|
-
publishTiming("starting-metro", performance.now() - metroStarted
|
|
31347
|
+
publishTiming("starting-metro", performance.now() - metroStarted, {
|
|
31348
|
+
...metroRetryCount > 0 ? {
|
|
31349
|
+
reason: "metro-port-conflict",
|
|
31350
|
+
retryCount: metroRetryCount
|
|
31351
|
+
} : {}
|
|
31352
|
+
});
|
|
31292
31353
|
await runNativeCommands(nativeCommands);
|
|
31293
31354
|
setState("ready");
|
|
31294
31355
|
let closed = false;
|
|
@@ -41095,5 +41156,5 @@ export {
|
|
|
41095
41156
|
writeAbsoluteMobileUpdateRegistry
|
|
41096
41157
|
};
|
|
41097
41158
|
|
|
41098
|
-
//# debugId=
|
|
41159
|
+
//# debugId=9BE9503423C665BD64756E2164756E21
|
|
41099
41160
|
//# sourceMappingURL=index.js.map
|