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

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
@@ -458,6 +458,7 @@ var registeredPids, exitHandlerRegistered = false, instanceFilePath = (pid) => j
458
458
  frameworks: toStringArray(parsed.frameworks),
459
459
  host: typeof parsed.host === "string" ? parsed.host : "localhost",
460
460
  https: parsed.https === true,
461
+ ...typeof parsed.iosRemoteMac === "string" ? { iosRemoteMac: parsed.iosRemoteMac } : {},
461
462
  logFile: typeof parsed.logFile === "string" ? parsed.logFile : null,
462
463
  name: typeof parsed.name === "string" ? parsed.name : "unknown",
463
464
  pid: parsed.pid,
@@ -2844,17 +2845,109 @@ var init_iosRelease = __esm(() => {
2844
2845
  ]);
2845
2846
  });
2846
2847
 
2848
+ // src/mobile/iosPhysicalDeviceTransport.ts
2849
+ import { randomUUID as randomUUID2, X509Certificate } from "crypto";
2850
+ import { createServer as createServer3 } from "http";
2851
+ import {
2852
+ connect as connectTcp,
2853
+ createServer as createTcpServer,
2854
+ isIP
2855
+ } from "net";
2856
+ import { readFile as readFile5 } from "fs/promises";
2857
+ var closeServer = (server) => new Promise((resolve7, reject) => {
2858
+ server.close((error) => {
2859
+ if (error)
2860
+ reject(error);
2861
+ else
2862
+ resolve7();
2863
+ });
2864
+ }), listen = (server, port) => new Promise((resolve7, reject) => {
2865
+ server.once("error", reject);
2866
+ server.listen(port, "0.0.0.0", () => {
2867
+ server.off("error", reject);
2868
+ const address = server.address();
2869
+ if (!address || typeof address === "string") {
2870
+ reject(new Error("Could not determine the iOS device helper port."));
2871
+ return;
2872
+ }
2873
+ resolve7(address.port);
2874
+ });
2875
+ }), findEphemeralPort = async () => {
2876
+ const probe = createTcpServer();
2877
+ const port = await new Promise((resolve7, reject) => {
2878
+ probe.once("error", reject);
2879
+ probe.listen(0, "127.0.0.1", () => {
2880
+ const address = probe.address();
2881
+ if (!address || typeof address === "string") {
2882
+ reject(new Error("Could not allocate the iOS CA enrollment port."));
2883
+ return;
2884
+ }
2885
+ resolve7(address.port);
2886
+ });
2887
+ });
2888
+ await closeServer(probe);
2889
+ return port;
2890
+ }, normalizeAbsoluteIosDeviceHost = (value) => {
2891
+ const normalized = value.trim();
2892
+ if (!normalized || normalized.length > 253 || /[\0\s/?#]/u.test(normalized))
2893
+ throw new TypeError("Physical iOS development requires a valid LAN host.");
2894
+ return normalized;
2895
+ }, normalizeAbsoluteIosDeviceIdentifier = (value) => {
2896
+ const normalized = value.trim();
2897
+ if (!normalized || normalized.length > 256 || /[\0\r\n]/u.test(normalized))
2898
+ throw new TypeError("--ios-device requires a valid Xcode device identifier or name.");
2899
+ return normalized;
2900
+ }, urlForHost = (protocol, host, port) => {
2901
+ const url = new URL(`${protocol}://localhost:${port}`);
2902
+ const normalizedHost = normalizeAbsoluteIosDeviceHost(host);
2903
+ url.hostname = isIP(normalizedHost) === 6 ? `[${normalizedHost}]` : normalizedHost;
2904
+ return url;
2905
+ }, startAbsoluteIosCaEnrollmentServer = async (options) => {
2906
+ const certificate = new X509Certificate(await readFile5(options.certificateAuthorityPath));
2907
+ const certificateBytes = certificate.raw;
2908
+ const token = randomUUID2().replaceAll("-", "");
2909
+ const certificatePath = `/${token}/absolutejs-development-ca.cer`;
2910
+ const server = createServer3((request, response) => {
2911
+ if (request.method !== "GET" || request.url !== certificatePath) {
2912
+ response.writeHead(404, {
2913
+ "Cache-Control": "no-store",
2914
+ "Content-Type": "text/plain; charset=utf-8"
2915
+ });
2916
+ response.end("Not found.");
2917
+ return;
2918
+ }
2919
+ response.writeHead(200, {
2920
+ "Cache-Control": "no-store",
2921
+ "Content-Disposition": 'attachment; filename="absolutejs-development-ca.cer"',
2922
+ "Content-Length": String(certificateBytes.byteLength),
2923
+ "Content-Type": "application/x-x509-ca-cert",
2924
+ "X-Content-Type-Options": "nosniff"
2925
+ });
2926
+ response.end(certificateBytes);
2927
+ });
2928
+ const port = await findEphemeralPort();
2929
+ await listen(server, port);
2930
+ const url = urlForHost("http", options.displayHost, port);
2931
+ url.pathname = certificatePath;
2932
+ return {
2933
+ url: url.href,
2934
+ close: () => closeServer(server)
2935
+ };
2936
+ };
2937
+ var init_iosPhysicalDeviceTransport = () => {};
2938
+
2847
2939
  // src/mobile/iosSimulatorController.ts
2848
- import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
2940
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
2849
2941
  import {
2850
2942
  access as access5,
2851
2943
  copyFile as copyFile3,
2852
2944
  mkdir as mkdir4,
2853
- readFile as readFile5,
2945
+ readFile as readFile6,
2854
2946
  rename as rename4,
2855
2947
  rm as rm4,
2856
2948
  writeFile as writeFile4
2857
2949
  } from "fs/promises";
2950
+ import { isIP as isIP2 } from "net";
2858
2951
  import { dirname as dirname5, isAbsolute as isAbsolute3, join as join10, relative as relative4, resolve as resolve7, sep as sep3 } from "path";
2859
2952
  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
2953
  try {
@@ -2965,7 +3058,24 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
2965
3058
  options.certificateAuthorityPath
2966
3059
  ], "iOS Simulator development CA trust", run, { signal: options.signal });
2967
3060
  log("Installed the AbsoluteJS development CA into this iOS Simulator trust store.");
2968
- }, requireCapturedSuccess = (result, label) => {
3061
+ }, iosLaunchCommand = (project, udid, physical) => physical ? [
3062
+ project.xcrun,
3063
+ "devicectl",
3064
+ "device",
3065
+ "process",
3066
+ "launch",
3067
+ "--terminate-existing",
3068
+ "--device",
3069
+ udid,
3070
+ project.config.appId
3071
+ ] : [
3072
+ project.xcrun,
3073
+ "simctl",
3074
+ "launch",
3075
+ "--terminate-running-process",
3076
+ udid,
3077
+ project.config.appId
3078
+ ], requireCapturedSuccess = (result, label) => {
2969
3079
  if (result.exitCode !== 0) {
2970
3080
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
2971
3081
  }
@@ -3088,7 +3198,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3088
3198
  await rm4(paths.root, { force: true, recursive: true });
3089
3199
  return false;
3090
3200
  }
3091
- const journal = await readFile5(paths.journal, "utf8").then((source) => parseJournal2(JSON.parse(source))).catch(() => null);
3201
+ const journal = await readFile6(paths.journal, "utf8").then((source) => parseJournal2(JSON.parse(source))).catch(() => null);
3092
3202
  if (!journal || !isInside2(projectRoot, journal.nativeConfigPath) || !isInside2(projectRoot, journal.infoPath) || !isInside2(paths.root, journal.configBackupPath) || !isInside2(paths.root, journal.infoBackupPath)) {
3093
3203
  throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
3094
3204
  }
@@ -3117,14 +3227,14 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3117
3227
  <key>NSAllowsArbitraryLoads</key>
3118
3228
  <true/>
3119
3229
  </dict>`);
3120
- }, writeDevProjection = async (project, port, https) => {
3230
+ }, writeDevProjection = async (project, port, https, serverHost = "localhost") => {
3121
3231
  const paths = journalPaths2(project.projectRoot);
3122
3232
  await repairAbsoluteIosDevSession(project.projectRoot);
3123
3233
  const nativeConfigPath = join10(project.nativeDirectory, "App", "App", "capacitor.config.json");
3124
3234
  const infoPath = join10(project.nativeDirectory, "App", "App", "Info.plist");
3125
3235
  const [configSource, infoSource] = await Promise.all([
3126
- readFile5(nativeConfigPath, "utf8"),
3127
- readFile5(infoPath, "utf8")
3236
+ readFile6(nativeConfigPath, "utf8"),
3237
+ readFile6(infoPath, "utf8")
3128
3238
  ]);
3129
3239
  const parsed = JSON.parse(configSource);
3130
3240
  if (!isRecord3(parsed))
@@ -3146,6 +3256,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3146
3256
  flag: "wx"
3147
3257
  });
3148
3258
  const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
3259
+ developmentUrl.hostname = isIP2(serverHost) === 6 ? `[${serverHost}]` : serverHost;
3149
3260
  developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
3150
3261
  const existingServer = parsed.server;
3151
3262
  parsed.server = {
@@ -3173,9 +3284,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3173
3284
  String(identity)
3174
3285
  ]))
3175
3286
  };
3176
- }, readNativeCache2 = (projectRoot) => readFile5(nativeCachePath2(projectRoot), "utf8").then((source) => parseNativeCache2(JSON.parse(source))).catch(() => null), writeNativeCache2 = async (projectRoot, cache) => {
3287
+ }, readNativeCache2 = (projectRoot) => readFile6(nativeCachePath2(projectRoot), "utf8").then((source) => parseNativeCache2(JSON.parse(source))).catch(() => null), writeNativeCache2 = async (projectRoot, cache) => {
3177
3288
  const destination = nativeCachePath2(projectRoot);
3178
- const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
3289
+ const temporary = `${destination}.${process.pid}.${randomUUID3()}.tmp`;
3179
3290
  await mkdir4(dirname5(destination), { recursive: true });
3180
3291
  try {
3181
3292
  await writeFile4(temporary, `${JSON.stringify(cache, null, "\t")}
@@ -3257,6 +3368,29 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3257
3368
  "app"
3258
3369
  ]);
3259
3370
  return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
3371
+ }, validatePhysicalIosDevice = (project, identifier, capture) => {
3372
+ const result = capture([
3373
+ project.xcrun,
3374
+ "devicectl",
3375
+ "device",
3376
+ "info",
3377
+ "details",
3378
+ "--device",
3379
+ identifier
3380
+ ]);
3381
+ if (result.exitCode !== 0)
3382
+ 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."}`);
3383
+ }, physicalIosAppIsInstalled = (project, identifier, capture) => {
3384
+ const result = capture([
3385
+ project.xcrun,
3386
+ "devicectl",
3387
+ "device",
3388
+ "info",
3389
+ "apps",
3390
+ "--device",
3391
+ identifier
3392
+ ]);
3393
+ return result.exitCode === 0 && result.stdout.includes(project.config.appId);
3260
3394
  }, buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
3261
3395
  const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
3262
3396
  await mkdir4(derivedDataPath, { recursive: true });
@@ -3278,6 +3412,59 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3278
3412
  if (!await pathExists4(appPath))
3279
3413
  throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
3280
3414
  return appPath;
