@absolutejs/absolute 0.20.0-beta.33 → 0.20.0-beta.34

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/dist/cli/index.js CHANGED
@@ -2844,17 +2844,109 @@ var init_iosRelease = __esm(() => {
2844
2844
  ]);
2845
2845
  });
2846
2846
 
2847
+ // src/mobile/iosPhysicalDeviceTransport.ts
2848
+ import { randomUUID as randomUUID2, X509Certificate } from "crypto";
2849
+ import { createServer as createServer3 } from "http";
2850
+ import {
2851
+ connect as connectTcp,
2852
+ createServer as createTcpServer,
2853
+ isIP
2854
+ } from "net";
2855
+ import { readFile as readFile5 } from "fs/promises";
2856
+ var closeServer = (server) => new Promise((resolve7, reject) => {
2857
+ server.close((error) => {
2858
+ if (error)
2859
+ reject(error);
2860
+ else
2861
+ resolve7();
2862
+ });
2863
+ }), listen = (server, port) => new Promise((resolve7, reject) => {
2864
+ server.once("error", reject);
2865
+ server.listen(port, "0.0.0.0", () => {
2866
+ server.off("error", reject);
2867
+ const address = server.address();
2868
+ if (!address || typeof address === "string") {
2869
+ reject(new Error("Could not determine the iOS device helper port."));
2870
+ return;
2871
+ }
2872
+ resolve7(address.port);
2873
+ });
2874
+ }), findEphemeralPort = async () => {
2875
+ const probe = createTcpServer();
2876
+ const port = await new Promise((resolve7, reject) => {
2877
+ probe.once("error", reject);
2878
+ probe.listen(0, "127.0.0.1", () => {
2879
+ const address = probe.address();
2880
+ if (!address || typeof address === "string") {
2881
+ reject(new Error("Could not allocate the iOS CA enrollment port."));
2882
+ return;
2883
+ }
2884
+ resolve7(address.port);
2885
+ });
2886
+ });
2887
+ await closeServer(probe);
2888
+ return port;
2889
+ }, normalizeAbsoluteIosDeviceHost = (value) => {
2890
+ const normalized = value.trim();
2891
+ if (!normalized || normalized.length > 253 || /[\0\s/?#]/u.test(normalized))
2892
+ throw new TypeError("Physical iOS development requires a valid LAN host.");
2893
+ return normalized;
2894
+ }, normalizeAbsoluteIosDeviceIdentifier = (value) => {
2895
+ const normalized = value.trim();
2896
+ if (!normalized || normalized.length > 256 || /[\0\r\n]/u.test(normalized))
2897
+ throw new TypeError("--ios-device requires a valid Xcode device identifier or name.");
2898
+ return normalized;
2899
+ }, urlForHost = (protocol, host, port) => {
2900
+ const url = new URL(`${protocol}://localhost:${port}`);
2901
+ const normalizedHost = normalizeAbsoluteIosDeviceHost(host);
2902
+ url.hostname = isIP(normalizedHost) === 6 ? `[${normalizedHost}]` : normalizedHost;
2903
+ return url;
2904
+ }, startAbsoluteIosCaEnrollmentServer = async (options) => {
2905
+ const certificate = new X509Certificate(await readFile5(options.certificateAuthorityPath));
2906
+ const certificateBytes = certificate.raw;
2907
+ const token = randomUUID2().replaceAll("-", "");
2908
+ const certificatePath = `/${token}/absolutejs-development-ca.cer`;
2909
+ const server = createServer3((request, response) => {
2910
+ if (request.method !== "GET" || request.url !== certificatePath) {
2911
+ response.writeHead(404, {
2912
+ "Cache-Control": "no-store",
2913
+ "Content-Type": "text/plain; charset=utf-8"
2914
+ });
2915
+ response.end("Not found.");
2916
+ return;
2917
+ }
2918
+ response.writeHead(200, {
2919
+ "Cache-Control": "no-store",
2920
+ "Content-Disposition": 'attachment; filename="absolutejs-development-ca.cer"',
2921
+ "Content-Length": String(certificateBytes.byteLength),
2922
+ "Content-Type": "application/x-x509-ca-cert",
2923
+ "X-Content-Type-Options": "nosniff"
2924
+ });
2925
+ response.end(certificateBytes);
2926
+ });
2927
+ const port = await findEphemeralPort();
2928
+ await listen(server, port);
2929
+ const url = urlForHost("http", options.displayHost, port);
2930
+ url.pathname = certificatePath;
2931
+ return {
2932
+ url: url.href,
2933
+ close: () => closeServer(server)
2934
+ };
2935
+ };
2936
+ var init_iosPhysicalDeviceTransport = () => {};
2937
+
2847
2938
  // src/mobile/iosSimulatorController.ts
2848
- import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
2939
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
2849
2940
  import {
2850
2941
  access as access5,
2851
2942
  copyFile as copyFile3,
2852
2943
  mkdir as mkdir4,
2853
- readFile as readFile5,
2944
+ readFile as readFile6,
2854
2945
  rename as rename4,
2855
2946
  rm as rm4,
2856
2947
  writeFile as writeFile4
2857
2948
  } from "fs/promises";
2949
+ import { isIP as isIP2 } from "net";
2858
2950
  import { dirname as dirname5, isAbsolute as isAbsolute3, join as join10, relative as relative4, resolve as resolve7, sep as sep3 } from "path";
2859
2951
  var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000, BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT2 = 1, NATIVE_CACHE_FORMAT2 = 1, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), pathExists4 = async (path) => {
2860
2952
  try {
@@ -2965,7 +3057,24 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
2965
3057
  options.certificateAuthorityPath
2966
3058
  ], "iOS Simulator development CA trust", run, { signal: options.signal });
2967
3059
  log("Installed the AbsoluteJS development CA into this iOS Simulator trust store.");
2968
- }, requireCapturedSuccess = (result, label) => {
3060
+ }, iosLaunchCommand = (project, udid, physical) => physical ? [
3061
+ project.xcrun,
3062
+ "devicectl",
3063
+ "device",
3064
+ "process",
3065
+ "launch",
3066
+ "--terminate-existing",
3067
+ "--device",
3068
+ udid,
3069
+ project.config.appId
3070
+ ] : [
3071
+ project.xcrun,
3072
+ "simctl",
3073
+ "launch",
3074
+ "--terminate-running-process",
3075
+ udid,
3076
+ project.config.appId
3077
+ ], requireCapturedSuccess = (result, label) => {
2969
3078
  if (result.exitCode !== 0) {
2970
3079
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
2971
3080
  }
@@ -3088,7 +3197,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3088
3197
  await rm4(paths.root, { force: true, recursive: true });
3089
3198
  return false;
3090
3199
  }
3091
- const journal = await readFile5(paths.journal, "utf8").then((source) => parseJournal2(JSON.parse(source))).catch(() => null);
3200
+ const journal = await readFile6(paths.journal, "utf8").then((source) => parseJournal2(JSON.parse(source))).catch(() => null);
3092
3201
  if (!journal || !isInside2(projectRoot, journal.nativeConfigPath) || !isInside2(projectRoot, journal.infoPath) || !isInside2(paths.root, journal.configBackupPath) || !isInside2(paths.root, journal.infoBackupPath)) {
3093
3202
  throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
3094
3203
  }
@@ -3117,14 +3226,14 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3117
3226
  <key>NSAllowsArbitraryLoads</key>
3118
3227
  <true/>
3119
3228
  </dict>`);
3120
- }, writeDevProjection = async (project, port, https) => {
3229
+ }, writeDevProjection = async (project, port, https, serverHost = "localhost") => {
3121
3230
  const paths = journalPaths2(project.projectRoot);
3122
3231
  await repairAbsoluteIosDevSession(project.projectRoot);
3123
3232
  const nativeConfigPath = join10(project.nativeDirectory, "App", "App", "capacitor.config.json");
3124
3233
  const infoPath = join10(project.nativeDirectory, "App", "App", "Info.plist");
3125
3234
  const [configSource, infoSource] = await Promise.all([
3126
- readFile5(nativeConfigPath, "utf8"),
3127
- readFile5(infoPath, "utf8")
3235
+ readFile6(nativeConfigPath, "utf8"),
3236
+ readFile6(infoPath, "utf8")
3128
3237
  ]);
3129
3238
  const parsed = JSON.parse(configSource);
3130
3239
  if (!isRecord3(parsed))
@@ -3146,6 +3255,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3146
3255
  flag: "wx"
3147
3256
  });
3148
3257
  const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
3258
+ developmentUrl.hostname = isIP2(serverHost) === 6 ? `[${serverHost}]` : serverHost;
3149
3259
  developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
3150
3260
  const existingServer = parsed.server;
3151
3261
  parsed.server = {
@@ -3173,9 +3283,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3173
3283
  String(identity)
3174
3284
  ]))
3175
3285
  };
3176
- }, readNativeCache2 = (projectRoot) => readFile5(nativeCachePath2(projectRoot), "utf8").then((source) => parseNativeCache2(JSON.parse(source))).catch(() => null), writeNativeCache2 = async (projectRoot, cache) => {
3286
+ }, readNativeCache2 = (projectRoot) => readFile6(nativeCachePath2(projectRoot), "utf8").then((source) => parseNativeCache2(JSON.parse(source))).catch(() => null), writeNativeCache2 = async (projectRoot, cache) => {
3177
3287
  const destination = nativeCachePath2(projectRoot);
3178
- const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
3288
+ const temporary = `${destination}.${process.pid}.${randomUUID3()}.tmp`;
3179
3289
  await mkdir4(dirname5(destination), { recursive: true });
3180
3290
  try {
3181
3291
  await writeFile4(temporary, `${JSON.stringify(cache, null, "\t")}
