@absolutejs/absolute 0.20.0-beta.97 → 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.
@@ -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
- const plan = planAbsoluteExpoDevSession(options.config, options);
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
- const metroCommand = plan.commands.find((command) => command.role === "metro");
31110
- const nativeCommands = plan.commands.filter((command) => command.role === "native-build");
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
- const metro = metroCommand ? run(executable, metroCommand.args, {
31142
- cwd: plan.project,
31143
- env: { ...process.env, ...metroCommand.env },
31144
- stdio: ["ignore", "pipe", "pipe"]
31145
- }) : undefined;
31146
- let metroReady = false;
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
- resolveMetro = () => {
31157
- clearTimeout(timeout);
31158
- resolve15();
31159
- };
31160
- metro.once("exit", (code) => {
31161
- if (!metroReady) {
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
- reject(new Error(`Expo Metro exited with status ${code ?? 1}.`));
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
- if (metro)
31168
- forwardLines(metro, (line) => {
31169
- if (line)
31170
- log(`[metro] ${line}`);
31171
- if (!metroReady && /(?:Waiting on|Metro waiting on|Dev server ready)/iu.test(line)) {
31172
- metroReady = true;
31173
- resolveMetro?.();
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, options.metroPort, options.androidDevice) : [executable, ...command.args];
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, options.metroPort, capture, options.androidDevice);
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", options.metroPort)
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 metroPromise;
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;
@@ -31336,6 +31397,97 @@ var startAbsoluteExpoDevSession = async (options) => {
31336
31397
  throw error;
31337
31398
  }
31338
31399
  };
31400
+ // src/mobile/expoAndroidQuality.ts
31401
+ var KIB_PER_MIB = 1024;
31402
+ var XML_BOUND_BOTTOM_INDEX = 4;
31403
+ var XML_BOUND_LEFT_INDEX = 1;
31404
+ var XML_BOUND_RIGHT_INDEX = 3;
31405
+ var XML_BOUND_TOP_INDEX = 2;
31406
+ var DEFAULT_ABSOLUTE_EXPO_ANDROID_QUALITY_BUDGETS = {
31407
+ bridgeP95Ms: 500,
31408
+ coldLaunchMs: 25000,
31409
+ hmrP95Ms: 1e4,
31410
+ maxMemoryGrowthMiB: 160,
31411
+ maxTotalPssMiB: 700,
31412
+ minTouchTargetDp: 44,
31413
+ warmLaunchMs: 5000
31414
+ };
31415
+ var requiredInteger = (source, name) => {
31416
+ const match = new RegExp(`^${name}:\\s*(\\d+)$`, "mu").exec(source);
31417
+ const value = Number(match?.[1]);
31418
+ if (!Number.isSafeInteger(value))
31419
+ throw new TypeError(`Android launch output is missing ${name}.`);
31420
+ return value;
31421
+ };
31422
+ var optionalInteger = (source, name) => {
31423
+ const match = new RegExp(`^${name}:\\s*(\\d+)$`, "mu").exec(source);
31424
+ if (!match)
31425
+ return;
31426
+ const value = Number(match[1]);
31427
+ if (!Number.isSafeInteger(value))
31428
+ throw new TypeError(`Android launch output has invalid ${name}.`);
31429
+ return value;
31430
+ };
31431
+ var parseAbsoluteExpoAndroidLaunchTiming = (source) => ({
31432
+ launchState: /^LaunchState:\s*(\S+)$/mu.exec(source)?.[1],
31433
+ thisTimeMs: optionalInteger(source, "ThisTime"),
31434
+ totalTimeMs: requiredInteger(source, "TotalTime"),
31435
+ waitTimeMs: requiredInteger(source, "WaitTime")
31436
+ });
31437
+ var parseAbsoluteExpoAndroidMemory = (source) => {
31438
+ const summary = /^\s*TOTAL PSS:\s*(\d+)/mu.exec(source)?.[1];
31439
+ const table = /^\s*TOTAL\s+(\d+)\s+/mu.exec(source)?.[1];
31440
+ const totalPssKiB = Number(summary ?? table);
31441
+ if (!Number.isSafeInteger(totalPssKiB) || totalPssKiB < 0)
31442
+ throw new TypeError("Android meminfo output is missing total PSS.");
31443
+ return { totalPssKiB };
31444
+ };
31445
+ var decodeXml = (value) => value.replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
31446
+ var nodeAttributes = (source) => Object.fromEntries([...source.matchAll(/([\w:-]+)="([^"]*)"/gu)].map(([, name = "", value = ""]) => [name, decodeXml(value)]));
31447
+ var nodeBounds = (source) => {
31448
+ const match = /^\[(\d+),(\d+)\]\[(\d+),(\d+)\]$/u.exec(source);
31449
+ if (!match)
31450
+ return { bottom: 0, left: 0, right: 0, top: 0 };
31451
+ return {
31452
+ bottom: Number(match[XML_BOUND_BOTTOM_INDEX]),
31453
+ left: Number(match[XML_BOUND_LEFT_INDEX]),
31454
+ right: Number(match[XML_BOUND_RIGHT_INDEX]),
31455
+ top: Number(match[XML_BOUND_TOP_INDEX])
31456
+ };
31457
+ };
31458
+ var absoluteAndroidNodeLabel = (node) => node.contentDescription.trim() || node.text.trim();
31459
+ var absoluteAndroidTouchTargetDp = (node, density) => {
31460
+ if (!Number.isFinite(density) || density <= 0)
31461
+ throw new TypeError("Android display density must be positive.");
31462
+ return {
31463
+ height: (node.bounds.bottom - node.bounds.top) / density,
31464
+ width: (node.bounds.right - node.bounds.left) / density
31465
+ };
31466
+ };
31467
+ var absolutePercentile = (values, ratio) => {
31468
+ if (values.length === 0)
31469
+ throw new TypeError("A percentile requires at least one measurement.");
31470
+ if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1)
31471
+ throw new TypeError("A percentile ratio must be between zero and one.");
31472
+ const ordered = [...values].sort((left, right) => left - right);
31473
+ const index = Math.ceil(ratio * ordered.length) - 1;
31474
+ return ordered[Math.max(0, index)] ?? 0;
31475
+ };
31476
+ var absolutePssMiB = (memory) => memory.totalPssKiB / KIB_PER_MIB;
31477
+ var parseAbsoluteAndroidAccessibilityHierarchy = (source) => [...source.matchAll(/<node\s+([^>]*?)\/?>(?:<\/node>)?/gu)].map(([, raw = ""]) => {
31478
+ const attributes = nodeAttributes(raw);
31479
+ return {
31480
+ bounds: nodeBounds(attributes.bounds ?? ""),
31481
+ className: attributes.class ?? "",
31482
+ clickable: attributes.clickable === "true",
31483
+ contentDescription: attributes["content-desc"] ?? "",
31484
+ enabled: attributes.enabled === "true",
31485
+ packageName: attributes.package ?? "",
31486
+ resourceId: attributes["resource-id"] ?? "",
31487
+ text: attributes.text ?? "",
31488
+ visible: attributes["visible-to-user"] !== "false"
31489
+ };
31490
+ });
31339
31491
  // src/mobile/expoNativeWatcher.ts
31340
31492
  import { watch as watch2 } from "fs";
31341
31493
  import { access as access12, readdir as readdir5, readFile as readFile16 } from "fs/promises";
@@ -40794,8 +40946,11 @@ export {
40794
40946
  ANDROID_ASSOCIATION_PATH,
40795
40947
  APPLE_ASSOCIATION_PATH,
40796
40948
  AbsoluteMobilePageProtocolError,
40949
+ DEFAULT_ABSOLUTE_EXPO_ANDROID_QUALITY_BUDGETS,
40797
40950
  DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE,
40798
40951
  MOBILE_PAGE_REQUEST_HEADERS,
40952
+ absoluteAndroidNodeLabel,
40953
+ absoluteAndroidTouchTargetDp,
40799
40954
  absoluteDeviceNativeRequirements,
40800
40955
  absoluteExpoExecutable,
40801
40956
  absoluteExpoNativeObservabilityFiles,
@@ -40803,6 +40958,8 @@ export {
40803
40958
  absoluteIosDeviceAcceptanceCommands,
40804
40959
  absoluteMobilePreviewDocument,
40805
40960
  absoluteMobileUpdateSigningPayload,
40961
+ absolutePercentile,
40962
+ absolutePssMiB,
40806
40963
  absoluteRemoteMacSshBase,
40807
40964
  absoluteRemoteProjectSyncCommands,
40808
40965
  absoluteRemoteReleaseInputSyncCommands,
@@ -40918,7 +41075,10 @@ export {
40918
41075
  normalizeAbsoluteMobileUpdatePath,
40919
41076
  openAbsoluteMobileSheet,
40920
41077
  pairAbsoluteRemoteMac,
41078
+ parseAbsoluteAndroidAccessibilityHierarchy,
40921
41079
  parseAbsoluteAndroidInstalledApp,
41080
+ parseAbsoluteExpoAndroidLaunchTiming,
41081
+ parseAbsoluteExpoAndroidMemory,
40922
41082
  parseAbsoluteExpoBridgeMessage,
40923
41083
  parseAbsoluteExpoUpdateDescriptor,
40924
41084
  parseAbsoluteIosHmrLog,
@@ -40996,5 +41156,5 @@ export {
40996
41156
  writeAbsoluteMobileUpdateRegistry
40997
41157
  };
40998
41158
 
40999
- //# debugId=CC8C4D11922DDEB264756E2164756E21
41159
+ //# debugId=9BE9503423C665BD64756E2164756E21
41000
41160
  //# sourceMappingURL=index.js.map