@absolutejs/absolute 0.20.0-beta.101 → 0.20.0-beta.102

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.
@@ -7,12 +7,12 @@ import {
7
7
  } from "./index-zh6hhrwy.js";
8
8
  import {
9
9
  start
10
- } from "./index-fdycyj4v.js";
10
+ } from "./index-rvpd4w9t.js";
11
11
  import {
12
12
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
13
13
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
14
14
  resolveAbsoluteMobileRoute
15
- } from "./index-0bcf7pdw.js";
15
+ } from "./index-6ff78meg.js";
16
16
  import"./index-bzs19w3r.js";
17
17
  import"./index-s2tazax7.js";
18
18
  import {
@@ -67,10 +67,10 @@ import {
67
67
  syncAbsoluteExpoWebAssets,
68
68
  unsignedAbsoluteMobileUpdate,
69
69
  writeAbsoluteExpoProject
70
- } from "./index-qd6mqgsc.js";
70
+ } from "./index-d5fr1xp3.js";
71
71
  import {
72
72
  buildAbsoluteExpoAndroidWslBundle
73
- } from "./index-kr23b0fp.js";
73
+ } from "./index-28nxv57v.js";
74
74
  import {
75
75
  normalizeAbsoluteIosDeviceIdentifier
76
76
  } from "./index-j66p8hn5.js";
@@ -88,6 +88,7 @@ import {
88
88
  writeAbsoluteCapacitorConfig
89
89
  } from "./index-28pbrd3q.js";