@@ -3257,6 +3367,29 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3257
3367
  "app"
3258
3368
  ]);
3259
3369
  return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
3370
+ }, validatePhysicalIosDevice = (project, identifier, capture) => {
3371
+ const result = capture([
3372
+ project.xcrun,
3373
+ "devicectl",
3374
+ "device",
3375
+ "info",
3376
+ "details",
3377
+ "--device",
3378
+ identifier
3379
+ ]);
3380
+ if (result.exitCode !== 0)
3381
+ throw new Error(`Physical iOS device ${JSON.stringify(identifier)} is unavailable: ${result.stderr.trim() || result.stdout.trim() || "pair it in Xcode Device Hub, trust this Mac, unlock it, and enable Developer Mode."}`);
3382
+ }, physicalIosAppIsInstalled = (project, identifier, capture) => {
3383
+ const result = capture([
3384
+ project.xcrun,
3385
+ "devicectl",
3386
+ "device",
3387
+ "info",
3388
+ "apps",
3389
+ "--device",
3390
+ identifier
3391
+ ]);
3392
+ return result.exitCode === 0 && result.stdout.includes(project.config.appId);
3260
3393
  }, buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
3261
3394
  const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
3262
3395
  await mkdir4(derivedDataPath, { recursive: true });
@@ -3278,6 +3411,59 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3278
3411
  if (!await pathExists4(appPath))
3279
3412
  throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
3280
3413
  return appPath;