3415
+ }, buildPhysicalIosDebugApp = async (project, identifier, run, signal) => {
3416
+ const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
3417
+ await mkdir4(derivedDataPath, { recursive: true });
3418
+ await requireSuccess2([
3419
+ project.xcodebuild,
3420
+ "-workspace",
3421
+ join10(project.nativeDirectory, "App", "App.xcworkspace"),
3422
+ "-scheme",
3423
+ "App",
3424
+ "-configuration",
3425
+ "Debug",
3426
+ "-destination",
3427
+ `platform=iOS,id=${identifier}`,
3428
+ "-derivedDataPath",
3429
+ derivedDataPath,
3430
+ "-allowProvisioningUpdates",
3431
+ "build"
3432
+ ], "iOS physical-device build (configure automatic signing and a Development Team in Xcode if this is the first run)", run, { cwd: project.nativeDirectory, signal });
3433
+ const appPath = join10(derivedDataPath, "Build", "Products", "Debug-iphoneos", "App.app");
3434
+ if (!await pathExists4(appPath))
3435
+ throw new Error(`Xcode did not produce the physical-device app at ${appPath}.`);
3436
+ return appPath;
3437
+ }, ensurePhysicalIosDebugApp = async (options) => {
3438
+ 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);
3439
+ if (cacheHit) {
3440
+ options.log(`iOS native app is unchanged on the selected physical device; skipped Xcode build and install.`);
3441
+ return true;
3442
+ }
3443
+ options.log("iOS native inputs changed or the physical-device install is stale; rebuilding.");
3444
+ options.transition("building");
3445
+ const appPath = await buildPhysicalIosDebugApp(options.project, options.identifier, options.run, options.signal);
3446
+ throwIfAborted2(options.signal);
3447
+ options.transition("installing");
3448
+ await requireSuccess2([
3449
+ options.project.xcrun,
3450
+ "devicectl",
3451
+ "device",
3452
+ "install",
3453
+ "app",
3454
+ "--device",
3455
+ options.identifier,
3456
+ appPath
3457
+ ], "iOS physical-device app installation", options.run, { signal: options.signal });
3458
+ await writeNativeCache2(options.project.projectRoot, {
3459
+ appId: options.project.config.appId,
3460
+ fingerprint: options.fingerprint,
3461
+ format: NATIVE_CACHE_FORMAT2,
3462
+ installations: {
3463
+ ...options.cache?.appId === options.project.config.appId ? options.cache.installations : {},
3464
+ [options.identifier]: options.fingerprint
3465
+ }
3466
+ }).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
3467
+ return false;
3281
3468
  }, ensureIosDebugApp = async (options) => {
3282
3469
  const installed = installedAppIdentity(options.project, options.udid, options.capture);
3283
3470
  const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
@@ -3317,7 +3504,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3317
3504
  if (!options.nativeLog)
3318
3505
  return null;
3319
3506
  const start = options.startNativeLogs ?? defaultStartNativeLogs;
3320
- return start([
3507
+ const command = options.deviceIdentifier ? [
3508
+ project.xcrun,
3509
+ "devicectl",
3510
+ "device",
3511
+ "process",
3512
+ "launch",
3513
+ "--console",
3514
+ "--terminate-existing",
3515
+ "--device",
3516
+ udid,
3517
+ project.config.appId
3518
+ ] : [
3321
3519
  project.xcrun,
3322
3520
  "simctl",
3323
3521
  "spawn",
@@ -3330,7 +3528,8 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3330
3528
  "debug",
3331
3529
  "--predicate",
3332
3530
  'process == "App"'
3333
- ], { signal: options.signal }, (line) => {
3531
+ ];
3532
+ return start(command, { signal: options.signal }, (line) => {
3334
3533
  const entry = parseAbsoluteIosLogLine(line);
3335
3534
  if (entry)
3336
3535
  options.nativeLog?.(entry);
@@ -3340,12 +3539,13 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3340
3539
  return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
3341
3540
  }).filter((value) => value !== null).join(", "), prepareAbsoluteIosDevProject = async (config, options) => {
3342
3541
  if (detectAbsoluteMobileHost() !== "macos")
3343
- throw new Error("iOS simulation requires macOS and Xcode.");
3542
+ throw new Error("iOS development requires macOS and Xcode.");
3543
+ const target = options.target ?? "simulator";
3344
3544
  const projectRoot = resolve7(options.projectRoot);
3345
3545
  const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
3346
- const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
3546
+ const failed = checks.filter((check) => check.platform === "ios" && !(target === "device" && check.id === "ios.runtime") && (check.status === "fail" || check.status === "warn"));
3347
3547
  if (failed.length > 0)
3348
- throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
3548
+ throw new Error(`iOS ${target} development is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
3349
3549
  const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
3350
3550
  const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
3351
3551
  if (!xcrun || !xcodebuild)
@@ -3375,8 +3575,53 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3375
3575
  xcodebuild,
3376
3576
  xcrun
3377
3577
  };
3578
+ }, preparePhysicalIosTarget = async (options) => {
3579
+ options.transition("connecting");
3580
+ validatePhysicalIosDevice(options.project, options.deviceIdentifier, options.capture);
3581
+ if (!options.startOptions.https)
3582
+ return {
3583
+ caEnrollmentServer: null,
3584
+ startedSimulator: false,
3585
+ udid: options.deviceIdentifier
3586
+ };
3587
+ if (!options.startOptions.certificateAuthorityPath)
3588
+ throw new Error("Physical iOS HTTPS development requires the AbsoluteJS development CA certificate.");
3589
+ options.transition("enrolling-trust");
3590
+ const startEnrollment = options.startOptions.startCaEnrollmentServer ?? startAbsoluteIosCaEnrollmentServer;
3591
+ const caEnrollmentServer = await startEnrollment({
3592
+ certificateAuthorityPath: options.startOptions.certificateAuthorityPath,
3593
+ displayHost: options.serverHost
3594
+ });
3595
+ 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.`);
3596
+ return {
3597
+ caEnrollmentServer,
3598
+ startedSimulator: false,
3599
+ udid: options.deviceIdentifier
3600
+ };
3601
+ }, prepareIosSimulatorTarget = async (options) => {
3602
+ options.transition("booting");
3603
+ const managed = await ensureManagedSimulator(options.project, options.capture);
3604
+ const { device } = managed;
3605
+ const startedSimulator = managed.created || device.state !== "Booted";
3606
+ bootSimulator(options.project, device, options.capture);
3607
+ options.spawn([
3608
+ "open",
3609
+ "-a",
3610
+ "Simulator",
3611
+ "--args",
3612
+ "-CurrentDeviceUDID",
3613
+ device.udid
3614
+ ]);
3615
+ options.transition("connecting");
3616
+ await waitForBootedSimulator(options.project, device.udid, options.capture, options.sleep, options.startOptions.signal);
3617
+ await requireSuccess2([options.project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", options.run, { signal: options.startOptions.signal });
3618
+ await trustIosSimulatorDevelopmentCa(options.startOptions, device.udid, options.run, options.log);
3619
+ return { caEnrollmentServer: null, startedSimulator, udid: device.udid };
3378
3620
  }, startAbsoluteIosDevSession = async (options) => {
3379
3621
  const { project } = options;
3622
+ const deviceIdentifier = options.deviceIdentifier ? normalizeAbsoluteIosDeviceIdentifier(options.deviceIdentifier) : undefined;
3623
+ const targetKind = deviceIdentifier ? "device" : "simulator";
3624
+ const serverHost = deviceIdentifier ? normalizeAbsoluteIosDeviceHost(options.serverHost ?? "") : "localhost";
3380
3625
  const capture = options.capture ?? defaultCapture3;
3381
3626
  const run = options.run ?? defaultRun3;
3382
3627
  const sleep = options.sleep ?? Bun.sleep;
@@ -3404,6 +3649,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3404
3649
  options.onStateChange?.(next);
3405
3650
  };
3406
3651
  let nativeLogs = null;
3652
+ let caEnrollmentServer = null;
3407
3653
  const closeLogs = async () => {
3408
3654
  const stream = nativeLogs;
3409
3655
  nativeLogs = null;
@@ -3411,39 +3657,66 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3411
3657
  return;
3412
3658
  });
3413
3659
  };
3660
+ const relaunchTarget = async (udid) => {
3661
+ if (deviceIdentifier && options.nativeLog) {
3662
+ await closeLogs();
3663
+ nativeLogs = attachNativeLogs(project, udid, options);
3664
+ return;
3665
+ }
3666
+ await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app relaunch", run, { signal: options.signal });
3667
+ };
3414
3668
  try {
3415
3669
  await repairAbsoluteIosDevSession(project.projectRoot);
3416
3670
  throwIfAborted2(options.signal);
3417
3671
  transition("syncing");
3418
3672
  await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
3419
3673
  transition("configuring");
3420
- await writeDevProjection(project, options.port, options.https === true);
3674
+ await writeDevProjection(project, options.port, options.https === true, serverHost);
3421
3675
  throwIfAborted2(options.signal);
3422
3676
  const fingerprintStartedAt = performance.now();
3423
3677
  const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
3424
3678
  timings.fingerprinting = performance.now() - fingerprintStartedAt;
3425
3679
  return fingerprint2;
3426
3680
  });
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);
3681
+ const target = deviceIdentifier ? await preparePhysicalIosTarget({
3682
+ capture,
3683
+ deviceIdentifier,
3684
+ log,
3685
+ project,
3686
+ serverHost,
3687
+ startOptions: options,
3688
+ transition
3689
+ }) : await prepareIosSimulatorTarget({
3690
+ capture,
3691
+ log,
3692
+ project,
3693
+ run,
3694
+ sleep,
3695
+ spawn,
3696
+ startOptions: options,
3697
+ transition
3698
+ });
3699
+ const {
3700
+ caEnrollmentServer: targetCaEnrollmentServer,
3701
+ startedSimulator,
3702
+ udid
3703
+ } = target;
3704
+ caEnrollmentServer = targetCaEnrollmentServer;
3443
3705
  const fingerprint = await fingerprintPromise;
3444
3706
  transition("checking-native");
3445
- const nativeCacheHit = await ensureIosDebugApp({
3446
- cache: await readNativeCache2(project.projectRoot),
3707
+ const cache = await readNativeCache2(project.projectRoot);
3708
+ const nativeCacheHit = deviceIdentifier ? await ensurePhysicalIosDebugApp({
3709
+ cache,
3710
+ capture,
3711
+ fingerprint,
3712
+ identifier: deviceIdentifier,
3713
+ log,
3714
+ project,
3715
+ run,
3716
+ signal: options.signal,
3717
+ transition
3718
+ }) : await ensureIosDebugApp({
3719
+ cache,
3447
3720
  capture,
3448
3721
  fingerprint,
3449
3722
  log,
@@ -3451,24 +3724,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3451
3724
  run,
3452
3725
  signal: options.signal,
3453
3726
  transition,
3454
- udid: device.udid
3727
+ udid
3455
3728
  });
3456
3729
  throwIfAborted2(options.signal);
3457
3730
  if (options.nativeLog)
3458
3731
  transition("streaming-logs");
3459
- nativeLogs = attachNativeLogs(project, device.udid, options);
3732
+ nativeLogs = attachNativeLogs(project, udid, options);
3460
3733
  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 });
3734
+ if (!deviceIdentifier || !nativeLogs)
3735
+ await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app launch", run, { signal: options.signal });
3469
3736
  transition("ready");
3470
3737
  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"}).`);
3738
+ log(`iOS ${targetKind} connected with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
3472
3739
  log(`iOS startup: ${timingSummary(timings)}.`);
3473
3740
  let closed = false;
3474
3741
  const close = async () => {
@@ -3477,6 +3744,10 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3477
3744
  closed = true;
3478
3745
  transition("closing");
3479
3746
  await closeLogs();
3747
+ await caEnrollmentServer?.close().catch(() => {
3748
+ return;
3749
+ });
3750
+ caEnrollmentServer = null;
3480
3751
  await repairAbsoluteIosDevSession(project.projectRoot);
3481
3752
  transition("closed");
3482
3753
  };
@@ -3484,8 +3755,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3484
3755
  close,
3485
3756
  nativeCacheHit,
3486
3757
  startedSimulator,
3758
+ targetKind,
3487
3759
  timings: { ...timings },
3488
- udid: device.udid,
3760
+ udid,
3489
3761
  rebuild: async () => {
3490
3762
  if (closed)
3491
3763
  throw new Error("iOS development session is closed.");
@@ -3498,22 +3770,17 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3498
3770
  throw new Error("iOS development session is closed.");
3499
3771
  transition("launching");
3500
3772
  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 });
3773
+ await relaunchTarget(udid);
3509
3774
  transition("ready");
3510
- log(`iOS app relaunched on ${device.udid}.`);
3775
+ log(`iOS app relaunched on the selected ${targetKind}.`);
3511
3776
  } catch (error) {
3512
3777
  transition("failed");
3513
3778
  throw error;
3514
3779
  }
3515
3780
  },
3516
3781
  screenshot: async (destination) => {
3782
+ if (deviceIdentifier)
3783
+ throw new Error("Physical iOS screenshots are captured in Xcode Device Hub; the CLI never records a device screen automatically.");
3517
3784
  const resolved = resolve7(project.projectRoot, destination);
3518
3785
  if (!isInside2(project.projectRoot, resolved))
3519
3786
  throw new Error("iOS screenshot destination must remain inside the project.");
@@ -3522,7 +3789,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3522
3789
  project.xcrun,
3523
3790
  "simctl",
3524
3791
  "io",
3525
- device.udid,
3792
+ udid,
3526
3793
  "screenshot",
3527
3794
  resolved
3528
3795
  ], "iOS simulator screenshot", run, { signal: options.signal });
@@ -3535,6 +3802,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
3535
3802
  } catch (error) {
3536
3803
  transition("failed");
3537
3804
  await closeLogs();
3805
+ await caEnrollmentServer?.close().catch(() => {
3806
+ return;
3807
+ });
3538
3808
  await repairAbsoluteIosDevSession(project.projectRoot);
3539
3809
  throw error;
3540
3810
  }
@@ -3543,6 +3813,7 @@ var init_iosSimulatorController = __esm(() => {
3543
3813
  init_emulatorDoctor();
3544
3814
  init_capacitorProject();
3545
3815
  init_iosRelease();
3816
+ init_iosPhysicalDeviceTransport();
3546
3817
  init_getDurationString();
3547
3818
  SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
3548
3819
  BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
@@ -3554,6 +3825,7 @@ var init_iosSimulatorController = __esm(() => {
3554
3825
  ["fingerprinting", "fingerprint"],
3555
3826
  ["booting", "simulator"],
3556
3827
  ["connecting", "device ready"],
3828
+ ["enrolling-trust", "HTTPS trust"],
3557
3829
  ["checking-native", "app check"],
3558
3830
  ["building", "Xcode"],
3559
3831
  ["installing", "install"],
@@ -3566,9 +3838,10 @@ var init_iosSimulatorController = __esm(() => {
3566
3838
  var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t", ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
3567
3839
 
3568
3840
  // 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";
3841
+ import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
3842
+ import { chmod, mkdir as mkdir5, readFile as readFile7, rename as rename5, writeFile as writeFile5 } from "fs/promises";
3571
3843
  import { homedir as homedir4 } from "os";
3844
+ import { isIP as isIP3 } from "net";
3572
3845
  import {
3573
3846
  dirname as dirname6,
3574
3847
  isAbsolute as isAbsolute4,
@@ -3583,7 +3856,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3583
3856
  profiles: {}
3584
3857
  }), loadStore = async (path = defaultProfilePath()) => {
3585
3858
  try {
3586
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3859
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3587
3860
  if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
3588
3861
  throw new Error("Unsupported remote Mac profile format.");
3589
3862
  for (const [key, profile] of Object.entries(parsed.profiles)) {
@@ -3600,7 +3873,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3600
3873
  }
3601
3874
  }, saveStore = async (store, path = defaultProfilePath()) => {
3602
3875
  await mkdir5(dirname6(path), { recursive: true });
3603
- const temporary = `${path}.${randomUUID3()}.tmp`;
3876
+ const temporary = `${path}.${randomUUID4()}.tmp`;
3604
3877
  await writeFile5(temporary, `${JSON.stringify(store, null, 2)}
3605
3878
  `, {
3606
3879
  mode: 384
@@ -3651,6 +3924,17 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3651
3924
  if (result.exitCode !== 0)
3652
3925
  throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
3653
3926
  return result.stdout.trim();
3927
+ }, captureAbsoluteRemoteMacCommand = async (profile, command, transport) => {
3928
+ if (command.length === 0)
3929
+ throw new TypeError("A Remote Mac command cannot be empty.");
3930
+ if (command.some((argument) => /[\r\n\0]/u.test(argument)))
3931
+ throw new TypeError("Remote Mac command arguments cannot contain controls.");
3932
+ const capture = transport?.capture ?? defaultTransport.capture;
3933
+ return capture([
3934
+ ...absoluteRemoteMacSshBase(profile),
3935
+ "/bin/sh -lc",
3936
+ shellQuote(command.map((argument) => shellQuote(argument)).join(" "))
3937
+ ]);
3654
3938
  }, getAbsoluteRemoteMacProfile = async (name, profilePath) => {
3655
3939
  const store = await loadStore(profilePath);
3656
3940
  const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
@@ -3682,6 +3966,18 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3682
3966
  if (!xcodeVersion?.startsWith("Xcode "))
3683
3967
  throw new Error("The remote Mac must have full Xcode installed and selected.");
3684
3968
  return { bunPath, home, os: operatingSystem, xcodeVersion };
3969
+ }, inspectAbsoluteRemoteMacLanHost = async (profile, transport) => {
3970
+ const capture = transport?.capture ?? defaultTransport.capture;
3971
+ 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`;
3972
+ const result = await capture([
3973
+ ...absoluteRemoteMacSshBase(profile),
3974
+ "/bin/sh -lc",
3975
+ shellQuote(script)
3976
+ ]);
3977
+ const host = requireRemoteSuccess(result, "Remote Mac LAN address discovery").trim();
3978
+ if (isIP3(host) === 0)
3979
+ throw new Error("The Remote Mac did not report a device-reachable LAN address.");
3980
+ return host;
3685
3981
  }, listAbsoluteRemoteMacProfiles = async (profilePath) => {
3686
3982
  const store = await loadStore(profilePath);
3687
3983
  return {
@@ -3748,7 +4044,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3748
4044
  ]);
3749
4045
  if (verified.exitCode === 0)
3750
4046
  return { ...artifact, remotePath, uploaded: false };
3751
- const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
4047
+ const temporary = posix.join(directory, `.agent-${randomUUID4()}.tmp`);
3752
4048
  const installScript = [
3753
4049
  "set -eu",
3754
4050
  "umask 077",
@@ -3835,7 +4131,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3835
4131
  }), absoluteRemoteProjectSyncCommands = (project) => {
3836
4132
  const current = project.remoteProjectRoot;
3837
4133
  const parent = posix.dirname(current);
3838
- const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
4134
+ const staging = posix.join(parent, `.incoming-${randomUUID4()}`);
3839
4135
  const previous = posix.join(parent, ".previous");
3840
4136
  const script = [
3841
4137
  "set -eu",
@@ -3924,7 +4220,13 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3924
4220
  await syncProject(options.project);
3925
4221
  const syncDuration = performance.now() - syncStartedAt;
3926
4222
  const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
3927
- const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile6(options.certificateAuthorityPath)).toString("base64url") : undefined;
4223
+ const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile7(options.certificateAuthorityPath)).toString("base64url") : undefined;
4224
+ const physicalDevice = options.deviceIdentifier !== undefined;
4225
+ let relayPort;
4226
+ if (physicalDevice)
4227
+ relayPort = options.port <= 49151 ? options.port + 16384 : options.port - 16384;
4228
+ if (physicalDevice && !options.serverHost)
4229
+ throw new Error("Remote physical iOS development requires the Remote Mac LAN host.");
3928
4230
  const remoteCommand = [
3929
4231
  `cd ${shellQuote(options.project.remoteProjectRoot)}`,
3930
4232
  "&&",
@@ -3939,14 +4241,22 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
3939
4241
  "--certificate-authority",
3940
4242
  shellQuote(encodedCertificateAuthority)
3941
4243
  ] : [],