90
90
  import {
91
+ ABSOLUTE_ANDROID_AVD_NAME,
91
92
  absoluteManagedAndroidSdkRoot,
92
93
  detectAbsoluteMobileHost,
93
94
  inspectAbsoluteMobileToolchain
@@ -4411,9 +4412,9 @@ var require_sha256 = __commonJS(function(exports, module) {
4411
4412
  var forge = require_forge();
4412
4413
  require_md();
4413
4414
  require_util();
4414
- var sha2562 = module.exports = forge.sha256 = forge.sha256 || {};
4415
- forge.md.sha256 = forge.md.algorithms.sha256 = sha2562;
4416
- sha2562.create = function() {
4415
+ var sha2563 = module.exports = forge.sha256 = forge.sha256 || {};
4416
+ forge.md.sha256 = forge.md.algorithms.sha256 = sha2563;
4417
+ sha2563.create = function() {
4417
4418
  if (!_initialized) {
4418
4419
  _init();
4419
4420
  }
@@ -16775,15 +16776,15 @@ var require_main = __commonJS(function(exports) {
16775
16776
 
16776
16777
  // src/cli/scripts/mobile.ts
16777
16778
  import {
16778
- access as access12,
16779
- mkdir as mkdir12,
16780
- mkdtemp as mkdtemp3,
16781
- readFile as readFile16,
16782
- rm as rm6,
16783
- writeFile as writeFile14
16779
+ access as access13,
16780
+ mkdir as mkdir13,
16781
+ mkdtemp as mkdtemp4,
16782
+ readFile as readFile17,
16783
+ rm as rm7,
16784
+ writeFile as writeFile15
16784
16785
  } from "fs/promises";
16785
16786
  import { createPublicKey as createPublicKey2 } from "crypto";
16786
- import { join as join13, relative as relative11, resolve as resolve12 } from "path";
16787
+ import { join as join14, relative as relative12, resolve as resolve13 } from "path";
16787
16788
  import { createInterface } from "readline/promises";
16788
16789
 
16789
16790
  // src/mobile/nativeDeepLinks.ts
@@ -19838,21 +19839,23 @@ var expoEmbeddedAssetsCheck = async (config) => {
19838
19839
  cwd: assetsRoot,
19839
19840
  onlyFiles: true
19840
19841
  }))).sort();
19841
- if (sourceFiles.length === 0 || sourceFiles.length !== embeddedFiles.length)
19842
+ if (sourceFiles.length === 0 || embeddedFiles.length !== 1)
19842
19843
  throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
19843
- const matches = await Promise.all(sourceFiles.map(async (path, index) => {
19844
- const embedded = embeddedFiles[index];
19845
- if (!embedded)
19846
- return false;
19847
- const [left, right] = await Promise.all([
19848
- readFile8(join7(config.bundleDirectory, path)),
19849
- readFile8(join7(assetsRoot, embedded))
19850
- ]);
19851
- return left.equals(right);
19852
- }));
19853
- if (matches.some((value) => !value))
19844
+ const contents = await Promise.all(sourceFiles.map((path) => readFile8(join7(config.bundleDirectory, path))));
19845
+ let offset = 0;
19846
+ const entries = sourceFiles.map((path, index) => {
19847
+ const entry = {
19848
+ length: contents[index]?.byteLength ?? 0,
19849
+ offset,
19850
+ path: path.replaceAll("\\", "/")
19851
+ };
19852
+ offset += entry.length;
19853
+ return entry;
19854
+ });
19855
+ const archive = await readFile8(join7(assetsRoot, embeddedFiles[0] ?? ""));
19856
+ if (!archive.equals(Buffer.concat(contents)) || entries.some((entry) => !source.includes(JSON.stringify(entry))))
19854
19857
  throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
19855
- return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as ${embeddedFiles.length} opaque asset(s).`, generated);
19858
+ return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as one opaque archive containing ${sourceFiles.length} file(s).`, generated);
19856
19859
  } catch (error) {
19857
19860
  return fail("expo.bundle-projection", error instanceof Error ? error.message : "Generated Expo assets could not be validated.", generated, "Run `absolute mobile build <platform>` to regenerate and verify the production asset projection.");
19858
19861
  }
@@ -20588,6 +20591,343 @@ var buildAbsoluteAndroidRelease = async (options) => {
20588
20591
  return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
20589
20592
  };
20590
20593
 
20594
+ // src/mobile/androidReleaseAcceptance.ts
20595
+ import { createHash as createHash3 } from "crypto";
20596
+ import {
20597
+ access as access6,
20598
+ mkdir as mkdir7,
20599
+ mkdtemp as mkdtemp2,
20600
+ readFile as readFile10,
20601
+ rename as rename8,
20602
+ rm as rm5,
20603
+ stat as stat3,
20604
+ writeFile as writeFile9
20605
+ } from "fs/promises";
20606
+ import { homedir } from "os";
20607
+ import {
20608
+ basename,
20609
+ dirname as dirname7,
20610
+ isAbsolute as isAbsolute3,
20611
+ join as join9,
20612
+ relative as relative5,
20613
+ resolve as resolve6,
20614
+ sep as sep3
20615
+ } from "path";
20616
+
20617
+ // src/mobile/androidUpgradeConformance.ts
20618
+ var captureCommand2 = async (command) => {
20619
+ const process2 = Bun.spawn(command, { stderr: "pipe", stdout: "pipe" });
20620
+ const [exitCode, stderr, stdout] = await Promise.all([
20621
+ process2.exited,
20622
+ new Response(process2.stderr).text(),
20623
+ new Response(process2.stdout).text()
20624
+ ]);
20625
+ return { exitCode, stderr, stdout };
20626
+ };
20627
+ var field = (output, name) => new RegExp(`^\\s*${name}=([^\\r\\n]+)$`, "mu").exec(output)?.[1]?.trim();
20628
+ var inspectAbsoluteAndroidInstalledApp = async (adb, serial, appId, run = captureCommand2) => {
20629
+ const result = await run([
20630
+ adb,
20631
+ "-s",
20632
+ serial,
20633
+ "shell",
20634
+ "dumpsys",
20635
+ "package",
20636
+ appId
20637
+ ]);
20638
+ if (result.exitCode !== 0)
20639
+ throw new Error(`Could not inspect installed Android app ${appId}: ${result.stderr.trim() || result.stdout.trim()}`);
20640
+ const installed = parseAbsoluteAndroidInstalledApp(appId, result.stdout);
20641
+ if (!installed)
20642
+ throw new Error(`Android app ${appId} is not installed on ${serial}.`);
20643
+ return installed;
20644
+ };
20645
+ var parseAbsoluteAndroidInstalledApp = (appId, output) => {
20646
+ const packageName = /^\s*Package \[([^\]]+)\]/mu.exec(output)?.[1];
20647
+ if (packageName !== appId && !output.includes(`package:${appId}`))
20648
+ return null;
20649
+ const versionCodeText = /^\s*versionCode=(\d+)/mu.exec(output)?.[1];
20650
+ return {
20651
+ appId,
20652
+ ...field(output, "dataDir") ? { dataDirectory: field(output, "dataDir") } : {},
20653
+ ...field(output, "firstInstallTime") ? { firstInstallTime: field(output, "firstInstallTime") } : {},
20654
+ ...field(output, "lastUpdateTime") ? { lastUpdateTime: field(output, "lastUpdateTime") } : {},
20655
+ ...field(output, "userId") || field(output, "appId") ? { uid: field(output, "userId") ?? field(output, "appId") } : {},
20656
+ ...versionCodeText ? { versionCode: Number(versionCodeText) } : {},
20657
+ ...field(output, "versionName") ? { versionName: field(output, "versionName") } : {}
20658
+ };
20659
+ };
20660
+
20661
+ // src/mobile/androidReleaseAcceptance.ts
20662
+ var ABSOLUTE_BUNDLETOOL_VERSION = "1.18.3";
20663
+ var ABSOLUTE_BUNDLETOOL_SHA256 = "a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29";
20664
+ var ABSOLUTE_BUNDLETOOL_URL = `https://github.com/google/bundletool/releases/download/${ABSOLUTE_BUNDLETOOL_VERSION}/bundletool-all-${ABSOLUTE_BUNDLETOOL_VERSION}.jar`;
20665
+ var EMBEDDED_READY_MARKER = "Expo embedded web content ready";
20666
+ var RELEASE_READY_POLL_MS = 250;
20667
+ var RELEASE_READY_TIMEOUT_MS = 30000;
20668
+ var RELEASE_STABILITY_MS = 2000;
20669
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20670
+ var exists2 = async (path) => {
20671
+ try {
20672
+ await access6(path);
20673
+ return true;
20674
+ } catch {
20675
+ return false;
20676
+ }
20677
+ };
20678
+ var sha256 = async (path) => createHash3("sha256").update(await readFile10(path)).digest("hex");
20679
+ var inside = (root, candidate) => {
20680
+ const path = relative5(resolve6(root), resolve6(candidate));
20681
+ return path === "" || !isAbsolute3(path) && path !== ".." && !path.startsWith(`..${sep3}`);
20682
+ };
20683
+ var requireString2 = (value, name) => {
20684
+ if (typeof value !== "string" || !value)
20685
+ throw new TypeError(`Android release ${name} is invalid.`);
20686
+ return value;
20687
+ };
20688
+ var parseMetadata = (value) => {
20689
+ if (!isRecord5(value))
20690
+ throw new TypeError("Android release metadata must contain an object.");
20691
+ const { bytes } = value;
20692
+ const { versionCode } = value;
20693
+ if (!Number.isSafeInteger(bytes) || Number(bytes) < 1)
20694
+ throw new TypeError("Android release bytes is invalid.");
20695
+ if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || Number(versionCode) < 1))
20696
+ throw new TypeError("Android release versionCode is invalid.");
20697
+ if (value.format !== 1 || value.platform !== "android" || value.type !== "aab" || value.engine !== "capacitor" && value.engine !== "expo" || typeof value.signed !== "boolean")
20698
+ throw new TypeError("Android release metadata contract is invalid.");
20699
+ const metadata = {
20700
+ appBuild: requireString2(value.appBuild, "appBuild"),
20701
+ appId: requireString2(value.appId, "appId"),
20702
+ artifact: requireString2(value.artifact, "artifact"),
20703
+ bytes: Number(bytes),
20704
+ engine: value.engine,
20705
+ format: 1,
20706
+ platform: "android",
20707
+ releaseId: requireString2(value.releaseId, "releaseId"),
20708
+ runtime: requireString2(value.runtime, "runtime"),
20709
+ sha256: requireString2(value.sha256, "sha256"),
20710
+ signed: value.signed,
20711
+ type: "aab",
20712
+ ...versionCode === undefined ? {} : { versionCode: Number(versionCode) }
20713
+ };
20714
+ if (!/^[a-f0-9]{64}$/u.test(metadata.sha256))
20715
+ throw new TypeError("Android release sha256 is invalid.");
20716
+ if (metadata.releaseId !== `amobile_android_${metadata.sha256}`)
20717
+ throw new TypeError("Android release ID does not match its artifact digest.");
20718
+ return metadata;
20719
+ };
20720
+ var absoluteBundletoolPath = (root = process.env.ABSOLUTE_BUNDLETOOL_ROOT ?? join9(homedir(), ".absolutejs", "tools", "bundletool")) => join9(root, ABSOLUTE_BUNDLETOOL_VERSION, "bundletool.jar");
20721
+ var inspectAbsoluteBundletool = async (root) => {
20722
+ const path = absoluteBundletoolPath(root);
20723
+ if (!await exists2(path))
20724
+ return { path, ready: false };
20725
+ if (await sha256(path) !== ABSOLUTE_BUNDLETOOL_SHA256)
20726
+ throw new TypeError(`Managed Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} failed checksum verification. Remove ${path} and rerun the command.`);
20727
+ return { path, ready: true };
20728
+ };
20729
+ var readAbsoluteAndroidRelease = async (projectRoot, requested) => {
20730
+ const root = resolve6(projectRoot);
20731
+ const selected = resolve6(root, requested);
20732
+ if (!inside(root, selected))
20733
+ throw new TypeError("mobile test android --release must remain inside the project.");
20734
+ const metadataPath = basename(selected) === "release.json" ? selected : join9(selected, "release.json");
20735
+ const releaseRoot = dirname7(metadataPath);
20736
+ const metadata = parseMetadata(JSON.parse(await readFile10(metadataPath, "utf8")));
20737
+ const artifactPath = resolve6(releaseRoot, metadata.artifact);
20738
+ if (!inside(releaseRoot, artifactPath) || basename(artifactPath) !== metadata.artifact)
20739
+ throw new TypeError("Android release artifact path is invalid.");
20740
+ const [artifactStat, artifactSha256] = await Promise.all([
20741
+ stat3(artifactPath),
20742
+ sha256(artifactPath)
20743
+ ]);
20744
+ if (artifactStat.size !== metadata.bytes || artifactSha256 !== metadata.sha256)
20745
+ throw new TypeError(`Immutable Android release ${metadata.releaseId} is missing or modified.`);
20746
+ return { artifactPath, metadata, metadataPath, releaseRoot };
20747
+ };
20748
+ var defaultDownload = async (url) => {
20749
+ const response = await fetch(url);
20750
+ if (!response.ok)
20751
+ throw new Error(`Bundletool download failed (${response.status}).`);
20752
+ return new Uint8Array(await response.arrayBuffer());
20753
+ };
20754
+ var ensureAbsoluteBundletool = async (options) => {
20755
+ const inspected = await inspectAbsoluteBundletool(options.root);
20756
+ if (inspected.ready)
20757
+ return inspected.path;
20758
+ if (!options.approved)
20759
+ throw new TypeError(`Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} is required. Rerun with --yes to download the checksum-pinned official release.`);
20760
+ const bytes = await (options.download ?? defaultDownload)(ABSOLUTE_BUNDLETOOL_URL);
20761
+ const digest = createHash3("sha256").update(bytes).digest("hex");
20762
+ if (digest !== ABSOLUTE_BUNDLETOOL_SHA256)
20763
+ throw new TypeError("Downloaded Bundletool failed checksum verification.");
20764
+ await mkdir7(dirname7(inspected.path), { recursive: true });
20765
+ const staging = await mkdtemp2(join9(dirname7(inspected.path), ".install-"));
20766
+ const candidate = join9(staging, "bundletool.jar");
20767
+ try {
20768
+ await writeFile9(candidate, bytes, { flag: "wx" });
20769
+ await rename8(candidate, inspected.path).catch(async (error) => {
20770
+ if (await exists2(inspected.path))
20771
+ return;
20772
+ throw error;
20773
+ });
20774
+ } finally {
20775
+ await rm5(staging, { force: true, recursive: true });
20776
+ }
20777
+ if (await sha256(inspected.path) !== ABSOLUTE_BUNDLETOOL_SHA256)
20778
+ throw new TypeError("Installed Bundletool failed checksum verification.");
20779
+ return inspected.path;
20780
+ };
20781
+ var defaultRun = async (command) => {
20782
+ const process2 = Bun.spawn(command, {
20783
+ stderr: "pipe",
20784
+ stdin: "ignore",
20785
+ stdout: "pipe"
20786
+ });
20787
+ const [exitCode, stderr, stdout] = await Promise.all([
20788
+ process2.exited,
20789
+ new Response(process2.stderr).text(),
20790
+ new Response(process2.stdout).text()
20791
+ ]);
20792
+ return { exitCode, stderr, stdout };
20793
+ };
20794
+ var requireSuccess = async (run, command, label) => {
20795
+ const result = await run(command);
20796
+ if (result.exitCode !== 0)
20797
+ throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim()}`);
20798
+ return result;
20799
+ };
20800
+ var adb = (path, serial, ...args) => [
20801
+ path,
20802
+ "-s",
20803
+ serial,
20804
+ ...args
20805
+ ];
20806
+ var waitForMarker = async (run, adbPath, serial, appId, timeoutMs) => {
20807
+ const startedAt = performance.now();
20808
+ const inspect = async () => {
20809
+ if (performance.now() - startedAt >= timeoutMs)
20810
+ throw new Error("The installed release launched but its embedded web content did not become ready offline.");
20811
+ const [logs, process2] = await Promise.all([
20812
+ run(adb(adbPath, serial, "logcat", "-d", "-s", "AbsoluteJS:I", "*:S")),
20813
+ run(adb(adbPath, serial, "shell", "pidof", appId))
20814
+ ]);
20815
+ if (process2.exitCode !== 0 || !process2.stdout.trim())
20816
+ throw new Error("The installed Android release exited before becoming ready.");
20817
+ if (logs.stdout.includes(EMBEDDED_READY_MARKER))
20818
+ return;
20819
+ const failedPhase = /Expo embedded web phase: (assets-(?:root|directory|destination|module|source|download|copy|read|write|finalize|unexpected)|devices)-failed/u.exec(logs.stdout)?.[1];
20820
+ if (failedPhase)
20821
+ throw new Error(`The installed release could not initialize its embedded ${failedPhase} runtime offline.`);
20822
+ await Bun.sleep(RELEASE_READY_POLL_MS);
20823
+ await inspect();
20824
+ };
20825
+ return inspect();
20826
+ };
20827
+ var launch = async (run, adbPath, serial, appId, launchUrl) => {
20828
+ const resolved = await requireSuccess(run, adb(adbPath, serial, "shell", "cmd", "package", "resolve-activity", "--brief", appId), "Android launch activity discovery");
20829
+ const activity = resolved.stdout.split(/\r?\n/u).map((line) => line.trim()).findLast((line) => line.includes("/"));
20830
+ if (!activity)
20831
+ throw new Error(`Android could not resolve a launch activity for ${appId}.`);
20832
+ const startedAt = performance.now();
20833
+ await requireSuccess(run, adb(adbPath, serial, "shell", "am", "start", "-W", "-n", activity, ...launchUrl ? ["-a", "android.intent.action.VIEW", "-d", launchUrl] : []), "Android release launch");
20834
+ return Math.round(performance.now() - startedAt);
20835
+ };
20836
+ var runAbsoluteAndroidReleaseAcceptance = async (options) => {
20837
+ if (options.release.metadata.engine !== "expo")
20838
+ throw new TypeError("Installed AAB release acceptance currently requires the Expo engine.");
20839
+ if (!options.serial.startsWith("emulator-"))
20840
+ throw new TypeError("Installed release acceptance changes network state and therefore requires an Android emulator.");
20841
+ const run = options.run ?? defaultRun;
20842
+ const startedAt = performance.now();
20843
+ await mkdir7(options.artifactDirectory, { recursive: true });
20844
+ const apksPath = join9(options.artifactDirectory, "release.apks");
20845
+ const windowsJava = (options.host ?? detectAbsoluteMobileHost()) === "wsl" && options.java.toLowerCase().endsWith(".exe");
20846
+ const toolPath = async (path) => {
20847
+ if (!windowsJava)
20848
+ return path;
20849
+ const converted = await requireSuccess(run, ["wslpath", "-w", path], "Bundletool Windows path translation");
20850
+ const translated = converted.stdout.trim();
20851
+ if (!translated)
20852
+ throw new Error(`Bundletool could not translate ${basename(path)} for the Windows JDK.`);
20853
+ return translated;
20854
+ };
20855
+ const [bundletoolPath, artifactPath, outputPath, adbPath] = await Promise.all([
20856
+ toolPath(options.bundletool),
20857
+ toolPath(options.release.artifactPath),
20858
+ toolPath(apksPath),
20859
+ toolPath(options.adb)
20860
+ ]);
20861
+ await requireSuccess(run, [
20862
+ options.java,
20863
+ "-jar",
20864
+ bundletoolPath,
20865
+ "build-apks",
20866
+ `--bundle=${artifactPath}`,
20867
+ `--output=${outputPath}`,
20868
+ "--mode=universal",
20869
+ "--overwrite"
20870
+ ], "Bundletool APK generation");
20871
+ const installStartedAt = performance.now();
20872
+ await requireSuccess(run, [
20873
+ options.java,
20874
+ "-jar",
20875
+ bundletoolPath,
20876
+ "install-apks",
20877
+ `--apks=${outputPath}`,
20878
+ `--adb=${adbPath}`,
20879
+ `--device-id=${options.serial}`
20880
+ ], "Bundletool release installation");
20881
+ const installMs = Math.round(performance.now() - installStartedAt);
20882
+ const installed = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.release.metadata.appId, run);
20883
+ if (options.release.metadata.versionCode !== undefined && installed.versionCode !== options.release.metadata.versionCode)
20884
+ throw new Error(`Installed Android versionCode ${installed.versionCode ?? "unknown"} does not match release ${options.release.metadata.versionCode}.`);
20885
+ await requireSuccess(run, adb(options.adb, options.serial, "logcat", "-c"), "Android release log isolation");
20886
+ const [wifi, mobile] = await Promise.all([
20887
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "settings", "get", "global", "wifi_on"), "Android Wi-Fi state inspection"),
20888
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "settings", "get", "global", "mobile_data"), "Android mobile-data state inspection")
20889
+ ]);
20890
+ let launchMs;
20891
+ let relaunchMs;
20892
+ try {
20893
+ await Promise.all([
20894
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "wifi", "disable"), "Android Wi-Fi disable"),
20895
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "data", "disable"), "Android mobile-data disable")
20896
+ ]);
20897
+ await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release stop");
20898
+ launchMs = await launch(run, options.adb, options.serial, options.release.metadata.appId, options.launchUrl);
20899
+ await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, RELEASE_READY_TIMEOUT_MS);
20900
+ await Bun.sleep(options.stabilityMs ?? RELEASE_STABILITY_MS);
20901
+ await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release relaunch stop");
20902
+ await requireSuccess(run, adb(options.adb, options.serial, "logcat", "-c"), "Android release relaunch log isolation");
20903
+ relaunchMs = await launch(run, options.adb, options.serial, options.release.metadata.appId, options.launchUrl);
20904
+ await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, RELEASE_READY_TIMEOUT_MS);
20905
+ } finally {
20906
+ await Promise.all([
20907
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "wifi", wifi.stdout.trim() === "1" ? "enable" : "disable"), "Android Wi-Fi restore"),
20908
+ requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "data", mobile.stdout.trim() === "1" ? "enable" : "disable"), "Android mobile-data restore")
20909
+ ]);
20910
+ }
20911
+ return {
20912
+ apksBytes: await stat3(apksPath).then(({ size }) => size),
20913
+ artifactBytes: options.release.metadata.bytes,
20914
+ artifactSha256: options.release.metadata.sha256,
20915
+ durationMs: Math.round(performance.now() - startedAt),
20916
+ embeddedOffline: true,
20917
+ engine: options.release.metadata.engine,
20918
+ installed: {
20919
+ ...installed.versionCode === undefined ? {} : { versionCode: installed.versionCode },
20920
+ ...installed.versionName === undefined ? {} : { versionName: installed.versionName }
20921
+ },
20922
+ installMs,
20923
+ launchMs,
20924
+ relaunchMs,
20925
+ releaseId: options.release.metadata.releaseId,
20926
+ signed: options.release.metadata.signed,
20927
+ status: "pass"
20928
+ };
20929
+ };
20930
+
20591
20931
  // src/mobile/iosDeviceAcceptance.ts
20592
20932
  var absoluteIosDeviceAcceptanceCommands = (options) => {
20593
20933
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
@@ -20606,20 +20946,20 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
20606
20946
  ]
20607
20947
  };
20608
20948
  };
20609
- var requireSuccess = (result, message) => {
20949
+ var requireSuccess2 = (result, message) => {
20610
20950
  if (result.exitCode !== 0)
20611
20951
  throw new Error(message);
20612
20952
  return result;
20613
20953
  };
20614
20954
  var testAbsoluteIosPhysicalDevice = async (options) => {
20615
20955
  const commands = absoluteIosDeviceAcceptanceCommands(options);
20616
- requireSuccess(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.");
20617
- const apps = requireSuccess(await options.capture(commands.apps), "AbsoluteJS could not inspect installed apps on the physical iOS device.");
20956
+ requireSuccess2(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.");
20957
+ const apps = requireSuccess2(await options.capture(commands.apps), "AbsoluteJS could not inspect installed apps on the physical iOS device.");
20618
20958
  if (!apps.stdout.includes(options.appId))
20619
20959
  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.");
20620
20960
  const now = options.now ?? performance.now.bind(performance);
20621
20961
  const startedAt = now();
20622
- requireSuccess(await options.capture(commands.launch), "AbsoluteJS could not relaunch the app on the physical iOS device.");
20962
+ requireSuccess2(await options.capture(commands.launch), "AbsoluteJS could not relaunch the app on the physical iOS device.");
20623
20963
  await options.waitForHmr();
20624
20964
  return {
20625
20965
  hmrConnected: true,
@@ -20629,7 +20969,7 @@ var testAbsoluteIosPhysicalDevice = async (options) => {
20629
20969
  };
20630
20970
 
20631
20971
  // src/mobile/iosConformance.ts
20632
- import { readFile as readFile10, stat as stat3 } from "fs/promises";
20972
+ import { readFile as readFile11, stat as stat4 } from "fs/promises";
20633
20973
  var HMR_LINE = new RegExp(String.raw`\[hmr:ios\]\s+([^\n]*?)\s+(applied in|falling back to reload after|failed after)\s+(\d+)ms(?:; server\s+(\d+)ms, client\s+(\d+)ms)?`, "u");
20634
20974
  var parseAbsoluteIosHmrLog = (line) => {
20635
20975
  const match = HMR_LINE.exec(line);
@@ -20661,13 +21001,13 @@ var waitForAbsoluteIosHmrLog = async (options) => {
20661
21001
  const sleep = options.sleep ?? Bun.sleep;
20662
21002
  const timeoutMs = options.timeoutMs ?? 30000;
20663
21003
  const deadline = Date.now() + timeoutMs;
20664
- let offset = options.startOffset ?? await stat3(options.logPath).then(({ size }) => size).catch(() => 0);
21004
+ let offset = options.startOffset ?? await stat4(options.logPath).then(({ size }) => size).catch(() => 0);
20665
21005
  let buffered = "";
20666
21006
  const poll = async () => {
20667
21007
  if (Date.now() > deadline)
20668
21008
  throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
20669
21009
  options.signal?.throwIfAborted();
20670
- const contents = await readFile10(options.logPath).catch(() => Buffer.alloc(0));
21010
+ const contents = await readFile11(options.logPath).catch(() => Buffer.alloc(0));
20671
21011
  if (contents.byteLength < offset) {
20672
21012
  offset = 0;
20673
21013
  buffered = "";
@@ -20688,8 +21028,8 @@ var waitForAbsoluteIosHmrLog = async (options) => {
20688
21028
  };
20689
21029
 
20690
21030
  // src/mobile/nativeTestReport.ts
20691
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
20692
- import { join as join9 } from "path";
21031
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
21032
+ import { join as join10 } from "path";
20693
21033
  var secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
20694
21034
  var bearerPattern = /bearer\s+[^\s,;]+/giu;
20695
21035
  var coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
@@ -20713,6 +21053,14 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20713
21053
  artifactDetails = "Physical-device screen capture was intentionally skipped; use Xcode Device Hub for manual visual evidence.";
20714
21054
  artifactResult = "SKIPPED";
20715
21055
  }
21056
+ let developmentDetails = run.status === "pass" ? `The app launched and connected to native HMR in ${run.durationMs}ms.${routeDetails}` : `The automated native run failed after ${run.durationMs}ms: ${run.error ?? "No error detail was available."}`;
21057
+ if (run.release)
21058
+ developmentDetails = run.status === "pass" ? `The ${run.release.engine} release was installed from its generated APK set, launched offline in ${run.release.launchMs}ms, and relaunched in ${run.release.relaunchMs}ms.` : `Installed ${run.release.engine} release acceptance failed after ${run.durationMs}ms.`;
21059
+ let hmrDetails = "Correlated edit timing was not requested. Rerun with --wait-for-hmr.";
21060
+ if (run.release)
21061
+ hmrDetails = "HMR is intentionally not part of installed production-release acceptance.";
21062
+ if (run.hmr)
21063
+ hmrDetails = `Observed native HMR ${run.hmr.outcome} in ${run.hmr.durationMs}ms${run.hmr.serverMs === undefined ? "" : ` (server ${run.hmr.serverMs}ms, client ${run.hmr.clientMs}ms)`}.`;
20716
21064
  const checks = [
20717
21065
  {
20718
21066
  details: `Captured host, toolchain, Bun, and AbsoluteJS metadata for ${target}.`,
@@ -20720,13 +21068,13 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20720
21068
  result: "PASS"
20721
21069
  },
20722
21070
  {
20723
- details: run.status === "pass" ? `The app launched and connected to native HMR in ${run.durationMs}ms.${routeDetails}` : `The automated native run failed after ${run.durationMs}ms: ${run.error ?? "No error detail was available."}`,
21071
+ details: developmentDetails,
20724
21072
  ...evidence ? { evidence } : {},
20725
21073
  id: "AUTO-DEV-01",
20726
21074
  result: run.status === "pass" ? "PASS" : "FAIL"
20727
21075
  },
20728
21076
  {
20729
- details: run.hmr ? `Observed native HMR ${run.hmr.outcome} in ${run.hmr.durationMs}ms${run.hmr.serverMs === undefined ? "" : ` (server ${run.hmr.serverMs}ms, client ${run.hmr.clientMs}ms)`}.` : "Correlated edit timing was not requested. Rerun with --wait-for-hmr.",
21077
+ details: hmrDetails,
20730
21078
  id: "AUTO-HMR-01",
20731
21079
  result: hmrResult
20732
21080
  },
@@ -20737,6 +21085,17 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20737
21085
  result: artifactResult
20738
21086
  }
20739
21087
  ];
21088
+ if (run.release) {
21089
+ checks.push({
21090
+ details: `Validated immutable release ${run.release.releaseId} (${run.release.artifactBytes} bytes, SHA-256 ${run.release.artifactSha256}); Bundletool produced a ${run.release.apksBytes}-byte APK set and installed it in ${run.release.installMs}ms.`,
21091
+ id: "AUTO-RELEASE-01",
21092
+ result: run.status === "pass" ? "PASS" : "FAIL"
21093
+ }, {
21094
+ details: run.release.embeddedOffline ? "The generated release rendered its embedded web content and survived a process relaunch with Wi-Fi and mobile data disabled." : "The generated release did not prove embedded offline rendering.",
21095
+ id: "AUTO-RELEASE-OFFLINE-01",
21096
+ result: run.release.embeddedOffline ? "PASS" : "FAIL"
21097
+ });
21098
+ }
20740
21099
  if (run.upgrade) {
20741
21100
  const { upgrade } = run;
20742
21101
  checks.push({
@@ -20799,7 +21158,7 @@ var createAbsoluteNativeTestReport = (options) => ({
20799
21158
  run: options.run
20800
21159
  });
20801
21160
  var readPackageVersionForNativeReport = async (packageJsonPath) => {
20802
- const manifest = JSON.parse(await readFile11(packageJsonPath, "utf8"));
21161
+ const manifest = JSON.parse(await readFile12(packageJsonPath, "utf8"));
20803
21162
  if (typeof manifest !== "object" || manifest === null)
20804
21163
  return "unknown";
20805
21164
  const version = Reflect.get(manifest, "version");
@@ -20837,13 +21196,13 @@ ${table(report.manualChecks)}
20837
21196
  `;