3414
+ }, buildPhysicalIosDebugApp = async (project, identifier, run, signal) => {
3415
+ const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
3416
+ await mkdir4(derivedDataPath, { recursive: true });
3417
+ await requireSuccess2([
3418
+ project.xcodebuild,
3419
+ "-workspace",
3420
+ join10(project.nativeDirectory, "App", "App.xcworkspace"),
3421
+ "-scheme",
3422
+ "App",
3423
+ "-configuration",
3424
+ "Debug",
3425
+ "-destination",
3426
+ `platform=iOS,id=${identifier}`,
3427
+ "-derivedDataPath",
3428
+ derivedDataPath,
3429
+ "-allowProvisioningUpdates",
3430
+ "build"
3431
+ ], "iOS physical-device build (configure automatic signing and a Development Team in Xcode if this is the first run)", run, { cwd: project.nativeDirectory, signal });
3432
+ const appPath = join10(derivedDataPath, "Build", "Products", "Debug-iphoneos", "App.app");
3433
+ if (!await pathExists4(appPath))
3434
+ throw new Error(`Xcode did not produce the physical-device app at ${appPath}.`);
3435
+ return appPath;
3436
+ }, ensurePhysicalIosDebugApp = async (options) => {
3437
+ const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && options.cache.installations[options.identifier] === options.fingerprint && physicalIosAppIsInstalled(options.project, options.identifier, options.capture);
3438
+ if (cacheHit) {
3439
+ options.log(`iOS native app is unchanged on the selected physical device; skipped Xcode build and install.`);
3440
+ return true;
3441
+ }
3442
+ options.log("iOS native inputs changed or the physical-device install is stale; rebuilding.");
3443
+ options.transition("building");
3444
+ const appPath = await buildPhysicalIosDebugApp(options.project, options.identifier, options.run, options.signal);
3445
+ throwIfAborted2(options.signal);
3446
+ options.transition("installing");
3447
+ await requireSuccess2([
3448
+ options.project.xcrun,
3449
+ "devicectl",
3450
+ "device",
3451
+ "install",
3452
+ "app",
3453
+ "--device",
3454
+ options.identifier,
3455
+ appPath
3456
+ ], "iOS physical-device app installation", options.run, { signal: options.signal });
3457
+ await writeNativeCache2(options.project.projectRoot, {
3458
+ appId: options.project.config.appId,
3459
+ fingerprint: options.fingerprint,
3460
+ format: NATIVE_CACHE_FORMAT2,
3461
+ installations: {
3462
+ ...options.cache?.appId === options.project.config.appId ? options.cache.installations : {},
3463
+ [options.identifier]: options.fingerprint
3464
+ }
3465
+ }).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
3466
+ return false;
3281
3467
  }, ensureIosDebugApp = async (options) => {
3282
3468
  const installed = installedAppIdentity(options.project, options.udid, options.capture);
3283
3469
  const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
@@ -3317,7 +3503,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3317
3503
  if (!options.nativeLog)
3318
3504
  return null;
3319
3505
  const start = options.startNativeLogs ?? defaultStartNativeLogs;
3320
- return start([
3506
+ const command = options.deviceIdentifier ? [
3507
+ project.xcrun,
3508
+ "devicectl",
3509
+ "device",
3510
+ "process",
3511
+ "launch",
3512
+ "--console",
3513
+ "--terminate-existing",
3514
+ "--device",
3515
+ udid,
3516
+ project.config.appId
3517
+ ] : [
3321
3518
  project.xcrun,
3322
3519
  "simctl",
3323
3520
  "spawn",
@@ -3330,7 +3527,8 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3330
3527
  "debug",
3331
3528
  "--predicate",
3332
3529
  'process == "App"'
3333
- ], { signal: options.signal }, (line) => {
3530
+ ];
3531
+ return start(command, { signal: options.signal }, (line) => {
3334
3532
  const entry = parseAbsoluteIosLogLine(line);
3335
3533
  if (entry)
3336
3534
  options.nativeLog?.(entry);
@@ -3340,12 +3538,13 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3340
3538
  return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
3341
3539
  }).filter((value) => value !== null).join(", "), prepareAbsoluteIosDevProject = async (config, options) => {
3342
3540
  if (detectAbsoluteMobileHost() !== "macos")
3343
- throw new Error("iOS simulation requires macOS and Xcode.");
3541
+ throw new Error("iOS development requires macOS and Xcode.");
3542
+ const target = options.target ?? "simulator";
3344
3543
  const projectRoot = resolve7(options.projectRoot);
3345
3544
  const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
3346
- const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
3545
+ const failed = checks.filter((check) => check.platform === "ios" && !(target === "device" && check.id === "ios.runtime") && (check.status === "fail" || check.status === "warn"));
3347
3546
  if (failed.length > 0)
3348
- throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
3547
+ throw new Error(`iOS ${target} development is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
3349
3548
  const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
3350
3549
  const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
3351
3550
  if (!xcrun || !xcodebuild)
@@ -3375,8 +3574,53 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3375
3574
  xcodebuild,
3376
3575
  xcrun
3377
3576
  };
3577
+ }, preparePhysicalIosTarget = async (options) => {
3578
+ options.transition("connecting");
3579
+ validatePhysicalIosDevice(options.project, options.deviceIdentifier, options.capture);
3580
+ if (!options.startOptions.https)
3581
+ return {
3582
+ caEnrollmentServer: null,
3583
+ startedSimulator: false,
3584
+ udid: options.deviceIdentifier
3585
+ };
3586
+ if (!options.startOptions.certificateAuthorityPath)
3587
+ throw new Error("Physical iOS HTTPS development requires the AbsoluteJS development CA certificate.");
3588
+ options.transition("enrolling-trust");
3589
+ const startEnrollment = options.startOptions.startCaEnrollmentServer ?? startAbsoluteIosCaEnrollmentServer;
3590
+ const caEnrollmentServer = await startEnrollment({
3591
+ certificateAuthorityPath: options.startOptions.certificateAuthorityPath,
3592
+ displayHost: options.serverHost
3593
+ });
3594
+ options.log(`On the iOS device, open ${caEnrollmentServer.url}, install the AbsoluteJS development CA profile, then enable it under Settings > General > About > Certificate Trust Settings. This public CA endpoint exists only for this dev session.`);
3595
+ return {
3596
+ caEnrollmentServer,
3597
+ startedSimulator: false,
3598
+ udid: options.deviceIdentifier
3599
+ };
3600
+ }, prepareIosSimulatorTarget = async (options) => {
3601
+ options.transition("booting");
3602
+ const managed = await ensureManagedSimulator(options.project, options.capture);
3603
+ const { device } = managed;
3604
+ const startedSimulator = managed.created || device.state !== "Booted";
3605
+ bootSimulator(options.project, device, options.capture);
3606
+ options.spawn([
3607
+ "open",
3608
+ "-a",
3609
+ "Simulator",
3610
+ "--args",
3611
+ "-CurrentDeviceUDID",
3612
+ device.udid
3613
+ ]);
3614
+ options.transition("connecting");
3615
+ await waitForBootedSimulator(options.project, device.udid, options.capture, options.sleep, options.startOptions.signal);
3616
+ await requireSuccess2([options.project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", options.run, { signal: options.startOptions.signal });
3617
+ await trustIosSimulatorDevelopmentCa(options.startOptions, device.udid, options.run, options.log);
3618
+ return { caEnrollmentServer: null, startedSimulator, udid: device.udid };
3378
3619
  }, startAbsoluteIosDevSession = async (options) => {
3379
3620
  const { project } = options;
3621
+ const deviceIdentifier = options.deviceIdentifier ? normalizeAbsoluteIosDeviceIdentifier(options.deviceIdentifier) : undefined;
3622
+ const targetKind = deviceIdentifier ? "device" : "simulator";
3623
+ const serverHost = deviceIdentifier ? normalizeAbsoluteIosDeviceHost(options.serverHost ?? "") : "localhost";
3380
3624
  const capture = options.capture ?? defaultCapture3;
3381
3625
  const run = options.run ?? defaultRun3;
3382
3626
  const sleep = options.sleep ?? Bun.sleep;
@@ -3404,6 +3648,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3404
3648
  options.onStateChange?.(next);
3405
3649
  };
3406
3650
  let nativeLogs = null;
3651
+ let caEnrollmentServer = null;
3407
3652
  const closeLogs = async () => {
3408
3653
  const stream = nativeLogs;
3409
3654
  nativeLogs = null;
@@ -3411,39 +3656,66 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3411
3656
  return;
3412
3657
  });
3413
3658
  };
3659
+ const relaunchTarget = async (udid) => {
3660
+ if (deviceIdentifier && options.nativeLog) {
3661
+ await closeLogs();
3662
+ nativeLogs = attachNativeLogs(project, udid, options);
3663
+ return;
3664
+ }
3665
+ await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app relaunch", run, { signal: options.signal });
3666
+ };
3414
3667
  try {
3415
3668
  await repairAbsoluteIosDevSession(project.projectRoot);
3416
3669
  throwIfAborted2(options.signal);
3417
3670
  transition("syncing");
3418
3671
  await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
3419
3672
  transition("configuring");
3420
- await writeDevProjection(project, options.port, options.https === true);
3673
+ await writeDevProjection(project, options.port, options.https === true, serverHost);
3421
3674
  throwIfAborted2(options.signal);
3422
3675
  const fingerprintStartedAt = performance.now();
3423
3676
  const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
3424
3677
  timings.fingerprinting = performance.now() - fingerprintStartedAt;
3425
3678
  return fingerprint2;
3426
3679
  });
3427
- transition("booting");
3428
- const { created, device } = await ensureManagedSimulator(project, capture);
3429
- const startedSimulator = created || device.state !== "Booted";
3430
- bootSimulator(project, device, capture);
3431
- spawn([
3432
- "open",
3433
- "-a",
3434
- "Simulator",
3435
- "--args",
3436
- "-CurrentDeviceUDID",
3437
- device.udid
3438
- ]);
3439
- transition("connecting");
3440
- await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
3441
- await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
3442
- await trustIosSimulatorDevelopmentCa(options, device.udid, run, log);
3680
+ const target = deviceIdentifier ? await preparePhysicalIosTarget({
3681
+ capture,
3682
+ deviceIdentifier,
3683
+ log,
3684
+ project,
3685
+ serverHost,
3686
+ startOptions: options,
3687
+ transition
3688
+ }) : await prepareIosSimulatorTarget({
3689
+ capture,
3690
+ log,
3691
+ project,
3692
+ run,
3693
+ sleep,
3694
+ spawn,
3695
+ startOptions: options,
3696
+ transition
3697
+ });
3698
+ const {
3699
+ caEnrollmentServer: targetCaEnrollmentServer,
3700
+ startedSimulator,
3701
+ udid
3702
+ } = target;
3703
+ caEnrollmentServer = targetCaEnrollmentServer;
3443
3704
  const fingerprint = await fingerprintPromise;
3444
3705
  transition("checking-native");
3445
- const nativeCacheHit = await ensureIosDebugApp({
3446
- cache: await readNativeCache2(project.projectRoot),
3706
+ const cache = await readNativeCache2(project.projectRoot);
3707
+ const nativeCacheHit = deviceIdentifier ? await ensurePhysicalIosDebugApp({
3708
+ cache,
3709
+ capture,
3710
+ fingerprint,
3711
+ identifier: deviceIdentifier,
3712
+ log,
3713
+ project,
3714
+ run,
3715
+ signal: options.signal,
3716
+ transition
3717
+ }) : await ensureIosDebugApp({
3718
+ cache,
3447
3719
  capture,
3448
3720
  fingerprint,
3449
3721
  log,
@@ -3451,24 +3723,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3451
3723
  run,
3452
3724
  signal: options.signal,
3453
3725
  transition,
3454
- udid: device.udid
3726
+ udid
3455
3727
  });
3456
3728
  throwIfAborted2(options.signal);
3457
3729
  if (options.nativeLog)
3458
3730
  transition("streaming-logs");
3459
- nativeLogs = attachNativeLogs(project, device.udid, options);
3731
+ nativeLogs = attachNativeLogs(project, udid, options);
3460
3732
  transition("launching");
3461
- await requireSuccess2([
3462
- project.xcrun,
3463
- "simctl",
3464
- "launch",
3465
- "--terminate-running-process",
3466
- device.udid,
3467
- project.config.appId
3468
- ], "iOS app launch", run, { signal: options.signal });
3733
+ if (!deviceIdentifier || !nativeLogs)
3734
+ await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app launch", run, { signal: options.signal });
3469
3735
  transition("ready");
3470
3736
  timings.total = performance.now() - startedAt;
3471
- log(`iOS simulator connected (${device.udid}) with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
3737
+ log(`iOS ${targetKind} connected with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
3472
3738
  log(`iOS startup: ${timingSummary(timings)}.`);
3473
3739
  let closed = false;
3474
3740
  const close = async () => {
@@ -3477,6 +3743,10 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3477
3743
  closed = true;
3478
3744
  transition("closing");
3479
3745
  await closeLogs();
3746
+ await caEnrollmentServer?.close().catch(() => {
3747
+ return;
3748
+ });
3749
+ caEnrollmentServer = null;
3480
3750
  await repairAbsoluteIosDevSession(project.projectRoot);
3481
3751
  transition("closed");
3482
3752
  };
@@ -3484,8 +3754,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3484
3754
  close,
3485
3755
  nativeCacheHit,
3486
3756
  startedSimulator,
3757
+ targetKind,
3487
3758
  timings: { ...timings },
3488
- udid: device.udid,
3759
+ udid,
3489
3760
  rebuild: async () => {
3490
3761
  if (closed)
3491
3762
  throw new Error("iOS development session is closed.");
@@ -3498,22 +3769,17 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3498
3769
  throw new Error("iOS development session is closed.");
3499
3770
  transition("launching");
3500
3771
  try {
3501
- await requireSuccess2([
3502
- project.xcrun,
3503
- "simctl",
3504
- "launch",
3505
- "--terminate-running-process",
3506
- device.udid,
3507
- project.config.appId
3508
- ], "iOS app relaunch", run, { signal: options.signal });
3772
+ await relaunchTarget(udid);
3509
3773
  transition("ready");
3510
- log(`iOS app relaunched on ${device.udid}.`);
3774
+ log(`iOS app relaunched on the selected ${targetKind}.`);
3511
3775
  } catch (error) {
3512
3776
  transition("failed");
3513
3777
  throw error;
3514
3778
  }
3515
3779
  },
3516
3780
  screenshot: async (destination) => {
3781
+ if (deviceIdentifier)
3782
+ throw new Error("Physical iOS screenshots are captured in Xcode Device Hub; the CLI never records a device screen automatically.");
3517
3783
  const resolved = resolve7(project.projectRoot, destination);
3518
3784
  if (!isInside2(project.projectRoot, resolved))
3519
3785
  throw new Error("iOS screenshot destination must remain inside the project.");
@@ -3522,7 +3788,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3522
3788
  project.xcrun,
3523
3789
  "simctl",
3524
3790
  "io",
3525
- device.udid,
3791
+ udid,
3526
3792
  "screenshot",
3527
3793
  resolved
3528
3794
  ], "iOS simulator screenshot", run, { signal: options.signal });
@@ -3535,6 +3801,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3535
3801
  } catch (error) {
3536
3802
  transition("failed");
3537
3803
  await closeLogs();
3804
+ await caEnrollmentServer?.close().catch(() => {
3805
+ return;
3806
+ });
3538
3807
  await repairAbsoluteIosDevSession(project.projectRoot);
3539
3808
  throw error;
3540
3809
  }
@@ -3543,6 +3812,7 @@ var init_iosSimulatorController = __esm(() => {
3543
3812
  init_emulatorDoctor();
3544
3813
  init_capacitorProject();
3545
3814
  init_iosRelease();
3815
+ init_iosPhysicalDeviceTransport();
3546
3816
  init_getDurationString();
3547
3817
  SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
3548
3818
  BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
@@ -3554,6 +3824,7 @@ var init_iosSimulatorController = __esm(() => {
3554
3824
  ["fingerprinting", "fingerprint"],
3555
3825
  ["booting", "simulator"],
3556
3826
  ["connecting", "device ready"],
3827
+ ["enrolling-trust", "HTTPS trust"],
3557
3828
  ["checking-native", "app check"],
3558
3829
  ["building", "Xcode"],
3559
3830
  ["installing", "install"],
@@ -3566,9 +3837,10 @@ var init_iosSimulatorController = __esm(() => {
3566
3837
  var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t", ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
3567
3838
 
3568
3839
  // src/mobile/remoteMacProtocol.ts
3569
- import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
3570
- import { chmod, mkdir as mkdir5, readFile as readFile6, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3840
+ import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
3841
+ import { chmod, mkdir as mkdir5, readFile as readFile7, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3571
3842
  import { homedir as homedir4 } from "os";
3843
+ import { isIP as isIP3 } from "net";
3572
3844
  import {
3573
3845
  dirname as dirname6,
3574
3846
  isAbsolute as isAbsolute4,
@@ -3583,7 +3855,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3583
3855
  profiles: {}
3584
3856
  }), loadStore = async (path = defaultProfilePath()) => {
3585
3857
  try {
3586
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3858
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3587
3859
  if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
3588
3860
  throw new Error("Unsupported remote Mac profile format.");
3589
3861
  for (const [key, profile] of Object.entries(parsed.profiles)) {
@@ -3600,7 +3872,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3600
3872
  }
3601
3873
  }, saveStore = async (store, path = defaultProfilePath()) => {
3602
3874
  await mkdir5(dirname6(path), { recursive: true });
3603
- const temporary = `${path}.${randomUUID3()}.tmp`;
3875
+ const temporary = `${path}.${randomUUID4()}.tmp`;
3604
3876
  await writeFile5(temporary, `${JSON.stringify(store, null, 2)}
3605
3877
  `, {
3606
3878
  mode: 384
@@ -3682,6 +3954,18 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3682
3954
  if (!xcodeVersion?.startsWith("Xcode "))
3683
3955
  throw new Error("The remote Mac must have full Xcode installed and selected.");
3684
3956
  return { bunPath, home, os: operatingSystem, xcodeVersion };
3957
+ }, inspectAbsoluteRemoteMacLanHost = async (profile, transport) => {
3958
+ const capture = transport?.capture ?? defaultTransport.capture;
3959
+ const script = `default_interface="$(/sbin/route -n get default 2>/dev/null | /usr/bin/awk '/interface:/{print $2; exit}')"; for interface in "$default_interface" en0 en1; do [ -n "$interface" ] || continue; address="$(/usr/sbin/ipconfig getifaddr "$interface" 2>/dev/null || true)"; if [ -n "$address" ]; then printf '%s\\n' "$address"; exit 0; fi; done; exit 1`;
3960
+ const result = await capture([
3961
+ ...absoluteRemoteMacSshBase(profile),
3962
+ "/bin/sh -lc",
3963
+ shellQuote(script)
3964
+ ]);
3965
+ const host = requireRemoteSuccess(result, "Remote Mac LAN address discovery").trim();
3966
+ if (isIP3(host) === 0)
3967
+ throw new Error("The Remote Mac did not report a device-reachable LAN address.");
3968
+ return host;
3685
3969
  }, listAbsoluteRemoteMacProfiles = async (profilePath) => {
3686
3970
  const store = await loadStore(profilePath);
3687
3971
  return {
@@ -3748,7 +4032,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3748
4032
  ]);
3749
4033
  if (verified.exitCode === 0)
3750
4034
  return { ...artifact, remotePath, uploaded: false };
3751
- const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
4035
+ const temporary = posix.join(directory, `.agent-${randomUUID4()}.tmp`);
3752
4036
  const installScript = [
3753
4037
  "set -eu",
3754
4038
  "umask 077",
@@ -3835,7 +4119,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3835
4119
  }), absoluteRemoteProjectSyncCommands = (project) => {
3836
4120
  const current = project.remoteProjectRoot;
3837
4121
  const parent = posix.dirname(current);
3838
- const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
4122
+ const staging = posix.join(parent, `.incoming-${randomUUID4()}`);
3839
4123
  const previous = posix.join(parent, ".previous");
3840
4124
  const script = [
3841
4125
  "set -eu",
@@ -3924,7 +4208,13 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3924
4208
  await syncProject(options.project);
3925
4209
  const syncDuration = performance.now() - syncStartedAt;
3926
4210
  const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
3927
- const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile6(options.certificateAuthorityPath)).toString("base64url") : undefined;
4211
+ const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile7(options.certificateAuthorityPath)).toString("base64url") : undefined;
4212
+ const physicalDevice = options.deviceIdentifier !== undefined;
4213
+ let relayPort;
4214
+ if (physicalDevice)
4215
+ relayPort = options.port <= 49151 ? options.port + 16384 : options.port - 16384;
4216
+ if (physicalDevice && !options.serverHost)
4217
+ throw new Error("Remote physical iOS development requires the Remote Mac LAN host.");
3928
4218
  const remoteCommand = [
3929
4219
  `cd ${shellQuote(options.project.remoteProjectRoot)}`,
3930
4220
  "&&",
@@ -3939,14 +4229,22 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3939
4229
  "--certificate-authority",
3940
4230
  shellQuote(encodedCertificateAuthority)
3941
4231
  ] : [],
3942
- ...options.https ? ["--https"] : []
4232
+ ...options.https ? ["--https"] : [],
4233
+ ...options.deviceIdentifier ? [
4234
+ "--ios-device",
4235
+ shellQuote(options.deviceIdentifier),
4236
+ "--server-host",
4237
+ shellQuote(options.serverHost ?? ""),
4238
+ "--relay-port",
4239
+ String(relayPort)
4240
+ ] : []
3943
4241
  ].join(" ");
3944
4242
  const command = [
3945
4243
  ...absoluteRemoteMacSshBase(options.project.profile),
3946
4244
  "-o",
3947
4245
  "ExitOnForwardFailure=yes",
3948
4246
  "-R",
3949
- `${options.port}:127.0.0.1:${options.port}`,
4247
+ physicalDevice ? `127.0.0.1:${relayPort}:127.0.0.1:${options.port}` : `${options.port}:127.0.0.1:${options.port}`,
3950
4248
  "/bin/sh -lc",
3951
4249
  shellQuote(remoteCommand)
3952
4250
  ];
@@ -4034,7 +4332,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
4034
4332
  };
4035
4333
  options.log?.(`Remote Mac connected (${options.project.profile.name}); agent ${agent.uploaded ? "uploaded" : "cache hit"}, project synced, and iOS ready in ${totalDuration.toFixed(2)}ms.`);
4036
4334
  const request = (commandName) => {
4037
- const id = randomUUID3();
4335
+ const id = randomUUID4();
4038
4336
  const response = new Promise((resolve8, reject) => pending.set(id, { reject, resolve: resolve8 }));
4039
4337
  process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
4040
4338
  `);
@@ -4067,6 +4365,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
4067
4365
  close,
4068
4366
  nativeCacheHit: currentReady.nativeCacheHit,
4069
4367
  startedSimulator: currentReady.startedSimulator,
4368
+ targetKind: currentReady.targetKind,
4070
4369
  timings: currentReady.timings,
4071
4370
  udid: currentReady.udid,
4072
4371
  rebuild: async () => {
@@ -4136,8 +4435,8 @@ import {
4136
4435
  readFileSync as readFileSync7,
4137
4436
  rmSync
4138
4437
  } from "fs";
4139
- import { X509Certificate } from "crypto";
4140
- import { isIP } from "net";
4438
+ import { X509Certificate as X509Certificate2 } from "crypto";
4439
+ import { isIP as isIP4 } from "net";
4141
4440
  import { platform as platform2 } from "os";
4142
4441
  import { join as join12 } from "path";
4143
4442
  var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE_HOSTS, CERTIFICATE_HOSTNAME_PATTERN, devLog = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[36m[dev]\x1B[0m ${msg}`), devWarn = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[33m[dev]\x1B[0m \x1B[33m${msg}\x1B[0m`), certFilesExist = () => existsSync4(CERT_PATH) && existsSync4(KEY_PATH), normalizeDevCertificateHosts = (hosts = []) => {
@@ -4146,7 +4445,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4146
4445
  const value = host.trim().toLowerCase();
4147
4446
  if (!value || value === "0.0.0.0" || value === "::")
4148
4447
  continue;
4149
- if (isIP(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
4448
+ if (isIP4(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
4150
4449
  throw new TypeError(`Invalid development certificate host: ${host}`);
4151
4450
  }
4152
4451
  normalized.add(value);
@@ -4155,10 +4454,10 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4155
4454
  }, certificateIsUsable = (hosts) => {
4156
4455
  try {
4157
4456
  const certPem = readFileSync7(CERT_PATH, "utf-8");
4158
- const certificate = new X509Certificate(certPem);
4457
+ const certificate = new X509Certificate2(certPem);
4159
4458
  if (new Date(certificate.validTo).getTime() <= Date.now())
4160
4459
  return false;
4161
- return normalizeDevCertificateHosts(hosts).every((host) => isIP(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
4460
+ return normalizeDevCertificateHosts(hosts).every((host) => isIP4(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
4162
4461
  } catch {
4163
4462
  return false;
4164
4463
  }
@@ -4186,7 +4485,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4186
4485
  throw new Error(`mkcert failed: ${err}`);
4187
4486
  }
4188
4487
  }, generateSelfSigned = (hosts = []) => {
4189
- const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP(host) ? "IP" : "DNS"}:${host}`).join(",");
4488
+ const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP4(host) ? "IP" : "DNS"}:${host}`).join(",");
4190
4489
  const proc = Bun.spawnSync([
4191
4490
  "openssl",
4192
4491
  "req",
@@ -5834,7 +6133,7 @@ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-
5834
6133
 
5835
6134
  // src/mobile/buildRelease.ts
5836
6135
  import { createHash as createHash8 } from "crypto";
5837
- import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
6136
+ import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
5838
6137
  import { basename as basename6, dirname as dirname9, extname as extname3, join as join16, relative as relative9, resolve as resolve13 } from "path";
5839
6138
  var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATIC_SCRIPT_PATTERN, rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
5840
6139
  if (path.endsWith("/htmx.min.js"))
@@ -5856,7 +6155,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5856
6155
  }
5857
6156
  let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
5858
6157
  if (metadata.framework === "html" || metadata.framework === "htmx") {
5859
- const source = await readFile7(resolvedAssetPath, "utf8");
6158
+ const source = await readFile8(resolvedAssetPath, "utf8");
5860
6159
  const rewritten = rewriteStaticScriptPaths(source, manifest);
5861
6160
  const documentHash = sha256(new TextEncoder().encode(rewritten));
5862
6161
  resolvedAssetPath = join16(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
@@ -5870,8 +6169,8 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5870
6169
  ].map((key) => manifest[key]).find((path) => typeof path === "string");
5871
6170
  const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
5872
6171
  const [bytes, styleBytes] = await Promise.all([
5873
- readFile7(resolvedAssetPath),
5874
- resolvedStylePath ? readFile7(resolvedStylePath) : undefined
6172
+ readFile8(resolvedAssetPath),
6173
+ resolvedStylePath ? readFile8(resolvedStylePath) : undefined
5875
6174
  ]);
5876
6175
  const bundlePath = `/${relative9(resolve13(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
5877
6176
  const styleBundlePath = resolvedStylePath ? `/${relative9(resolve13(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
@@ -5890,7 +6189,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5890
6189
  }, buildAbsoluteMobileCompatibilityRelease = async (options) => {
5891
6190
  const [captured, producerBytes] = await Promise.all([
5892
6191
  captureAbsoluteMobileRouteGraph(options.app),
5893
- readFile7(options.producerPath)
6192
+ readFile8(options.producerPath)
5894
6193
  ]);
5895
6194
  if (captured.length === 0) {
5896
6195
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -6070,7 +6369,7 @@ import {
6070
6369
  copyFile as copyFile4,
6071
6370
  mkdir as mkdir8,
6072
6371
  mkdtemp as mkdtemp3,
6073
- readFile as readFile8,
6372
+ readFile as readFile9,
6074
6373
  rename as rename6,
6075
6374
  rm as rm5,
6076
6375
  writeFile as writeFile7
@@ -6128,7 +6427,7 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
6128
6427
  const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
6129
6428
  const subpath = specifier.slice(packageName.length);
6130
6429
  const packageDirectory = join17(resolve14(projectRoot), "node_modules", packageName);
6131
- const manifest = JSON.parse(await readFile8(join17(packageDirectory, "package.json"), "utf8"));
6430
+ const manifest = JSON.parse(await readFile9(join17(packageDirectory, "package.json"), "utf8"));
6132
6431
  const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
6133
6432
  const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
6134
6433
  const target = importEntryTarget(entry);
@@ -6230,7 +6529,7 @@ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => creat
6230
6529
  ...localStylePath ? { localStylePath } : {}
6231
6530
  };
6232
6531
  }, absoluteClientImports = async (sourcePath, buildDirectory) => {
6233
- const source = await readFile8(sourcePath, "utf8");
6532
+ const source = await readFile9(sourcePath, "utf8");
6234
6533
  const extension = extname4(sourcePath).toLowerCase();
6235
6534
  let scriptLoader;
6236
6535
  if (extension === ".tsx")
@@ -6372,7 +6671,7 @@ import {
6372
6671
  access as access6,
6373
6672
  mkdir as mkdir9,
6374
6673
  mkdtemp as mkdtemp4,
6375
- readFile as readFile9,
6674
+ readFile as readFile10,
6376
6675
  rename as rename7,
6377
6676
  rm as rm6,
6378
6677
  writeFile as writeFile8
@@ -6472,7 +6771,7 @@ var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1, CURRENT_BUNDLE_FILE = "curre
6472
6771
  }, readAbsoluteMobileMaterializedReleases = async (root) => {
6473
6772
  const resolvedRoot = resolvePath2(root);
6474
6773
  try {
6475
- const serialized = await readFile9(join18(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
6774
+ const serialized = await readFile10(join18(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
6476
6775
  const parsed = JSON.parse(serialized);
6477
6776
  const index = parseBundleIndex(parsed);
6478
6777
  const bundleRoot = join18(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
@@ -7174,7 +7473,7 @@ var init_deviceCapabilities = __esm(() => {
7174
7473
  });
7175
7474
 
7176
7475
  // src/mobile/buildPipeline.ts
7177
- import { readFile as readFile10 } from "fs/promises";
7476
+ import { readFile as readFile11 } from "fs/promises";
7178
7477
  import { join as join21, resolve as resolve17 } from "path";
7179
7478
  import { pathToFileURL } from "url";
7180
7479
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes")), isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string"), serverExportName = (loaded, app) => {
@@ -7208,7 +7507,7 @@ var isElysiaApp = (value) => typeof value === "object" && value !== null && type
7208
7507
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
7209
7508
  const root = join21(buildDirectory, ".absolutejs", "mobile-compatibility");
7210
7509
  const [manifestSource, previous] = await Promise.all([
7211
- readFile10(join21(buildDirectory, "manifest.json"), "utf8"),
7510
+ readFile11(join21(buildDirectory, "manifest.json"), "utf8"),
7212
7511
  readAbsoluteMobileMaterializedReleases(root)
7213
7512
  ]);
7214
7513
  const manifest = JSON.parse(manifestSource);
@@ -10914,8 +11213,8 @@ export { value };
10914
11213
  host2.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => fileName === virtualPath ? ts6.createSourceFile(fileName, source, languageVersion, true) : getSourceFile(fileName, languageVersion, onError, shouldCreate);
10915
11214
  const fileExists = host2.fileExists.bind(host2);
10916
11215
  host2.fileExists = (fileName) => fileName === virtualPath ? true : fileExists(fileName);
10917
- const readFile11 = host2.readFile.bind(host2);
10918
- host2.readFile = (fileName) => fileName === virtualPath ? source : readFile11(fileName);
11216
+ const readFile12 = host2.readFile.bind(host2);
11217
+ host2.readFile = (fileName) => fileName === virtualPath ? source : readFile12(fileName);
10919
11218
  const program = ts6.createProgram([virtualPath], options, host2);
10920
11219
  const checker = program.getTypeChecker();
10921
11220
  const sourceFile = program.getSourceFile(virtualPath);
@@ -16538,10 +16837,10 @@ var init_compile = __esm(() => {
16538
16837
  });
16539
16838
 
16540
16839
  // src/mobile/nativeDeepLinks.ts
16541
- import { readFile as readFile11, rename as rename8, writeFile as writeFile9 } from "fs/promises";
16840
+ import { readFile as readFile12, rename as rename8, writeFile as writeFile9 } from "fs/promises";
16542
16841
  import { join as join46 } from "path";
16543
16842
  var START_MARKER = "<!-- absolutejs:deep-links:start -->", END_MARKER = "<!-- absolutejs:deep-links:end -->", IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements", NOT_FOUND = -1, escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"), writeChangedFile = async (path, source) => {
16544
- const current = await readFile11(path, "utf8");
16843
+ const current = await readFile12(path, "utf8");
16545
16844
  if (current === source)
16546
16845
  return false;
16547
16846
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -16589,7 +16888,7 @@ ${hosts}
16589
16888
  `;
16590
16889
  }, configureAndroid = async (config) => {
16591
16890
  const path = join46(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16592
- const source = await readFile11(path, "utf8");
16891
+ const source = await readFile12(path, "utf8");
16593
16892
  const mainActivity = source.indexOf('android:name=".MainActivity"');
16594
16893
  if (mainActivity === NOT_FOUND) {
16595
16894
  throw new TypeError("Android MainActivity was not found.");
@@ -16613,7 +16912,7 @@ ${hosts}
16613
16912
  ${END_MARKER}
16614
16913
  `, configureIosInfo = async (config) => {
16615
16914
  const path = join46(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16616
- const source = await readFile11(path, "utf8");
16915
+ const source = await readFile12(path, "utf8");
16617
16916
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
16618
16917
  ${END_MARKER}
16619
16918
  `;
@@ -16637,7 +16936,7 @@ ${domains}
16637
16936
  const path = join46(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
16638
16937
  let current = "";
16639
16938
  try {
16640
- current = await readFile11(path, "utf8");
16939
+ current = await readFile12(path, "utf8");
16641
16940
  } catch (error) {
16642
16941
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
16643
16942
  throw error;
@@ -16652,7 +16951,7 @@ ${domains}
16652
16951
  return true;
16653
16952
  }, configureIosProject = async (config) => {
16654
16953
  const path = join46(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
16655
- const source = await readFile11(path, "utf8");
16954
+ const source = await readFile12(path, "utf8");
16656
16955
  const declarations = [
16657
16956
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
16658
16957
  ].map((match) => match[1]);
@@ -16690,10 +16989,10 @@ ${domains}
16690
16989
  var init_nativeDeepLinks = () => {};
16691
16990
 
16692
16991
  // src/mobile/nativeDeviceCapabilities.ts
16693
- import { readFile as readFile12, rename as rename9, writeFile as writeFile10 } from "fs/promises";
16992
+ import { readFile as readFile13, rename as rename9, writeFile as writeFile10 } from "fs/promises";
16694
16993
  import { join as join47 } from "path";
16695
16994
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->", NOT_FOUND2 = -1, IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001", IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002", PUSH_START_MARKER = "absolutejs:push-notifications:start", PUSH_END_MARKER = "absolutejs:push-notifications:end", escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"), writeChangedFile2 = async (path, source) => {
16696
- const current = await readFile12(path, "utf8");
16995
+ const current = await readFile13(path, "utf8");
16697
16996
  if (current === source)
16698
16997
  return false;
16699
16998
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -16711,7 +17010,7 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
16711
17010
  return writeChangedFile2(path, source);
16712
17011
  }, optionalSource = async (path) => {
16713
17012
  try {
16714
- return await readFile12(path, "utf8");
17013
+ return await readFile13(path, "utf8");
16715
17014
  } catch (error) {
16716
17015
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
16717
17016
  return null;
@@ -16819,7 +17118,7 @@ ${entries}
16819
17118
  if (requirements.iosPrivacyAccessedApis.length === 0)
16820
17119
  return false;
16821
17120
  const projectPath = join47(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
16822
- const project = await readFile12(projectPath, "utf8");
17121
+ const project = await readFile13(projectPath, "utf8");
16823
17122
  return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
16824
17123
  }, addIosPrivacyProjectReference = (source) => {
16825
17124
  const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
@@ -16870,7 +17169,7 @@ ${next.slice(index)}`;
16870
17169
  return next;
16871
17170
  }, configureIos2 = async (config, plan) => {
16872
17171
  const path = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16873
- const source = await readFile12(path, "utf8");
17172
+ const source = await readFile13(path, "utf8");
16874
17173
  const requirements = absoluteDeviceNativeRequirements(plan);
16875
17174
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
16876
17175
  const ownedStart = source.indexOf(START_MARKER2);
@@ -16960,7 +17259,7 @@ ${content}
16960
17259
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
16961
17260
  }, configureAndroid2 = async (config, plan) => {
16962
17261
  const path = join47(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16963
- const source = await readFile12(path, "utf8");
17262
+ const source = await readFile13(path, "utf8");
16964
17263
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
16965
17264
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
16966
17265
  `);
@@ -17019,10 +17318,10 @@ var init_nativeDeviceCapabilities = __esm(() => {
17019
17318
  });
17020
17319
 
17021
17320
  // src/mobile/nativeBackgroundSync.ts
17022
- import { readFile as readFile13, rename as rename10, writeFile as writeFile11 } from "fs/promises";
17321
+ import { readFile as readFile14, rename as rename10, writeFile as writeFile11 } from "fs/promises";
17023
17322
  import { join as join48 } from "path";
17024
17323
  var writeChanged = async (path, source) => {
17025
- const current = await readFile13(path, "utf8");
17324
+ const current = await readFile14(path, "utf8");
17026
17325
  if (current === source)
17027
17326
  return false;
17028
17327
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -17104,10 +17403,10 @@ ${makeRegion(values)} </array>
17104
17403
  return { changed: false };
17105
17404
  const identifier = `${config.appId}.absolutejs.background-sync`;
17106
17405
  const infoPath = join48(config.nativeProjectDirectory, "ios/App/App/Info.plist");
17107
- const info2 = await readFile13(infoPath, "utf8");
17406
+ const info2 = await readFile14(infoPath, "utf8");
17108
17407
  const nextInfo = ensurePlistArrayValues(ensurePlistArrayValues(info2, "BGTaskSchedulerPermittedIdentifiers", [identifier], "background-sync-identifiers"), "UIBackgroundModes", ["fetch", "processing"], "background-sync-modes");
17109
17408
  const delegatePath = join48(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
17110
- let delegate = await readFile13(delegatePath, "utf8");
17409
+ let delegate = await readFile14(delegatePath, "utf8");
17111
17410
  if (!delegate.includes("import AbsoluteSyncCapacitor")) {
17112
17411
  const importIndex = delegate.lastIndexOf("import Capacitor");
17113
17412
  if (importIndex < 0)
@@ -17141,7 +17440,7 @@ var init_nativeBackgroundSync = __esm(() => {
17141
17440
  import {
17142
17441
  access as access7,
17143
17442
  mkdir as mkdir10,
17144
- readFile as readFile14,
17443
+ readFile as readFile15,
17145
17444
  rename as rename11,
17146
17445
  rm as rm7,
17147
17446
  writeFile as writeFile12
@@ -17199,7 +17498,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
17199
17498
  }, writeAtomic = async (path, source) => {
17200
17499
  let current;
17201
17500
  try {
17202
- current = await readFile14(path, "utf8");
17501
+ current = await readFile15(path, "utf8");
17203
17502
  } catch (error) {
17204
17503
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
17205
17504
  throw error;
@@ -17222,7 +17521,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
17222
17521
  const path = resolve37(root, OWNERSHIP_FILE);
17223
17522
  let ownership;
17224
17523
  try {
17225
- ownership = JSON.parse(await readFile14(path, "utf8"));
17524
+ ownership = JSON.parse(await readFile15(path, "utf8"));
17226
17525
  } catch {
17227
17526
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
17228
17527
  }
@@ -17735,7 +18034,7 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
17735
18034
  };
17736
18035
 
17737
18036
  // src/mobile/releaseDoctor.ts
17738
- import { access as access8, readFile as readFile15, readdir as readdir4 } from "fs/promises";
18037
+ import { access as access8, readFile as readFile16, readdir as readdir4 } from "fs/promises";
17739
18038
  import { dirname as dirname29, extname as extname8, join as join49, relative as relative24 } from "path";
17740
18039
  var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17741
18040
  try {
@@ -17749,7 +18048,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17749
18048
  return findHmrAsset(path);
17750
18049
  if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
17751
18050
  return;
17752
- const source = await readFile15(path, "utf8");
18051
+ const source = await readFile16(path, "utf8");
17753
18052
  return HMR_ASSET_PATTERN.test(source) ? path : undefined;
17754
18053
  }, findHmrAsset = async (root) => {
17755
18054
  if (!await pathExists5(root))
@@ -17788,18 +18087,18 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17788
18087
  if (!await pathExists5(nativeConfigPath)) {
17789
18088
  return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
17790
18089
  }
17791
- const unsafe = isUnsafeCapacitorConfig(await readFile15(nativeConfigPath, "utf8"));
18090
+ const unsafe = isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"));
17792
18091
  return unsafe ? fail5("android.capacitor-config", "Android Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync android`; do not ship development transport overrides.") : pass("android.capacitor-config", "Android Capacitor config contains no development transport overrides.", nativeConfigPath);
17793
18092
  }, manifestReleaseCheck = async (manifestPath) => {
17794
18093
  if (!await pathExists5(manifestPath)) {
17795
18094
  return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
17796
18095
  }
17797
- const source = await readFile15(manifestPath, "utf8");
18096
+ const source = await readFile16(manifestPath, "utf8");
17798
18097
  const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
17799
18098
  const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
17800
18099
  const networkConfigPath = networkConfigName ? join49(dirname29(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
17801
18100
  const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
17802
- const developmentTrustContents = networkConfigPath ? await readFile15(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
18101
+ const developmentTrustContents = networkConfigPath ? await readFile16(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
17803
18102
  const developmentTrust = developmentTrustReference || developmentTrustContents;
17804
18103
  return cleartext || developmentTrust ? fail5("android.cleartext", developmentTrust ? "Android still references the AbsoluteJS development certificate authority." : "Android explicitly permits cleartext traffic.", manifestPath, "Run `absolute mobile sync android`; do not ship development transport or trust overrides.") : pass("android.cleartext", "Android does not explicitly permit cleartext traffic.", manifestPath);
17805
18104
  }, hmrAssetsReleaseCheck = async (publicRoot) => {
@@ -17831,7 +18130,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17831
18130
  if (!config.platforms.includes("android") || permissions.length === 0)
17832
18131
  return;
17833
18132
  const path = join49(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
17834
- const source = await readFile15(path, "utf8");
18133
+ const source = await readFile16(path, "utf8");
17835
18134
  const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
17836
18135
  if (missing.length === 0)
17837
18136
  return;
@@ -17840,7 +18139,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17840
18139
  if (!config.platforms.includes("ios") || purposes.length === 0)
17841
18140
  return;
17842
18141
  const path = join49(config.nativeProjectDirectory, "ios/App/App/Info.plist");
17843
- const source = await readFile15(path, "utf8");
18142
+ const source = await readFile16(path, "utf8");
17844
18143
  const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
17845
18144
  if (missing.length === 0)
17846
18145
  return;
@@ -17893,7 +18192,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17893
18192
  }
17894
18193
  if (!await pathExists5(nativeConfigPath)) {
17895
18194
  checks.push(fail5("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
17896
- } else if (isUnsafeCapacitorConfig(await readFile15(nativeConfigPath, "utf8"))) {
18195
+ } else if (isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"))) {
17897
18196
  checks.push(fail5("ios.capacitor-config", "iOS Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync ios`; do not ship development transport overrides."));
17898
18197
  } else {
17899
18198
  checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
@@ -17901,7 +18200,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17901
18200
  if (!await pathExists5(infoPath)) {
17902
18201
  checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
17903
18202
  } else {
17904
- const info2 = await readFile15(infoPath, "utf8");
18203
+ const info2 = await readFile16(infoPath, "utf8");
17905
18204
  checks.push(/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info2) ? fail5("ios.transport-security", "iOS App Transport Security permits arbitrary network loads.", infoPath, "Remove NSAllowsArbitraryLoads from the release Info.plist.") : pass("ios.transport-security", "iOS App Transport Security does not permit arbitrary loads.", infoPath));
17906
18205
  }
17907
18206
  const hmrAsset = await findHmrAsset(publicRoot);
@@ -17954,7 +18253,7 @@ import {
17954
18253
  copyFile as copyFile5,
17955
18254
  mkdir as mkdir12,
17956
18255
  mkdtemp as mkdtemp5,
17957
- readFile as readFile16,
18256
+ readFile as readFile17,
17958
18257
  rename as rename12,
17959
18258
  rm as rm8,
17960
18259
  stat as stat2,
@@ -18010,7 +18309,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18010
18309
  artifactPath
18011
18310
  ]);
18012
18311
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
18013
- }, sha256File2 = async (path) => createHash12("sha256").update(await readFile16(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
18312
+ }, sha256File2 = async (path) => createHash12("sha256").update(await readFile17(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
18014
18313
  const root = resolve39(projectRoot);
18015
18314
  const output = resolve39(root, requested ?? ".absolutejs/mobile/releases/android");
18016
18315
  const projectRelative = relative25(root, output);
@@ -18023,7 +18322,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18023
18322
  const artifactName = "app-release.aab";
18024
18323
  const destination = join50(releaseRoot, artifactName);
18025
18324
  if (await pathExists6(releaseRoot)) {
18026
- const existing = requireManifestIdentity(JSON.parse(await readFile16(join50(releaseRoot, "release.json"), "utf8")), metadata);
18325
+ const existing = requireManifestIdentity(JSON.parse(await readFile17(join50(releaseRoot, "release.json"), "utf8")), metadata);
18027
18326
  const [installedBytes, installedSha256] = await Promise.all([
18028
18327
  stat2(destination).then(({ size }) => size),
18029
18328
  sha256File2(destination)
@@ -18070,7 +18369,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18070
18369
  const host2 = options.host ?? detectAbsoluteMobileHost();
18071
18370
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
18072
18371
  const nativeDirectory = join50(options.config.nativeProjectDirectory, "android");
18073
- const manifest = requireManifest2(JSON.parse(await readFile16(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
18372
+ const manifest = requireManifest2(JSON.parse(await readFile17(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
18074
18373
  if (manifest.appId !== options.config.appId) {
18075
18374
  throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
18076
18375
  }
@@ -18134,7 +18433,7 @@ var init_androidRelease = __esm(() => {
18134
18433
  });
18135
18434
 
18136
18435
  // src/mobile/iosConformance.ts
18137
- import { readFile as readFile17, stat as stat3 } from "fs/promises";
18436
+ import { readFile as readFile18, stat as stat3 } from "fs/promises";
18138
18437
  var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
18139
18438
  const match = HMR_LINE.exec(line);
18140
18439
  if (!match)
@@ -18169,7 +18468,7 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
18169
18468
  if (Date.now() > deadline)
18170
18469
  throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
18171
18470
  options.signal?.throwIfAborted();
18172
- const contents = await readFile17(options.logPath).catch(() => Buffer.alloc(0));
18471
+ const contents = await readFile18(options.logPath).catch(() => Buffer.alloc(0));
18173
18472
  if (contents.byteLength < offset) {
18174
18473
  offset = 0;
18175
18474
  buffered = "";
@@ -18193,7 +18492,7 @@ var init_iosConformance = __esm(() => {
18193
18492
  });
18194
18493
 
18195
18494
  // src/mobile/nativeTestReport.ts
18196
- import { mkdir as mkdir13, readFile as readFile18, writeFile as writeFile15 } from "fs/promises";
18495
+ import { mkdir as mkdir13, readFile as readFile19, writeFile as writeFile15 } from "fs/promises";
18197
18496
  import { join as join51 } from "path";
18198
18497
  var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sanitizeNativeReportText = (value) => value.replace(nativeCredentialPattern, "[REDACTED]").replace(bearerPattern, "Bearer [REDACTED]").replace(secretPattern, "$1$2[REDACTED]").replace(coordinatePattern, "$1$2[REDACTED]").replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/giu, "$1?[REDACTED]"), markdownCell = (value) => sanitizeNativeReportText(value).replaceAll("|", "\\|").replaceAll(`
18199
18498
  `, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
@@ -18272,7 +18571,7 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
18272
18571
  reportVersion: 1,
18273
18572
  run: options.run
18274
18573
  }), readPackageVersionForNativeReport = async (packageJsonPath) => {
18275
- const manifest = JSON.parse(await readFile18(packageJsonPath, "utf8"));
18574
+ const manifest = JSON.parse(await readFile19(packageJsonPath, "utf8"));
18276
18575
  if (typeof manifest !== "object" || manifest === null)
18277
18576
  return "unknown";
18278
18577
  const version2 = Reflect.get(manifest, "version");
@@ -18613,11 +18912,11 @@ var exports_mobile = {};
18613
18912
  __export(exports_mobile, {
18614
18913
  runMobile: () => runMobile
18615
18914
  });
18616
- import { access as access11, mkdir as mkdir14, readFile as readFile19, writeFile as writeFile16 } from "fs/promises";
18915
+ import { access as access11, mkdir as mkdir14, readFile as readFile20, writeFile as writeFile16 } from "fs/promises";
18617
18916
  import { join as join52, resolve as resolve41 } from "path";
18618
18917
  import { createInterface } from "readline/promises";
18619
18918
  var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
18620
- const manifest = JSON.parse(await readFile19(join52(projectRoot, "package.json"), "utf8"));
18919
+ const manifest = JSON.parse(await readFile20(join52(projectRoot, "package.json"), "utf8"));
18621
18920
  if (!isRecord15(manifest))
18622
18921
  throw new TypeError("Application package.json must contain an object.");
18623
18922
  const names = new Set;
@@ -18630,7 +18929,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
18630
18929
  return names;
18631
18930
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
18632
18931
  try {
18633
- const manifest = JSON.parse(await readFile19(join52(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
18932
+ const manifest = JSON.parse(await readFile20(join52(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
18634
18933
  return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
18635
18934
  } catch {
18636
18935
  return;
@@ -21208,13 +21507,15 @@ var androidToolchainReady = (checks, target = "emulator") => {
21208
21507
  ]);
21209
21508
  return checks.every((check) => check.platform !== "android" || target === "device" && deviceOnlyChecks.has(check.id) || check.status !== "fail" && check.status !== "warn");
21210
21509
  };
21211
- var iosToolchainReady = (checks) => checks.every((check) => check.platform !== "ios" || check.status !== "fail" && check.status !== "warn");
21510
+ var iosToolchainReady = (checks, target = "simulator") => checks.every((check) => check.platform !== "ios" || target === "device" && check.id === "ios.runtime" || check.status !== "fail" && check.status !== "warn");
21212
21511
  var dev = async (serverEntry, configPath2, options = {}) => {
21213
21512
  let httpsEnabled = false;
21214
21513
  let devCertificateAuthorityPath = null;
21215
21514
  let resolvedDev;
21216
21515
  let buildDirectory = resolve8(process.cwd(), "build");
21217
21516
  let mobileConfig;
21517
+ let iosPhysicalServerHost;
21518
+ let selectedRemoteMacProfile;
21218
21519
  try {
21219
21520
  const config = await loadConfig(configPath2);
21220
21521
  mobileConfig = config?.mobile;
@@ -21229,17 +21530,35 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21229
21530
  resolvedDev = resolveDevConfig(undefined);
21230
21531
  httpsEnabled = resolvedDev.https;
21231
21532
  }
21232
- if (options.androidDevice && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
21533
+ if ((options.androidDevice || options.iosDevice && detectAbsoluteMobileHost() === "macos") && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
21233
21534
  resolvedDev.host = "0.0.0.0";
21234
21535
  }
21235
- if (httpsEnabled)
21236
- devCertificateAuthorityPath = await setupHttpsCert(options.androidDevice ? [mobileReachableHost(resolvedDev.host)] : [resolvedDev.host]);
21237
- if (options.androidDevice && !mobileConfig) {
21238
- throw new TypeError("--android-device requires an absolute.config.ts mobile configuration.");
21536
+ if ((options.androidDevice || options.iosDevice) && !mobileConfig) {
21537
+ throw new TypeError("Physical-device development requires an absolute.config.ts mobile configuration.");
21239
21538
  }
21240
21539
  if (options.androidDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("android")) {
21241
21540
  throw new TypeError("--android-device requires android in mobile.platforms.");
21242
21541
  }
21542
+ if (options.iosDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("ios")) {
21543
+ throw new TypeError("--ios-device requires ios in mobile.platforms.");
21544
+ }
21545
+ if (options.iosDevice) {
21546
+ if (detectAbsoluteMobileHost() === "macos")
21547
+ iosPhysicalServerHost = mobileReachableHost(resolvedDev.host);
21548
+ else {
21549
+ selectedRemoteMacProfile = await getAbsoluteRemoteMacProfile();
21550
+ if (!selectedRemoteMacProfile)
21551
+ throw new Error("--ios-device requires macOS or a paired Remote Mac.");
21552
+ iosPhysicalServerHost = await inspectAbsoluteRemoteMacLanHost(selectedRemoteMacProfile);
21553
+ }
21554
+ }
21555
+ if (httpsEnabled) {
21556
+ const certificateHosts = [
21557
+ ...options.androidDevice ? [mobileReachableHost(resolvedDev.host)] : [],
21558
+ ...iosPhysicalServerHost ? [iosPhysicalServerHost] : []
21559
+ ];
21560
+ devCertificateAuthorityPath = await setupHttpsCert(certificateHosts.length > 0 ? certificateHosts : [resolvedDev.host]);
21561
+ }
21243
21562
  let androidDevProject = null;
21244
21563
  let iosDevProject = null;
21245
21564
  const mobileInteractive = options.mobile !== false && process.env.ABSOLUTE_NO_MOBILE !== "1" && process.stdin.isTTY === true && process.stdout.isTTY === true;
@@ -21275,9 +21594,9 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21275
21594
  }
21276
21595
  if (normalized.platforms.includes("ios")) {
21277
21596
  if (detectAbsoluteMobileHost() !== "macos") {
21278
- const remote = await getAbsoluteRemoteMacProfile();
21597
+ const remote = selectedRemoteMacProfile ?? await getAbsoluteRemoteMacProfile();
21279
21598
  if (!remote) {
21280
- console.log(cliTag("\x1B[33m", "iOS simulator skipped. Pair a Mac with `absolute mobile pair mac <name> <user@host>`."));
21599
+ console.log(cliTag("\x1B[33m", "iOS target skipped. Pair a Mac with `absolute mobile pair mac <name> <user@host>`."));
21281
21600
  } else {
21282
21601
  const nativeDirectory = join13(normalized.nativeProjectDirectory, "ios");
21283
21602
  if (!existsSync5(nativeDirectory)) {
@@ -21288,12 +21607,13 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21288
21607
  }
21289
21608
  }
21290
21609
  } else {
21291
- let ready = iosToolchainReady(await inspectAbsoluteMobileToolchain());
21610
+ const iosTarget = options.iosDevice ? "device" : "simulator";
21611
+ let ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
21292
21612
  if (!ready) {
21293
- const install = await confirmPrompt("iOS simulation is not configured. Install the missing simulator runtime now?");
21613
+ const install = await confirmPrompt(options.iosDevice ? "Physical iOS development is not configured. Open the guided Xcode setup now?" : "iOS simulation is not configured. Install the missing simulator runtime now?");
21294
21614
  if (install) {
21295
21615
  await fixAbsoluteMobileEmulatorToolchain("ios");
21296
- ready = iosToolchainReady(await inspectAbsoluteMobileToolchain());
21616
+ ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
21297
21617
  } else {
21298
21618
  console.log(cliTag("\x1B[33m", "Mobile simulator skipped. Run `absolute mobile doctor ios --fix` when ready."));
21299
21619
  }
@@ -21307,7 +21627,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21307
21627
  if (existsSync5(nativeDirectory) || createNativeProject) {
21308
21628
  iosDevProject = await prepareAbsoluteIosDevProject(normalized, {
21309
21629
  createNativeProject,
21310
- projectRoot: process.cwd()
21630
+ projectRoot: process.cwd(),
21631
+ target: iosTarget
21311
21632
  });
21312
21633
  }
21313
21634
  }
@@ -21351,7 +21672,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21351
21672
  serverEntry,
21352
21673
  ...configPath2 ? ["--config", configPath2] : [],
21353
21674
  ...options.mobile === false ? ["--no-mobile"] : [],
21354
- ...options.androidDevice ? ["--android-device", options.androidDevice] : []
21675
+ ...options.androidDevice ? ["--android-device", options.androidDevice] : [],
21676
+ ...options.iosDevice ? ["--ios-device", options.iosDevice] : []
21355
21677
  ].filter((part) => part.length > 0);
21356
21678
  registerInstance({
21357
21679
  command: relaunchCommand,
@@ -21523,6 +21845,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21523
21845
  platform: "ios",
21524
21846
  provider: "capacitor",
21525
21847
  startedSimulator: session.startedSimulator,
21848
+ target: session.targetKind,
21526
21849
  timings: session.timings
21527
21850
  });
21528
21851
  if (session.timings.building === undefined)
@@ -21533,14 +21856,17 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21533
21856
  installMs: session.timings.installing,
21534
21857
  platform: "ios",
21535
21858
  provider: "capacitor",
21536
- success: true
21859
+ success: true,
21860
+ target: session.targetKind
21537
21861
  });
21538
21862
  };
21539
21863
  const openIosDevSession = (iosProject) => {
21540
21864
  const sessionOptions = {
21541
21865
  certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
21866
+ deviceIdentifier: options.iosDevice,
21542
21867
  https: httpsEnabled,
21543
21868
  port,
21869
+ serverHost: iosPhysicalServerHost,
21544
21870
  signal: iosDevAbort.signal,
21545
21871
  log: (message) => printNativeOutput(cliTag("\x1B[35m", message)),
21546
21872
  nativeLog: (entry) => printNativeOutput(iosLogTag(entry)),
@@ -21554,7 +21880,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21554
21880
  iosDevState = state;
21555
21881
  if (state === "ready" || state === "closed")
21556
21882
  return;
21557
- printNativeOutput(cliTag("\x1B[35m", `iOS simulator: ${state}.`));
21883
+ printNativeOutput(cliTag("\x1B[35m", `iOS ${options.iosDevice ? "device" : "simulator"}: ${state}.`));
21558
21884
  }
21559
21885
  };
21560
21886
  if (iosProject.remote)
@@ -21593,6 +21919,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21593
21919
  provider: "capacitor",
21594
21920
  rootInputChanged: change.rootInputChanged,
21595
21921
  success: true,
21922
+ target: replacement.targetKind,
21596
21923
  timings: replacement.timings
21597
21924
  });
21598
21925
  },
@@ -21603,7 +21930,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21603
21930
  host: iosTelemetryHost(iosProject),
21604
21931
  platform: "ios",
21605
21932
  provider: "capacitor",
21606
- success: false
21933
+ success: false,
21934
+ target: options.iosDevice ? "device" : "simulator"
21607
21935
  });
21608
21936
  }
21609
21937
  });
@@ -21630,9 +21958,10 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21630
21958
  phase: iosDevState,
21631
21959
  platform: "ios",
21632
21960
  provider: "capacitor",
21961
+ target: options.iosDevice ? "device" : "simulator",
21633
21962
  timings: iosPhaseTimings
21634
21963
  });
21635
- console.error(cliTag("\x1B[31m", `iOS simulator failed: ${error instanceof Error ? error.message : String(error)}`));
21964
+ console.error(cliTag("\x1B[31m", `iOS ${options.iosDevice ? "device" : "simulator"} failed: ${error instanceof Error ? error.message : String(error)}`));
21636
21965
  }).finally(() => {
21637
21966
  iosDevStart = null;
21638
21967
  });
@@ -23982,16 +24311,24 @@ if (command === "dev") {
23982
24311
  sendTelemetryEvent("cli:command", { command });
23983
24312
  const configPath2 = parseNamedArg("--config");
23984
24313
  const androidDevice = parseNamedArg("--android-device");
24314
+ const iosDevice = parseNamedArg("--ios-device");
23985
24315
  if (args.includes("--android-device") && !androidDevice) {
23986
24316
  throw new TypeError("--android-device requires an ADB device serial.");
23987
24317
  }
23988
24318
  if (androidDevice && args.includes("--no-mobile")) {
23989
24319
  throw new TypeError("--android-device cannot be combined with --no-mobile.");
23990
24320
  }
23991
- const positionalArgs2 = stripNamedArgs("--config", "--android-device").filter((arg) => arg !== "--no-mobile");
24321
+ if (args.includes("--ios-device") && !iosDevice) {
24322
+ throw new TypeError("--ios-device requires an Xcode device identifier or name.");
24323
+ }
24324
+ if (iosDevice && args.includes("--no-mobile")) {
24325
+ throw new TypeError("--ios-device cannot be combined with --no-mobile.");
24326
+ }
24327
+ const positionalArgs2 = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile");
23992
24328
  const serverEntry = positionalArgs2[0] ?? DEFAULT_SERVER_ENTRY;
23993
24329
  await dev(serverEntry, configPath2, {
23994
24330
  androidDevice,
24331
+ iosDevice,
23995
24332
  mobile: !args.includes("--no-mobile")
23996
24333
  });
23997
24334
  } else if (command === "start") {
@@ -24150,13 +24487,13 @@ if (command === "dev") {
24150
24487
  console.error(message);
24151
24488
  console.error("Usage: absolute <command>");
24152
24489
  console.error("Commands:");
24153
- console.error(" dev [entry] [--no-mobile] [--android-device serial] Start web and configured mobile development");
24490
+ console.error(" dev [entry] [--no-mobile] [--android-device serial] [--ios-device identifier] Start web and configured mobile development");
24154
24491
  console.error(" workspace dev [--no-tui] Start multi-service workspace dev");
24155
24492
  console.error(" build [--outdir dir] [--profile] Build production assets");
24156
24493
  console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
24157
24494
  console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
24158
24495
  console.error(" compile [entry] [--outdir dir] [--outfile path] Compile standalone executable");
24159
- console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects, local or remote simulators, guided setup, and deep links");
24496
+ console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
24160
24497
  console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
24161
24498
  console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
24162
24499
  console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");