3942
- ...options.https ? ["--https"] : []
4244
+ ...options.https ? ["--https"] : [],
4245
+ ...options.deviceIdentifier ? [
4246
+ "--ios-device",
4247
+ shellQuote(options.deviceIdentifier),
4248
+ "--server-host",
4249
+ shellQuote(options.serverHost ?? ""),
4250
+ "--relay-port",
4251
+ String(relayPort)
4252
+ ] : []
3943
4253
  ].join(" ");
3944
4254
  const command = [
3945
4255
  ...absoluteRemoteMacSshBase(options.project.profile),
3946
4256
  "-o",
3947
4257
  "ExitOnForwardFailure=yes",
3948
4258
  "-R",
3949
- `${options.port}:127.0.0.1:${options.port}`,
4259
+ physicalDevice ? `127.0.0.1:${relayPort}:127.0.0.1:${options.port}` : `${options.port}:127.0.0.1:${options.port}`,
3950
4260
  "/bin/sh -lc",
3951
4261
  shellQuote(remoteCommand)
3952
4262
  ];
@@ -4034,7 +4344,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
4034
4344
  };
4035
4345
  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
4346
  const request = (commandName) => {
4037
- const id = randomUUID3();
4347
+ const id = randomUUID4();
4038
4348
  const response = new Promise((resolve8, reject) => pending.set(id, { reject, resolve: resolve8 }));
4039
4349
  process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
4040
4350
  `);
@@ -4067,6 +4377,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
4067
4377
  close,
4068
4378
  nativeCacheHit: currentReady.nativeCacheHit,
4069
4379
  startedSimulator: currentReady.startedSimulator,
4380
+ targetKind: currentReady.targetKind,
4070
4381
  timings: currentReady.timings,
4071
4382
  udid: currentReady.udid,
4072
4383
  rebuild: async () => {
@@ -4136,8 +4447,8 @@ import {
4136
4447
  readFileSync as readFileSync7,
4137
4448
  rmSync
4138
4449
  } from "fs";
4139
- import { X509Certificate } from "crypto";
4140
- import { isIP } from "net";
4450
+ import { X509Certificate as X509Certificate2 } from "crypto";
4451
+ import { isIP as isIP4 } from "net";
4141
4452
  import { platform as platform2 } from "os";
4142
4453
  import { join as join12 } from "path";
4143
4454
  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 +4457,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4146
4457
  const value = host.trim().toLowerCase();
4147
4458
  if (!value || value === "0.0.0.0" || value === "::")
4148
4459
  continue;
4149
- if (isIP(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
4460
+ if (isIP4(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
4150
4461
  throw new TypeError(`Invalid development certificate host: ${host}`);
4151
4462
  }
4152
4463
  normalized.add(value);
@@ -4155,10 +4466,10 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4155
4466
  }, certificateIsUsable = (hosts) => {
4156
4467
  try {
4157
4468
  const certPem = readFileSync7(CERT_PATH, "utf-8");
4158
- const certificate = new X509Certificate(certPem);
4469
+ const certificate = new X509Certificate2(certPem);
4159
4470
  if (new Date(certificate.validTo).getTime() <= Date.now())
4160
4471
  return false;
4161
- return normalizeDevCertificateHosts(hosts).every((host) => isIP(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
4472
+ return normalizeDevCertificateHosts(hosts).every((host) => isIP4(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
4162
4473
  } catch {
4163
4474
  return false;
4164
4475
  }
@@ -4186,7 +4497,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE
4186
4497
  throw new Error(`mkcert failed: ${err}`);
4187
4498
  }
4188
4499
  }, generateSelfSigned = (hosts = []) => {
4189
- const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP(host) ? "IP" : "DNS"}:${host}`).join(",");
4500
+ const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP4(host) ? "IP" : "DNS"}:${host}`).join(",");
4190
4501
  const proc = Bun.spawnSync([
4191
4502
  "openssl",
4192
4503
  "req",
@@ -5834,7 +6145,7 @@ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-
5834
6145
 
5835
6146
  // src/mobile/buildRelease.ts
5836
6147
  import { createHash as createHash8 } from "crypto";
5837
- import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
6148
+ import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
5838
6149
  import { basename as basename6, dirname as dirname9, extname as extname3, join as join16, relative as relative9, resolve as resolve13 } from "path";
5839
6150
  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
6151
  if (path.endsWith("/htmx.min.js"))
@@ -5856,7 +6167,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5856
6167
  }
5857
6168
  let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
5858
6169
  if (metadata.framework === "html" || metadata.framework === "htmx") {
5859
- const source = await readFile7(resolvedAssetPath, "utf8");
6170
+ const source = await readFile8(resolvedAssetPath, "utf8");
5860
6171
  const rewritten = rewriteStaticScriptPaths(source, manifest);
5861
6172
  const documentHash = sha256(new TextEncoder().encode(rewritten));
5862
6173
  resolvedAssetPath = join16(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
@@ -5870,8 +6181,8 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5870
6181
  ].map((key) => manifest[key]).find((path) => typeof path === "string");
5871
6182
  const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
5872
6183
  const [bytes, styleBytes] = await Promise.all([
5873
- readFile7(resolvedAssetPath),
5874
- resolvedStylePath ? readFile7(resolvedStylePath) : undefined
6184
+ readFile8(resolvedAssetPath),
6185
+ resolvedStylePath ? readFile8(resolvedStylePath) : undefined
5875
6186
  ]);
5876
6187
  const bundlePath = `/${relative9(resolve13(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
5877
6188
  const styleBundlePath = resolvedStylePath ? `/${relative9(resolve13(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
@@ -5890,7 +6201,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
5890
6201
  }, buildAbsoluteMobileCompatibilityRelease = async (options) => {
5891
6202
  const [captured, producerBytes] = await Promise.all([
5892
6203
  captureAbsoluteMobileRouteGraph(options.app),
5893
- readFile7(options.producerPath)
6204
+ readFile8(options.producerPath)
5894
6205
  ]);
5895
6206
  if (captured.length === 0) {
5896
6207
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -6070,7 +6381,7 @@ import {
6070
6381
  copyFile as copyFile4,
6071
6382
  mkdir as mkdir8,
6072
6383
  mkdtemp as mkdtemp3,
6073
- readFile as readFile8,
6384
+ readFile as readFile9,
6074
6385
  rename as rename6,
6075
6386
  rm as rm5,
6076
6387
  writeFile as writeFile7
@@ -6128,7 +6439,7 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
6128
6439
  const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
6129
6440
  const subpath = specifier.slice(packageName.length);
6130
6441
  const packageDirectory = join17(resolve14(projectRoot), "node_modules", packageName);
6131
- const manifest = JSON.parse(await readFile8(join17(packageDirectory, "package.json"), "utf8"));
6442
+ const manifest = JSON.parse(await readFile9(join17(packageDirectory, "package.json"), "utf8"));
6132
6443
  const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
6133
6444
  const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
6134
6445
  const target = importEntryTarget(entry);
@@ -6230,7 +6541,7 @@ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => creat
6230
6541
  ...localStylePath ? { localStylePath } : {}
6231
6542
  };
6232
6543
  }, absoluteClientImports = async (sourcePath, buildDirectory) => {
6233
- const source = await readFile8(sourcePath, "utf8");
6544
+ const source = await readFile9(sourcePath, "utf8");
6234
6545
  const extension = extname4(sourcePath).toLowerCase();
6235
6546
  let scriptLoader;
6236
6547
  if (extension === ".tsx")
@@ -6372,7 +6683,7 @@ import {
6372
6683
  access as access6,
6373
6684
  mkdir as mkdir9,
6374
6685
  mkdtemp as mkdtemp4,
6375
- readFile as readFile9,
6686
+ readFile as readFile10,
6376
6687
  rename as rename7,
6377
6688
  rm as rm6,
6378
6689
  writeFile as writeFile8
@@ -6472,7 +6783,7 @@ var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1, CURRENT_BUNDLE_FILE = "curre
6472
6783
  }, readAbsoluteMobileMaterializedReleases = async (root) => {
6473
6784
  const resolvedRoot = resolvePath2(root);
6474
6785
  try {
6475
- const serialized = await readFile9(join18(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
6786
+ const serialized = await readFile10(join18(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
6476
6787
  const parsed = JSON.parse(serialized);
6477
6788
  const index = parseBundleIndex(parsed);
6478
6789
  const bundleRoot = join18(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
@@ -7174,7 +7485,7 @@ var init_deviceCapabilities = __esm(() => {
7174
7485
  });
7175
7486
 
7176
7487
  // src/mobile/buildPipeline.ts
7177
- import { readFile as readFile10 } from "fs/promises";
7488
+ import { readFile as readFile11 } from "fs/promises";
7178
7489
  import { join as join21, resolve as resolve17 } from "path";
7179
7490
  import { pathToFileURL } from "url";
7180
7491
  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 +7519,7 @@ var isElysiaApp = (value) => typeof value === "object" && value !== null && type
7208
7519
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
7209
7520
  const root = join21(buildDirectory, ".absolutejs", "mobile-compatibility");
7210
7521
  const [manifestSource, previous] = await Promise.all([
7211
- readFile10(join21(buildDirectory, "manifest.json"), "utf8"),
7522
+ readFile11(join21(buildDirectory, "manifest.json"), "utf8"),
7212
7523
  readAbsoluteMobileMaterializedReleases(root)
7213
7524
  ]);
7214
7525
  const manifest = JSON.parse(manifestSource);
@@ -10914,8 +11225,8 @@ export { value };
10914
11225
  host2.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => fileName === virtualPath ? ts6.createSourceFile(fileName, source, languageVersion, true) : getSourceFile(fileName, languageVersion, onError, shouldCreate);
10915
11226
  const fileExists = host2.fileExists.bind(host2);
10916
11227
  host2.fileExists = (fileName) => fileName === virtualPath ? true : fileExists(fileName);
10917
- const readFile11 = host2.readFile.bind(host2);
10918
- host2.readFile = (fileName) => fileName === virtualPath ? source : readFile11(fileName);
11228
+ const readFile12 = host2.readFile.bind(host2);
11229
+ host2.readFile = (fileName) => fileName === virtualPath ? source : readFile12(fileName);
10919
11230
  const program = ts6.createProgram([virtualPath], options, host2);
10920
11231
  const checker = program.getTypeChecker();
10921
11232
  const sourceFile = program.getSourceFile(virtualPath);
@@ -16538,10 +16849,10 @@ var init_compile = __esm(() => {
16538
16849
  });
16539
16850
 
16540
16851
  // src/mobile/nativeDeepLinks.ts
16541
- import { readFile as readFile11, rename as rename8, writeFile as writeFile9 } from "fs/promises";
16852
+ import { readFile as readFile12, rename as rename8, writeFile as writeFile9 } from "fs/promises";
16542
16853
  import { join as join46 } from "path";
16543
16854
  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");
16855
+ const current = await readFile12(path, "utf8");
16545
16856
  if (current === source)
16546
16857
  return false;
16547
16858
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -16589,7 +16900,7 @@ ${hosts}
16589
16900
  `;
16590
16901
  }, configureAndroid = async (config) => {
16591
16902
  const path = join46(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16592
- const source = await readFile11(path, "utf8");
16903
+ const source = await readFile12(path, "utf8");
16593
16904
  const mainActivity = source.indexOf('android:name=".MainActivity"');
16594
16905
  if (mainActivity === NOT_FOUND) {
16595
16906
  throw new TypeError("Android MainActivity was not found.");
@@ -16613,7 +16924,7 @@ ${hosts}
16613
16924
  ${END_MARKER}
16614
16925
  `, configureIosInfo = async (config) => {
16615
16926
  const path = join46(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16616
- const source = await readFile11(path, "utf8");
16927
+ const source = await readFile12(path, "utf8");
16617
16928
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
16618
16929
  ${END_MARKER}
16619
16930
  `;
@@ -16637,7 +16948,7 @@ ${domains}
16637
16948
  const path = join46(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
16638
16949
  let current = "";
16639
16950
  try {
16640
- current = await readFile11(path, "utf8");
16951
+ current = await readFile12(path, "utf8");
16641
16952
  } catch (error) {
16642
16953
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
16643
16954
  throw error;
@@ -16652,7 +16963,7 @@ ${domains}
16652
16963
  return true;
16653
16964
  }, configureIosProject = async (config) => {
16654
16965
  const path = join46(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
16655
- const source = await readFile11(path, "utf8");
16966
+ const source = await readFile12(path, "utf8");
16656
16967
  const declarations = [
16657
16968
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
16658
16969
  ].map((match) => match[1]);
@@ -16690,10 +17001,10 @@ ${domains}
16690
17001
  var init_nativeDeepLinks = () => {};
16691
17002
 
16692
17003
  // src/mobile/nativeDeviceCapabilities.ts
16693
- import { readFile as readFile12, rename as rename9, writeFile as writeFile10 } from "fs/promises";
17004
+ import { readFile as readFile13, rename as rename9, writeFile as writeFile10 } from "fs/promises";
16694
17005
  import { join as join47 } from "path";
16695
17006
  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");
17007
+ const current = await readFile13(path, "utf8");
16697
17008
  if (current === source)
16698
17009
  return false;
16699
17010
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -16711,7 +17022,7 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
16711
17022
  return writeChangedFile2(path, source);
16712
17023
  }, optionalSource = async (path) => {
16713
17024
  try {
16714
- return await readFile12(path, "utf8");
17025
+ return await readFile13(path, "utf8");
16715
17026
  } catch (error) {
16716
17027
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
16717
17028
  return null;
@@ -16819,7 +17130,7 @@ ${entries}
16819
17130
  if (requirements.iosPrivacyAccessedApis.length === 0)
16820
17131
  return false;
16821
17132
  const projectPath = join47(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
16822
- const project = await readFile12(projectPath, "utf8");
17133
+ const project = await readFile13(projectPath, "utf8");
16823
17134
  return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
16824
17135
  }, addIosPrivacyProjectReference = (source) => {
16825
17136
  const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
@@ -16870,7 +17181,7 @@ ${next.slice(index)}`;
16870
17181
  return next;
16871
17182
  }, configureIos2 = async (config, plan) => {
16872
17183
  const path = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16873
- const source = await readFile12(path, "utf8");
17184
+ const source = await readFile13(path, "utf8");
16874
17185
  const requirements = absoluteDeviceNativeRequirements(plan);
16875
17186
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
16876
17187
  const ownedStart = source.indexOf(START_MARKER2);
@@ -16960,7 +17271,7 @@ ${content}
16960
17271
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
16961
17272
  }, configureAndroid2 = async (config, plan) => {
16962
17273
  const path = join47(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16963
- const source = await readFile12(path, "utf8");
17274
+ const source = await readFile13(path, "utf8");
16964
17275
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
16965
17276
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
16966
17277
  `);
@@ -17019,10 +17330,10 @@ var init_nativeDeviceCapabilities = __esm(() => {
17019
17330
  });
17020
17331
 
17021
17332
  // src/mobile/nativeBackgroundSync.ts
17022
- import { readFile as readFile13, rename as rename10, writeFile as writeFile11 } from "fs/promises";
17333
+ import { readFile as readFile14, rename as rename10, writeFile as writeFile11 } from "fs/promises";
17023
17334
  import { join as join48 } from "path";
17024
17335
  var writeChanged = async (path, source) => {
17025
- const current = await readFile13(path, "utf8");
17336
+ const current = await readFile14(path, "utf8");
17026
17337
  if (current === source)
17027
17338
  return false;
17028
17339
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
@@ -17104,10 +17415,10 @@ ${makeRegion(values)} </array>
17104
17415
  return { changed: false };
17105
17416
  const identifier = `${config.appId}.absolutejs.background-sync`;
17106
17417
  const infoPath = join48(config.nativeProjectDirectory, "ios/App/App/Info.plist");
17107
- const info2 = await readFile13(infoPath, "utf8");
17418
+ const info2 = await readFile14(infoPath, "utf8");
17108
17419
  const nextInfo = ensurePlistArrayValues(ensurePlistArrayValues(info2, "BGTaskSchedulerPermittedIdentifiers", [identifier], "background-sync-identifiers"), "UIBackgroundModes", ["fetch", "processing"], "background-sync-modes");
17109
17420
  const delegatePath = join48(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
17110
- let delegate = await readFile13(delegatePath, "utf8");
17421
+ let delegate = await readFile14(delegatePath, "utf8");
17111
17422
  if (!delegate.includes("import AbsoluteSyncCapacitor")) {
17112
17423
  const importIndex = delegate.lastIndexOf("import Capacitor");
17113
17424
  if (importIndex < 0)
@@ -17141,7 +17452,7 @@ var init_nativeBackgroundSync = __esm(() => {
17141
17452
  import {
17142
17453
  access as access7,
17143
17454
  mkdir as mkdir10,
17144
- readFile as readFile14,
17455
+ readFile as readFile15,
17145
17456
  rename as rename11,
17146
17457
  rm as rm7,
17147
17458
  writeFile as writeFile12
@@ -17199,7 +17510,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
17199
17510
  }, writeAtomic = async (path, source) => {
17200
17511
  let current;
17201
17512
  try {
17202
- current = await readFile14(path, "utf8");
17513
+ current = await readFile15(path, "utf8");
17203
17514
  } catch (error) {
17204
17515
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
17205
17516
  throw error;
@@ -17222,7 +17533,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
17222
17533
  const path = resolve37(root, OWNERSHIP_FILE);
17223
17534
  let ownership;
17224
17535
  try {
17225
- ownership = JSON.parse(await readFile14(path, "utf8"));
17536
+ ownership = JSON.parse(await readFile15(path, "utf8"));
17226
17537
  } catch {
17227
17538
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
17228
17539
  }
@@ -17735,7 +18046,7 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
17735
18046
  };
17736
18047
 
17737
18048
  // src/mobile/releaseDoctor.ts
17738
- import { access as access8, readFile as readFile15, readdir as readdir4 } from "fs/promises";
18049
+ import { access as access8, readFile as readFile16, readdir as readdir4 } from "fs/promises";
17739
18050
  import { dirname as dirname29, extname as extname8, join as join49, relative as relative24 } from "path";
17740
18051
  var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17741
18052
  try {
@@ -17749,7 +18060,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17749
18060
  return findHmrAsset(path);
17750
18061
  if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
17751
18062
  return;
17752
- const source = await readFile15(path, "utf8");
18063
+ const source = await readFile16(path, "utf8");
17753
18064
  return HMR_ASSET_PATTERN.test(source) ? path : undefined;
17754
18065
  }, findHmrAsset = async (root) => {
17755
18066
  if (!await pathExists5(root))
@@ -17788,18 +18099,18 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17788
18099
  if (!await pathExists5(nativeConfigPath)) {
17789
18100
  return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
17790
18101
  }
17791
- const unsafe = isUnsafeCapacitorConfig(await readFile15(nativeConfigPath, "utf8"));
18102
+ const unsafe = isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"));
17792
18103
  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
18104
  }, manifestReleaseCheck = async (manifestPath) => {
17794
18105
  if (!await pathExists5(manifestPath)) {
17795
18106
  return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
17796
18107
  }
17797
- const source = await readFile15(manifestPath, "utf8");
18108
+ const source = await readFile16(manifestPath, "utf8");
17798
18109
  const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
17799
18110
  const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
17800
18111
  const networkConfigPath = networkConfigName ? join49(dirname29(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
17801
18112
  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;
18113
+ const developmentTrustContents = networkConfigPath ? await readFile16(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
17803
18114
  const developmentTrust = developmentTrustReference || developmentTrustContents;
17804
18115
  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
18116
  }, hmrAssetsReleaseCheck = async (publicRoot) => {
@@ -17831,7 +18142,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17831
18142
  if (!config.platforms.includes("android") || permissions.length === 0)
17832
18143
  return;
17833
18144
  const path = join49(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
17834
- const source = await readFile15(path, "utf8");
18145
+ const source = await readFile16(path, "utf8");
17835
18146
  const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
17836
18147
  if (missing.length === 0)
17837
18148
  return;
@@ -17840,7 +18151,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17840
18151
  if (!config.platforms.includes("ios") || purposes.length === 0)
17841
18152
  return;
17842
18153
  const path = join49(config.nativeProjectDirectory, "ios/App/App/Info.plist");
17843
- const source = await readFile15(path, "utf8");
18154
+ const source = await readFile16(path, "utf8");
17844
18155
  const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
17845
18156
  if (missing.length === 0)
17846
18157
  return;
@@ -17893,7 +18204,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17893
18204
  }
17894
18205
  if (!await pathExists5(nativeConfigPath)) {
17895
18206
  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"))) {
18207
+ } else if (isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"))) {
17897
18208
  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
18209
  } else {
17899
18210
  checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
@@ -17901,7 +18212,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17901
18212
  if (!await pathExists5(infoPath)) {
17902
18213
  checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
17903
18214
  } else {
17904
- const info2 = await readFile15(infoPath, "utf8");
18215
+ const info2 = await readFile16(infoPath, "utf8");
17905
18216
  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
18217
  }
17907
18218
  const hmrAsset = await findHmrAsset(publicRoot);
@@ -17954,7 +18265,7 @@ import {
17954
18265
  copyFile as copyFile5,
17955
18266
  mkdir as mkdir12,
17956
18267
  mkdtemp as mkdtemp5,
17957
- readFile as readFile16,
18268
+ readFile as readFile17,
17958
18269
  rename as rename12,
17959
18270
  rm as rm8,
17960
18271
  stat as stat2,
@@ -18010,7 +18321,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18010
18321
  artifactPath
18011
18322
  ]);
18012
18323
  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) => {
18324
+ }, sha256File2 = async (path) => createHash12("sha256").update(await readFile17(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
18014
18325
  const root = resolve39(projectRoot);
18015
18326
  const output = resolve39(root, requested ?? ".absolutejs/mobile/releases/android");
18016
18327
  const projectRelative = relative25(root, output);
@@ -18023,7 +18334,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18023
18334
  const artifactName = "app-release.aab";
18024
18335
  const destination = join50(releaseRoot, artifactName);
18025
18336
  if (await pathExists6(releaseRoot)) {
18026
- const existing = requireManifestIdentity(JSON.parse(await readFile16(join50(releaseRoot, "release.json"), "utf8")), metadata);
18337
+ const existing = requireManifestIdentity(JSON.parse(await readFile17(join50(releaseRoot, "release.json"), "utf8")), metadata);
18027
18338
  const [installedBytes, installedSha256] = await Promise.all([
18028
18339
  stat2(destination).then(({ size }) => size),
18029
18340
  sha256File2(destination)
@@ -18070,7 +18381,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
18070
18381
  const host2 = options.host ?? detectAbsoluteMobileHost();
18071
18382
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
18072
18383
  const nativeDirectory = join50(options.config.nativeProjectDirectory, "android");
18073
- const manifest = requireManifest2(JSON.parse(await readFile16(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
18384
+ const manifest = requireManifest2(JSON.parse(await readFile17(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
18074
18385
  if (manifest.appId !== options.config.appId) {
18075
18386
  throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
18076
18387
  }
@@ -18133,8 +18444,46 @@ var init_androidRelease = __esm(() => {
18133
18444
  init_androidEmulatorController();
18134
18445
  });
18135
18446
 
18447
+ // src/mobile/iosDeviceAcceptance.ts
18448
+ var absoluteIosDeviceAcceptanceCommands = (options) => {
18449
+ const xcrun = options.xcrun ?? "/usr/bin/xcrun";
18450
+ const prefix = [xcrun, "devicectl", "device"];
18451
+ return {
18452
+ apps: [...prefix, "info", "apps", "--device", options.device],
18453
+ details: [...prefix, "info", "details", "--device", options.device],
18454
+ launch: [
18455
+ ...prefix,
18456
+ "process",
18457
+ "launch",
18458
+ "--terminate-existing",
18459
+ "--device",
18460
+ options.device,
18461
+ options.appId
18462
+ ]
18463
+ };
18464
+ }, requireSuccess3 = (result, message) => {
18465
+ if (result.exitCode !== 0)
18466
+ throw new Error(message);
18467
+ return result;
18468
+ }, testAbsoluteIosPhysicalDevice = async (options) => {
18469
+ const commands = absoluteIosDeviceAcceptanceCommands(options);
18470
+ requireSuccess3(await options.capture(commands.details), "The selected physical iOS device is unavailable. Pair it in Xcode Device Hub, trust this Mac, unlock it, and enable Developer Mode.");
18471
+ const apps = requireSuccess3(await options.capture(commands.apps), "AbsoluteJS could not inspect installed apps on the physical iOS device.");
18472
+ if (!apps.stdout.includes(options.appId))
18473
+ throw new Error("The AbsoluteJS app is not installed on the selected physical iOS device. Start bun dev with the same --ios-device value first.");
18474
+ const now = options.now ?? performance.now.bind(performance);
18475
+ const startedAt = now();
18476
+ requireSuccess3(await options.capture(commands.launch), "AbsoluteJS could not relaunch the app on the physical iOS device.");
18477
+ await options.waitForHmr();
18478
+ return {
18479
+ hmrConnected: true,
18480
+ installed: true,
18481
+ relaunchMs: Math.round(now() - startedAt)
18482
+ };
18483
+ };
18484
+
18136
18485
  // src/mobile/iosConformance.ts
18137
- import { readFile as readFile17, stat as stat3 } from "fs/promises";
18486
+ import { readFile as readFile18, stat as stat3 } from "fs/promises";
18138
18487
  var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
18139
18488
  const match = HMR_LINE.exec(line);
18140
18489
  if (!match)
@@ -18169,7 +18518,7 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
18169
18518
  if (Date.now() > deadline)
18170
18519
  throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
18171
18520
  options.signal?.throwIfAborted();
18172
- const contents = await readFile17(options.logPath).catch(() => Buffer.alloc(0));
18521
+ const contents = await readFile18(options.logPath).catch(() => Buffer.alloc(0));
18173
18522
  if (contents.byteLength < offset) {
18174
18523
  offset = 0;
18175
18524
  buffered = "";
@@ -18193,7 +18542,7 @@ var init_iosConformance = __esm(() => {
18193
18542
  });
18194
18543
 
18195
18544
  // src/mobile/nativeTestReport.ts
18196
- import { mkdir as mkdir13, readFile as readFile18, writeFile as writeFile15 } from "fs/promises";
18545
+ import { mkdir as mkdir13, readFile as readFile19, writeFile as writeFile15 } from "fs/promises";
18197
18546
  import { join as join51 } from "path";
18198
18547
  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
18548
  `, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
@@ -18203,6 +18552,15 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
18203
18552
  if (run.hmr)
18204
18553
  hmrResult = run.hmr.outcome === "failed" ? "FAIL" : "PASS";
18205
18554
  const routeDetails = run.routes?.length ? ` Routes: ${run.routes.join(", ")}.` : "";
18555
+ let artifactDetails = "No target screenshot was captured.";
18556
+ let artifactResult = "FAIL";
18557
+ if (run.screenshot) {
18558
+ artifactDetails = "A target screenshot was captured. Visually review it before sharing this directory.";
18559
+ artifactResult = "PASS";
18560
+ } else if (run.targetKind === "device") {
18561
+ artifactDetails = "Physical-device screen capture was intentionally skipped; use Xcode Device Hub for manual visual evidence.";
18562
+ artifactResult = "SKIPPED";
18563
+ }
18206
18564
  const checks = [
18207
18565
  {
18208
18566
  details: `Captured host, toolchain, Bun, and AbsoluteJS metadata for ${target}.`,
@@ -18221,10 +18579,10 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
18221
18579
  result: hmrResult
18222
18580
  },
18223
18581
  {
18224
- details: run.screenshot ? "A target screenshot was captured. Visually review it before sharing this directory." : "No target screenshot was captured.",
18582
+ details: artifactDetails,
18225
18583
  ...evidence ? { evidence } : {},
18226
18584
  id: "AUTO-ARTIFACT-01",
18227
- result: run.screenshot ? "PASS" : "FAIL"
18585
+ result: artifactResult
18228
18586
  }
18229
18587
  ];
18230
18588
  if (run.upgrade) {
@@ -18257,22 +18615,37 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
18257
18615
  result: Object.values(syncMigration.state).every(Boolean) ? "PASS" : "FAIL"
18258
18616
  });
18259
18617
  }
18618
+ if (run.deviceAcceptance) {
18619
+ checks.push({
18620
+ details: `The installed physical-device app relaunched and reconnected to native HMR in ${run.deviceAcceptance.relaunchMs}ms.`,
18621
+ id: "AUTO-DEVICE-01",
18622
+ result: "PASS"
18623
+ }, {
18624
+ details: run.deviceAcceptance.https ? "The physical app reached the HTTPS development server and established native HMR; this proves the active app session accepted the development trust path." : "Physical-device acceptance did not use HTTPS.",
18625
+ id: "AUTO-DEVICE-HTTPS-01",
18626
+ result: run.deviceAcceptance.https ? "PASS" : "FAIL"
18627
+ }, {
18628
+ details: run.deviceAcceptance.remote ? "The lifecycle commands ran on the paired Remote Mac and HMR returned through the active relay." : "The acceptance run used the local Mac.",
18629
+ id: "AUTO-DEVICE-REMOTE-01",
18630
+ result: run.deviceAcceptance.remote ? "PASS" : "SKIPPED"
18631
+ });
18632
+ }
18260
18633
  return checks;
18261
18634
  }, createAbsoluteNativeTestReport = (options) => ({
18262
18635
  automatedChecks: options.automatedChecks ?? createAbsoluteNativeAutomatedChecks(options.run),
18263
18636
  generatedAt: options.generatedAt ?? new Date().toISOString(),
18264
- manualChecks: options.manualChecks.map(([id, details]) => ({
18637
+ manualChecks: options.manualChecks.map(([id, details]) => options.manualCheckResults?.[id] ?? {
18265
18638
  details,
18266
18639
  id,
18267
18640
  result: "NOT_RUN"
18268
- })),
18641
+ }),
18269
18642
  metadata: options.metadata,
18270
18643
  overallResult: options.run.status === "fail" ? "FAIL" : "INCOMPLETE",
18271
18644
  platform: options.run.platform,
18272
18645
  reportVersion: 1,
18273
18646
  run: options.run
18274
18647
  }), readPackageVersionForNativeReport = async (packageJsonPath) => {
18275
- const manifest = JSON.parse(await readFile18(packageJsonPath, "utf8"));
18648
+ const manifest = JSON.parse(await readFile19(packageJsonPath, "utf8"));
18276
18649
  if (typeof manifest !== "object" || manifest === null)
18277
18650
  return "unknown";
18278
18651
  const version2 = Reflect.get(manifest, "version");
@@ -18327,10 +18700,18 @@ var init_nativeTestReport = __esm(() => {
18327
18700
 
18328
18701
  // src/mobile/iosTestReport.ts
18329
18702
  var MANUAL_CHECKS, readPackageVersionForIosReport, sanitizeIosReportText, writeAbsoluteIosPartnerReport, createAbsoluteIosPartnerReport = (options) => {
18330
- const { udid, ...run } = options.run;
18703
+ const { targetId, targetKind, ...run } = options.run;
18704
+ const physicalResults = targetKind === "device" && run.deviceAcceptance ? {
18705
+ "DEVICEDEV-01": {
18706
+ details: "AbsoluteJS selected a physical target from the running development session without using Simulator.",
18707
+ id: "DEVICEDEV-01",
18708
+ result: "PASS"
18709
+ }
18710
+ } : undefined;
18331
18711
  return createAbsoluteNativeTestReport({
18332
18712
  ...options.generatedAt ? { generatedAt: options.generatedAt } : {},
18333
18713
  manualChecks: MANUAL_CHECKS,
18714
+ ...physicalResults ? { manualCheckResults: physicalResults } : {},
18334
18715
  metadata: {
18335
18716
  absolutejsVersion: options.absolutejsVersion,
18336
18717
  bunVersion: options.bunVersion,
@@ -18341,8 +18722,8 @@ var MANUAL_CHECKS, readPackageVersionForIosReport, sanitizeIosReportText, writeA
18341
18722
  run: {
18342
18723
  ...run,
18343
18724
  platform: "ios",
18344
- targetId: udid,
18345
- targetKind: "simulator"
18725
+ targetId,
18726
+ targetKind
18346
18727
  }
18347
18728
  });
18348
18729
  };
@@ -18359,6 +18740,10 @@ var init_iosTestReport = __esm(() => {
18359
18740
  ["SETUP-05", "Confirm generated iOS project signing and Xcode warnings."],
18360
18741
  ["DEV-01", "Record cold and warm bun dev startup timings."],
18361
18742
  ["DEV-02", "Complete route traversal, HMR, relaunch, and recovery checks."],
18743
+ ...Array.from({ length: 10 }, (_, index) => [
18744
+ `DEVICEDEV-${String(index + 1).padStart(2, "0")}`,
18745
+ `Complete physical-device development runbook check DEVICEDEV-${String(index + 1).padStart(2, "0")}.`
18746
+ ]),
18362
18747
  ["CAP-01", "Complete automatic device-capability provisioning checks."],
18363
18748
  ...Array.from({ length: 8 }, (_, index) => [
18364
18749
  `SYSUI-${String(index + 1).padStart(2, "0")}`,
@@ -18613,11 +18998,11 @@ var exports_mobile = {};
18613
18998
  __export(exports_mobile, {
18614
18999
  runMobile: () => runMobile
18615
19000
  });
18616
- import { access as access11, mkdir as mkdir14, readFile as readFile19, writeFile as writeFile16 } from "fs/promises";
19001
+ import { access as access11, mkdir as mkdir14, readFile as readFile20, writeFile as writeFile16 } from "fs/promises";
18617
19002
  import { join as join52, resolve as resolve41 } from "path";
18618
19003
  import { createInterface } from "readline/promises";
18619
19004
  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"));
19005
+ const manifest = JSON.parse(await readFile20(join52(projectRoot, "package.json"), "utf8"));
18621
19006
  if (!isRecord15(manifest))
18622
19007
  throw new TypeError("Application package.json must contain an object.");
18623
19008
  const names = new Set;
@@ -18630,7 +19015,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
18630
19015
  return names;
18631
19016
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
18632
19017
  try {
18633
- const manifest = JSON.parse(await readFile19(join52(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
19018
+ const manifest = JSON.parse(await readFile20(join52(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
18634
19019
  return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
18635
19020
  } catch {
18636
19021
  return;
@@ -19536,7 +19921,7 @@ Emulator setup verification:`);
19536
19921
  if (!instance?.logFile)
19537
19922
  throw new TypeError("The selected dev server has no session log. Run `bun dev` normally before requesting --wait-for-hmr.");
19538
19923
  if (!args.includes("--json"))
19539
- console.log("iOS simulator is ready. Save a source edit now; waiting for a native HMR acknowledgement\u2026");
19924
+ console.log("iOS app is ready. Save a source edit now; waiting for a native HMR acknowledgement\u2026");
19540
19925
  return waitForAbsoluteIosHmrLog({
19541
19926
  logPath: instance.logFile,
19542
19927
  timeoutMs
@@ -19546,7 +19931,7 @@ Emulator setup verification:`);
19546
19931
  console.log(JSON.stringify(report, null, 2));
19547
19932
  return;
19548
19933
  }
19549
- console.log(`\u2713 iOS simulator ${report.udid}: ${report.appId} launched; screenshot ${report.screenshot}.`);
19934
+ console.log(report.target === "device" ? `\u2713 Physical iOS app ${report.appId} relaunched and reconnected to HMR; no device identifier or screenshot was recorded.` : `\u2713 iOS simulator ${report.targetId}: ${report.appId} launched; screenshot ${report.screenshot}.`);
19550
19935
  if (!report.hmrApply)
19551
19936
  return;
19552
19937
  const timing = report.hmrApply.serverMs === undefined ? "" : ` (server ${report.hmrApply.serverMs}ms, client ${report.hmrApply.clientMs}ms)`;
@@ -19557,6 +19942,41 @@ Emulator setup verification:`);
19557
19942
  if (!xcrun)
19558
19943
  throw new TypeError("iOS simulator tools are unavailable. Run this command on macOS after `absolute mobile doctor ios --fix`.");
19559
19944
  return xcrun;
19945
+ }, runningIosDevice = (instance) => {
19946
+ if (!instance)
19947
+ return;
19948
+ const index = instance.command.indexOf("--ios-device");
19949
+ return index === NOT_FOUND3 ? undefined : instance.command[index + 1];
19950
+ }, physicalIosCapture = async (options) => {
19951
+ const remoteName = valueAfter(options.args, "--remote");
19952
+ if (remoteName && options.instance.iosRemoteMac && remoteName !== options.instance.iosRemoteMac)
19953
+ throw new TypeError("--remote must match the Remote Mac used by the running bun dev session.");
19954
+ const selectedRemoteName = remoteName ?? options.instance.iosRemoteMac;
19955
+ const remote = process.platform === "darwin" && !selectedRemoteName ? undefined : await getAbsoluteRemoteMacProfile(selectedRemoteName);
19956
+ if (process.platform !== "darwin" && !remote)
19957
+ throw new TypeError("Physical iOS acceptance requires macOS or a paired Remote Mac.");
19958
+ if (remote) {
19959
+ const macos = await captureAbsoluteRemoteMacCommand(remote, [
19960
+ "/usr/bin/sw_vers",
19961
+ "-productVersion"
19962
+ ]);
19963
+ if (macos.exitCode !== 0)
19964
+ throw new Error("Remote Mac version inspection failed.");
19965
+ return {
19966
+ macosVersion: macos.stdout.trim(),
19967
+ remote: true,
19968
+ xcodeVersion: remote.xcodeVersion,
19969
+ xcrun: "/usr/bin/xcrun",
19970
+ capture: (command) => captureAbsoluteRemoteMacCommand(remote, command)
19971
+ };
19972
+ }
19973
+ return {
19974
+ macosVersion: requireCapturedCommand(["/usr/bin/sw_vers", "-productVersion"], "macOS version inspection").stdout.trim(),
19975
+ remote: false,
19976
+ xcodeVersion: requireCapturedCommand(["/usr/bin/xcodebuild", "-version"], "Xcode version inspection").stdout.trim(),
19977
+ xcrun: "/usr/bin/xcrun",
19978
+ capture: async (command) => captureCommand4(command)
19979
+ };
19560
19980
  }, selectIosSimulator = (xcrun, explicitUdid) => {
19561
19981
  const result = captureCommand4([
19562
19982
  xcrun,
@@ -19662,12 +20082,124 @@ Emulator setup verification:`);
19662
20082
  const reportRoot = nativeReportRoot(options.args, options.projectRoot, "ios");
19663
20083
  if (!reportRoot)
19664
20084
  return;
19665
- const metadata = await iosReportMetadata(options.xcrun);
20085
+ const metadata = options.metadata ?? (options.xcrun ? await iosReportMetadata(options.xcrun) : undefined);
20086
+ if (!metadata)
20087
+ throw new Error("iOS report metadata could not be inspected.");
19666
20088
  const paths = await writeAbsoluteIosPartnerReport(reportRoot, createAbsoluteIosPartnerReport({ ...metadata, run: options.run }));
19667
20089
  const print = options.args.includes("--json") ? console.error : console.log;
19668
20090
  print(`iOS partner report: ${paths.markdownPath}`);
19669
20091
  print(`Return this report directory: ${paths.directory}`);
19670
20092
  return paths;
20093
+ }, testPhysicalIos = async (options) => {
20094
+ const {
20095
+ args,
20096
+ device,
20097
+ https,
20098
+ instance,
20099
+ mobile,
20100
+ port,
20101
+ projectRoot,
20102
+ timeoutMs
20103
+ } = options;
20104
+ const transport = await physicalIosCapture({ args, instance });
20105
+ const reportRoot = nativeReportRoot(args, projectRoot, "ios");
20106
+ const startedAt = performance.now();
20107
+ try {
20108
+ const acceptance = await testAbsoluteIosPhysicalDevice({
20109
+ appId: mobile.appId,
20110
+ capture: transport.capture,
20111
+ device,
20112
+ xcrun: transport.xcrun,
20113
+ waitForHmr: () => waitForIosHmrClient({ https, port, timeoutMs })
20114
+ });
20115
+ const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
20116
+ const report = {
20117
+ appId: mobile.appId,
20118
+ durationMs: Math.round(performance.now() - startedAt),
20119
+ ...hmrApply ? { hmrApply } : {},
20120
+ hmrConnected: true,
20121
+ platform: "ios",
20122
+ port,
20123
+ provider: "capacitor",
20124
+ status: "pass",
20125
+ target: "device",
20126
+ targetId: "physical-device"
20127
+ };
20128
+ sendTelemetryEvent("mobile:ios-device-conformance", {
20129
+ durationMs: report.durationMs,
20130
+ platform: report.platform,
20131
+ provider: report.provider,
20132
+ remote: transport.remote,
20133
+ success: true,
20134
+ waitedForHmr: args.includes("--wait-for-hmr")
20135
+ });
20136
+ printIosTestReport(report, args.includes("--json"));
20137
+ await writeRequestedIosReport({
20138
+ args,
20139
+ metadata: {
20140
+ absolutejsVersion: await absolutejsVersionForReport(),
20141
+ bunVersion: Bun.version,
20142
+ macosVersion: transport.macosVersion,
20143
+ xcodeVersion: transport.xcodeVersion
20144
+ },
20145
+ projectRoot,
20146
+ run: {
20147
+ appId: report.appId,
20148
+ deviceAcceptance: {
20149
+ https: true,
20150
+ relaunchMs: acceptance.relaunchMs,
20151
+ remote: transport.remote
20152
+ },
20153
+ durationMs: report.durationMs,
20154
+ ...hmrApply ? {
20155
+ hmr: {
20156
+ durationMs: hmrApply.duration,
20157
+ outcome: hmrApply.outcome,
20158
+ ...hmrApply.clientMs === undefined ? {} : { clientMs: hmrApply.clientMs },
20159
+ ...hmrApply.serverMs === undefined ? {} : { serverMs: hmrApply.serverMs }
20160
+ }
20161
+ } : {},
20162
+ hmrConnected: true,
20163
+ port,
20164
+ status: "pass",
20165
+ targetId: "physical-device",
20166
+ targetKind: "device"
20167
+ }
20168
+ });
20169
+ return report;
20170
+ } catch (error) {
20171
+ const durationMs = Math.round(performance.now() - startedAt);
20172
+ sendTelemetryEvent("mobile:ios-device-conformance", {
20173
+ durationMs,
20174
+ platform: "ios",
20175
+ provider: "capacitor",
20176
+ remote: transport.remote,
20177
+ success: false,
20178
+ waitedForHmr: args.includes("--wait-for-hmr")
20179
+ });
20180
+ if (reportRoot)
20181
+ await writeRequestedIosReport({
20182
+ args,
20183
+ metadata: {
20184
+ absolutejsVersion: await absolutejsVersionForReport(),
20185
+ bunVersion: Bun.version,
20186
+ macosVersion: transport.macosVersion,
20187
+ xcodeVersion: transport.xcodeVersion
20188
+ },
20189
+ projectRoot,
20190
+ run: {
20191
+ appId: mobile.appId,
20192
+ durationMs,
20193
+ error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
20194
+ hmrConnected: false,
20195
+ port,
20196
+ status: "fail",
20197
+ targetId: "physical-device",
20198
+ targetKind: "device"
20199
+ }
20200
+ });
20201
+ throw error;
20202
+ }
19671
20203
  }, testIos = async (args) => {
19672
20204
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
19673
20205
  const { https, instance, port } = requireIosTestContext(args, projectRoot);
@@ -19676,6 +20208,35 @@ Emulator setup verification:`);
19676
20208
  if (args.includes("--route"))
19677
20209
  throw new TypeError("iOS simulator route selection is not exposed through simctl; configure mobile.entry for the native route matrix.");
19678
20210
  const timeoutMs = androidTestTimeout(args);
20211
+ const requestedDevice = valueAfter(args, "--device");
20212
+ if (args.includes("--device") && !requestedDevice)
20213
+ throw new TypeError("mobile test ios --device requires a device identifier or name.");
20214
+ if (requestedDevice && (valueAfter(args, "--udid") || valueAfter(args, "--serial")))
20215
+ throw new TypeError("mobile test ios --device cannot be combined with a simulator selector.");
20216
+ if (!requestedDevice && args.includes("--remote"))
20217
+ throw new TypeError("mobile test ios --remote is available only with --device.");
20218
+ if (requestedDevice) {
20219
+ const device = normalizeAbsoluteIosDeviceIdentifier(requestedDevice);
20220
+ const activeDevice = runningIosDevice(instance);
20221
+ if (!activeDevice)
20222
+ throw new TypeError("The selected dev server is not running a physical iOS session. Start bun dev with --ios-device first.");
20223
+ if (activeDevice !== device)
20224
+ throw new TypeError("--device must match the --ios-device value used by the running bun dev session.");
20225
+ if (!https)
20226
+ throw new TypeError("Physical iOS acceptance requires dev.https: true so the report can prove the native trust path.");
20227
+ if (!instance)
20228
+ throw new TypeError("Physical iOS acceptance requires a registered bun dev session.");
20229
+ return testPhysicalIos({
20230
+ args,
20231
+ device,
20232
+ https,
20233
+ instance,
20234
+ mobile,
20235
+ port,
20236
+ projectRoot,
20237
+ timeoutMs
20238
+ });
20239
+ }
19679
20240
  const xcrun = await requireIosXcrun();
19680
20241
  const simulator = selectIosSimulator(xcrun, valueAfter(args, "--udid") ?? valueAfter(args, "--serial"));
19681
20242
  const reportRoot = nativeReportRoot(args, projectRoot, "ios");
@@ -19713,7 +20274,8 @@ Emulator setup verification:`);
19713
20274
  provider: "capacitor",
19714
20275
  screenshot,
19715
20276
  status: "pass",
19716
- udid: simulator.udid
20277
+ target: "simulator",
20278
+ targetId: simulator.udid
19717
20279
  };
19718
20280
  sendTelemetryEvent("mobile:ios-conformance", {
19719
20281
  durationMs: report.durationMs,
@@ -19741,7 +20303,8 @@ Emulator setup verification:`);
19741
20303
  port: report.port,
19742
20304
  screenshot: report.screenshot,
19743
20305
  status: report.status,
19744
- udid: report.udid
20306
+ targetId: report.targetId,
20307
+ targetKind: report.target
19745
20308
  },
19746
20309
  xcrun
19747
20310
  });
@@ -19774,7 +20337,8 @@ Emulator setup verification:`);
19774
20337
  port,
19775
20338
  ...screenshot ? { screenshot } : {},
19776
20339
  status: "fail",
19777
- udid: simulator.udid
20340
+ targetId: simulator.udid,
20341
+ targetKind: "simulator"
19778
20342
  },
19779
20343
  xcrun
19780
20344
  });
@@ -19834,7 +20398,7 @@ Emulator setup verification:`);
19834
20398
  await publishIos(args.slice(2));
19835
20399
  return;
19836
20400
  }
19837
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--udid id] [--artifacts dir] [--json]> [--config path]");
20401
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
19838
20402
  };
19839
20403
  var init_mobile = __esm(() => {
19840
20404
  init_dependencies();
@@ -19855,6 +20419,7 @@ var init_mobile = __esm(() => {
19855
20419
  init_androidRelease();
19856
20420
  init_iosRelease();
19857
20421
  init_iosSimulatorController();
20422
+ init_iosPhysicalDeviceTransport();
19858
20423
  init_iosConformance();
19859
20424
  init_iosTestReport();
19860
20425
  init_androidTestReport();
@@ -21208,13 +21773,15 @@ var androidToolchainReady = (checks, target = "emulator") => {
21208
21773
  ]);
21209
21774
  return checks.every((check) => check.platform !== "android" || target === "device" && deviceOnlyChecks.has(check.id) || check.status !== "fail" && check.status !== "warn");
21210
21775
  };
21211
- var iosToolchainReady = (checks) => checks.every((check) => check.platform !== "ios" || check.status !== "fail" && check.status !== "warn");
21776
+ var iosToolchainReady = (checks, target = "simulator") => checks.every((check) => check.platform !== "ios" || target === "device" && check.id === "ios.runtime" || check.status !== "fail" && check.status !== "warn");
21212
21777
  var dev = async (serverEntry, configPath2, options = {}) => {
21213
21778
  let httpsEnabled = false;
21214
21779
  let devCertificateAuthorityPath = null;
21215
21780
  let resolvedDev;
21216
21781
  let buildDirectory = resolve8(process.cwd(), "build");
21217
21782
  let mobileConfig;
21783
+ let iosPhysicalServerHost;
21784
+ let selectedRemoteMacProfile;
21218
21785
  try {
21219
21786
  const config = await loadConfig(configPath2);
21220
21787
  mobileConfig = config?.mobile;
@@ -21229,17 +21796,35 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21229
21796
  resolvedDev = resolveDevConfig(undefined);
21230
21797
  httpsEnabled = resolvedDev.https;
21231
21798
  }
21232
- if (options.androidDevice && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
21799
+ if ((options.androidDevice || options.iosDevice && detectAbsoluteMobileHost() === "macos") && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
21233
21800
  resolvedDev.host = "0.0.0.0";
21234
21801
  }
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.");
21802
+ if ((options.androidDevice || options.iosDevice) && !mobileConfig) {
21803
+ throw new TypeError("Physical-device development requires an absolute.config.ts mobile configuration.");
21239
21804
  }
21240
21805
  if (options.androidDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("android")) {
21241
21806
  throw new TypeError("--android-device requires android in mobile.platforms.");
21242
21807
  }
21808
+ if (options.iosDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("ios")) {
21809
+ throw new TypeError("--ios-device requires ios in mobile.platforms.");
21810
+ }
21811
+ if (options.iosDevice) {
21812
+ if (detectAbsoluteMobileHost() === "macos")
21813
+ iosPhysicalServerHost = mobileReachableHost(resolvedDev.host);
21814
+ else {
21815
+ selectedRemoteMacProfile = await getAbsoluteRemoteMacProfile();
21816
+ if (!selectedRemoteMacProfile)
21817
+ throw new Error("--ios-device requires macOS or a paired Remote Mac.");
21818
+ iosPhysicalServerHost = await inspectAbsoluteRemoteMacLanHost(selectedRemoteMacProfile);
21819
+ }
21820
+ }
21821
+ if (httpsEnabled) {
21822
+ const certificateHosts = [
21823
+ ...options.androidDevice ? [mobileReachableHost(resolvedDev.host)] : [],
21824
+ ...iosPhysicalServerHost ? [iosPhysicalServerHost] : []
21825
+ ];
21826
+ devCertificateAuthorityPath = await setupHttpsCert(certificateHosts.length > 0 ? certificateHosts : [resolvedDev.host]);
21827
+ }
21243
21828
  let androidDevProject = null;
21244
21829
  let iosDevProject = null;
21245
21830
  const mobileInteractive = options.mobile !== false && process.env.ABSOLUTE_NO_MOBILE !== "1" && process.stdin.isTTY === true && process.stdout.isTTY === true;
@@ -21275,9 +21860,9 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21275
21860
  }
21276
21861
  if (normalized.platforms.includes("ios")) {
21277
21862
  if (detectAbsoluteMobileHost() !== "macos") {
21278
- const remote = await getAbsoluteRemoteMacProfile();
21863
+ const remote = selectedRemoteMacProfile ?? await getAbsoluteRemoteMacProfile();
21279
21864
  if (!remote) {
21280
- console.log(cliTag("\x1B[33m", "iOS simulator skipped. Pair a Mac with `absolute mobile pair mac <name> <user@host>`."));
21865
+ console.log(cliTag("\x1B[33m", "iOS target skipped. Pair a Mac with `absolute mobile pair mac <name> <user@host>`."));
21281
21866
  } else {
21282
21867
  const nativeDirectory = join13(normalized.nativeProjectDirectory, "ios");
21283
21868
  if (!existsSync5(nativeDirectory)) {
@@ -21288,12 +21873,13 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21288
21873
  }
21289
21874
  }
21290
21875
  } else {
21291
- let ready = iosToolchainReady(await inspectAbsoluteMobileToolchain());
21876
+ const iosTarget = options.iosDevice ? "device" : "simulator";
21877
+ let ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
21292
21878
  if (!ready) {
21293
- const install = await confirmPrompt("iOS simulation is not configured. Install the missing simulator runtime now?");
21879
+ 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
21880
  if (install) {
21295
21881
  await fixAbsoluteMobileEmulatorToolchain("ios");
21296
- ready = iosToolchainReady(await inspectAbsoluteMobileToolchain());
21882
+ ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
21297
21883
  } else {
21298
21884
  console.log(cliTag("\x1B[33m", "Mobile simulator skipped. Run `absolute mobile doctor ios --fix` when ready."));
21299
21885
  }
@@ -21307,7 +21893,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21307
21893
  if (existsSync5(nativeDirectory) || createNativeProject) {
21308
21894
  iosDevProject = await prepareAbsoluteIosDevProject(normalized, {
21309
21895
  createNativeProject,
21310
- projectRoot: process.cwd()
21896
+ projectRoot: process.cwd(),
21897
+ target: iosTarget
21311
21898
  });
21312
21899
  }
21313
21900
  }
@@ -21351,7 +21938,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21351
21938
  serverEntry,
21352
21939
  ...configPath2 ? ["--config", configPath2] : [],
21353
21940
  ...options.mobile === false ? ["--no-mobile"] : [],
21354
- ...options.androidDevice ? ["--android-device", options.androidDevice] : []
21941
+ ...options.androidDevice ? ["--android-device", options.androidDevice] : [],
21942
+ ...options.iosDevice ? ["--ios-device", options.iosDevice] : []
21355
21943
  ].filter((part) => part.length > 0);
21356
21944
  registerInstance({
21357
21945
  command: relaunchCommand,
@@ -21361,6 +21949,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21361
21949
  frameworks: [],
21362
21950
  host: resolvedDev.host,
21363
21951
  https: httpsEnabled,
21952
+ ...selectedRemoteMacProfile ? { iosRemoteMac: selectedRemoteMacProfile.name } : {},
21364
21953
  logFile: instanceLogFile,
21365
21954
  name: resolveProjectName(process.cwd()),
21366
21955
  pid: instancePid,
@@ -21523,6 +22112,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21523
22112
  platform: "ios",
21524
22113
  provider: "capacitor",
21525
22114
  startedSimulator: session.startedSimulator,
22115
+ target: session.targetKind,
21526
22116
  timings: session.timings
21527
22117
  });
21528
22118
  if (session.timings.building === undefined)
@@ -21533,14 +22123,17 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21533
22123
  installMs: session.timings.installing,
21534
22124
  platform: "ios",
21535
22125
  provider: "capacitor",
21536
- success: true
22126
+ success: true,
22127
+ target: session.targetKind
21537
22128
  });
21538
22129
  };
21539
22130
  const openIosDevSession = (iosProject) => {
21540
22131
  const sessionOptions = {
21541
22132
  certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
22133
+ deviceIdentifier: options.iosDevice,
21542
22134
  https: httpsEnabled,
21543
22135
  port,
22136
+ serverHost: iosPhysicalServerHost,
21544
22137
  signal: iosDevAbort.signal,
21545
22138
  log: (message) => printNativeOutput(cliTag("\x1B[35m", message)),
21546
22139
  nativeLog: (entry) => printNativeOutput(iosLogTag(entry)),
@@ -21554,7 +22147,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21554
22147
  iosDevState = state;
21555
22148
  if (state === "ready" || state === "closed")
21556
22149
  return;
21557
- printNativeOutput(cliTag("\x1B[35m", `iOS simulator: ${state}.`));
22150
+ printNativeOutput(cliTag("\x1B[35m", `iOS ${options.iosDevice ? "device" : "simulator"}: ${state}.`));
21558
22151
  }
21559
22152
  };
21560
22153
  if (iosProject.remote)
@@ -21593,6 +22186,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21593
22186
  provider: "capacitor",
21594
22187
  rootInputChanged: change.rootInputChanged,
21595
22188
  success: true,
22189
+ target: replacement.targetKind,
21596
22190
  timings: replacement.timings
21597
22191
  });
21598
22192
  },
@@ -21603,7 +22197,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21603
22197
  host: iosTelemetryHost(iosProject),
21604
22198
  platform: "ios",
21605
22199
  provider: "capacitor",
21606
- success: false
22200
+ success: false,
22201
+ target: options.iosDevice ? "device" : "simulator"
21607
22202
  });
21608
22203
  }
21609
22204
  });
@@ -21630,9 +22225,10 @@ var dev = async (serverEntry, configPath2, options = {}) => {
21630
22225
  phase: iosDevState,
21631
22226
  platform: "ios",
21632
22227
  provider: "capacitor",
22228
+ target: options.iosDevice ? "device" : "simulator",
21633
22229
  timings: iosPhaseTimings
21634
22230
  });
21635
- console.error(cliTag("\x1B[31m", `iOS simulator failed: ${error instanceof Error ? error.message : String(error)}`));
22231
+ console.error(cliTag("\x1B[31m", `iOS ${options.iosDevice ? "device" : "simulator"} failed: ${error instanceof Error ? error.message : String(error)}`));
21636
22232
  }).finally(() => {
21637
22233
  iosDevStart = null;
21638
22234
  });
@@ -23982,16 +24578,24 @@ if (command === "dev") {
23982
24578
  sendTelemetryEvent("cli:command", { command });
23983
24579
  const configPath2 = parseNamedArg("--config");
23984
24580
  const androidDevice = parseNamedArg("--android-device");
24581
+ const iosDevice = parseNamedArg("--ios-device");
23985
24582
  if (args.includes("--android-device") && !androidDevice) {
23986
24583
  throw new TypeError("--android-device requires an ADB device serial.");
23987
24584
  }
23988
24585
  if (androidDevice && args.includes("--no-mobile")) {
23989
24586
  throw new TypeError("--android-device cannot be combined with --no-mobile.");
23990
24587
  }
23991
- const positionalArgs2 = stripNamedArgs("--config", "--android-device").filter((arg) => arg !== "--no-mobile");
24588
+ if (args.includes("--ios-device") && !iosDevice) {
24589
+ throw new TypeError("--ios-device requires an Xcode device identifier or name.");
24590
+ }
24591
+ if (iosDevice && args.includes("--no-mobile")) {
24592
+ throw new TypeError("--ios-device cannot be combined with --no-mobile.");
24593
+ }
24594
+ const positionalArgs2 = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile");
23992
24595
  const serverEntry = positionalArgs2[0] ?? DEFAULT_SERVER_ENTRY;
23993
24596
  await dev(serverEntry, configPath2, {
23994
24597
  androidDevice,
24598
+ iosDevice,
23995
24599
  mobile: !args.includes("--no-mobile")
23996
24600
  });
23997
24601
  } else if (command === "start") {
@@ -24150,13 +24754,13 @@ if (command === "dev") {
24150
24754
  console.error(message);
24151
24755
  console.error("Usage: absolute <command>");
24152
24756
  console.error("Commands:");
24153
- console.error(" dev [entry] [--no-mobile] [--android-device serial] Start web and configured mobile development");
24757
+ console.error(" dev [entry] [--no-mobile] [--android-device serial] [--ios-device identifier] Start web and configured mobile development");
24154
24758
  console.error(" workspace dev [--no-tui] Start multi-service workspace dev");
24155
24759
  console.error(" build [--outdir dir] [--profile] Build production assets");
24156
24760
  console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
24157
24761
  console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
24158
24762
  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");
24763
+ console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
24160
24764
  console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
24161
24765
  console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
24162
24766
  console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");