20838
21197
  };
20839
21198
  var writeAbsoluteNativeTestReport = async (directory, report) => {
20840
- await mkdir7(directory, { recursive: true });
20841
- const jsonPath = join9(directory, "report.json");
20842
- const markdownPath = join9(directory, "report.md");
21199
+ await mkdir8(directory, { recursive: true });
21200
+ const jsonPath = join10(directory, "report.json");
21201
+ const markdownPath = join10(directory, "report.md");
20843
21202
  await Promise.all([
20844
- writeFile9(jsonPath, `${JSON.stringify(report, null, 2)}
21203
+ writeFile10(jsonPath, `${JSON.stringify(report, null, 2)}
20845
21204
  `),
20846
- writeFile9(markdownPath, renderAbsoluteNativeTestReport(report))
21205
+ writeFile10(markdownPath, renderAbsoluteNativeTestReport(report))
20847
21206
  ]);
20848
21207
  return { directory, jsonPath, markdownPath };
20849
21208
  };
@@ -21071,6 +21430,8 @@ var MANUAL_CHECKS2 = [
21071
21430
  "Review this directory for sensitive content and complete every row."
21072
21431
  ]
21073
21432
  ];
21433
+ var portableAdbVersion = (version) => version.split(/\r?\n/u).filter((line) => !/^\s*(?:Installed as|Running on)\b/iu.test(line)).join(`
21434
+ `).trim();
21074
21435
  var createAbsoluteAndroidTestReport = (options) => {
21075
21436
  const { serial, ...run } = options.run;
21076
21437
  return createAbsoluteNativeTestReport({
@@ -21078,10 +21439,10 @@ var createAbsoluteAndroidTestReport = (options) => {
21078
21439
  manualChecks: MANUAL_CHECKS2,
21079
21440
  metadata: {
21080
21441
  absolutejsVersion: options.absolutejsVersion,
21081
- adbVersion: options.adbVersion,
21442
+ adbVersion: portableAdbVersion(options.adbVersion),
21082
21443
  bunVersion: options.bunVersion,
21083
21444
  host: options.host,
21084
- provider: "capacitor"
21445
+ provider: options.provider ?? "capacitor"
21085
21446
  },
21086
21447
  run: {
21087
21448
  ...run,
@@ -21093,8 +21454,8 @@ var createAbsoluteAndroidTestReport = (options) => {
21093
21454
  };
21094
21455
 
21095
21456
  // src/mobile/releasePublisher.ts
21096
- import { access as access6 } from "fs/promises";
21097
- import { isAbsolute as isAbsolute3, relative as relative5, resolve as resolve6, sep as sep3 } from "path";
21457
+ import { access as access7 } from "fs/promises";
21458
+ import { isAbsolute as isAbsolute4, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
21098
21459
  import { pathToFileURL as pathToFileURL2 } from "url";
21099
21460
  var prepareAbsoluteIosRelease = async (publisher, options) => {
21100
21461
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -21117,24 +21478,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
21117
21478
  }
21118
21479
  return versionCode;
21119
21480
  };
21120
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21121
- var isPublisher = (value) => isRecord5(value) && typeof value.publish === "function";
21481
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21482
+ var isPublisher = (value) => isRecord6(value) && typeof value.publish === "function";
21122
21483
  var publisherModulePath = (projectRoot, requested) => {
21123
- const root = resolve6(projectRoot);
21124
- const path = resolve6(root, requested);
21125
- const projectRelative = relative5(root, path);
21126
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
21484
+ const root = resolve7(projectRoot);
21485
+ const path = resolve7(root, requested);
21486
+ const projectRelative = relative6(root, path);
21487
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep4}`) || isAbsolute4(projectRelative)) {
21127
21488
  throw new TypeError("mobile publish --registry must remain inside the project.");
21128
21489
  }
21129
21490
  return path;
21130
21491
  };
21131
21492
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
21132
21493
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
21133
- await access6(modulePath).catch(() => {
21494
+ await access7(modulePath).catch(() => {
21134
21495
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
21135
21496
  });
21136
21497
  const loaded = await import(pathToFileURL2(modulePath).href);
21137
- const publisher = isRecord5(loaded) ? loaded.default ?? loaded.registry : undefined;
21498
+ const publisher = isRecord6(loaded) ? loaded.default ?? loaded.registry : undefined;
21138
21499
  if (!isPublisher(publisher)) {
21139
21500
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
21140
21501
  }
@@ -21192,8 +21553,8 @@ var publishAbsoluteIosRelease = async (options) => {
21192
21553
  };
21193
21554
 
21194
21555
  // src/mobile/mobileInspect.ts
21195
- import { access as access7, readFile as readFile12 } from "fs/promises";
21196
- import { join as join10, relative as relative6, resolve as resolve7 } from "path";
21556
+ import { access as access8, readFile as readFile13 } from "fs/promises";
21557
+ import { join as join11, relative as relative7, resolve as resolve8 } from "path";
21197
21558
  var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
21198
21559
  var MOBILE_PACKAGE_NAMES = new Set([
21199
21560
  "@absolutejs/absolute",
@@ -21208,19 +21569,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
21208
21569
  ]);
21209
21570
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21210
21571
  var portablePath2 = (projectRoot, path) => {
21211
- const value = relative6(resolve7(projectRoot), resolve7(path)).replaceAll("\\", "/");
21572
+ const value = relative7(resolve8(projectRoot), resolve8(path)).replaceAll("\\", "/");
21212
21573
  return value || ".";
21213
21574
  };
21214
21575
  var pathExists4 = async (path) => {
21215
21576
  try {
21216
- await access7(path);
21577
+ await access8(path);
21217
21578
  return true;
21218
21579
  } catch {
21219
21580
  return false;
21220
21581
  }
21221
21582
  };
21222
21583
  var readObject2 = async (path) => {
21223
- const value = JSON.parse(await readFile12(path, "utf8"));
21584
+ const value = JSON.parse(await readFile13(path, "utf8"));
21224
21585
  if (!isObject2(value))
21225
21586
  throw new TypeError("JSON root must be an object.");
21226
21587
  return value;
@@ -21232,13 +21593,13 @@ var addPackageDeclarations = (declarations, value) => {
21232
21593
  declarations.set(name, declared);
21233
21594
  };
21234
21595
  var packageInspections = async (projectRoot, additionalNames) => {
21235
- const project = await readObject2(join10(projectRoot, "package.json"));
21596
+ const project = await readObject2(join11(projectRoot, "package.json"));
21236
21597
  const declarations = new Map;
21237
- for (const field of ["dependencies", "devDependencies"])
21238
- addPackageDeclarations(declarations, project[field]);
21598
+ for (const field2 of ["dependencies", "devDependencies"])
21599
+ addPackageDeclarations(declarations, project[field2]);
21239
21600
  const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
21240
21601
  return Promise.all(names.map(async (name) => {
21241
- const installedManifest = await readObject2(join10(projectRoot, "node_modules", name, "package.json")).catch(() => {
21602
+ const installedManifest = await readObject2(join11(projectRoot, "node_modules", name, "package.json")).catch(() => {
21242
21603
  return;
21243
21604
  });
21244
21605
  const installed = installedManifest?.version;
@@ -21297,7 +21658,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
21297
21658
  },
21298
21659
  format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
21299
21660
  nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
21300
- const path = join10(config.nativeProjectDirectory, platform);
21661
+ const path = join11(config.nativeProjectDirectory, platform);
21301
21662
  return {
21302
21663
  initialized: await pathExists4(path),
21303
21664
  path: portablePath2(projectRoot, path),
@@ -21354,8 +21715,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
21354
21715
 
21355
21716
  // src/mobile/ciWorkflow.ts
21356
21717
  import { existsSync } from "fs";
21357
- import { access as access8, mkdir as mkdir8, readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
21358
- import { dirname as dirname7, extname as extname2, relative as relative7, resolve as resolve8, sep as sep4 } from "path";
21718
+ import { access as access9, mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
21719
+ import { dirname as dirname8, extname as extname2, relative as relative8, resolve as resolve9, sep as sep5 } from "path";
21359
21720
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
21360
21721
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
21361
21722
  var CI_ENV_INDENTATION = 6;
@@ -21374,34 +21735,34 @@ var RESERVED_SECRET_NAMES = new Set([
21374
21735
  "APP_STORE_CONNECT_KEY_ID",
21375
21736
  "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
21376
21737
  ]);
21377
- var exists2 = async (path) => {
21738
+ var exists3 = async (path) => {
21378
21739
  try {
21379
- await access8(path);
21740
+ await access9(path);
21380
21741
  return true;
21381
21742
  } catch {
21382
21743
  return false;
21383
21744
  }
21384
21745
  };
21385
21746
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
21386
- var projectPath2 = (projectRoot, value, field, options = {}) => {
21387
- const root = resolve8(projectRoot);
21388
- const path = resolve8(root, value);
21389
- const portable = relative7(root, path).replaceAll("\\", "/");
21390
- if (portable === ".." || portable.startsWith(`..${sep4}`) || portable.startsWith("../") || portable === "") {
21391
- throw new TypeError(`${field} must remain inside the project root.`);
21747
+ var projectPath2 = (projectRoot, value, field2, options = {}) => {
21748
+ const root = resolve9(projectRoot);
21749
+ const path = resolve9(root, value);
21750
+ const portable = relative8(root, path).replaceAll("\\", "/");
21751
+ if (portable === ".." || portable.startsWith(`..${sep5}`) || portable.startsWith("../") || portable === "") {
21752
+ throw new TypeError(`${field2} must remain inside the project root.`);
21392
21753
  }
21393
21754
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
21394
- throw new TypeError(`${field} contains an unsafe path.`);
21755
+ throw new TypeError(`${field2} contains an unsafe path.`);
21395
21756
  if (!options.allowMissing && !existsSync(path))
21396
- throw new TypeError(`${field} does not exist inside the project.`);
21757
+ throw new TypeError(`${field2} does not exist inside the project.`);
21397
21758
  return portable;
21398
21759
  };
21399
21760
  var workflowOutputPath = (projectRoot, value) => {
21400
- const root = resolve8(projectRoot);
21401
- const workflows = resolve8(root, ".github/workflows");
21402
- const path = resolve8(root, value ?? ".github/workflows/absolute-mobile.yml");
21403
- const portable = relative7(workflows, path);
21404
- if (portable === ".." || portable.startsWith(`..${sep4}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
21761
+ const root = resolve9(projectRoot);
21762
+ const workflows = resolve9(root, ".github/workflows");
21763
+ const path = resolve9(root, value ?? ".github/workflows/absolute-mobile.yml");
21764
+ const portable = relative8(workflows, path);
21765
+ if (portable === ".." || portable.startsWith(`..${sep5}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
21405
21766
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
21406
21767
  }
21407
21768
  return path;
@@ -21830,12 +22191,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
21830
22191
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
21831
22192
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
21832
22193
  const generated = createAbsoluteMobileGithubWorkflow(options);
21833
- const previous = await exists2(path) ? await readFile13(path, "utf8") : undefined;
22194
+ const previous = await exists3(path) ? await readFile14(path, "utf8") : undefined;
21834
22195
  if (previous !== undefined && previous !== generated.workflow && !options.force)
21835
- throw new TypeError(`${relative7(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
22196
+ throw new TypeError(`${relative8(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
21836
22197
  if (previous !== generated.workflow) {
21837
- await mkdir8(dirname7(path), { recursive: true });
21838
- await writeFile10(path, generated.workflow);
22198
+ await mkdir9(dirname8(path), { recursive: true });
22199
+ await writeFile11(path, generated.workflow);
21839
22200
  }
21840
22201
  return {
21841
22202
  changed: previous !== generated.workflow,
@@ -21848,46 +22209,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
21848
22209
  };
21849
22210
 
21850
22211
  // src/mobile/updateSigning.ts
21851
- import { createHash as createHash3, sign, verify } from "crypto";
22212
+ import { createHash as createHash4, sign, verify } from "crypto";
21852
22213
  import {
21853
22214
  cp,
21854
- mkdir as mkdir9,
21855
- mkdtemp as mkdtemp2,
22215
+ mkdir as mkdir10,
22216
+ mkdtemp as mkdtemp3,
21856
22217
  readdir as readdir4,
21857
- readFile as readFile14,
21858
- rename as rename8,
21859
- rm as rm5,
21860
- stat as stat4,
21861
- writeFile as writeFile11
22218
+ readFile as readFile15,
22219
+ rename as rename9,
22220
+ rm as rm6,
22221
+ stat as stat5,
22222
+ writeFile as writeFile12
21862
22223
  } from "fs/promises";
21863
- import { dirname as dirname8, join as join11, relative as relative8, resolve as resolve9 } from "path";
22224
+ import { dirname as dirname9, join as join12, relative as relative9, resolve as resolve10 } from "path";
21864
22225
  var UPDATE_MANIFEST_FILE = "update.json";
21865
22226
  var UPDATE_FILES_DIRECTORY = "files";
21866
- var sha256 = (value) => createHash3("sha256").update(value).digest("hex");
22227
+ var sha2562 = (value) => createHash4("sha256").update(value).digest("hex");
21867
22228
  var listFiles = async (root, directory = root) => {
21868
22229
  const entries = await readdir4(directory, { withFileTypes: true });
21869
22230
  const paths = await Promise.all(entries.map(async (entry) => {
21870
- const path = join11(directory, entry.name);
22231
+ const path = join12(directory, entry.name);
21871
22232
  if (entry.isDirectory())
21872
22233
  return listFiles(root, path);
21873
22234
  if (!entry.isFile())
21874
22235
  throw new TypeError("Mobile updates cannot contain links or special files.");
21875
- return [relative8(root, path).replaceAll("\\", "/")];
22236
+ return [relative9(root, path).replaceAll("\\", "/")];
21876
22237
  }));
21877
22238
  return paths.flat().sort((left, right) => left.localeCompare(right));
21878
22239
  };
21879
22240
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
21880
- const source = join11(root, path);
22241
+ const source = join12(root, path);
21881
22242
  const [metadata, contents] = await Promise.all([
21882
- stat4(source),
21883
- readFile14(source)
22243
+ stat5(source),
22244
+ readFile15(source)
21884
22245
  ]);
21885
- return { bytes: metadata.size, path, sha256: sha256(contents) };
22246
+ return { bytes: metadata.size, path, sha256: sha2562(contents) };
21886
22247
  }));
21887
- var releaseIdFor = (value) => `amu_${sha256(canonicalizeAbsoluteMobileUpdate(value))}`;
22248
+ var releaseIdFor = (value) => `amu_${sha2562(canonicalizeAbsoluteMobileUpdate(value))}`;
21888
22249
  var buildAbsoluteMobileUpdate = async (options) => {
21889
- const bundleDirectory = resolve9(options.bundleDirectory);
21890
- const outputRoot = resolve9(options.outputDirectory);
22250
+ const bundleDirectory = resolve10(options.bundleDirectory);
22251
+ const outputRoot = resolve10(options.outputDirectory);
21891
22252
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
21892
22253
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
21893
22254
  const paths = await listFiles(bundleDirectory);
@@ -21915,30 +22276,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
21915
22276
  value: signature.toString("base64")
21916
22277
  }
21917
22278
  });
21918
- await mkdir9(outputRoot, { recursive: true });
21919
- const outputDirectory = join11(outputRoot, manifest.releaseId);
21920
- const staging = await mkdtemp2(join11(outputRoot, ".stage-"));
22279
+ await mkdir10(outputRoot, { recursive: true });
22280
+ const outputDirectory = join12(outputRoot, manifest.releaseId);
22281
+ const staging = await mkdtemp3(join12(outputRoot, ".stage-"));
21921
22282
  try {
21922
- await cp(bundleDirectory, join11(staging, UPDATE_FILES_DIRECTORY), {
22283
+ await cp(bundleDirectory, join12(staging, UPDATE_FILES_DIRECTORY), {
21923
22284
  force: true,
21924
22285
  recursive: true
21925
22286
  });
21926
- await writeFile11(join11(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
22287
+ await writeFile12(join12(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
21927
22288
  `);
21928
- await rename8(staging, outputDirectory);
22289
+ await rename9(staging, outputDirectory);
21929
22290
  } catch (error) {
21930
- await rm5(staging, { force: true, recursive: true });
22291
+ await rm6(staging, { force: true, recursive: true });
21931
22292
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
21932
22293
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
21933
22294
  throw error;
21934
22295
  }
21935
22296
  return {
21936
22297
  manifest,
21937
- manifestPath: join11(outputDirectory, UPDATE_MANIFEST_FILE),
22298
+ manifestPath: join12(outputDirectory, UPDATE_MANIFEST_FILE),
21938
22299
  outputDirectory
21939
22300
  };
21940
22301
  };
21941
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile14(join11(resolve9(directory), UPDATE_MANIFEST_FILE), "utf8")));
22302
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile15(join12(resolve10(directory), UPDATE_MANIFEST_FILE), "utf8")));
21942
22303
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21943
22304
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
21944
22305
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -21948,17 +22309,17 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21948
22309
  };
21949
22310
 
21950
22311
  // src/mobile/expoUpdate.ts
21951
- import { access as access9, mkdir as mkdir10, readFile as readFile15, writeFile as writeFile12 } from "fs/promises";
21952
- import { dirname as dirname9, extname as extname3, join as join12 } from "path";
22312
+ import { access as access10, mkdir as mkdir11, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
22313
+ import { dirname as dirname10, extname as extname3, join as join13 } from "path";
21953
22314
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
21954
22315
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
21955
22316
  var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21956
- var safePath = (value, field) => {
22317
+ var safePath = (value, field2) => {
21957
22318
  if (typeof value !== "string" || value.length === 0)
21958
- throw new TypeError(`Expo update ${field} is invalid.`);
22319
+ throw new TypeError(`Expo update ${field2} is invalid.`);
21959
22320
  const path = value.replaceAll("\\", "/");
21960
22321
  if (path.startsWith("/") || path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
21961
- throw new TypeError(`Expo update ${field} must be a relative path.`);
22322
+ throw new TypeError(`Expo update ${field2} must be a relative path.`);
21962
22323
  return path;
21963
22324
  };
21964
22325
  var extension = (value, path) => {
@@ -22015,7 +22376,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
22015
22376
  };
22016
22377
  };
22017
22378
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
22018
- const metadata = JSON.parse(await readFile15(join12(options.exportDirectory, "metadata.json"), "utf8"));
22379
+ const metadata = JSON.parse(await readFile16(join13(options.exportDirectory, "metadata.json"), "utf8"));
22019
22380
  if (!object2(metadata) || !object2(metadata.fileMetadata))
22020
22381
  throw new TypeError("Expo export metadata.json is invalid.");
22021
22382
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -22026,33 +22387,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
22026
22387
  runtimeVersion: options.runtimeVersion
22027
22388
  });
22028
22389
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
22029
- await Promise.all(referenced.map(({ path }) => access9(join12(options.exportDirectory, path)).catch(() => {
22390
+ await Promise.all(referenced.map(({ path }) => access10(join13(options.exportDirectory, path)).catch(() => {
22030
22391
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
22031
22392
  })));
22032
- const destination = join12(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
22033
- await mkdir10(dirname9(destination), { recursive: true });
22034
- await writeFile12(destination, `${JSON.stringify(descriptor, null, "\t")}
22393
+ const destination = join13(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
22394
+ await mkdir11(dirname10(destination), { recursive: true });
22395
+ await writeFile13(destination, `${JSON.stringify(descriptor, null, "\t")}
22035
22396
  `);
22036
22397
  return { descriptor, path: destination };
22037
22398
  };
22038
22399
 
22039
22400
  // src/mobile/expoCodeSigning.ts
22040
22401
  var import_code_signing_certificates = __toESM(require_main(), 1);
22041
- import { access as access10, mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
22042
- import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative9, resolve as resolve10, sep as sep5 } from "path";
22043
- var exists3 = async (path) => access10(path).then(() => true).catch(() => false);
22044
- var inside = (root, path) => {
22045
- const location = relative9(root, path);
22046
- return location === "" || location !== ".." && !location.startsWith(`..${sep5}`) && !isAbsolute4(location);
22402
+ import { access as access11, mkdir as mkdir12, writeFile as writeFile14 } from "fs/promises";
22403
+ import { dirname as dirname11, isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep6 } from "path";
22404
+ var exists4 = async (path) => access11(path).then(() => true).catch(() => false);
22405
+ var inside2 = (root, path) => {
22406
+ const location = relative10(root, path);
22407
+ return location === "" || location !== ".." && !location.startsWith(`..${sep6}`) && !isAbsolute5(location);
22047
22408
  };
22048
22409
  var generateAbsoluteExpoCodeSigning = async (options) => {
22049
- const root = resolve10(options.projectRoot);
22050
- const certificatePath = resolve10(root, options.certificatePath);
22051
- const privateKeyPath = resolve10(root, options.privateKeyPath);
22052
- const publicKeyPath = resolve10(root, options.publicKeyPath ?? resolve10(dirname10(privateKeyPath), "public-key.pem"));
22053
- if (!inside(root, certificatePath))
22410
+ const root = resolve11(options.projectRoot);
22411
+ const certificatePath = resolve11(root, options.certificatePath);
22412
+ const privateKeyPath = resolve11(root, options.privateKeyPath);
22413
+ const publicKeyPath = resolve11(root, options.publicKeyPath ?? resolve11(dirname11(privateKeyPath), "public-key.pem"));
22414
+ if (!inside2(root, certificatePath))
22054
22415
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
22055
- if (inside(root, privateKeyPath) || inside(root, publicKeyPath))
22416
+ if (inside2(root, privateKeyPath) || inside2(root, publicKeyPath))
22056
22417
  throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
22057
22418
  const commonName = options.commonName.trim();
22058
22419
  if (!commonName || commonName.length > 64)
@@ -22061,7 +22422,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22061
22422
  if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
22062
22423
  throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
22063
22424
  const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
22064
- exists: await exists3(path),
22425
+ exists: await exists4(path),
22065
22426
  path
22066
22427
  })))).filter((entry) => entry.exists);
22067
22428
  if (collisions.length > 0)
@@ -22079,11 +22440,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22079
22440
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
22080
22441
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
22081
22442
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
22082
- await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir11(dirname10(path), { recursive: true })));
22443
+ await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir12(dirname11(path), { recursive: true })));
22083
22444
  await Promise.all([
22084
- writeFile13(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
22085
- writeFile13(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
22086
- writeFile13(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
22445
+ writeFile14(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
22446
+ writeFile14(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
22447
+ writeFile14(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
22087
22448
  ]);
22088
22449
  return {
22089
22450
  certificatePath,
@@ -22094,8 +22455,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22094
22455
  };
22095
22456
 
22096
22457
  // src/mobile/updatePublisher.ts
22097
- import { access as access11 } from "fs/promises";
22098
- import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep6 } from "path";
22458
+ import { access as access12 } from "fs/promises";
22459
+ import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep7 } from "path";
22099
22460
  import { pathToFileURL as pathToFileURL3 } from "url";
22100
22461
  var lifecycleMethod = (publisher, name) => {
22101
22462
  const method = publisher[name];
@@ -22204,16 +22565,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
22204
22565
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22205
22566
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
22206
22567
  var projectPath3 = (projectRoot, requested, label) => {
22207
- const root = resolve11(projectRoot);
22208
- const path = resolve11(root, requested);
22209
- const projectRelative = relative10(root, path);
22210
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute5(projectRelative))
22568
+ const root = resolve12(projectRoot);
22569
+ const path = resolve12(root, requested);
22570
+ const projectRelative = relative11(root, path);
22571
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute6(projectRelative))
22211
22572
  throw new TypeError(`${label} must remain inside the project.`);
22212
22573
  return path;
22213
22574
  };
22214
22575
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
22215
22576
  const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
22216
- await access11(modulePath).catch(() => {
22577
+ await access12(modulePath).catch(() => {
22217
22578
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
22218
22579
  });
22219
22580
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -22266,10 +22627,12 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
22266
22627
 
22267
22628
  // src/cli/scripts/mobile.ts
22268
22629
  var NOT_FOUND4 = -1;
22269
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22630
+ var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
22631
+ var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
22632
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22270
22633
  var expoPublicConfig = (config) => {
22271
22634
  const result = { ...config };
22272
- if (!isRecord6(result.updates))
22635
+ if (!isRecord7(result.updates))
22273
22636
  return result;
22274
22637
  const {
22275
22638
  codeSigningCertificate: _codeSigningCertificate,
@@ -22309,13 +22672,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
22309
22672
  ];
22310
22673
  var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
22311
22674
  var directProjectPackages = async (projectRoot) => {
22312
- const manifest = JSON.parse(await readFile16(join13(projectRoot, "package.json"), "utf8"));
22313
- if (!isRecord6(manifest))
22675
+ const manifest = JSON.parse(await readFile17(join14(projectRoot, "package.json"), "utf8"));
22676
+ if (!isRecord7(manifest))
22314
22677
  throw new TypeError("Application package.json must contain an object.");
22315
22678
  const names = new Set;
22316
- for (const field of ["dependencies", "devDependencies"]) {
22317
- const dependencies = Reflect.get(manifest, field);
22318
- if (isRecord6(dependencies))
22679
+ for (const field2 of ["dependencies", "devDependencies"]) {
22680
+ const dependencies = Reflect.get(manifest, field2);
22681
+ if (isRecord7(dependencies))
22319
22682
  for (const name of Object.keys(dependencies))
22320
22683
  names.add(name);
22321
22684
  }
@@ -22323,8 +22686,8 @@ var directProjectPackages = async (projectRoot) => {
22323
22686
  };
22324
22687
  var resolvedPackageVersion = async (projectRoot, packageName) => {
22325
22688
  try {
22326
- const manifest = JSON.parse(await readFile16(join13(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
22327
- return isRecord6(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
22689
+ const manifest = JSON.parse(await readFile17(join14(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
22690
+ return isRecord7(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
22328
22691
  } catch {
22329
22692
  return;
22330
22693
  }
@@ -22374,9 +22737,9 @@ var requireMobileConfig = (value) => {
22374
22737
  return value;
22375
22738
  };
22376
22739
  var capacitorExecutable = async (projectRoot) => {
22377
- const executable = join13(projectRoot, "node_modules", ".bin", "cap");
22740
+ const executable = join14(projectRoot, "node_modules", ".bin", "cap");
22378
22741
  try {
22379
- await access12(executable);
22742
+ await access13(executable);
22380
22743
  return executable;
22381
22744
  } catch {
22382
22745
  throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
@@ -22397,9 +22760,9 @@ var runCapacitor = async (projectRoot, args) => {
22397
22760
  };
22398
22761
  var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
22399
22762
  var expoExecutable = async (project) => {
22400
- const executable = join13(project, "node_modules", ".bin", "expo");
22763
+ const executable = join14(project, "node_modules", ".bin", "expo");
22401
22764
  try {
22402
- await access12(executable);
22765
+ await access13(executable);
22403
22766
  return executable;
22404
22767
  } catch {
22405
22768
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -22431,14 +22794,14 @@ var runExpo = async (project, args, options = {}) => {
22431
22794
  };
22432
22795
  var ensureExpoPackages = async (project, args) => {
22433
22796
  try {
22434
- const manifest = JSON.parse(await readFile16(join13(project, "package.json"), "utf8"));
22797
+ const manifest = JSON.parse(await readFile17(join14(project, "package.json"), "utf8"));
22435
22798
  const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
22436
22799
  if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
22437
22800
  throw new TypeError("Generated Expo dependencies are invalid.");
22438
22801
  await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
22439
22802
  if (typeof expected !== "string")
22440
22803
  throw new TypeError("Generated Expo dependency version is invalid.");
22441
- const installed = JSON.parse(await readFile16(join13(project, "node_modules", name, "package.json"), "utf8"));
22804
+ const installed = JSON.parse(await readFile17(join14(project, "node_modules", name, "package.json"), "utf8"));
22442
22805
  const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
22443
22806
  if (typeof actual !== "string")
22444
22807
  throw new TypeError("Installed Expo dependency version is invalid.");
@@ -22565,7 +22928,7 @@ var unpairRemoteMac = async (args) => {
22565
22928
  var initialize = async (args) => {
22566
22929
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22567
22930
  if (mobile.engine === "expo") {
22568
- console.warn("Experimental: Expo Android builds and publishing are available; iOS releases and physical-device acceptance are not complete.");
22931
+ console.warn("Experimental: Expo Android and iOS release automation is available; real macOS and physical-device acceptance remains pending.");
22569
22932
  const generated2 = await writeAbsoluteExpoProject(mobile, {
22570
22933
  force: args.includes("--force"),
22571
22934
  projectRoot
@@ -22634,7 +22997,7 @@ var sync = async (args) => {
22634
22997
  };
22635
22998
  var associations = async (args) => {
22636
22999
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22637
- const outputDirectory = resolve12(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
23000
+ const outputDirectory = resolve13(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
22638
23001
  if (args.includes("--verify")) {
22639
23002
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
22640
23003
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -22686,7 +23049,7 @@ var generateGithubCi = async (args) => {
22686
23049
  const publicResult = {
22687
23050
  changed: result.changed,
22688
23051
  format: result.format,
22689
- path: relative11(projectRoot, result.path).replaceAll("\\", "/"),
23052
+ path: relative12(projectRoot, result.path).replaceAll("\\", "/"),
22690
23053
  platforms: result.platforms,
22691
23054
  publishing: result.publishing,
22692
23055
  requiredSecrets: result.requiredSecrets
@@ -22797,9 +23160,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
22797
23160
  temporaryDirectory: undefined
22798
23161
  };
22799
23162
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
22800
- const temporaryParent = join13(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
22801
- await mkdir12(temporaryParent, { recursive: true });
22802
- const temporaryDirectory = await mkdtemp3(join13(temporaryParent, ".stage-"));
23163
+ const temporaryParent = join14(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
23164
+ await mkdir13(temporaryParent, { recursive: true });
23165
+ const temporaryDirectory = await mkdtemp4(join14(temporaryParent, ".stage-"));
22803
23166
  try {
22804
23167
  await runExpo(mobile.nativeProjectDirectory, [
22805
23168
  "export",
@@ -22811,9 +23174,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
22811
23174
  temporaryDirectory,
22812
23175
  "--clear"
22813
23176
  ], { production: true });
22814
- const generatedApp = JSON.parse(await readFile16(join13(mobile.nativeProjectDirectory, "app.json"), "utf8"));
22815
- const expoConfig = isRecord6(generatedApp) ? generatedApp.expo : undefined;
22816
- if (!isRecord6(expoConfig))
23177
+ const generatedApp = JSON.parse(await readFile17(join14(mobile.nativeProjectDirectory, "app.json"), "utf8"));
23178
+ const expoConfig = isRecord7(generatedApp) ? generatedApp.expo : undefined;
23179
+ if (!isRecord7(expoConfig))
22817
23180
  throw new TypeError("Generated Expo app configuration is invalid.");
22818
23181
  const publicExpoConfig = expoPublicConfig(expoConfig);
22819
23182
  const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
@@ -22829,7 +23192,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
22829
23192
  temporaryDirectory
22830
23193
  };
22831
23194
  } catch (error) {
22832
- await rm6(temporaryDirectory, { force: true, recursive: true });
23195
+ await rm7(temporaryDirectory, { force: true, recursive: true });
22833
23196
  throw error;
22834
23197
  }
22835
23198
  };
@@ -22854,11 +23217,11 @@ var buildMobileUpdate = async (args) => {
22854
23217
  let expoExportDirectory;
22855
23218
  try {
22856
23219
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
22857
- const embedded = JSON.parse(await readFile16(join13(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
22858
- const runtimeFingerprint = isRecord6(embedded) ? embedded.nativeRuntime : undefined;
23220
+ const embedded = JSON.parse(await readFile17(join14(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
23221
+ const runtimeFingerprint = isRecord7(embedded) ? embedded.nativeRuntime : undefined;
22859
23222
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
22860
23223
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
22861
- const privateKey = await readFile16(resolve12(projectRoot, signingKeyPath));
23224
+ const privateKey = await readFile17(resolve13(projectRoot, signingKeyPath));
22862
23225
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
22863
23226
  const derivedPublicKey = createPublicKey2(privateKey).export({
22864
23227
  format: "der",
@@ -22879,7 +23242,7 @@ var buildMobileUpdate = async (args) => {
22879
23242
  channel: mobile.updates.channel,
22880
23243
  classification,
22881
23244
  keyId,
22882
- outputDirectory: valueAfter(args, "--outdir") ?? join13(projectRoot, ".absolutejs", "mobile", "updates"),
23245
+ outputDirectory: valueAfter(args, "--outdir") ?? join14(projectRoot, ".absolutejs", "mobile", "updates"),
22883
23246
  privateKey,
22884
23247
  runtimeFingerprint
22885
23248
  });
@@ -22891,7 +23254,7 @@ var buildMobileUpdate = async (args) => {
22891
23254
  return result;
22892
23255
  } finally {
22893
23256
  if (expoExportDirectory)
22894
- await rm6(expoExportDirectory, { force: true, recursive: true });
23257
+ await rm7(expoExportDirectory, { force: true, recursive: true });
22895
23258
  sendTelemetryEvent("mobile:update-build", {
22896
23259
  classification: classification ?? "invalid",
22897
23260
  durationMs: Math.round(performance.now() - startedAt),
@@ -22921,7 +23284,7 @@ var generateExpoUpdateSigning = async (args) => {
22921
23284
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
22922
23285
  ...validityYears === undefined ? {} : { validityYears }
22923
23286
  });
22924
- const certificatePath = relative11(projectRoot, result.certificatePath).replaceAll("\\", "/");
23287
+ const certificatePath = relative12(projectRoot, result.certificatePath).replaceAll("\\", "/");
22925
23288
  sendTelemetryEvent("mobile:update-code-signing-generated", {
22926
23289
  engine: "expo",
22927
23290
  validityYears: validityYears ?? 10
@@ -22983,7 +23346,7 @@ var provisionMobileUpdate = async (args) => {
22983
23346
  publicKeys: mobile.updates.publicKeys,
22984
23347
  storage: requestedStorage
22985
23348
  });
22986
- console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative11(projectRoot, path).replaceAll("\\", "/")}.`);
23349
+ console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative12(projectRoot, path).replaceAll("\\", "/")}.`);
22987
23350
  if (requestedStorage === "local")
22988
23351
  console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
22989
23352
  else
@@ -23458,7 +23821,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
23458
23821
  const durationMs = Math.round(performance.now() - startedAt);
23459
23822
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
23460
23823
  console.log(`Artifact: ${release.artifactPath}`);
23461
- console.log(`Metadata: ${join13(release.releaseRoot, "release.json")}`);
23824
+ console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
23462
23825
  return release;
23463
23826
  } finally {
23464
23827
  sendTelemetryEvent("mobile:android-release-build", {
@@ -23614,7 +23977,7 @@ var buildIos = async (args, prepareBuildNumber) => {
23614
23977
  const durationMs = Math.round(performance.now() - startedAt);
23615
23978
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
23616
23979
  console.log(`Artifact: ${release.artifactPath}`);
23617
- console.log(`Metadata: ${join13(release.releaseRoot, "release.json")}`);
23980
+ console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
23618
23981
  return release;
23619
23982
  } finally {
23620
23983
  stopListeningForCancellation();
@@ -23726,7 +24089,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
23726
24089
  checks.push({
23727
24090
  id: "sync.storage-schema",
23728
24091
  label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
23729
- path: join13(projectRoot, "package.json"),
24092
+ path: join14(projectRoot, "package.json"),
23730
24093
  platform: "host",
23731
24094
  status: "pass"
23732
24095
  });
@@ -23734,7 +24097,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
23734
24097
  checks.push({
23735
24098
  id: "sync.storage-schema",
23736
24099
  label: "Offline schema metadata is invalid",
23737
- path: join13(projectRoot, "package.json"),
24100
+ path: join14(projectRoot, "package.json"),
23738
24101
  platform: "host",
23739
24102
  remediation: error instanceof Error ? error.message : String(error),
23740
24103
  status: "fail"
@@ -23801,7 +24164,7 @@ AbsoluteJS can configure ${installPlatform} emulation:`);
23801
24164
  Emulator setup verification:`);
23802
24165
  printDoctorChecks(result.checks.filter((check) => check.platform === "host" || check.platform === installPlatform));
23803
24166
  };
23804
- var captureCommand2 = (command) => {
24167
+ var captureCommand3 = (command) => {
23805
24168
  const result = Bun.spawnSync(command, {
23806
24169
  stderr: "pipe",
23807
24170
  stdin: "ignore",
@@ -23822,7 +24185,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
23822
24185
  }
23823
24186
  return { https: args.includes("--https"), port };
23824
24187
  }
23825
- const instances = listLiveInstances().filter((instance2) => resolve12(instance2.cwd) === resolve12(projectRoot) && instance2.source === "dev" && instance2.port !== null);
24188
+ const instances = listLiveInstances().filter((instance2) => resolve13(instance2.cwd) === resolve13(projectRoot) && instance2.source === "dev" && instance2.port !== null);
23826
24189
  if (instances.length !== 1) {
23827
24190
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
23828
24191
  }
@@ -23844,14 +24207,14 @@ var androidTestTimeout = (args) => {
23844
24207
  };
23845
24208
  var requireAndroidAdb = async () => {
23846
24209
  const checks = await inspectAbsoluteMobileToolchain();
23847
- const adb = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
23848
- if (!adb) {
24210
+ const adb2 = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
24211
+ if (!adb2) {
23849
24212
  throw new TypeError("Android Debug Bridge is unavailable. Run `absolute mobile doctor android --fix`.");
23850
24213
  }
23851
- return adb;
24214
+ return adb2;
23852
24215
  };
23853
- var selectAndroidSerial = (adb, explicitSerial) => {
23854
- const devices = captureCommand2([adb, "devices"]);
24216
+ var selectAndroidSerial = (adb2, explicitSerial) => {
24217
+ const devices = captureCommand3([adb2, "devices"]);
23855
24218
  if (devices.exitCode !== 0) {
23856
24219
  throw new Error(`Could not list Android targets: ${devices.stderr.trim() || devices.stdout.trim()}`);
23857
24220
  }
@@ -23868,9 +24231,56 @@ var selectAndroidSerial = (adb, explicitSerial) => {
23868
24231
  }
23869
24232
  return selected;
23870
24233
  };
24234
+ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial, json) => {
24235
+ if (explicitSerial)
24236
+ return selectAndroidSerial(adb2, explicitSerial);
24237
+ const current = captureCommand3([adb2, "devices"]);
24238
+ const ready = current.exitCode === 0 ? parseAdbDevices(current.stdout).find((serial) => serial.startsWith("emulator-")) : undefined;
24239
+ if (ready)
24240
+ return ready;
24241
+ if (!emulator)
24242
+ throw new TypeError("Android Emulator is unavailable. Run `absolute mobile doctor android --fix`.");
24243
+ (json ? console.error : console.log)(`Starting managed Android emulator ${ABSOLUTE_ANDROID_AVD_NAME}\u2026`);
24244
+ const launched = Bun.spawn([
24245
+ emulator,
24246
+ "-avd",
24247
+ ABSOLUTE_ANDROID_AVD_NAME,
24248
+ "-netdelay",
24249
+ "none",
24250
+ "-netspeed",
24251
+ "full"
24252
+ ], { stderr: "ignore", stdin: "ignore", stdout: "ignore" });
24253
+ launched.unref();
24254
+ const startedAt = performance.now();
24255
+ const bootedSerial = () => {
24256
+ const devices = captureCommand3([adb2, "devices"]);
24257
+ const serial = devices.exitCode === 0 ? parseAdbDevices(devices.stdout).find((candidate) => candidate.startsWith("emulator-")) : undefined;
24258
+ if (!serial)
24259
+ return;
24260
+ const booted = captureCommand3([
24261
+ adb2,
24262
+ "-s",
24263
+ serial,
24264
+ "shell",
24265
+ "getprop",
24266
+ "sys.boot_completed"
24267
+ ]);
24268
+ return booted.exitCode === 0 && booted.stdout.trim() === "1" ? serial : undefined;
24269
+ };
24270
+ const waitForBoot = async () => {
24271
+ if (performance.now() - startedAt >= ANDROID_EMULATOR_BOOT_TIMEOUT_MS)
24272
+ throw new Error(`Managed Android emulator ${ABSOLUTE_ANDROID_AVD_NAME} did not finish booting within 180 seconds.`);
24273
+ const serial = bootedSerial();
24274
+ if (serial)
24275
+ return serial;
24276
+ await Bun.sleep(ANDROID_EMULATOR_BOOT_POLL_MS);
24277
+ return waitForBoot();
24278
+ };
24279
+ return waitForBoot();
24280
+ };
23871
24281
  var safeArtifactRoot = (projectRoot, value) => {
23872
- const root = resolve12(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
23873
- if (root !== projectRoot && !root.startsWith(`${resolve12(projectRoot)}/`)) {
24282
+ const root = resolve13(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
24283
+ if (root !== projectRoot && !root.startsWith(`${resolve13(projectRoot)}/`)) {
23874
24284
  throw new TypeError("mobile test --artifacts must remain inside the project.");
23875
24285
  }
23876
24286
  return root;
@@ -23898,12 +24308,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
23898
24308
  });
23899
24309
  };
23900
24310
  var writeAndroidFailureArtifacts = async (options) => {
23901
- await mkdir12(options.artifactRoot, { recursive: true });
23902
- const screenshot = options.session ? await options.session.screenshot(join13(options.artifactRoot, "android-failure.png")).catch(() => {
24311
+ await mkdir13(options.artifactRoot, { recursive: true });
24312
+ const screenshot = options.session ? await options.session.screenshot(join14(options.artifactRoot, "android-failure.png")).catch(() => {
23903
24313
  return;
23904
24314
  }) : undefined;
23905
- const diagnosticPath = join13(options.artifactRoot, "android-failure.json");
23906
- await writeFile14(diagnosticPath, `${JSON.stringify({
24315
+ const diagnosticPath = join14(options.artifactRoot, "android-failure.json");
24316
+ await writeFile15(diagnosticPath, `${JSON.stringify({
23907
24317
  diagnostics: options.session?.diagnostics ?? [],
23908
24318
  error: options.error instanceof Error ? options.error.message : String(options.error),
23909
24319
  platform: "android",
@@ -23915,16 +24325,171 @@ var writeAndroidFailureArtifacts = async (options) => {
23915
24325
  `);
23916
24326
  return { diagnosticPath, screenshot };
23917
24327
  };
24328
+ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
24329
+ const process2 = Bun.spawn([adb2, "-s", serial, "exec-out", "screencap", "-p"], { stderr: "pipe", stdin: "ignore", stdout: "pipe" });
24330
+ const [exitCode, bytes, stderr] = await Promise.all([
24331
+ process2.exited,
24332
+ new Response(process2.stdout).bytes(),
24333
+ new Response(process2.stderr).text()
24334
+ ]);
24335
+ if (exitCode !== 0)
24336
+ throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
24337
+ await writeFile15(destination, bytes);
24338
+ return destination;
24339
+ };
24340
+ var printAndroidReleaseAcceptance = (result, json) => {
24341
+ if (json) {
24342
+ console.log(JSON.stringify(result, null, 2));
24343
+ return;
24344
+ }
24345
+ console.log(`\u2713 Installed immutable ${result.engine} release ${result.releaseId} with Bundletool in ${getDurationString(result.installMs)}.`);
24346
+ console.log(`\u2713 Embedded web content booted offline in ${getDurationString(result.launchMs)} and relaunched in ${getDurationString(result.relaunchMs)}.`);
24347
+ };
24348
+ var testAndroidRelease = async (args, mobile, projectRoot) => {
24349
+ const requested = valueAfter(args, "--release");
24350
+ if (!requested || requested.startsWith("--"))
24351
+ throw new TypeError("mobile test android --release requires a release directory or release.json path.");
24352
+ if (!mobile.platforms.includes("android"))
24353
+ throw new TypeError("mobile test android requires android in mobile.platforms.");
24354
+ if (mobile.engine !== "expo")
24355
+ throw new TypeError("mobile test android --release currently requires mobile.engine: expo; use the ordinary test command for Capacitor.");
24356
+ const release = await readAbsoluteAndroidRelease(projectRoot, requested);
24357
+ if (release.metadata.appId !== mobile.appId)
24358
+ throw new TypeError(`Android release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
24359
+ if (release.metadata.engine !== mobile.engine)
24360
+ throw new TypeError(`Android release engine ${release.metadata.engine} does not match configured engine ${mobile.engine}.`);
24361
+ if (mobile.engine === "expo" && mobile.expoNativeRoutes[mobile.entry])
24362
+ throw new TypeError("Offline Expo release acceptance requires mobile.entry to be an embedded web route; the configured entry is native and requires trusted server page data.");
24363
+ const checks = await inspectAbsoluteMobileToolchain();
24364
+ const adb2 = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
24365
+ const java = checks.find((check) => check.id === "android.java" && check.status === "pass")?.path;
24366
+ const emulator = checks.find((check) => check.id === "android.emulator" && check.status === "pass")?.path;
24367
+ if (!adb2)
24368
+ throw new TypeError("Android Debug Bridge is unavailable. Run `absolute mobile doctor android --fix`.");
24369
+ if (!java)
24370
+ throw new TypeError("Java is unavailable. Install the JDK required by the Android toolchain and rerun `absolute mobile doctor android`.");
24371
+ const serial = await selectOrStartAndroidReleaseEmulator(adb2, emulator, valueAfter(args, "--serial"), args.includes("--json"));
24372
+ const inspectedBundletool = await inspectAbsoluteBundletool();
24373
+ const approved = inspectedBundletool.ready || args.includes("--yes") || await confirmInstall(`Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} is required to install the exact App Bundle. Download and checksum-verify it now?`);
24374
+ const bundletool = await ensureAbsoluteBundletool({
24375
+ approved: approved === true
24376
+ });
24377
+ const reportRoot = nativeReportRoot(args, projectRoot, "android");
24378
+ const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts") ?? `.absolutejs/mobile/test-artifacts/${release.metadata.releaseId}`);
24379
+ const startedAt = performance.now();
24380
+ try {
24381
+ const result = await runAbsoluteAndroidReleaseAcceptance({
24382
+ adb: adb2,
24383
+ artifactDirectory: artifactRoot,
24384
+ bundletool,
24385
+ host: detectAbsoluteMobileHost(),
24386
+ java,
24387
+ ...mobile.deepLinkScheme ? {
24388
+ launchUrl: `${mobile.deepLinkScheme}://${mobile.entry}`
24389
+ } : {},
24390
+ release,
24391
+ serial
24392
+ });
24393
+ const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join14(reportRoot, "android-release.png")) : undefined;
24394
+ sendTelemetryEvent("mobile:android-release-conformance", {
24395
+ durationMs: result.durationMs,
24396
+ engine: result.engine,
24397
+ installMs: result.installMs,
24398
+ launchMs: result.launchMs,
24399
+ platform: "android",
24400
+ relaunchMs: result.relaunchMs,
24401
+ success: true
24402
+ });
24403
+ await writeRequestedAndroidReport({
24404
+ adb: adb2,
24405
+ args,
24406
+ projectRoot,
24407
+ provider: mobile.engine,
24408
+ reportRoot,
24409
+ run: {
24410
+ appId: mobile.appId,
24411
+ durationMs: result.durationMs,
24412
+ hmrConnected: false,
24413
+ release: {
24414
+ apksBytes: result.apksBytes,
24415
+ artifactBytes: result.artifactBytes,
24416
+ artifactSha256: result.artifactSha256,
24417
+ embeddedOffline: result.embeddedOffline,
24418
+ engine: result.engine,
24419
+ installMs: result.installMs,
24420
+ launchMs: result.launchMs,
24421
+ relaunchMs: result.relaunchMs,
24422
+ releaseId: result.releaseId,
24423
+ signed: result.signed
24424
+ },
24425
+ ...screenshot ? { screenshot } : {},
24426
+ serial,
24427
+ status: "pass"
24428
+ }
24429
+ });
24430
+ printAndroidReleaseAcceptance(result, args.includes("--json"));
24431
+ return result;
24432
+ } catch (error) {
24433
+ const durationMs = Math.round(performance.now() - startedAt);
24434
+ sendTelemetryEvent("mobile:android-release-conformance", {
24435
+ durationMs,
24436
+ engine: mobile.engine,
24437
+ platform: "android",
24438
+ success: false
24439
+ });
24440
+ await mkdir13(artifactRoot, { recursive: true });
24441
+ const diagnosticPath = join14(artifactRoot, "android-release-failure.json");
24442
+ await writeFile15(diagnosticPath, `${JSON.stringify({
24443
+ error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
24444
+ platform: "android",
24445
+ provider: mobile.engine,
24446
+ releaseId: release.metadata.releaseId,
24447
+ status: "fail"
24448
+ }, null, 2)}
24449
+ `);
24450
+ await writeRequestedAndroidReport({
24451
+ adb: adb2,
24452
+ args,
24453
+ projectRoot,
24454
+ provider: mobile.engine,
24455
+ reportRoot,
24456
+ run: {
24457
+ appId: mobile.appId,
24458
+ durationMs,
24459
+ error: "Installed release acceptance failed; inspect the local diagnostic artifact before sharing evidence.",
24460
+ hmrConnected: false,
24461
+ release: {
24462
+ apksBytes: 0,
24463
+ artifactBytes: release.metadata.bytes,
24464
+ artifactSha256: release.metadata.sha256,
24465
+ embeddedOffline: false,
24466
+ engine: release.metadata.engine,
24467
+ installMs: 0,
24468
+ launchMs: 0,
24469
+ relaunchMs: 0,
24470
+ releaseId: release.metadata.releaseId,
24471
+ signed: release.metadata.signed
24472
+ },
24473
+ serial,
24474
+ status: "fail"
24475
+ }
24476
+ });
24477
+ throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}`, { cause: error });
24478
+ }
24479
+ };
23918
24480
  var testAndroid = async (args) => {
23919
24481
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
24482
+ if (args.includes("--release")) {
24483
+ return testAndroidRelease(args, mobile, projectRoot);
24484
+ }
23920
24485
  requireCapacitorEngine(mobile, "mobile test android");
23921
24486
  if (!mobile.platforms.includes("android")) {
23922
24487
  throw new TypeError("mobile test android requires android in mobile.platforms.");
23923
24488
  }
23924
24489
  const { https, port } = requireAndroidTestPort(args, projectRoot);
23925
24490
  const timeoutMs = androidTestTimeout(args);
23926
- const adb = await requireAndroidAdb();
23927
- const serial = selectAndroidSerial(adb, valueAfter(args, "--serial"));
24491
+ const adb2 = await requireAndroidAdb();
24492
+ const serial = selectAndroidSerial(adb2, valueAfter(args, "--serial"));
23928
24493
  const routes = valuesAfter(args, "--route");
23929
24494
  if (routes.length === 0)
23930
24495
  routes.push(mobile.entry);
@@ -23934,7 +24499,7 @@ var testAndroid = async (args) => {
23934
24499
  let session;
23935
24500
  try {
23936
24501
  session = await attachAbsoluteAndroidWebView({
23937
- adb,
24502
+ adb: adb2,
23938
24503
  appId: mobile.appId,
23939
24504
  serial,
23940
24505
  timeoutMs
@@ -23968,9 +24533,9 @@ var testAndroid = async (args) => {
23968
24533
  console.log(JSON.stringify(report, null, 2));
23969
24534
  else
23970
24535
  printAndroidTestReport(report);
23971
- const screenshot = reportRoot ? await session.screenshot(join13(artifactRoot, "android-emulator.png")) : undefined;
24536
+ const screenshot = reportRoot ? await session.screenshot(join14(artifactRoot, "android-emulator.png")) : undefined;
23972
24537
  await writeRequestedAndroidReport({
23973
- adb,
24538
+ adb: adb2,
23974
24539
  args,
23975
24540
  projectRoot,
23976
24541
  run: {
@@ -24011,7 +24576,7 @@ var testAndroid = async (args) => {
24011
24576
  session
24012
24577
  });
24013
24578
  await writeRequestedAndroidReport({
24014
- adb,
24579
+ adb: adb2,
24015
24580
  args,
24016
24581
  projectRoot,
24017
24582
  run: {
@@ -24037,14 +24602,14 @@ var requireIosTestContext = (args, projectRoot) => {
24037
24602
  const port = Number(explicit);
24038
24603
  if (!Number.isInteger(port) || port < 1 || port > 65535)
24039
24604
  throw new TypeError("mobile test --port must be a valid TCP port.");
24040
- const instance2 = listLiveInstances().find((candidate) => resolve12(candidate.cwd) === resolve12(projectRoot) && candidate.source === "dev" && candidate.port === port);
24605
+ const instance2 = listLiveInstances().find((candidate) => resolve13(candidate.cwd) === resolve13(projectRoot) && candidate.source === "dev" && candidate.port === port);
24041
24606
  return {
24042
24607
  https: instance2?.https ?? args.includes("--https"),
24043
24608
  instance: instance2,
24044
24609
  port
24045
24610
  };
24046
24611
  }
24047
- const instances = listLiveInstances().filter((instance2) => resolve12(instance2.cwd) === resolve12(projectRoot) && instance2.source === "dev" && instance2.port !== null);
24612
+ const instances = listLiveInstances().filter((instance2) => resolve13(instance2.cwd) === resolve13(projectRoot) && instance2.source === "dev" && instance2.port !== null);
24048
24613
  if (instances.length !== 1)
24049
24614
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
24050
24615
  const [instance] = instances;
@@ -24062,7 +24627,7 @@ var waitForIosHmrClient = async (options) => {
24062
24627
  return;
24063
24628
  });
24064
24629
  const status = response?.ok ? await response.json().catch(() => null) : null;
24065
- const targets = isRecord6(status) && isRecord6(status.connectedTargets) ? status.connectedTargets : undefined;
24630
+ const targets = isRecord7(status) && isRecord7(status.connectedTargets) ? status.connectedTargets : undefined;
24066
24631
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
24067
24632
  return;
24068
24633
  await Bun.sleep(100);
@@ -24134,11 +24699,11 @@ var physicalIosCapture = async (options) => {
24134
24699
  remote: false,
24135
24700
  xcodeVersion: requireCapturedCommand(["/usr/bin/xcodebuild", "-version"], "Xcode version inspection").stdout.trim(),
24136
24701
  xcrun: "/usr/bin/xcrun",
24137
- capture: async (command) => captureCommand2(command)
24702
+ capture: async (command) => captureCommand3(command)
24138
24703
  };
24139
24704
  };
24140
24705
  var selectIosSimulator = (xcrun, explicitUdid) => {
24141
- const result = captureCommand2([
24706
+ const result = captureCommand3([
24142
24707
  xcrun,
24143
24708
  "simctl",
24144
24709
  "list",
@@ -24161,15 +24726,15 @@ var selectIosSimulator = (xcrun, explicitUdid) => {
24161
24726
  return selected;
24162
24727
  };
24163
24728
  var requireCapturedCommand = (command, label) => {
24164
- const result = captureCommand2(command);
24729
+ const result = captureCommand3(command);
24165
24730
  if (result.exitCode !== 0)
24166
24731
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
24167
24732
  return result;
24168
24733
  };
24169
24734
  var writeIosFailureArtifacts = async (options) => {
24170
- await mkdir12(options.artifactRoot, { recursive: true });
24171
- const screenshot = join13(options.artifactRoot, "ios-failure.png");
24172
- const screenshotResult = captureCommand2([
24735
+ await mkdir13(options.artifactRoot, { recursive: true });
24736
+ const screenshot = join14(options.artifactRoot, "ios-failure.png");
24737
+ const screenshotResult = captureCommand3([
24173
24738
  options.xcrun,
24174
24739
  "simctl",
24175
24740
  "io",
@@ -24177,8 +24742,8 @@ var writeIosFailureArtifacts = async (options) => {
24177
24742
  "screenshot",
24178
24743
  screenshot
24179
24744
  ]);
24180
- const diagnosticPath = join13(options.artifactRoot, "ios-failure.json");
24181
- await writeFile14(diagnosticPath, `${JSON.stringify({
24745
+ const diagnosticPath = join14(options.artifactRoot, "ios-failure.json");
24746
+ await writeFile15(diagnosticPath, `${JSON.stringify({
24182
24747
  appId: options.appId,
24183
24748
  error: options.error instanceof Error ? options.error.message : String(options.error),
24184
24749
  platform: "ios",
@@ -24205,8 +24770,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
24205
24770
  var absolutejsVersionForReport = async () => {
24206
24771
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
24207
24772
  const versions = await Promise.all([
24208
- resolve12(import.meta.dir, "..", "..", "package.json"),
24209
- resolve12(import.meta.dir, "..", "..", "..", "package.json")
24773
+ resolve13(import.meta.dir, "..", "..", "package.json"),
24774
+ resolve13(import.meta.dir, "..", "..", "..", "package.json")
24210
24775
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
24211
24776
  for (const version of versions) {
24212
24777
  if (version === "unknown")
@@ -24217,7 +24782,7 @@ var absolutejsVersionForReport = async () => {
24217
24782
  return absolutejsVersion;
24218
24783
  };
24219
24784
  var writeRequestedAndroidReport = async (options) => {
24220
- const reportRoot = nativeReportRoot(options.args, options.projectRoot, "android");
24785
+ const reportRoot = options.reportRoot ?? nativeReportRoot(options.args, options.projectRoot, "android");
24221
24786
  if (!reportRoot)
24222
24787
  return;
24223
24788
  const adbVersion = requireCapturedCommand([options.adb, "version"], "ADB version inspection").stdout.trim();
@@ -24226,6 +24791,7 @@ var writeRequestedAndroidReport = async (options) => {
24226
24791
  adbVersion,
24227
24792
  bunVersion: Bun.version,
24228
24793
  host: `${process.platform}-${process.arch}`,
24794
+ ...options.provider ? { provider: options.provider } : {},
24229
24795
  run: options.run
24230
24796
  });
24231
24797
  const paths = await writeAbsoluteNativeTestReport(reportRoot, report);
@@ -24430,8 +24996,8 @@ var testIos = async (args) => {
24430
24996
  mobile.appId
24431
24997
  ], "iOS app launch");
24432
24998
  await waitForIosHmrClient({ https, port, timeoutMs });
24433
- await mkdir12(artifactRoot, { recursive: true });
24434
- const screenshot = join13(artifactRoot, "ios-simulator.png");
24999
+ await mkdir13(artifactRoot, { recursive: true });
25000
+ const screenshot = join14(artifactRoot, "ios-simulator.png");
24435
25001
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
24436
25002
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
24437
25003
  const report = {
@@ -24617,7 +25183,7 @@ var runMobile = async (args) => {
24617
25183
  await publishIos(args.slice(2));
24618
25184
  return;
24619
25185
  }
24620
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | 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] [--remote name] [--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]> [--config path]");
25186
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | 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] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path]");
24621
25187
  };
24622
25188
  export {
24623
25189
  runMobile